• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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