├── .gitignore ├── README.md ├── bin └── rpn.js ├── examples ├── simple-example.rpn ├── test.html └── with-error.rpn ├── lib ├── rpn.js └── rpn │ ├── ast.js │ ├── bnf.js │ └── lex.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | examples/*.js 2 | examples/*.map 3 | node_modules/ 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fitzgen/rpn-js/0d041df637ee8c2e53d1bb40850533978656c357/README.md -------------------------------------------------------------------------------- /bin/rpn.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require("fs"); 3 | var rpn = require("rpn"); 4 | 5 | process.argv.slice(2).forEach(function (sourceFilename) { 6 | 7 | var codeFilename = sourceFilename.replace(/\.[\w]+$/, ".js"); 8 | var mapFilename = codeFilename + ".map"; 9 | 10 | var input = fs.readFileSync(sourceFilename); 11 | 12 | var rootSourceNode = rpn.compile(input, {originalFilename: sourceFilename}); 13 | 14 | // output :: { code :: String, map :: SourceMapGenerator } 15 | var output = rootSourceNode.toStringWithSourceMap({ file: mapFilename}); 16 | 17 | //We must add the //# sourceMappingURL comment directive 18 | //so that the browser’s debugger knows where to find the source map. 19 | output.code += "\n//# sourceMappingURL=" + mapFilename; 20 | 21 | fs.writeFileSync(codeFilename, output.code); 22 | fs.writeFileSync(mapFilename, output.map); 23 | 24 | }); 25 | -------------------------------------------------------------------------------- /examples/simple-example.rpn: -------------------------------------------------------------------------------- 1 | a 8 =; 2 | b 3 =; 3 | c a b 1 - / =; 4 | c 1 print; 5 | -------------------------------------------------------------------------------- /examples/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | RPN Test Page! 5 | 6 | 7 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /examples/with-error.rpn: -------------------------------------------------------------------------------- 1 | a 9 =; 2 | b 3 =; 3 | c a b / =; 4 | c a b c - / =; 5 | c 1 print; -------------------------------------------------------------------------------- /lib/rpn.js: -------------------------------------------------------------------------------- 1 | var jison = require("jison"); 2 | var sourceMap = require("source-map"); 3 | var lex = require("./rpn/lex").lex; 4 | var bnf = require("./rpn/bnf").bnf; 5 | 6 | var parser = new jison.Parser({ 7 | lex: lex, 8 | bnf: bnf 9 | }); 10 | 11 | parser.yy = require("./rpn/ast"); 12 | 13 | function getPreamble () { 14 | return new sourceMap.SourceNode(null, null, null, "") 15 | .add("var __rpn = {};\n") 16 | .add("__rpn._stack = [];\n") 17 | .add("__rpn.temp = 0;\n") 18 | 19 | .add("__rpn.push = function (val) {\n") 20 | .add(" __rpn._stack.push(val);\n") 21 | .add("};\n") 22 | 23 | .add("__rpn.pop = function () {\n") 24 | .add(" if (__rpn._stack.length > 0) {\n") 25 | .add(" return __rpn._stack.pop();\n") 26 | .add(" }\n") 27 | .add(" else {\n") 28 | .add(" throw new Error('can\\\'t pop from empty stack');\n") 29 | .add(" }\n") 30 | .add("};\n") 31 | 32 | .add("__rpn.print = function (val, repeat) {\n") 33 | .add(" while (repeat-- > 0) {\n") 34 | .add(" var el = document.createElement('div');\n") 35 | .add(" var txt = document.createTextNode(val);\n") 36 | .add(" el.appendChild(txt);\n") 37 | .add(" document.body.appendChild(el);\n") 38 | .add(" }\n") 39 | .add("};\n"); 40 | } 41 | 42 | exports.compile = function (input, data) { 43 | var expressions = parser.parse(input.toString()); 44 | var preamble = getPreamble(); 45 | 46 | var result = new sourceMap.SourceNode(null, null, null, preamble); 47 | result.add(expressions.map(function (exp) { 48 | return exp.compile(data); 49 | })); 50 | 51 | return result; 52 | }; 53 | -------------------------------------------------------------------------------- /lib/rpn/ast.js: -------------------------------------------------------------------------------- 1 | var sourceMap = require("source-map"); 2 | var SourceNode = sourceMap.SourceNode; 3 | 4 | 5 | function push(val) { 6 | return ["__rpn.push(", val, ");\n"]; 7 | } 8 | 9 | 10 | var AstNode = function (line, column) { 11 | this._line = line; 12 | this._column = column; 13 | }; 14 | AstNode.prototype.compile = function (data) { 15 | throw new Error("Not Yet Implemented"); 16 | }; 17 | AstNode.prototype.compileReference = function (data) { 18 | return this.compile(data); 19 | }; 20 | AstNode.prototype._sn = function (originalFilename, chunk) { 21 | return new SourceNode(this._line, this._column, originalFilename, chunk); 22 | }; 23 | 24 | 25 | exports.Number = function (line, column, numberText) { 26 | AstNode.call(this, line, column); 27 | this._value = Number(numberText); 28 | }; 29 | exports.Number.prototype = Object.create(AstNode.prototype); 30 | exports.Number.prototype.compile = function (data) { 31 | return this._sn(data.originalFilename, 32 | push(this._value.toString())); 33 | }; 34 | 35 | 36 | exports.Variable = function (line, column, variableText) { 37 | AstNode.call(this, line, column); 38 | this._name = variableText; 39 | }; 40 | exports.Variable.prototype = Object.create(AstNode.prototype); 41 | exports.Variable.prototype.compileReference = function (data) { 42 | return this._sn(data.originalFilename, 43 | push(["'", this._name, "'"])); 44 | }; 45 | exports.Variable.prototype.compile = function (data) { 46 | return this._sn(data.originalFilename, 47 | push(["window.", this._name])); 48 | }; 49 | 50 | 51 | exports.Expression = function (line, column, operand1, operand2, operator) { 52 | AstNode.call(this, line, column); 53 | this._left = operand1; 54 | this._right = operand2; 55 | this._operator = operator; 56 | }; 57 | exports.Expression.prototype = Object.create(AstNode.prototype); 58 | exports.Expression.prototype.compile = function (data) { 59 | var temp = "__rpn.temp"; 60 | var output = this._sn(data.originalFilename, ""); 61 | 62 | switch (this._operator.symbol) { 63 | case 'print': 64 | return output 65 | .add(this._left.compile(data)) 66 | .add(this._right.compile(data)) 67 | .add([temp, " = __rpn.pop();\n"]) 68 | .add(["if (", temp, " <= 0) throw new Error('argument must be greater than 0');\n"]) 69 | .add(["if (Math.floor(", temp, ") != ", temp, 70 | ") throw new Error('argument must be an integer');\n"]) 71 | .add([this._operator.compile(data), "(__rpn.pop(), ", temp, ");\n"]); 72 | case '=': 73 | return output 74 | .add(this._right.compile(data)) 75 | .add(this._left.compileReference(data)) 76 | .add(["window[__rpn.pop()] ", this._operator.compile(data), " __rpn.pop();\n"]); 77 | case '/': 78 | return output 79 | .add(this._left.compile(data)) 80 | .add(this._right.compile(data)) 81 | .add([temp, " = __rpn.pop();\n"]) 82 | .add(["if (", temp, " === 0) throw new Error('divide by zero error');\n"]) 83 | .add(push(["__rpn.pop() ", this._operator.compile(data), " ", temp])); 84 | default: 85 | return output 86 | .add(this._left.compile(data)) 87 | .add(this._right.compile(data)) 88 | .add([temp, " = __rpn.pop();\n"]) 89 | .add(push(["__rpn.pop() ", this._operator.compile(data), " ", temp])); 90 | } 91 | }; 92 | 93 | 94 | exports.Operator = function (line, column, operatorText) { 95 | AstNode.call(this, line, column); 96 | this.symbol = operatorText; 97 | }; 98 | exports.Operator.prototype = Object.create(AstNode.prototype); 99 | exports.Operator.prototype.compile = function (data) { 100 | if (this.symbol === "print") { 101 | return this._sn(data.originalFilename, 102 | "__rpn.print"); 103 | } 104 | else { 105 | return this._sn(data.originalFilename, 106 | this.symbol); 107 | } 108 | }; 109 | -------------------------------------------------------------------------------- /lib/rpn/bnf.js: -------------------------------------------------------------------------------- 1 | exports.bnf = { 2 | start: [ 3 | ["input EOF", "return $$;"] 4 | ], 5 | input: [ 6 | ["", "$$ = [];"], 7 | ["line input", "$$ = [$1].concat($2);"] 8 | ], 9 | line: [ 10 | ["exp SEMICOLON", "$$ = $1;"] 11 | ], 12 | exp: [ 13 | ["NUMBER", "$$ = new yy.Number(@1.first_line, @1.first_column, yytext);"], 14 | ["VARIABLE", "$$ = new yy.Variable(@1.first_line, @1.first_column, yytext);"], 15 | ["exp exp operator", "$$ = new yy.Expression(@3.first_line, @3.first_column, $1, $2, $3);"] 16 | ], 17 | operator: [ 18 | ["PRINT", "$$ = new yy.Operator(@1.first_line, @1.first_column, yytext);"], 19 | ["=", "$$ = new yy.Operator(@1.first_line, @1.first_column, yytext);"], 20 | ["+", "$$ = new yy.Operator(@1.first_line, @1.first_column, yytext);"], 21 | ["-", "$$ = new yy.Operator(@1.first_line, @1.first_column, yytext);"], 22 | ["*", "$$ = new yy.Operator(@1.first_line, @1.first_column, yytext);"], 23 | ["/", "$$ = new yy.Operator(@1.first_line, @1.first_column, yytext);"] 24 | ] 25 | }; 26 | -------------------------------------------------------------------------------- /lib/rpn/lex.js: -------------------------------------------------------------------------------- 1 | exports.lex = { 2 | rules: [ 3 | ["\\s+", "/* Skip whitespace! */"], 4 | ["#.*\\n", "/* Skip comments! */"], 5 | [";", "return 'SEMICOLON'"], 6 | ["\\-?[0-9]+(\\.[0-9]+)?", "return 'NUMBER';"], 7 | ["print", "return 'PRINT';"], 8 | ["[a-zA-Z][a-zA-Z0-9_]*", "return 'VARIABLE';"], 9 | ["=", "return '=';"], 10 | ["\\+", "return '+';"], 11 | ["\\-", "return '-';"], 12 | ["\\*", "return '*';"], 13 | ["\\/", "return '/';"], 14 | ["$", "return 'EOF';"] 15 | ] 16 | }; 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": "Nick Fitzgerald (http://fitzgeraldnick.com)", 3 | "name": "rpn", 4 | "description": "A reverse polish notation -> JavaScript compiler using source maps", 5 | "version": "0.0.0", 6 | "main": "./lib/rpn.js", 7 | "directories": { "lib": "./lib" }, 8 | "dependencies": { 9 | "jison": ">=0.4.4", 10 | "source-map": ">=0.1.22" 11 | }, 12 | "devDependencies": {}, 13 | "optionalDependencies": {}, 14 | "engines": { 15 | "node": "*" 16 | }, 17 | "bin": { 18 | "rpn.js": "./bin/rpn.js" 19 | } 20 | } 21 | --------------------------------------------------------------------------------