UNPKG

neutrinoscript

Version:
663 lines (381 loc) 11 kB
/* Compile JS like C © 2015 - Guillaume Gonnet License GPLv2 Source at https://github.com/ParksProjets/Compile-JS-like-C */ // Options var Options = { enumHex: true, commentEscape: true, trimIncludes: true, spaceLineLimit: 0 }; var Functions = ''; // Requires var fs = require('fs'), path = require('path'); // Escape a regexp function escapeRegExp(str) { return str.replace(/([.*+?^=!:${}()|\[\]\/\\])/g, '\\$1'); } // Test if a string start with ... String.prototype.startsWith = function(str) { return this.indexOf(str) === 0; }; // Return the last character if the string String.prototype.last = function() { return this.slice(-1); }; // Replace all String.prototype.replaceAll = function(find, replace) { return this.replace(new RegExp(escapeRegExp(find), 'g'), replace); }; // Remove and add in the same time String.prototype.splice = function(idx, rem, s) { return (this.slice(0,idx) + s + this.slice(idx + rem)); }; // Test if a character is alpha numeric + _ var StringArray = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_'; String.prototype.isInSA = function(i) { return StringArray.indexOf(this[i]) != -1; }; function isInSA(c) { return StringArray.indexOf(c) != -1; } // Globals variables var spaceLines = 0; var currentLine = 0; var currentFile = null; var basePath = ''; var currentPath = ''; // Error manager function error(msg) { if (currentFile) msg = '(line ' + currentLine + ' in "' + currentFile + '") ' + msg; throw(msg); } // Constants container var Constantes = {}; // Macro container var Macro = {}; // Create a macro function CreateMacro(name, params, content) { var breaks = []; for (var i = 0; i < params.length; i++) { var i1 = -1, p = params[i], i2 = p.length; for(;;) { i1 = content.indexOf(p, i1+1); if (i1 == -1) break; if (content.isInSA(i1-1) || content.isInSA(i1+i2)) continue; breaks.push([ i1, i, i2 ]); } } breaks.sort(function(a, b) { return a[0] - b[0]; }); var offset = 0; var strs = [], prms = []; var i, l = 0; for (i = 0; i < breaks.length; i++) { strs[i] = content.slice(offset, breaks[i][0]); l += strs[i].length; offset = breaks[i][0] + breaks[i][2]; prms[i] = breaks[i][1]; } strs[i] = content.slice(offset); Macro[name] = { c: strs, n: params.length, l: l, p: prms, f: name + '(' }; } // Add constants and macros to a string function addConstantes(l) { var i1 = -1, i2; for (var i in Constantes) { i2 = i.length; i1 = -1; for (;;) { i1 = l.indexOf(i, i1+1); if (i1 == -1) break; if (!l.isInSA(i1-1) && !l.isInSA(i1+i2)) { l = l.splice(i1, i2, Constantes[i]); i1 += Constantes[i].length; } } } var f, str = ''; for (var i in Macro) { f = Macro[i]; i2 = f.f.length; i1 = -1; for (;;) { i1 = l.indexOf(f.f, i1+1); if (i1 == -1) break; if (l.isInSA(i1-1)) continue; var m = 0, s = i1+i2, e = s; var params = []; for(; l[e] !== undefined; e++) { if (l[e] == '(') m++; else if (l[e] == ',' && !m) { params.push(l.slice(s, e)); s = e+1; } else if (l[e] == ')') { if (!m) break; m--; } } params.push(l.slice(s, e)); if (params.length > f.n) error('too many arguments for macro "' + i + '"'); if (params.length < f.n) error('too few arguments for macro "' + i + '"'); str = f.c[0]; for (s = 0; s < f.p.length; s++) str += params[ f.p[s] ] + f.c[s+1]; l = l.splice(i1, e-i1+1, str); i1 += str.length; } } return l; } // Parse a file function ParseFile(fileContent, isContent) { // Read file if fileContent is a file name if (!isContent) { var stats = fs.statSync(fileContent); if (!stats) { error('can\'t read file "' + fileContent + '"'); } try { var txt = fs.readFileSync(fileContent); } catch(e) { error('can\'t read file "' + fileContent + '"'); } currentPath = path.dirname(fileContent); if (currentPath == '.') currentPath = ''; else currentPath += '/'; } else { var txt = fileContent; } // Current file and line if (isContent) currentFile = 'main'; else currentFile = fileContent; currentLine = 0; var array = txt.toString().split('\n'), index = 0; var result = ''; var l, str = '', cur = 0; function nextLine() { currentLine++; return array[index++]; } // Container var CommandFunc = {}; // Parse a /*# comment function comment() { var line; for(;;) { line = nextLine(); if (line === undefined || line.trimLeft().startsWith('#*/')) break; if (line === undefined || line.trimLeft().startsWith('-->')) break; if (line === undefined || line.trimLeft().startsWith('--%>')) break; } } // #define: create a constant / macro var CMD_DEFINE = function() { var name = str.match(/^[A-Za-z0-9_]+/)[0]; str = str.substr(name.length).trimLeft(); var func = false; if (str.search(/^\([A-Za-z0-9_,]+\)/) != -1) { func = true; var i1 = str.indexOf(')'), i2 = 1, i3 = 0; var params = []; while( (i3 = str.indexOf(',', i2+1)) != -1 ) { params.push(str.substr(i2, i3 - i2)); i2 = i3+1; } params.push(str.substr(i2, i1 - i2)); str = str.substr(i1+1).trimLeft(); } var content = ''; str = str.trimRight(); while (str.last() == '\\') { content += str.substr(0, str.length - 1) + '\n'; str = nextLine().trimRight(); } content += str; content = addConstantes(content); if (func) CreateMacro(name, params, content.trim()); else Constantes[name] = content.trim(); }; CommandFunc['define'] = CMD_DEFINE; CommandFunc['def'] = CMD_DEFINE; // #undef: delete a constant / macro CommandFunc['undef'] = function() { var name = str.match(/^[A-Za-z0-9_]+/)[0]; delete Constantes[name]; delete Functions[name]; }; // #include: include and parse a file var CMD_INC = function() { var libinclude = false; var l = currentLine, f = currentFile, p = currentPath; var name = str.match(/^"([A-Za-z0-9\-_\. \/\\]+)"/); if (!name || !name[1]) { libinclude = true; str = str.replace('<','"').replace('>','"'); name = str.match(/^"([A-Za-z0-9\-_\. \/\\]+)"/); if (!name || !name[1]) { libinclude = false; error('invalid include'); } } if (libinclude) { var resultfile = path.resolve('./lib/'+ name[1]); result += ParseFile(resultfile) + '\r\n'; } else { result += ParseFile(currentPath + name[1]) + '\r\n'; } currentLine = l; currentFile = f; currentPath = p; }; CommandFunc['import'] = CMD_INC; CommandFunc['imp'] = CMD_INC; CommandFunc['include'] = CMD_INC; CommandFunc['inc'] = CMD_INC; // #ifndeff / #ifdef: conditon CommandFunc['ifndef'] = function() { var name = str.match(/^[A-Za-z0-9_]+/)[0]; if (Constantes[name] != undefined || Functions[name] != undefined) condition(); }; CommandFunc['ifdef'] = function() { var name = str.match(/^[A-Za-z0-9_]+/)[0]; if (Constantes[name] == undefined && Functions[name] == undefined) condition(); }; CommandFunc['endif'] = CommandFunc['else'] = function() { // Escape the line }; // Parse a condition function condition() { var line, n = 1; for(;;) { line = nextLine(); if (line === undefined) return; line = line.trim(); if (line.startsWith('#ifdef') || line.startsWith('#ifndef')) n++; else if (line.startsWith('#else') && n == 1) return; else if (line.startsWith('#endif')) { n--; if (!n) return; } } } // #enum: c like enumeration CommandFunc['enum'] = function() { var line, str = ''; for(;;) { line = nextLine(); if (line === undefined || line.trimLeft().startsWith('#endenum')) break; str += line; } str.split(',').forEach(function(c, i) { Constantes[ c.trim() ] = Options.enumHex ? '0x'+i.toString(16) : i.toString(); }); }; // Parse line by line while ( (l = nextLine()) !== undefined ) { str = l.trimLeft(); // The line is empty if (!str.length) { if (Options.spaceLineLimit && spaceLines >= Options.spaceLineLimit) continue; spaceLines++; result += l + '\n'; continue; } // The line start with a comment to delete if (Options.commentEscape && str.startsWith('//#')) continue; if (Options.commentEscape && str.startsWith('/*#')) { comment(); continue; } if (Options.commentEscape && str.startsWith('<!--')) { comment(); continue; } if (Options.commentEscape && str.startsWith('<%--')) { comment(); continue; } if (str.split(' ')[0] in CommandFunc) { if (str[0] !== '#') { str = '#' + str; } } var type = str.match(/^#([A-Za-z0-9_]+)/); type = type && type[1] ? type[1] : 0; if (!type) { result += addConstantes(l) + '\n'; spaceLines = 0; continue; } str = str.substr(type.length + 1).trimLeft(); // The line is a command if (CommandFunc[type]) { CommandFunc[type](); } else if (!Options.commentEscape) { result += addConstantes(l); spaceLines = 0; } } // Trim or not the file if (Options.trimIncludes) return result.trim(); else return result; } // Entry point function StartParsing(fileContent, isContent) { if (isContent) { return ParseFile(fileContent, true); } basePath = (path.dirname(fileContent) || '.') + '/'; var fileName = path.basename(fileContent); return ParseFile(path.resolve(basePath + fileName)); } // Exports module.exports = { p: StartParsing, o: Options };