1/* 2* Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd. 3* Licensed under the Apache License, Version 2.0 (the "License"); 4* you may not use this file except in compliance with the License. 5* You may obtain a copy of the License at 6* 7* http://www.apache.org/licenses/LICENSE-2.0 8* 9* Unless required by applicable law or agreed to in writing, software 10* distributed under the License is distributed on an "AS IS" BASIS, 11* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12* See the License for the specific language governing permissions and 13* limitations under the License. 14*/ 15const { NapiLog } = require("../gen/tools/NapiLog"); 16const { writeFile } = require("../gen/tools/FileRW"); 17const path = require('path') 18const re = require("../gen/tools/re"); 19const fs = require("fs"); 20const os = require("os"); 21 22function parseFileAll(hFilePath) { 23 let execSync = require("child_process").execSync 24 let cmd = "" 25 if(fs.existsSync("./src/tsGen/header_parser.py")) { 26 // call python file (for debug test) 27 cmd = "python ./src/tsGen/header_parser.py " + hFilePath 28 } else { 29 // call exe file (for real runtime) 30 let sysInfo = os.platform() 31 let execPath = path.dirname(process.execPath) 32 let exeFile = sysInfo === 'win32' ? path.join(execPath, "header_parser.exe") : 33 path.join(execPath, "header_parser") 34 cmd = exeFile + " " + hFilePath 35 } 36 37 let parseResult = null 38 let stdout = execSync(cmd) 39 parseResult = JSON.parse(stdout.toString()).result 40 return parseResult 41} 42 43function createNameSpaceInfo(parseNameSpaceInfo) { 44 let nameSpaceInfo = { 45 "name": "", 46 "classes": [], 47 "functions": [] 48 } 49 nameSpaceInfo.name = parseNameSpaceInfo 50 return nameSpaceInfo 51} 52 53function analyzeNameSpace(rootInfo, parseResult) { 54 let parseNameSpaces = parseResult.namespaces 55 for(var i = 0; i < parseNameSpaces.length; ++i) { 56 let nameSpaceInfo = createNameSpaceInfo(parseNameSpaces[i]) 57 rootInfo.namespaces.push(nameSpaceInfo) 58 } 59} 60 61function basicC2js(cType) { 62 let jsType = "" 63 switch (cType) { 64 case 'string': 65 case 'std::string': 66 case 'char': 67 case 'wchar_t': 68 case 'char16_t': 69 case 'char32_t': 70 jsType = 'string' 71 break; 72 case 'bool': 73 jsType = 'boolean' 74 break 75 case 'short': 76 case 'int': 77 case 'uint32_t': 78 case 'size_t': 79 case 'long': 80 case 'long long': 81 case 'float': 82 case 'double': 83 case 'long double': 84 jsType = 'number' 85 break 86 default: 87 jsType = cType 88 break 89 } 90 return jsType 91} 92 93function getJsTypeFromC(cType, typeInfo) { 94 let basicCtype = cType 95 let matchs = re.match("(std::)?vector<([\x21-\x7e ]+)>", basicCtype); 96 if (matchs) { 97 basicCtype = re.getReg(basicCtype, matchs.regs[2]).trim() 98 typeInfo.array = 1 99 } 100 101 let unsignedIdx = basicCtype.indexOf('unsigned') 102 if ( unsignedIdx >= 0) { 103 // cut off the keywords 'unsigned' 104 basicCtype = basicCtype.substring(unsignedIdx + 8, basicCtype.length).trim() 105 } 106 let jsType = basicC2js(basicCtype) 107 if (typeInfo.array) { 108 jsType = "Array<%s>".format(jsType) 109 } 110 return jsType 111} 112 113function createParam(parseParamInfo) { 114 let param = { 115 "name": "", 116 "type": "" 117 } 118 param.name = parseParamInfo.name 119 let rawType = getJsTypeFromC(parseParamInfo.raw_type, parseParamInfo) 120 param.type = rawType 121 122 return param 123} 124 125function createFuncInfo(parseFuncInfo) { 126 let funcInfo = { 127 "name": "", 128 "params": [], 129 "namespace": "", 130 "retType": "" 131 } 132 funcInfo.name = parseFuncInfo.name 133 funcInfo.namespace = parseFuncInfo.namespace 134 let tokenIndex = funcInfo.namespace.indexOf('::') 135 if (tokenIndex >= 0) { 136 // delete '::' in namespace, get the pure space name. 137 funcInfo.namespace = funcInfo.namespace.substring(0, tokenIndex) 138 } 139 140 let parseParams = parseFuncInfo.parameters 141 for(var i = 0; i < parseParams.length; ++i) { 142 let param = createParam(parseParams[i]) 143 funcInfo.params.push(param) 144 } 145 146 let retType = parseFuncInfo.returns === '' ? parseFuncInfo.rtnType : parseFuncInfo.returns 147 funcInfo.retType = getJsTypeFromC(retType, parseFuncInfo) 148 149 return funcInfo 150} 151 152function putFuncIntoNamespace(funcInfo, namespaces) { 153 for (var i = 0; i < namespaces.length; ++i) { 154 if (namespaces[i].name === funcInfo.namespace) { 155 namespaces[i].functions.push(funcInfo) 156 return 157 } 158 } 159 NapiLog.logError('The namespace [%s] of function %s is not found.'.format(funcInfo.namespace, funcInfo.name)); 160} 161 162function analyzeRootFunction(rootInfo, parseResult) { 163 let parseFunctions = parseResult.functions 164 for(var i = 0; i < parseFunctions.length; ++i) { 165 let funcInfo = createFuncInfo(parseFunctions[i]) 166 if (parseFunctions[i].namespace != '') { 167 // function in namespace 168 putFuncIntoNamespace(funcInfo, rootInfo.namespaces) 169 } else { 170 // function without namespace, put on root 171 rootInfo.functions.push(funcInfo) 172 } 173 } 174} 175 176function createProperties(parseProperties) { 177 let propertyList = [] 178 for (var i = 0; i < parseProperties.length; ++i) { 179 let property = {} 180 property.name = parseProperties[i].name 181 property.type = getJsTypeFromC(parseProperties[i].raw_type, parseProperties[i]) 182 propertyList.push(property) 183 } 184 return propertyList 185} 186 187function createClassFunctions(parseFuncs) { 188 let funcList = [] 189 for(var i = 0; i < parseFuncs.length; ++i) { 190 let funcInfo = createFuncInfo(parseFuncs[i]) 191 funcInfo.isClassFunc = true 192 funcList.push(funcInfo) 193 } 194 return funcList 195} 196 197function createClassInfo(parseClassInfo) { 198 let classInfo = { 199 "name": "", 200 "namespace": "", 201 "properties": [], 202 "functions": [], 203 "extends":[] 204 } 205 classInfo.name = parseClassInfo.name 206 classInfo.namespace = parseClassInfo.namespace 207 classInfo.properties = createProperties(parseClassInfo.properties.public) 208 classInfo.functions = createClassFunctions(parseClassInfo.methods.public) 209 210 return classInfo 211} 212 213function putClassIntoNamespace(classInfo, namespaces) { 214 for (var i = 0; i < namespaces.length; ++i) { 215 if (namespaces[i].name === classInfo.namespace) { 216 namespaces[i].classes.push(classInfo) 217 return 218 } 219 } 220 NapiLog.logError('The namespace [%s] of class %s is not found.'.format(classInfo.namespace, classInfo.name)); 221} 222 223function analyzeClasses(rootInfo, parseResult) { 224 let parseClasses = parseResult.classes; 225 226 for(var className in parseClasses) { 227 let classInfo = createClassInfo(parseClasses[className]) 228 if (classInfo.namespace != '') { 229 // class in namespace 230 putClassIntoNamespace(classInfo, rootInfo.namespaces) 231 } else { 232 // class without namespace, put on root 233 rootInfo.classes.push(classInfo) 234 } 235 } 236} 237 238function getTab(tabLv) { 239 let tab = "" 240 for(var i = 0; i < tabLv; ++i) { 241 tab += " " 242 } 243 return tab 244} 245 246function genFunction(func, tabLv, needDeclare = false) { 247 let tab = getTab(tabLv) 248 let funcPrefix = func.isClassFunc ? "" : "function " 249 let funcParams = "" 250 for (var i = 0; i < func.params.length; ++i) { 251 funcParams += i > 0 ? ", " : "" 252 funcParams += func.params[i].name + ": " + func.params[i].type 253 } 254 let declareStr = needDeclare ? "declare " : "" 255 return "%s%s%s%s(%s): %s;\n".format(tab, declareStr, funcPrefix, func.name, funcParams, func.retType) 256} 257 258function genClass(classInfo, tabLv, needDeclare = false) { 259 let tab = getTab(tabLv) 260 let declareStr = needDeclare ? "declare " : "" 261 let tsClass = tab + declareStr + "class " + classInfo.name + " {\n" 262 let tab1 = getTab(tabLv+1) 263 for (var i = 0; i < classInfo.properties.length; ++i) { 264 tsClass += "%s%s: %s;\n".format(tab1, classInfo.properties[i].name, classInfo.properties[i].type) 265 } 266 for (var i = 0; i < classInfo.functions.length; ++i) { 267 tsClass += genFunction(classInfo.functions[i], tabLv+1) 268 } 269 tsClass += tab + "}\n" 270 return tsClass 271} 272 273function genNamespace(namespace, tabLv) { 274 let tab = getTab(tabLv) 275 let tsNamespace = tab + "declare namespace %s {\n".format(namespace.name) 276 for(var i = 0; i < namespace.functions.length; ++i) { 277 tsNamespace += genFunction(namespace.functions[i], tabLv+1) 278 } 279 for(var i = 0; i < namespace.classes.length; ++i) { 280 tsNamespace += genClass(namespace.classes[i], tabLv+1) 281 } 282 tsNamespace += tab + "}\n" 283 return tsNamespace 284} 285 286function genTsContent(rootInfo) { 287 let tsContent = rootInfo.needCallback ? "import { AsyncCallback, Callback } from './../basic';\n\n" : "" 288 289 for(var i = 0; i < rootInfo.classes.length; ++i) { 290 tsContent += genClass(rootInfo.classes[i], 0, true) 291 } 292 293 for(var i = 0; i < rootInfo.namespaces.length; ++i) { 294 tsContent += genNamespace(rootInfo.namespaces[i], 0) 295 } 296 297 for(var i = 0; i < rootInfo.functions.length; ++i) { 298 tsContent += genFunction(rootInfo.functions[i], 0, true) 299 } 300 301 if (rootInfo.namespaces.length > 0) { 302 // export the first namespace as default 303 tsContent += "\nexport default %s;".format(rootInfo.namespaces[0].name) 304 } 305 306 return tsContent 307} 308 309function doGenerate(hFilePath, destDir) { 310 let parseResult = parseFileAll(hFilePath) 311 let rootInfo = { 312 "namespaces": [], 313 "classes": [], 314 "functions": [], 315 "needCallback": false 316 } 317 analyzeNameSpace(rootInfo, parseResult) 318 analyzeRootFunction(rootInfo, parseResult) 319 analyzeClasses(rootInfo, parseResult) 320 let hfileName = path.basename(hFilePath, ".h") 321 let tsFilePath = re.pathJoin(destDir, "%s.d.ts".format(hfileName)) 322 let tsContent = genTsContent(rootInfo) 323 writeFile(tsFilePath,tsContent) 324} 325 326module.exports = { 327 doGenerate 328} 329