Skip to content

Commit

Permalink
Feature/foundation (#22)
Browse files Browse the repository at this point in the history
* [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.
  • Loading branch information
yulingtianxia authored Jun 15, 2020
1 parent 1d4538f commit 2eaa34b
Show file tree
Hide file tree
Showing 18 changed files with 7,119 additions and 5,249 deletions.
1 change: 1 addition & 0 deletions .npmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
engine-strict=true
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
language: node_js
node_js:
- 7
- 12.16

install:
- git clone https://github.com/flutter/flutter.git -b stable --depth 1
Expand Down
88 changes: 57 additions & 31 deletions bin/codegen.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

const { program } = require('commander')
const { execSync } = require('child_process')
var DNObjectiveConverter = require('../lib/objc/DNObjectiveConverter').DNObjectiveConverter
const fs = require("fs")
const path = require("path")
const yaml = require('js-yaml')
const { Worker } = require('worker_threads')

var outputDir
var outputPackage
var packageSet = new Set()
Expand Down Expand Up @@ -46,25 +47,13 @@ function recFindByExt(base, ext, files, result) {
return result
}

function writeOutputToFileByPath(result, srcPath){
function writeOutputToFileByPath(result, srcPath) {
var srcFile = srcPath.substr(srcPath.lastIndexOf('/') + 1)
var dartFile = srcFile.substring(0, srcFile.indexOf('.')).toLowerCase() + '.dart'
var outputFile = outputDir ? path.join(outputDir, dartFile) : dartFile
fs.writeFileSync(outputFile, result)
}

function callback(result, srcPath, error) {
if (!result) {
return
}

writeOutputToFileByPath(result.dartCode, srcPath)

if(outputPackage) {
result.packages.forEach(item => packageSet.add(item))
}
}

function formatDartFile(dartPath) {
var command = 'flutter format ' + path.dirname(dartPath)
execSync(command, { stdio: 'inherit' })
Expand All @@ -78,13 +67,45 @@ function createFlutterPackage(packageName) {
function writeDependencyToPubSpec(filePath) {
var doc = yaml.safeLoad(fs.readFileSync(filePath, 'utf8'));
packageSet.forEach(item => {
if(typeof(item) == "undefined") {
if (typeof (item) == "undefined") {
return
}
item = item.toLowerCase()
doc.dependencies[item] = { path : item}
doc.dependencies[item] = { path: item }
})
fs.writeFileSync(filePath, yaml.safeDump(doc).replace(/null/g, ''))
}

function generateDartWithWorker(path, script) {
return new Promise((resolve, reject) => {
const worker = new Worker(script, {
workerData: { path: path },
resourceLimits: { maxOldGenerationSizeMb: 8 * 1024 }
});
worker.on("message", resolve);
worker.on("error", reject);
});
};

async function runWorkItems(workItems) {
const promises = Object.keys(workItems).map((path) => {
let script = workItems[path]
return generateDartWithWorker(path, script).then((msg) => {
if (msg.error) {
console.log('filePath:' + msg.path + '\nerror:' + msg.error)
}
let result = msg.result
if (!result) {
return
}
writeOutputToFileByPath(result.dartCode, msg.path)

if (outputPackage) {
result.packages.forEach(item => packageSet.add(item))
}
})
})
fs.writeFileSync(filePath, yaml.safeDump(doc).replace(/null/g,''))
await Promise.all(promises)
}

program.version('1.0.2')
Expand All @@ -95,19 +116,20 @@ program
.option('-o, --output <output>', 'Output directory')
.option('-p, --package <package>', 'Generate a shareable Flutter project containing modular Dart code.')
.description('Generate dart code from native API.')
.action(function (input, options) {
.action(async function (input, options) {
language = options.language
if (!language) {
language = 'auto'
}

var extMap = {'objc': ['h'], 'java': ['java'], 'auto': ['h', 'java']}
var extMap = { 'objc': ['h'], 'java': ['java'], 'auto': ['h', 'java'] }
var extArray = extMap[language]

outputDir = options.output
if (outputDir) {
mkdirs(outputDir)
if (!outputDir) {
outputDir = process.cwd()
}
mkdirs(outputDir)

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

var baseOutputDir = outputDir

const langForExtension = { 'h': 'objc', 'java': 'java' }
// TODO: handle java here
const scriptForExtension = { 'h': path.join(__dirname, '../lib/objc/DNObjectiveCConverter.js') }

var workItems = new Map()
extArray.forEach((ext) => {
var files = recFindByExt(input, ext)
if (files.length == 0) {
let files = recFindByExt(input, ext)
let script = scriptForExtension[ext]
if (files.length == 0 || !script) {
return
}
var extToLang = {'h': 'objc', 'java': 'java'}
outputDir = path.join(baseOutputDir, extToLang[ext])

outputDir = path.join(baseOutputDir, langForExtension[ext])
mkdirs(outputDir)

files.forEach((file) => {
console.log('processing ' + file)
if (ext == 'h') {
new DNObjectiveConverter(file, callback)
} else if (ext == 'java') {
// TODO: handle java
}
workItems[file] = script;
})
})
await runWorkItems(workItems)

outputDir = baseOutputDir
formatDartFile(outputDir)

Expand Down
10 changes: 7 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
var DNObjectiveConverter = require('./lib/objc/DNObjectiveConverter').DNObjectiveConverter
let workerScript = './lib/objc/DNObjectiveCConverter'
let dataPath = "./test/objc/RuntimeStub.h"
let main = require(workerScript).main

new DNObjectiveConverter("./test/objc/RuntimeStub.h", callback)
main(dataPath, callback)

function callback(result, path, error) {
console.log('result:\n' + result.dartCode + '\n\npath:\n' + path)
if (result) {
console.log('result:\n' + result.dartCode + '\n\npath:\n' + path)
}
if (error) {
console.log('\nerror:\n' + error)
}
Expand Down
62 changes: 45 additions & 17 deletions lib/objc/DNObjectiveCContext.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,11 +149,14 @@ class DNEnumDefContext extends DNContext {
}

class DNArgumentContext extends DNContext {
constructor(internal, name, type) {
constructor(internal) {
super(internal)
this.name = name
this.type = type
this.anonDef = null //user for block arguement
// For keywordDeclarator
if (internal.name && internal.types) {
this.name = internal.name.start.text
}
this.type = null
this.isBlock = false //user for block arguement
this.isNullable = false
this.isOutParam = false
}
Expand All @@ -166,7 +169,7 @@ class DNMethodContext extends DNContext {
this.names = []
this.args = []
this.returnType = null
this.retValIsObj = false
this.callFromPointer = false
this.isClassMethod = false
this.macros = []
this.availability = []
Expand Down Expand Up @@ -232,12 +235,12 @@ class DNMethodContext extends DNContext {
var rawRetType = this.rawGenericType(this.returnType) //remove <> symbol
var isMutableRetType = DNObjectiveCTypeConverter.SupportMutableTypes.indexOf(rawRetType) > -1

if (!isMutableRetType && !this.retValIsObj) {
if (!isMutableRetType && !this.callFromPointer) {
return (this.returnType == 'void' ? '' : 'return') + impl
}

var newImpl = 'Pointer<Void> result = ' + impl.replace(');\n', '') + ' ,decodeRetVal: false);\n'
if (this.retValIsObj) {
if (this.callFromPointer) {
var supportType = DNObjectiveCTypeConverter.DNDartToOCMap[rawRetType]
if (supportType) {
newImpl += ' return ' + supportType + '.fromPointer(result).raw;\n'
Expand Down Expand Up @@ -279,7 +282,7 @@ class DNMethodContext extends DNContext {
nullableArgs.push(element)
} else {
var argType = element.isOutParam ? 'NSObjectRef<' + element.type + '>' : element.type
var arg = element.anonDef ? element.anonDef : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
var arg = element.isBlock ? argType : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
argList += arg + (index == this.args.length - 1 && nullableArgs.length == 0 ? '' : ', ')
}
})
Expand All @@ -288,7 +291,7 @@ class DNMethodContext extends DNContext {
argList += '{'
nullableArgs.forEach((element, index) => {
var argType = element.isOutParam ? 'NSObjectRef<' + element.type + '>' : element.type
var arg = element.anonDef ? element.anonDef : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
var arg = element.isBlock ? argType : this.convertMutableTypeIfNeed(argType) + ' ' + element.name
argList += arg + (index == nullableArgs.length - 1 ? '' : ', ')
})
argList += '}'
Expand Down Expand Up @@ -366,6 +369,12 @@ class DNPropertyContext extends DNContext {
}

parse() {
if (!this.name) {
return ''
}
if (!this.type) {
this.type = 'dynamic'
}
var annotation = ' ' + this.availability.map((a) => a.parse()).join(' ') + '\n'
var get = annotation + ' ' + this.type + ' get ' + this.name +
' => perform(\'' + this.name + '\'.toSEL());'
Expand All @@ -383,7 +392,10 @@ class DNProtocolContext extends DNContext {
this.name = internal.name.start.text
this.properties = []
this.methods = []
this.protocols = []
let protocols = internal.protocols
this.protocols = protocols ? protocols.list.map((p) => {
return p.name.start.text
}) : []
this.macros = []
this.availability = []
}
Expand All @@ -410,23 +422,32 @@ class DNClassContext extends DNContext {
constructor(internal) {
super(internal)
this.name = internal.className.start.text
this.superClass = internal.superclassName.start.text
if (internal.superclassName) {
this.superClass = internal.superclassName.start.text
}
this.properties = []
this.methods = []
this.protocols = []
let protocols = internal.protocols
this.protocols = protocols ? protocols.list.map((p) => {
return p.name.start.text
}) : []
this.macros = []
this.availability = []
}

parse() {
this.preMarkConstructMethods()
var result = this.availability.map((a) => a.parse()).join(' ') + '\n'
result += '@native\nclass ' + this.name + ' extends ' + this.superClass
result += '@native\nclass ' + this.name
if (this.superClass) {
result += ' extends ' + this.superClass
}
if (typeof this.protocols !== 'undefined' && this.protocols.length > 0) {
result += ' with ' + this.protocols.join(',')
}
result += ' {\n'
result += ' ' + this.name + '([Class isa]) : super(Class(\'' + this.name + '\'));\n'
result += ' ' + this.name + '.fromPointer(Pointer<Void> ptr) : super.fromPointer(ptr);\n'
this.properties.forEach(element => {
var parseRet = element.parse()
result += parseRet ? parseRet + '\n' : ''
Expand Down Expand Up @@ -463,15 +484,22 @@ class DNClassContext extends DNContext {
class DNCategoryContext extends DNContext {
constructor(internal) {
super(internal)
this.name = internal.children[1].start.text
this.host = internal.children[3].start.text
this.host = internal.className.start.text
if (internal.categoryName) {
this.name = internal.categoryName.start.text
} else {
this.name = 'DartNative'
}
this.properties = []
this.methods = []
this.protocols = []
let protocols = internal.protocols
this.protocols = protocols ? protocols.list.map((p) => {
return p.name.start.text
}) : []
}

parse() {
var result = 'extension ' + this.name + this.host + ' on ' + this.name
var result = 'extension ' + this.host + this.name + ' on ' + this.host
result += ' {\n'
this.properties.forEach(element => {
result += element.parse() + '\n'
Expand Down
45 changes: 45 additions & 0 deletions lib/objc/DNObjectiveCConverter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
let antlr4 = require('antlr4')
let rf = require("fs")
let ObjectiveCLexer = require('../../parser/objc/ObjectiveCLexer').ObjectiveCLexer
let ObjectiveCParser = require('../../parser/objc/ObjectiveCParser').ObjectiveCParser
let DNObjectiveCParserListener = require('./DNObjectiveCParserListener').DNObjectiveCParserListener
let ConsoleErrorListener = require('antlr4/error/ErrorListener').ConsoleErrorListener
const { parentPort, workerData } = require('worker_threads')

function main(path, cb) {
if (!path) {
if (!workerData) {
return
}
path = workerData.path
}
if (!cb) {
cb = callback
}
console.log('processing: ' + path)
try {
const content = rf.readFileSync(path, "utf-8")
const chars = new antlr4.InputStream(content)
const lexer = new ObjectiveCLexer(chars)
const errorListener = new ConsoleErrorListener()
lexer.addErrorListener(errorListener)

const tokens = new antlr4.CommonTokenStream(lexer)
const parser = new ObjectiveCParser(tokens)
parser.addErrorListener(errorListener)
const tree = parser.translationUnit()
const listener = new DNObjectiveCParserListener(cb, path)
antlr4.tree.ParseTreeWalker.DEFAULT.walk(listener, tree)
} catch (e) {
cb(null, path, e)
}
}

function callback(result, path, error) {
// Send a message to the main thread.
parentPort.postMessage({result: result, path: path, error: error});
}

main()

exports.main = main
Loading

0 comments on commit 2eaa34b

Please sign in to comment.