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 isStringType(cType) { 62 switch(cType) { 63 case 'string': 64 case 'std::string': 65 case 'char': 66 case 'wchar_t': 67 case 'char16_t': 68 case 'char32_t': 69 return true 70 default: 71 return false 72 } 73} 74 75function isBoolType(cType) { 76 if (cType == 'bool') { 77 return true 78 } 79 return false 80} 81 82function isNumberType(cType) { 83 switch(cType) { 84 case 'short': 85 case 'int': 86 case 'uint32_t': 87 case 'size_t': 88 case 'long': 89 case 'long long': 90 case 'float': 91 case 'double': 92 case 'long double': 93 case 'int16_t': 94 case 'uint16_t': 95 case 'int32_t': 96 case 'int64_t': 97 case 'uint64_t': 98 case 'double_t': 99 case 'float_t': 100 return true 101 default: 102 return false 103 } 104} 105 106function basicC2js(cType) { 107 let jsType = "" 108 if (isStringType(cType)) { 109 jsType = 'string' 110 } else if (isBoolType(cType)) { 111 jsType = 'boolean' 112 } else if (isNumberType(cType)) { 113 jsType = 'number' 114 } else { 115 jsType = cType 116 } 117 return jsType 118} 119 120function getJsTypeFromC(cType, typeInfo) { 121 let basicCtype = cType 122 let matchs = re.match("(std::)?vector<([\x21-\x7e ]+)>", basicCtype); 123 if (matchs) { 124 basicCtype = re.getReg(basicCtype, matchs.regs[2]).trim() 125 typeInfo.array = 1 126 } 127 128 let unsignedIdx = basicCtype.indexOf('unsigned') 129 if ( unsignedIdx >= 0) { 130 // cut off the keywords 'unsigned' 131 basicCtype = basicCtype.substring(unsignedIdx + 8, basicCtype.length).trim() 132 } 133 let jsType = basicC2js(basicCtype) 134 if (typeInfo.array) { 135 jsType = "Array<%s>".format(jsType) 136 } 137 return jsType 138} 139 140function createParam(parseParamInfo) { 141 let param = { 142 "name": "", 143 "type": "" 144 } 145 param.name = parseParamInfo.name 146 let rawType = getJsTypeFromC(parseParamInfo.raw_type, parseParamInfo) 147 param.type = rawType 148 149 return param 150} 151 152function createFuncInfo(parseFuncInfo, isClassFunc) { 153 let funcInfo = { 154 "name": "", 155 "params": [], 156 "namespace": "", 157 "retType": "", 158 "static":"" 159 } 160 funcInfo.name = parseFuncInfo.name 161 funcInfo.namespace = parseFuncInfo.namespace 162 let tokenIndex = funcInfo.namespace.indexOf('::') 163 if (tokenIndex >= 0) { 164 // delete '::' in namespace, get the pure space name. 165 funcInfo.namespace = funcInfo.namespace.substring(0, tokenIndex) 166 } 167 168 let parseParams = parseFuncInfo.parameters 169 for(var i = 0; i < parseParams.length; ++i) { 170 let param = createParam(parseParams[i]) 171 funcInfo.params.push(param) 172 } 173 174 funcInfo.isClassFunc = isClassFunc 175 176 if (parseFuncInfo.static && isClassFunc) { 177 funcInfo.static = "static " 178 } 179 let retType = parseFuncInfo.returns === '' ? parseFuncInfo.rtnType : parseFuncInfo.returns 180 funcInfo.retType = getJsTypeFromC(retType, parseFuncInfo) 181 return funcInfo 182} 183 184function putFuncIntoNamespace(funcInfo, namespaces) { 185 for (var i = 0; i < namespaces.length; ++i) { 186 if (namespaces[i].name === funcInfo.namespace) { 187 namespaces[i].functions.push(funcInfo) 188 return 189 } 190 } 191 NapiLog.logError('The namespace [%s] of function %s is not found.'.format(funcInfo.namespace, funcInfo.name)); 192} 193 194function analyzeRootFunction(rootInfo, parseResult) { 195 let parseFunctions = parseResult.functions 196 for(var i = 0; i < parseFunctions.length; ++i) { 197 let funcInfo = createFuncInfo(parseFunctions[i], false) 198 if (parseFunctions[i].namespace != '') { 199 // function in namespace 200 putFuncIntoNamespace(funcInfo, rootInfo.namespaces) 201 } else { 202 // function without namespace, put on root 203 rootInfo.functions.push(funcInfo) 204 } 205 } 206} 207 208function createProperties(parseProperties) { 209 let propertyList = [] 210 for (var i = 0; i < parseProperties.length; ++i) { 211 let property = {} 212 property.name = parseProperties[i].name 213 property.type = getJsTypeFromC(parseProperties[i].raw_type, parseProperties[i]) 214 propertyList.push(property) 215 } 216 return propertyList 217} 218 219function createClassFunctions(parseFuncs) { 220 let funcList = [] 221 for(var i = 0; i < parseFuncs.length; ++i) { 222 let funcInfo = createFuncInfo(parseFuncs[i], true) 223 funcList.push(funcInfo) 224 } 225 return funcList 226} 227 228function createClassInfo(parseClassInfo) { 229 let classInfo = { 230 "name": "", 231 "namespace": "", 232 "properties": [], 233 "functions": [], 234 "extends":[] 235 } 236 classInfo.name = parseClassInfo.name 237 classInfo.namespace = parseClassInfo.namespace 238 classInfo.properties = createProperties(parseClassInfo.properties.public) 239 classInfo.functions = createClassFunctions(parseClassInfo.methods.public) 240 241 return classInfo 242} 243 244function putClassIntoNamespace(classInfo, namespaces) { 245 for (var i = 0; i < namespaces.length; ++i) { 246 if (namespaces[i].name === classInfo.namespace) { 247 namespaces[i].classes.push(classInfo) 248 return 249 } 250 } 251 NapiLog.logError('The namespace [%s] of class %s is not found.'.format(classInfo.namespace, classInfo.name)); 252} 253 254function analyzeClasses(rootInfo, parseResult) { 255 let parseClasses = parseResult.classes; 256 257 for(var className in parseClasses) { 258 let classInfo = createClassInfo(parseClasses[className]) 259 if (classInfo.namespace != '') { 260 // class in namespace 261 putClassIntoNamespace(classInfo, rootInfo.namespaces) 262 } else { 263 // class without namespace, put on root 264 rootInfo.classes.push(classInfo) 265 } 266 } 267} 268 269function getTab(tabLv) { 270 let tab = "" 271 for(var i = 0; i < tabLv; ++i) { 272 tab += " " 273 } 274 return tab 275} 276 277function genFunction(func, tabLv, needDeclare = false) { 278 let tab = getTab(tabLv) 279 let funcPrefix = func.isClassFunc ? "" : "function " 280 let funcParams = "" 281 for (var i = 0; i < func.params.length; ++i) { 282 funcParams += i > 0 ? ", " : "" 283 funcParams += func.params[i].name + ": " + func.params[i].type 284 } 285 let declareStr = needDeclare ? "declare " : "" 286 return "%s%s%s%s%s(%s): %s;\n".format(tab, declareStr, funcPrefix, func.static, func.name, funcParams, func.retType) 287} 288 289function genClass(classInfo, tabLv, needDeclare = false) { 290 let tab = getTab(tabLv) 291 let declareStr = needDeclare ? "declare " : "" 292 let tsClass = tab + declareStr + "class " + classInfo.name + " {\n" 293 let tab1 = getTab(tabLv+1) 294 for (var i = 0; i < classInfo.properties.length; ++i) { 295 tsClass += "%s%s: %s;\n".format(tab1, classInfo.properties[i].name, classInfo.properties[i].type) 296 } 297 for (var i = 0; i < classInfo.functions.length; ++i) { 298 tsClass += genFunction(classInfo.functions[i], tabLv+1) 299 } 300 tsClass += tab + "}\n" 301 return tsClass 302} 303 304function genNamespace(namespace, tabLv) { 305 let tab = getTab(tabLv) 306 let tsNamespace = tab + "declare namespace %s {\n".format(namespace.name) 307 for(var i = 0; i < namespace.functions.length; ++i) { 308 tsNamespace += genFunction(namespace.functions[i], tabLv+1) 309 } 310 for(var i = 0; i < namespace.classes.length; ++i) { 311 tsNamespace += genClass(namespace.classes[i], tabLv+1) 312 } 313 tsNamespace += tab + "}\n" 314 return tsNamespace 315} 316 317function genTsContent(rootInfo) { 318 let tsContent = rootInfo.needCallback ? "import { AsyncCallback, Callback } from './../basic';\n\n" : "" 319 320 for(var i = 0; i < rootInfo.classes.length; ++i) { 321 tsContent += genClass(rootInfo.classes[i], 0, true) 322 } 323 324 for(var i = 0; i < rootInfo.namespaces.length; ++i) { 325 tsContent += genNamespace(rootInfo.namespaces[i], 0) 326 } 327 328 for(var i = 0; i < rootInfo.functions.length; ++i) { 329 tsContent += genFunction(rootInfo.functions[i], 0, true) 330 } 331 332 if (rootInfo.namespaces.length > 0) { 333 // export the first namespace as default 334 tsContent += "\nexport default %s;".format(rootInfo.namespaces[0].name) 335 } 336 337 return tsContent 338} 339 340function doGenerate(hFilePath, destDir) { 341 let parseResult = parseFileAll(hFilePath) 342 let rootInfo = { 343 "namespaces": [], 344 "classes": [], 345 "functions": [], 346 "needCallback": false 347 } 348 analyzeNameSpace(rootInfo, parseResult) 349 analyzeRootFunction(rootInfo, parseResult) 350 analyzeClasses(rootInfo, parseResult) 351 let hfileName = path.basename(hFilePath, ".h") 352 let tsFilePath = re.pathJoin(destDir, "%s.d.ts".format(hfileName)) 353 let tsContent = genTsContent(rootInfo) 354 writeFile(tsFilePath,tsContent) 355} 356 357module.exports = { 358 doGenerate 359} 360