Skip to content

Commit 2eaa34b

Browse files
Feature/foundation (#22)
* [WIP] feat: #18 * [WIP] feat: Function Pointer and protocol fix #18 * add Test file * feat: 解析 API_DEPRECATED_WITH_REPLACEMENT * feat: 支持 API_AVAILABLE 声明常量,支持 NS_SWIFT_NAME 解析 * feat: 增加 attribute 的判断 * feat: support declare typedef function. * feat: typedef struct 内容为函数指针的解析,修复 typeSpecifier 对指针的支持 * feat: delete unused token NS_TYPED_ENUM in lexer. * feat: support generic type with super class. * feat: add more token in direct channel. * feat: support dot in DIRECTIVE_CHANNEL * feat: 并行处理多个文件 #21 * feat: parse class without superclass or extension without category name. * feat: parse function definition with macros. * feat: method return type and arg type can be block or function pointer. * feat: specify required node.js version * fix ci * fix: handle empty script * fix condition * fix block and method type converter * feat: add fromPointer in class * fix: delete string in basic auto return type.
1 parent 1d4538f commit 2eaa34b

18 files changed

+7119
-5249
lines changed

.npmrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
engine-strict=true

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
language: node_js
22
node_js:
3-
- 7
3+
- 12.16
44

55
install:
66
- git clone https://github.com/flutter/flutter.git -b stable --depth 1

bin/codegen.js

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33
const { program } = require('commander')
44
const { execSync } = require('child_process')
5-
var DNObjectiveConverter = require('../lib/objc/DNObjectiveConverter').DNObjectiveConverter
65
const fs = require("fs")
76
const path = require("path")
87
const yaml = require('js-yaml')
8+
const { Worker } = require('worker_threads')
9+
910
var outputDir
1011
var outputPackage
1112
var packageSet = new Set()
@@ -46,25 +47,13 @@ function recFindByExt(base, ext, files, result) {
4647
return result
4748
}
4849

49-
function writeOutputToFileByPath(result, srcPath){
50+
function writeOutputToFileByPath(result, srcPath) {
5051
var srcFile = srcPath.substr(srcPath.lastIndexOf('/') + 1)
5152
var dartFile = srcFile.substring(0, srcFile.indexOf('.')).toLowerCase() + '.dart'
5253
var outputFile = outputDir ? path.join(outputDir, dartFile) : dartFile
5354
fs.writeFileSync(outputFile, result)
5455
}
5556

56-
function callback(result, srcPath, error) {
57-
if (!result) {
58-
return
59-
}
60-
61-
writeOutputToFileByPath(result.dartCode, srcPath)
62-
63-
if(outputPackage) {
64-
result.packages.forEach(item => packageSet.add(item))
65-
}
66-
}
67-
6857
function formatDartFile(dartPath) {
6958
var command = 'flutter format ' + path.dirname(dartPath)
7059
execSync(command, { stdio: 'inherit' })
@@ -78,13 +67,45 @@ function createFlutterPackage(packageName) {
7867
function writeDependencyToPubSpec(filePath) {
7968
var doc = yaml.safeLoad(fs.readFileSync(filePath, 'utf8'));
8069
packageSet.forEach(item => {
81-
if(typeof(item) == "undefined") {
70+
if (typeof (item) == "undefined") {
8271
return
8372
}
8473
item = item.toLowerCase()
85-
doc.dependencies[item] = { path : item}
74+
doc.dependencies[item] = { path: item }
75+
})
76+
fs.writeFileSync(filePath, yaml.safeDump(doc).replace(/null/g, ''))
77+
}
78+
79+
function generateDartWithWorker(path, script) {
80+
return new Promise((resolve, reject) => {
81+
const worker = new Worker(script, {
82+
workerData: { path: path },
83+
resourceLimits: { maxOldGenerationSizeMb: 8 * 1024 }
84+
});
85+
worker.on("message", resolve);
86+
worker.on("error", reject);
87+
});
88+
};
89+
90+
async function runWorkItems(workItems) {
91+
const promises = Object.keys(workItems).map((path) => {
92+
let script = workItems[path]
93+
return generateDartWithWorker(path, script).then((msg) => {
94+
if (msg.error) {
95+
console.log('filePath:' + msg.path + '\nerror:' + msg.error)
96+
}
97+
let result = msg.result
98+
if (!result) {
99+
return
100+
}
101+
writeOutputToFileByPath(result.dartCode, msg.path)
102+
103+
if (outputPackage) {
104+
result.packages.forEach(item => packageSet.add(item))
105+
}
106+
})
86107
})
87-
fs.writeFileSync(filePath, yaml.safeDump(doc).replace(/null/g,''))
108+
await Promise.all(promises)
88109
}
89110

90111
program.version('1.0.2')
@@ -95,19 +116,20 @@ program
95116
.option('-o, --output <output>', 'Output directory')
96117
.option('-p, --package <package>', 'Generate a shareable Flutter project containing modular Dart code.')
97118
.description('Generate dart code from native API.')
98-
.action(function (input, options) {
119+
.action(async function (input, options) {
99120
language = options.language
100121
if (!language) {
101122
language = 'auto'
102123
}
103124

104-
var extMap = {'objc': ['h'], 'java': ['java'], 'auto': ['h', 'java']}
125+
var extMap = { 'objc': ['h'], 'java': ['java'], 'auto': ['h', 'java'] }
105126
var extArray = extMap[language]
106127

107128
outputDir = options.output
108-
if (outputDir) {
109-
mkdirs(outputDir)
129+
if (!outputDir) {
130+
outputDir = process.cwd()
110131
}
132+
mkdirs(outputDir)
111133

112134
outputPackage = options.package
113135
if (outputPackage) {
@@ -119,24 +141,28 @@ program
119141
console.log('Output Dir: ' + outputDir)
120142

121143
var baseOutputDir = outputDir
144+
145+
const langForExtension = { 'h': 'objc', 'java': 'java' }
146+
// TODO: handle java here
147+
const scriptForExtension = { 'h': path.join(__dirname, '../lib/objc/DNObjectiveCConverter.js') }
148+
149+
var workItems = new Map()
122150
extArray.forEach((ext) => {
123-
var files = recFindByExt(input, ext)
124-
if (files.length == 0) {
151+
let files = recFindByExt(input, ext)
152+
let script = scriptForExtension[ext]
153+
if (files.length == 0 || !script) {
125154
return
126155
}
127-
var extToLang = {'h': 'objc', 'java': 'java'}
128-
outputDir = path.join(baseOutputDir, extToLang[ext])
156+
157+
outputDir = path.join(baseOutputDir, langForExtension[ext])
129158
mkdirs(outputDir)
130159

131160
files.forEach((file) => {
132-
console.log('processing ' + file)
133-
if (ext == 'h') {
134-
new DNObjectiveConverter(file, callback)
135-
} else if (ext == 'java') {
136-
// TODO: handle java
137-
}
161+
workItems[file] = script;
138162
})
139163
})
164+
await runWorkItems(workItems)
165+
140166
outputDir = baseOutputDir
141167
formatDartFile(outputDir)
142168

index.js

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,13 @@
1-
var DNObjectiveConverter = require('./lib/objc/DNObjectiveConverter').DNObjectiveConverter
1+
let workerScript = './lib/objc/DNObjectiveCConverter'
2+
let dataPath = "./test/objc/RuntimeStub.h"
3+
let main = require(workerScript).main
24

3-
new DNObjectiveConverter("./test/objc/RuntimeStub.h", callback)
5+
main(dataPath, callback)
46

57
function callback(result, path, error) {
6-
console.log('result:\n' + result.dartCode + '\n\npath:\n' + path)
8+
if (result) {
9+
console.log('result:\n' + result.dartCode + '\n\npath:\n' + path)
10+
}
711
if (error) {
812
console.log('\nerror:\n' + error)
913
}

lib/objc/DNObjectiveCContext.js

Lines changed: 45 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -149,11 +149,14 @@ class DNEnumDefContext extends DNContext {
149149
}
150150

151151
class DNArgumentContext extends DNContext {
152-
constructor(internal, name, type) {
152+
constructor(internal) {
153153
super(internal)
154-
this.name = name
155-
this.type = type
156-
this.anonDef = null //user for block arguement
154+
// For keywordDeclarator
155+
if (internal.name && internal.types) {
156+
this.name = internal.name.start.text
157+
}
158+
this.type = null
159+
this.isBlock = false //user for block arguement
157160
this.isNullable = false
158161
this.isOutParam = false
159162
}
@@ -166,7 +169,7 @@ class DNMethodContext extends DNContext {
166169
this.names = []
167170
this.args = []
168171
this.returnType = null
169-
this.retValIsObj = false
172+
this.callFromPointer = false
170173
this.isClassMethod = false
171174
this.macros = []
172175
this.availability = []
@@ -232,12 +235,12 @@ class DNMethodContext extends DNContext {
232235
var rawRetType = this.rawGenericType(this.returnType) //remove <> symbol
233236
var isMutableRetType = DNObjectiveCTypeConverter.SupportMutableTypes.indexOf(rawRetType) > -1
234237

235-
if (!isMutableRetType && !this.retValIsObj) {
238+
if (!isMutableRetType && !this.callFromPointer) {
236239
return (this.returnType == 'void' ? '' : 'return') + impl
237240
}
238241

239242
var newImpl = 'Pointer<Void> result = ' + impl.replace(');\n', '') + ' ,decodeRetVal: false);\n'
240-
if (this.retValIsObj) {
243+
if (this.callFromPointer) {
241244
var supportType = DNObjectiveCTypeConverter.DNDartToOCMap[rawRetType]
242245
if (supportType) {
243246
newImpl += ' return ' + supportType + '.fromPointer(result).raw;\n'
@@ -279,7 +282,7 @@ class DNMethodContext extends DNContext {
279282
nullableArgs.push(element)
280283
} else {
281284
var argType = element.isOutParam ? 'NSObjectRef<' + element.type + '>' : element.type
282-
var arg = element.anonDef ? element.anonDef : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
285+
var arg = element.isBlock ? argType : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
283286
argList += arg + (index == this.args.length - 1 && nullableArgs.length == 0 ? '' : ', ')
284287
}
285288
})
@@ -288,7 +291,7 @@ class DNMethodContext extends DNContext {
288291
argList += '{'
289292
nullableArgs.forEach((element, index) => {
290293
var argType = element.isOutParam ? 'NSObjectRef<' + element.type + '>' : element.type
291-
var arg = element.anonDef ? element.anonDef : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
294+
var arg = element.isBlock ? argType : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
292295
argList += arg + (index == nullableArgs.length - 1 ? '' : ', ')
293296
})
294297
argList += '}'
@@ -366,6 +369,12 @@ class DNPropertyContext extends DNContext {
366369
}
367370

368371
parse() {
372+
if (!this.name) {
373+
return ''
374+
}
375+
if (!this.type) {
376+
this.type = 'dynamic'
377+
}
369378
var annotation = ' ' + this.availability.map((a) => a.parse()).join(' ') + '\n'
370379
var get = annotation + ' ' + this.type + ' get ' + this.name +
371380
' => perform(\'' + this.name + '\'.toSEL());'
@@ -383,7 +392,10 @@ class DNProtocolContext extends DNContext {
383392
this.name = internal.name.start.text
384393
this.properties = []
385394
this.methods = []
386-
this.protocols = []
395+
let protocols = internal.protocols
396+
this.protocols = protocols ? protocols.list.map((p) => {
397+
return p.name.start.text
398+
}) : []
387399
this.macros = []
388400
this.availability = []
389401
}
@@ -410,23 +422,32 @@ class DNClassContext extends DNContext {
410422
constructor(internal) {
411423
super(internal)
412424
this.name = internal.className.start.text
413-
this.superClass = internal.superclassName.start.text
425+
if (internal.superclassName) {
426+
this.superClass = internal.superclassName.start.text
427+
}
414428
this.properties = []
415429
this.methods = []
416-
this.protocols = []
430+
let protocols = internal.protocols
431+
this.protocols = protocols ? protocols.list.map((p) => {
432+
return p.name.start.text
433+
}) : []
417434
this.macros = []
418435
this.availability = []
419436
}
420437

421438
parse() {
422439
this.preMarkConstructMethods()
423440
var result = this.availability.map((a) => a.parse()).join(' ') + '\n'
424-
result += '@native\nclass ' + this.name + ' extends ' + this.superClass
441+
result += '@native\nclass ' + this.name
442+
if (this.superClass) {
443+
result += ' extends ' + this.superClass
444+
}
425445
if (typeof this.protocols !== 'undefined' && this.protocols.length > 0) {
426446
result += ' with ' + this.protocols.join(',')
427447
}
428448
result += ' {\n'
429449
result += ' ' + this.name + '([Class isa]) : super(Class(\'' + this.name + '\'));\n'
450+
result += ' ' + this.name + '.fromPointer(Pointer<Void> ptr) : super.fromPointer(ptr);\n'
430451
this.properties.forEach(element => {
431452
var parseRet = element.parse()
432453
result += parseRet ? parseRet + '\n' : ''
@@ -463,15 +484,22 @@ class DNClassContext extends DNContext {
463484
class DNCategoryContext extends DNContext {
464485
constructor(internal) {
465486
super(internal)
466-
this.name = internal.children[1].start.text
467-
this.host = internal.children[3].start.text
487+
this.host = internal.className.start.text
488+
if (internal.categoryName) {
489+
this.name = internal.categoryName.start.text
490+
} else {
491+
this.name = 'DartNative'
492+
}
468493
this.properties = []
469494
this.methods = []
470-
this.protocols = []
495+
let protocols = internal.protocols
496+
this.protocols = protocols ? protocols.list.map((p) => {
497+
return p.name.start.text
498+
}) : []
471499
}
472500

473501
parse() {
474-
var result = 'extension ' + this.name + this.host + ' on ' + this.name
502+
var result = 'extension ' + this.host + this.name + ' on ' + this.host
475503
result += ' {\n'
476504
this.properties.forEach(element => {
477505
result += element.parse() + '\n'

lib/objc/DNObjectiveCConverter.js

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
let antlr4 = require('antlr4')
2+
let rf = require("fs")
3+
let ObjectiveCLexer = require('../../parser/objc/ObjectiveCLexer').ObjectiveCLexer
4+
let ObjectiveCParser = require('../../parser/objc/ObjectiveCParser').ObjectiveCParser
5+
let DNObjectiveCParserListener = require('./DNObjectiveCParserListener').DNObjectiveCParserListener
6+
let ConsoleErrorListener = require('antlr4/error/ErrorListener').ConsoleErrorListener
7+
const { parentPort, workerData } = require('worker_threads')
8+
9+
function main(path, cb) {
10+
if (!path) {
11+
if (!workerData) {
12+
return
13+
}
14+
path = workerData.path
15+
}
16+
if (!cb) {
17+
cb = callback
18+
}
19+
console.log('processing: ' + path)
20+
try {
21+
const content = rf.readFileSync(path, "utf-8")
22+
const chars = new antlr4.InputStream(content)
23+
const lexer = new ObjectiveCLexer(chars)
24+
const errorListener = new ConsoleErrorListener()
25+
lexer.addErrorListener(errorListener)
26+
27+
const tokens = new antlr4.CommonTokenStream(lexer)
28+
const parser = new ObjectiveCParser(tokens)
29+
parser.addErrorListener(errorListener)
30+
const tree = parser.translationUnit()
31+
const listener = new DNObjectiveCParserListener(cb, path)
32+
antlr4.tree.ParseTreeWalker.DEFAULT.walk(listener, tree)
33+
} catch (e) {
34+
cb(null, path, e)
35+
}
36+
}
37+
38+
function callback(result, path, error) {
39+
// Send a message to the main thread.
40+
parentPort.postMessage({result: result, path: path, error: error});
41+
}
42+
43+
main()
44+
45+
exports.main = main

0 commit comments

Comments
 (0)