Skip to content

Feature/foundation #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 23 commits into from
Jun 15, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
3dc153d
[WIP] feat: https://github.com/dart-native/codegen/issues/18
yulingtianxia Jun 2, 2020
7e9c3b2
[WIP] feat: Function Pointer and protocol fix
yulingtianxia Jun 4, 2020
5321a7a
add Test file
yulingtianxia Jun 5, 2020
1fc0126
feat: 解析 API_DEPRECATED_WITH_REPLACEMENT
yulingtianxia Jun 10, 2020
6a94ab9
feat: 支持 API_AVAILABLE 声明常量,支持 NS_SWIFT_NAME 解析
yulingtianxia Jun 10, 2020
5fab033
feat: 增加 attribute 的判断
yulingtianxia Jun 10, 2020
c2a30cd
feat: support declare typedef function.
yulingtianxia Jun 11, 2020
1c28497
feat: typedef struct 内容为函数指针的解析,修复 typeSpecifier 对指针的支持
yulingtianxia Jun 11, 2020
1724654
feat: delete unused token NS_TYPED_ENUM in lexer.
yulingtianxia Jun 11, 2020
116203d
feat: support generic type with super class.
yulingtianxia Jun 11, 2020
aa90e69
feat: add more token in direct channel.
yulingtianxia Jun 12, 2020
02ff4a3
feat: support dot in DIRECTIVE_CHANNEL
yulingtianxia Jun 12, 2020
5245ba2
feat: 并行处理多个文件
yulingtianxia Jun 13, 2020
ffefd51
feat: parse class without superclass or extension without category name.
yulingtianxia Jun 13, 2020
a00b6ac
feat: parse function definition with macros.
yulingtianxia Jun 13, 2020
24b4e17
feat: method return type and arg type can be block or function pointer.
yulingtianxia Jun 14, 2020
bcc5b34
feat: specify required node.js version
yulingtianxia Jun 14, 2020
459242c
fix ci
yulingtianxia Jun 14, 2020
1f496f6
fix: handle empty script
yulingtianxia Jun 14, 2020
8d1ad55
fix condition
yulingtianxia Jun 14, 2020
73a54bc
fix block and method type converter
yulingtianxia Jun 14, 2020
49fded8
feat: add fromPointer in class
yulingtianxia Jun 14, 2020
3629755
fix: delete string in basic auto return type.
yulingtianxia Jun 14, 2020
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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