|
| 1 | +/* |
| 2 | +TODO: |
| 3 | + - replace ffi shared lib version of Tcc with native version (needed for OSX) |
| 4 | + - use EventEmitter for compile steps |
| 5 | + - use ref.coerceType where appropriate |
| 6 | + - enhance InlineGenerator with struct and array functionality |
| 7 | + */ |
| 8 | + |
| 9 | +var ffi = require('ffi'); |
| 10 | +var ref = require('ref'); |
| 11 | + |
| 12 | +var void_p = ref.refType(ref.types.void); |
| 13 | +var TCCState_p = void_p; |
| 14 | + |
| 15 | +function Tcc(tcclib, tccpath) { |
| 16 | + if (!(this instanceof Tcc)) |
| 17 | + return new Tcc(tcclib, tccpath); |
| 18 | + this.lib = ffi.Library(tcclib || './linux/lib/libtcc.so', { |
| 19 | + // missing: tcc_enable_debug, tcc_set_error_func, tcc_set_warning, tcc_add_sysinclude_path, tcc_output_file |
| 20 | + 'tcc_new': [TCCState_p, []], |
| 21 | + 'tcc_delete': ['void', [TCCState_p]], // TODO: explicit destructor |
| 22 | + 'tcc_set_lib_path': ['void', [TCCState_p, 'string']], |
| 23 | + 'tcc_set_output_type': ['int', [TCCState_p, 'int']], |
| 24 | + 'tcc_set_options': ['void', [TCCState_p, 'string']], |
| 25 | + 'tcc_define_symbol': ['void', [TCCState_p, 'string', 'string']], |
| 26 | + 'tcc_undefine_symbol': ['void', [TCCState_p, 'string']], |
| 27 | + 'tcc_add_include_path': ['int', [TCCState_p, 'string']], |
| 28 | + 'tcc_add_library': ['int', [TCCState_p, 'string']], |
| 29 | + 'tcc_add_library_path': ['int', [TCCState_p, 'string']], |
| 30 | + 'tcc_add_file': ['int', [TCCState_p, 'string']], // TODO: check unicode compat |
| 31 | + 'tcc_compile_string': ['int', [TCCState_p, 'string']], |
| 32 | + 'tcc_relocate': ['int', [TCCState_p, 'int']], |
| 33 | + 'tcc_add_symbol': ['int', [TCCState_p, 'string', 'string']], |
| 34 | + 'tcc_get_symbol': [void_p, [TCCState_p, 'string']], |
| 35 | + 'tcc_run': ['int', [TCCState_p, 'int', void_p]] |
| 36 | + }); |
| 37 | + this.ctx = this.lib.tcc_new(); |
| 38 | + this.lib.tcc_set_lib_path(this.ctx, tccpath || './linux/lib/tcc'); |
| 39 | + this.lib.tcc_set_output_type(this.ctx, 0); // memory build only |
| 40 | + this._obj_refs = {}; |
| 41 | +} |
| 42 | +Tcc.prototype.set_option = function(option) { |
| 43 | + this.lib.tcc_set_option(this.ctx, option); |
| 44 | +} |
| 45 | +Tcc.prototype.define = function(symbol, value) { |
| 46 | + this.lib.tcc_define_symbol(this.ctx, symbol, value); |
| 47 | +} |
| 48 | +Tcc.prototype.undefine = function(symbol) { |
| 49 | + this.lib.tcc_undefine_symbol(this.ctx, symbol); |
| 50 | +} |
| 51 | +Tcc.prototype.add_include_path = function(path) { |
| 52 | + if (this.lib.tcc_add_include_path(this.ctx, path) == -1) |
| 53 | + throw new Error('error add_include_path: ' + path); |
| 54 | +} |
| 55 | +Tcc.prototype.add_library = function(library) { |
| 56 | + if (this.lib.tcc_add_library(this.ctx, library) == -1) |
| 57 | + throw new Error('error add_library: ' + library); |
| 58 | +} |
| 59 | +Tcc.prototype.add_link_path = function(path) { |
| 60 | + if (this.lib.tcc_add_library_path(this.ctx, path) == -1) |
| 61 | + throw new Error('error add_link_path: ' + path); |
| 62 | +} |
| 63 | +Tcc.prototype.add_file = function(path) { |
| 64 | + if (this.lib.tcc_add_file(this.ctx, path) == -1) |
| 65 | + throw new Error('error add_file: ' + path); |
| 66 | +} |
| 67 | +Tcc.prototype.compile = function(code) { |
| 68 | + if (this.lib.tcc_compile_string(this.ctx, code) == -1) |
| 69 | + throw new Error('error compile'); |
| 70 | +} |
| 71 | +Tcc.prototype.relocate = function() { |
| 72 | + if (this.lib.tcc_relocate(this.ctx, 1) == -1) |
| 73 | + throw new Error('compile relocate'); |
| 74 | +} |
| 75 | +Tcc.prototype.run = function(argc, argv) { |
| 76 | + // TODO: handle string array |
| 77 | + return this.lib.tcc_run(this.ctx, argc, argv); |
| 78 | +} |
| 79 | +Tcc.prototype.add_symbol = function(symbol, value) { // TODO: hold ref for value |
| 80 | + if (this.lib.tcc_add_symbol(this.ctx, symbol, value) == -1) |
| 81 | + throw new Error('error add_symbol: ' + symbol); |
| 82 | +} |
| 83 | +Tcc.prototype.get_symbol = function(symbol) { |
| 84 | + return this.lib.tcc_get_symbol(this.ctx, symbol); |
| 85 | +} |
| 86 | +Tcc.prototype.resolve_symbol = function(symbol, type) { |
| 87 | + // TODO: support array and struct |
| 88 | + if (typeof type === "function") |
| 89 | + return type(this.get_symbol(symbol)); |
| 90 | + type = ref.coerceType(type); |
| 91 | + var res = this.get_symbol(symbol).reinterpret(type.size); |
| 92 | + res.type = type; |
| 93 | + return res; |
| 94 | +} |
| 95 | +Tcc.prototype.set_symbol = function(symbol, value) { // TODO: hold ref for value |
| 96 | + var buf = this.get_symbol(symbol).reinterpret(value.type.size); |
| 97 | + buf.type = value.type; |
| 98 | + ref.set(buf, 0, value.deref()); |
| 99 | +} |
| 100 | +Tcc.prototype.set_function = function(symbol, cb) { // TODO: hold ref for value |
| 101 | + ref.set(this.resolve_symbol(symbol, 'void *'), 0, cb); |
| 102 | +} |
| 103 | + |
| 104 | + |
| 105 | +/** |
| 106 | + * C function type. |
| 107 | + * wrapper for lazy evaluation of ffi.ForeignFunction |
| 108 | + */ |
| 109 | +function CFuncType(restype, args) { |
| 110 | + return function(pointer) { |
| 111 | + return ffi.ForeignFunction(pointer, restype, args); |
| 112 | + } |
| 113 | +} |
| 114 | + |
| 115 | + |
| 116 | +/** |
| 117 | + * Create a C code object to be used with InlineGenerator. |
| 118 | + */ |
| 119 | +function Declaration(code, forward, symbols) { |
| 120 | + if (!(this instanceof(Declaration))) |
| 121 | + return new Declaration(code, forward, symbols); |
| 122 | + this.code = code || ''; |
| 123 | + this.forward = forward || ''; |
| 124 | + this.symbols = symbols || []; |
| 125 | + this.symbols_resolved = {}; |
| 126 | +} |
| 127 | + |
| 128 | + |
| 129 | +/** |
| 130 | + * Code generator for inline C in Javascript. Handles back and forth symbol resolution. |
| 131 | + */ |
| 132 | +function InlineGenerator() { |
| 133 | + if (!(this instanceof InlineGenerator)) |
| 134 | + return new InlineGenerator(); |
| 135 | + var that = this; |
| 136 | + this.headerparts = []; |
| 137 | + this.parts = []; |
| 138 | + this.symbols = null; |
| 139 | + this.callables = []; |
| 140 | +} |
| 141 | + |
| 142 | +/** |
| 143 | + * Get C code of added declarations. |
| 144 | + */ |
| 145 | +InlineGenerator.prototype.code = function() { |
| 146 | + var top = this.headerparts.join('\n'); |
| 147 | + var forward = this.parts.map(function(el){return el.forward;}).join('\n'); |
| 148 | + var code = this.parts.map(function(el){return el.code;}).join('\n'); |
| 149 | + return [ |
| 150 | + '/* top */', top, '', |
| 151 | + '/* forward */', forward, '', |
| 152 | + '/* code */', code, '' |
| 153 | + ].join('\n'); |
| 154 | +} |
| 155 | + |
| 156 | +/** |
| 157 | + * Add a declaration to the generator. |
| 158 | + */ |
| 159 | +InlineGenerator.prototype.add_declaration = function(decl) { |
| 160 | + if (typeof decl === 'function') |
| 161 | + decl = decl.declaration; |
| 162 | + if (!(decl instanceof Declaration)) |
| 163 | + throw new Error('cannot add declaration'); |
| 164 | + this.parts.push(decl); |
| 165 | +} |
| 166 | + |
| 167 | +/** |
| 168 | + * Add a topdeclaration to the generator. |
| 169 | + */ |
| 170 | +InlineGenerator.prototype.add_topdeclaration = function(decl) { |
| 171 | + this.headerparts.push(decl.code); |
| 172 | +} |
| 173 | + |
| 174 | +/** |
| 175 | + * Bind a tcc state to this generator. |
| 176 | + * Resolves any pending symbols from declarations. |
| 177 | + * NOTE: the tcc state must be compiled and relocated. // TODO: test for relocate |
| 178 | + */ |
| 179 | +InlineGenerator.prototype.bind_state = function(state) { |
| 180 | + if (this.symbols) |
| 181 | + return this.symbols; |
| 182 | + var all_symbols = {}; |
| 183 | + for (var i=0; i<this.parts.length; ++i) { |
| 184 | + if (this.parts[i].symbols.length) |
| 185 | + for (var j=0; j<this.parts[i].symbols.length; ++j) { |
| 186 | + var sym = this.parts[i].symbols[j]; |
| 187 | + if (sym[0] instanceof FuncSymbol) { |
| 188 | + ref.set(state.resolve_symbol(sym[1], 'void *'), 0, sym[0].cb); |
| 189 | + } else { |
| 190 | + var resolved = state.resolve_symbol(sym[1], sym[0]); |
| 191 | + this.parts[i].symbols_resolved[sym[1]] = resolved; |
| 192 | + all_symbols[sym[1]] = resolved; |
| 193 | + } |
| 194 | + } |
| 195 | + } |
| 196 | + this.symbols = all_symbols; |
| 197 | + return all_symbols; |
| 198 | +} |
| 199 | + |
| 200 | +/** |
| 201 | + * FuncSymbol - thin wrapper of ffi.Callback |
| 202 | + * needed to distingish type in `InlineGenerator.bind_state` for reverse symbol resolution of JS functions |
| 203 | + */ |
| 204 | +function FuncSymbol(restype, args, f) { |
| 205 | + if (!(this instanceof FuncSymbol)) |
| 206 | + return new FuncSymbol(restype, args, f); |
| 207 | + this.cb = ffi.Callback(restype, args, f); |
| 208 | +} |
| 209 | + |
| 210 | +/** |
| 211 | + * Convenvient function to import a function symbol from JS to C code. |
| 212 | + */ |
| 213 | +function c_callable(restype, name, args, f) { |
| 214 | + return new Declaration( |
| 215 | + '', |
| 216 | + restype + ' ' + '(*' + name + ')(' + args.join(', ') + ') = 0;', |
| 217 | + [[new FuncSymbol(restype, args, f), name]] |
| 218 | + ); |
| 219 | +} |
| 220 | + |
| 221 | +/** |
| 222 | + * Convenient function to create a C function in C useable from JS |
| 223 | + */ |
| 224 | +var c_function = function(restype, name, args, code) { |
| 225 | + var header = restype + ' ' + name + '(' + args.map(function(el){return el.join(' ')}).join(', ') + ')'; |
| 226 | + var declaration = new Declaration( |
| 227 | + header + '\n{' + code + '}\n', |
| 228 | + header + ';', |
| 229 | + [[CFuncType(restype, args.map(function(el){return el[0];})), name]] |
| 230 | + ); |
| 231 | + var func = function() { |
| 232 | + if (func.declaration.symbols_resolved[name]) |
| 233 | + return func.declaration.symbols_resolved[name].apply(this, arguments); |
| 234 | + throw new Error('c_function "'+name+'" must be compiled and bound before usage'); |
| 235 | + }; |
| 236 | + func.declaration = declaration; |
| 237 | + return func; |
| 238 | +} |
| 239 | + |
| 240 | +module.exports.Tcc = Tcc; |
| 241 | +module.exports.CFuncType = CFuncType; |
| 242 | +module.exports.InlineGenerator = InlineGenerator; |
| 243 | +module.exports.Declaration = Declaration; |
| 244 | +module.exports.c_function = c_function; |
| 245 | +module.exports.c_callable = c_callable; |
0 commit comments