widl-nan
Version:
Auto generate native C++ addon source code by parsing Web IDL definition
331 lines (282 loc) • 9.66 kB
JavaScript
// Copyright (c) 2016 Intel Corporation. All rights reserved.
// Use of this source code is governed by a MIT-style license that can be
// found in the LICENSE file.
;
const fs = require('fs.promised');
const glob = require('glob');
const mkdirp = require('mkdirp');
const path = require('path');
const webIDL2 = require('webidl2');
const enumGenerator = require('./lib/cpp-enum.js');
const dot = require('dot');
dot.templateSettings.strip = false; // Do not remove spaces & linebreaks
dot.log = process.env.WIDL_NAN_VERBOSE || false;
const dots = dot.process({path: path.join(__dirname, 'templates')});
const _writeFile = function(name, text) {
return fs.writeFile(name, text);
};
const _packEmptyLines = function(str) {
/* eslint-disable indent */
return str.replace(/([ \t]*\n){3,}/g, '\n\n') // multiple blank lines into one
.replace(/public:\n\n/g, 'public:\n') // remove blank line after public:
.replace(/private:\n\n/g, 'private:\n') // remove blank line after private:
.replace(/\n\n};/g, '\n};') // remove blank line before };
.replace(/{\n\n/g, '{\n'); // remove blank line after {
/* eslint-enable indent */
};
const _parseIDL = function(idlText) {
return webIDL2.parse(idlText);
};
const _preprocessOverload = function(def) {
var map = {};
var arr = [];
def.members.forEach(member => {
if (!member.stringifier && member.type === 'operation') {
arr.push(member);
} // if (real operation)
}); // After this array is built
arr.forEach((member, idx) => {
var existing = map[member.name];
if (existing) {
existing.push({name: member.name, index: idx});
} else {
map[member.name] = [{name: member.name, index: idx}];
} // if-else (existing)
}); // map is built
def.operationMap = map;
};
const _addOrAppend = function(param) {
var map = param.map;
var key = param.key;
var value = param.value;
if (map[key]) {
var valueOrArray = map[key];
if (Array.isArray(valueOrArray)) {
valueOrArray.push(value);
} else {
map[key] = [valueOrArray, value];
}
} else {
map[key] = value;
}
};
const _preprocess = function(that) {
var typeMap = that.typeMap;
that.idlStore.forEach(idl => {
idl.tree = _parseIDL(idl.text);
idl.tree.forEach(def => {
if (def.type === 'interface') {
_preprocessOverload(def);
_addOrAppend({map: typeMap, key: def.name, value: def});
} else if (def.type === 'enum') {
_addOrAppend({map: typeMap, key: def.name, value: def});
} else if (def.type === 'callback') {
_addOrAppend({map: typeMap, key: def.name, value: def});
} else if (def.type === 'dictionary') {
_addOrAppend({map: typeMap, key: def.name, value: def});
}
});
});
that.idlStore.forEach(idl => {
idl.tree.forEach(def => {
def.refTypeMap = typeMap;
});
});
};
const _classNameToCppFilename = function(className) {
// Following https://google.github.io/styleguide/cppguide.html#File_Names
// If interface name is MyTestInterface, the C++ file name will be my_test_interface.cpp|h
return className.replace(/((?!^)[A-Z][a-z0-9]+)/g, '_$1').toLowerCase();
};
const _genWrapperHeaderName = function(def) {
return 'nan__' + _classNameToCppFilename(def.name) + '.h';
};
const _genWrapperCppName = function(def) {
return 'nan__' + _classNameToCppFilename(def.name) + '.cpp';
};
const _genImplHeaderName = function(def) {
return _classNameToCppFilename(def.name) + '.h';
};
const _genImplCppName = function(def) {
return _classNameToCppFilename(def.name) + '.cpp';
};
const _useImpl = function(def) {
var constantCounter = 0;
for (var i = 0; i < def.members.length; ++i) {
const member = def.members[i];
if (member.type === 'operation' || member.type === 'attribute') {
return true;
} else if (member.type === 'const') {
++constantCounter;
}
}
return constantCounter < def.members.length;
};
const widl2NanGenerator = function() {
const _readFile = function(path) {
return fs.readFile(path);
};
const defaultOption = {
targetDir: 'gen'
};
var generator = {
option: defaultOption,
idlStore: [],
typeMap: {}
};
generator.reset = function() {
this.option = defaultOption;
this.idlStore = [];
this.typeMap = {};
};
generator.setOption = function(option) {
this.option = option || {};
};
generator.addFile = function(fileName) {
return _readFile(fileName)
.then(data => {
this.addText(data.toString(), fileName);
});
};
generator.scanDir = function(dirName) {
return new Promise(function(resolve, reject) {
// options is optional
glob(path.join(dirName, '*.widl'), /* options, */ function(er, files) {
// deal with files
// files is an array of filenames.
// If the `nonull` option is set, and nothing
// was found, then files is ['**/*.js']
// er is an error object or null.
resolve('');
});
// glob(path.join(dirName, '*.h'), /*options, */function (er, files) {
// // TODO: use enumGenerator to deal with files
// });
});
};
generator.addText = function(str, name) {
this.idlStore.push({
name: name,
text: str,
tree: {},
classes: [],
wrapperH: [],
wrapperCpp: [],
dictH: [],
implH: [],
implCpp: []
});
};
generator.addCppEnum = function(path) {
const that = this;
const callback = function(fileName, idlText) {
that.addText(idlText, fileName);
};
return new Promise(function(resolve, reject) {
enumGenerator.addFile(path)
.then(function() {
enumGenerator.compile();
enumGenerator.writeTo(callback);
resolve(that.idlStore);
})
.catch(e => {
reject(e);
});
});
};
generator.compile = function() {
this.idlStore.forEach(idl => {
idl.tree = _parseIDL(idl.text);
});
_preprocess(this);
this.idlStore.forEach(idl => {
idl.tree.forEach(def => {
if (def.type === 'interface') {
idl.classes.push(def.name);
idl.wrapperH.push({
name: _genWrapperHeaderName(def),
text: _packEmptyLines(dots.nanCxxHeader(def))
});
idl.wrapperCpp.push({
name: _genWrapperCppName(def),
text: _packEmptyLines(dots.nanCxxImpl(def))
});
if (_useImpl(def)) {
idl.implH.push({
name: _genImplHeaderName(def),
text: _packEmptyLines(dots.implHeader(def))
});
idl.implCpp.push({
name: _genImplCppName(def),
text: _packEmptyLines(dots.implCpp(def))
});
}
} else if (def.type === 'exception') {
} else if (def.type === 'enum') {
} else if (def.type === 'callback') {
} else if (def.type === 'dictionary') {
idl.dictH.push({
name: _genImplHeaderName(def),
text: _packEmptyLines(dots.dictionaryHeader(def))
});
}
});
});
};
generator.writeToDir = function(dirName) {
this.option.targetDir = dirName;
var dirNameSkeleton = path.join(dirName, 'dont-build');
mkdirp.sync(dirName);
mkdirp.sync(dirNameSkeleton);
var all = [];
const write = function(array) {
array.forEach(item => {
all.push(_writeFile(path.join(dirName, item.name), item.text));
});
};
const writeImpl = function(array) {
array.forEach(item => {
all.push(_writeFile(path.join(dirNameSkeleton, item.name), item.text));
});
};
this.idlStore.forEach(idl => {
write(idl.wrapperH);
write(idl.wrapperCpp);
write(idl.dictH);
writeImpl(idl.implH);
writeImpl(idl.implCpp);
});
// generate some helper files
all.push(_writeFile(path.join(dirName, 'generator_helper.h'),
_packEmptyLines(dots.helperHeader({}))));
all.push(_writeFile(path.join(dirName, 'widl_dictionary_helper.h'),
_packEmptyLines(dots.helperHeaderWIDLDictionaryBase({}))));
all.push(_writeFile(path.join(dirName, 'promise-helper.h'),
_packEmptyLines(dots.helperHeaderPromise({}))));
all.push(_writeFile(path.join(dirName, 'array_helper.h'),
_packEmptyLines(dots.helperHeaderArray({}))));
all.push(_writeFile(path.join(dirName, 'thread-event-helper.h'),
_packEmptyLines(dots.helperHeaderThreadEvent({}))));
all.push(_writeFile(path.join(dirName, 'thread-event-helper.cpp'),
_packEmptyLines(dots.helperHeaderThreadEventCpp({}))));
all.push(_writeFile(path.join(dirName, 'widlnan_internal_raii_helper.h'),
_packEmptyLines(dots.helperHeaderRAII({}))));
all.push(_writeFile(path.join(dirName, 'widlnan_timer_helper.h'),
_packEmptyLines(dots.helperHeaderTimer({}))));
all.push(_writeFile(path.join(dirNameSkeleton, 'addon.cpp'),
_packEmptyLines(dots.addonCpp(this))));
all.push(_writeFile(path.join(dirNameSkeleton, 'binding.gyp'),
_packEmptyLines(dots.bindingGYP(this))));
return new Promise(function(resolve, reject) {
Promise.all(all).then(() => {
resolve('done');
});
});
};
generator.writeTo = function(stream) {
};
return generator;
};
module.exports = {
generator: widl2NanGenerator()
};