diff --git a/package.json b/package.json index f4e5001..e25e8e6 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,15 @@ { "name": "zerkel", - "version": "1.1.0", + "version": "1.2.0", "description": "A compiler for the zerkel language", "dependencies": { - "coffee-script": "1.6.3", + "coffee-script": "1.7.1", "browserify": "2.22.0" }, "devDependencies": { "chai": "1.2.0", "mocha": "1.4.0", - "pegjs": "0.7.0" + "jison": "0.4.13" }, "scripts": { "test": "node_modules/.bin/mocha -R dot" diff --git a/src/zerkel-parser.jison b/src/zerkel-parser.jison new file mode 100644 index 0000000..585d104 --- /dev/null +++ b/src/zerkel-parser.jison @@ -0,0 +1,67 @@ +/* lexical grammar */ +%lex +%% +\s+ {/* skip whitespace */} +"and"|"AND" {return 'AND';} +"or"|"OR" {return 'OR';} +"not"|"NOT" {return 'NOT';} +"=" {return '=';} +"<>" {return '<>';} +">"|"<"|"<="|">=" {return 'LTGT';} +"contains"|"CONTAINS" {return 'CONTAINS';} +"like"|"LIKE" {return 'LIKE';} +[0-9]+ {return 'INTEGER';} +\"[^\"]*\" {return 'STRING';} +[A-Za-z]+ {return 'VAR';} +"(" {return 'OPEN';} +")" {return 'CLOSE';} +<> {return 'EOF';} + +/lex + +/* operator associations and precedence */ + +%left '=' '<>' +%left 'LTGT' +%left 'AND' 'OR' +%left 'NOT' +%left 'CONTAINS' 'LIKE' + +%start expressions + +%% /* language grammar */ + +expressions + : e EOF + {return $1;} + ; +e + : 'NOT' e + {$$ = "!" + $2;} + | 'OPEN' e 'CLOSE' + {$$ = "(" + $2 + ")";} + | e 'AND' e + {$$ = $1 + " && " + $3;} + | e 'OR' e + {$$ = $1 + " || " + $3;} + | value '=' value + {$$ = $1 + "==" + $3;} + | value '<>' value + {$$ = $1 + "!=" + $3;} + | value 'LTGT' value + {$$ = $1 + $2 + $3} + | value 'CONTAINS' value + {$$ = "(" + $1 + "&&" + $1 + ".indexOf(" + $3 + ")" + " >= 0)"; } + | value 'LIKE' value + {$$ = "_helpers['match'](" + $1 + "," + $3 + ")";} + | value + {$$ = $1;} + ; +value + : INTEGER + {$$ = Number(yytext);} + | STRING + {$$ = yytext;} + | VAR + {$$ = "_env['" + yytext + "']";} + ; diff --git a/src/zerkel-parser.js b/src/zerkel-parser.js index 6314abd..89c1aca 100644 --- a/src/zerkel-parser.js +++ b/src/zerkel-parser.js @@ -1,1152 +1,643 @@ -module.exports = (function(){ - /* - * Generated by PEG.js 0.7.0. - * - * http://pegjs.majda.cz/ - */ - - function quote(s) { - /* - * ECMA-262, 5th ed., 7.8.4: All characters may appear literally in a - * string literal except for the closing quote character, backslash, - * carriage return, line separator, paragraph separator, and line feed. - * Any character may appear in the form of an escape sequence. - * - * For portability, we also escape escape all control and non-ASCII - * characters. Note that "\0" and "\v" escape sequences are not used - * because JSHint does not like the first and IE the second. - */ - return '"' + s - .replace(/\\/g, '\\\\') // backslash - .replace(/"/g, '\\"') // closing quote character - .replace(/\x08/g, '\\b') // backspace - .replace(/\t/g, '\\t') // horizontal tab - .replace(/\n/g, '\\n') // line feed - .replace(/\f/g, '\\f') // form feed - .replace(/\r/g, '\\r') // carriage return - .replace(/[\x00-\x07\x0B\x0E-\x1F\x80-\uFFFF]/g, escape) - + '"'; +/* parser generated by jison 0.4.13 */ +/* + Returns a Parser object of the following structure: + + Parser: { + yy: {} } - - var result = { - /* - * Parses the input with a generated parser. If the parsing is successfull, - * returns a value explicitly or implicitly specified by the grammar from - * which the parser was generated (see |PEG.buildParser|). If the parsing is - * unsuccessful, throws |PEG.parser.SyntaxError| describing the error. - */ - parse: function(input, startRule) { - var parseFunctions = { - "prog": parse_prog, - "combination": parse_combination, - "clause": parse_clause, - "primary": parse_primary, - "andOp": parse_andOp, - "orOp": parse_orOp, - "notOp": parse_notOp, - "and": parse_and, - "or": parse_or, - "not": parse_not, - "containsOp": parse_containsOp, - "likeOp": parse_likeOp, - "eqOp": parse_eqOp, - "notEqOp": parse_notEqOp, - "numCompOp": parse_numCompOp, - "eq": parse_eq, - "notEq": parse_notEq, - "numComp": parse_numComp, - "contains": parse_contains, - "like": parse_like, - "letter": parse_letter, - "space": parse_space, - "optionalSpace": parse_optionalSpace, - "requiredSpace": parse_requiredSpace, - "value": parse_value, - "var": parse_var, - "string": parse_string, - "integer": parse_integer - }; - - if (startRule !== undefined) { - if (parseFunctions[startRule] === undefined) { - throw new Error("Invalid rule name: " + quote(startRule) + "."); - } - } else { - startRule = "prog"; - } - - var pos = 0; - var reportFailures = 0; - var rightmostFailuresPos = 0; - var rightmostFailuresExpected = []; - - function padLeft(input, padding, length) { - var result = input; - - var padLength = length - input.length; - for (var i = 0; i < padLength; i++) { - result = padding + result; + + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), + + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), + + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, + + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, + } + } + + + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) + } + + + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var zerkelParser = (function(){ +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"expressions":3,"e":4,"EOF":5,"NOT":6,"OPEN":7,"CLOSE":8,"AND":9,"OR":10,"value":11,"=":12,"<>":13,"LTGT":14,"CONTAINS":15,"LIKE":16,"INTEGER":17,"STRING":18,"VAR":19,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOF",6:"NOT",7:"OPEN",8:"CLOSE",9:"AND",10:"OR",12:"=",13:"<>",14:"LTGT",15:"CONTAINS",16:"LIKE",17:"INTEGER",18:"STRING",19:"VAR"}, +productions_: [0,[3,2],[4,2],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,3],[4,1],[11,1],[11,1],[11,1]], +performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +/* this == yyval */ + +var $0 = $$.length - 1; +switch (yystate) { +case 1:return $$[$0-1]; +break; +case 2:this.$ = "!" + $$[$0]; +break; +case 3:this.$ = "(" + $$[$0-1] + ")"; +break; +case 4:this.$ = $$[$0-2] + " && " + $$[$0]; +break; +case 5:this.$ = $$[$0-2] + " || " + $$[$0]; +break; +case 6:this.$ = $$[$0-2] + "==" + $$[$0]; +break; +case 7:this.$ = $$[$0-2] + "!=" + $$[$0]; +break; +case 8:this.$ = $$[$0-2] + $$[$0-1] + $$[$0] +break; +case 9:this.$ = "(" + $$[$0-2] + "&&" + $$[$0-2] + ".indexOf(" + $$[$0] + ")" + " >= 0)"; +break; +case 10:this.$ = "_helpers['match'](" + $$[$0-2] + "," + $$[$0] + ")"; +break; +case 11:this.$ = $$[$0]; +break; +case 12:this.$ = Number(yytext); +break; +case 13:this.$ = yytext; +break; +case 14:this.$ = "_env['" + yytext + "']"; +break; +} +}, +table: [{3:1,4:2,6:[1,3],7:[1,4],11:5,17:[1,6],18:[1,7],19:[1,8]},{1:[3]},{5:[1,9],9:[1,10],10:[1,11]},{4:12,6:[1,3],7:[1,4],11:5,17:[1,6],18:[1,7],19:[1,8]},{4:13,6:[1,3],7:[1,4],11:5,17:[1,6],18:[1,7],19:[1,8]},{5:[2,11],8:[2,11],9:[2,11],10:[2,11],12:[1,14],13:[1,15],14:[1,16],15:[1,17],16:[1,18]},{5:[2,12],8:[2,12],9:[2,12],10:[2,12],12:[2,12],13:[2,12],14:[2,12],15:[2,12],16:[2,12]},{5:[2,13],8:[2,13],9:[2,13],10:[2,13],12:[2,13],13:[2,13],14:[2,13],15:[2,13],16:[2,13]},{5:[2,14],8:[2,14],9:[2,14],10:[2,14],12:[2,14],13:[2,14],14:[2,14],15:[2,14],16:[2,14]},{1:[2,1]},{4:19,6:[1,3],7:[1,4],11:5,17:[1,6],18:[1,7],19:[1,8]},{4:20,6:[1,3],7:[1,4],11:5,17:[1,6],18:[1,7],19:[1,8]},{5:[2,2],8:[2,2],9:[2,2],10:[2,2]},{8:[1,21],9:[1,10],10:[1,11]},{11:22,17:[1,6],18:[1,7],19:[1,8]},{11:23,17:[1,6],18:[1,7],19:[1,8]},{11:24,17:[1,6],18:[1,7],19:[1,8]},{11:25,17:[1,6],18:[1,7],19:[1,8]},{11:26,17:[1,6],18:[1,7],19:[1,8]},{5:[2,4],8:[2,4],9:[2,4],10:[2,4]},{5:[2,5],8:[2,5],9:[2,5],10:[2,5]},{5:[2,3],8:[2,3],9:[2,3],10:[2,3]},{5:[2,6],8:[2,6],9:[2,6],10:[2,6]},{5:[2,7],8:[2,7],9:[2,7],10:[2,7]},{5:[2,8],8:[2,8],9:[2,8],10:[2,8]},{5:[2,9],8:[2,9],9:[2,9],10:[2,9]},{5:[2,10],8:[2,10],9:[2,10],10:[2,10]}], +defaultActions: {9:[2,1]}, +parseError: function parseError(str, hash) { + if (hash.recoverable) { + this.trace(str); + } else { + throw new Error(str); + } +}, +parse: function parse(input) { + var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + var args = lstack.slice.call(arguments, 1); + this.lexer.setInput(input); + this.lexer.yy = this.yy; + this.yy.lexer = this.lexer; + this.yy.parser = this; + if (typeof this.lexer.yylloc == 'undefined') { + this.lexer.yylloc = {}; + } + var yyloc = this.lexer.yylloc; + lstack.push(yyloc); + var ranges = this.lexer.options && this.lexer.options.ranges; + if (typeof this.yy.parseError === 'function') { + this.parseError = this.yy.parseError; + } else { + this.parseError = Object.getPrototypeOf(this).parseError; + } + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + function lex() { + var token; + token = self.lexer.lex() || EOF; + if (typeof token !== 'number') { + token = self.symbols_[token] || token; } - - return result; - } - - function escape(ch) { - var charCode = ch.charCodeAt(0); - var escapeChar; - var length; - - if (charCode <= 0xFF) { - escapeChar = 'x'; - length = 2; + return token; + } + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; } else { - escapeChar = 'u'; - length = 4; - } - - return '\\' + escapeChar + padLeft(charCode.toString(16).toUpperCase(), '0', length); - } - - function matchFailed(failure) { - if (pos < rightmostFailuresPos) { - return; - } - - if (pos > rightmostFailuresPos) { - rightmostFailuresPos = pos; - rightmostFailuresExpected = []; - } - - rightmostFailuresExpected.push(failure); - } - - function parse_prog() { - var result0, result1, result2; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_optionalSpace(); - if (result0 !== null) { - result1 = parse_combination(); - if (result1 !== null) { - result2 = parse_optionalSpace(); - if (result2 !== null) { - result0 = [result0, result1, result2]; - } else { - result0 = null; - pos = pos1; + if (symbol === null || typeof symbol == 'undefined') { + symbol = lex(); } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, comb) { return comb; })(pos0, result0[1]); - } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_combination() { - var result0; - - result0 = parse_and(); - if (result0 === null) { - result0 = parse_or(); - if (result0 === null) { - result0 = parse_not(); - if (result0 === null) { - result0 = parse_clause(); - } - } - } - return result0; - } - - function parse_clause() { - var result0; - - result0 = parse_eq(); - if (result0 === null) { - result0 = parse_notEq(); - if (result0 === null) { - result0 = parse_numComp(); - if (result0 === null) { - result0 = parse_contains(); - if (result0 === null) { - result0 = parse_like(); - if (result0 === null) { - result0 = parse_primary(); + action = table[state] && table[state][symbol]; + } + if (typeof action === 'undefined' || !action.length || !action[0]) { + var errStr = ''; + expected = []; + for (p in table[state]) { + if (this.terminals_[p] && p > TERROR) { + expected.push('\'' + this.terminals_[p] + '\''); + } } - } - } - } - } - return result0; - } - - function parse_primary() { - var result0, result1, result2; - var pos0, pos1; - - result0 = parse_value(); - if (result0 === null) { - pos0 = pos; - pos1 = pos; - if (input.charCodeAt(pos) === 40) { - result0 = "("; - pos++; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"(\""); - } - } - if (result0 !== null) { - result1 = parse_combination(); - if (result1 !== null) { - if (input.charCodeAt(pos) === 41) { - result2 = ")"; - pos++; - } else { - result2 = null; - if (reportFailures === 0) { - matchFailed("\")\""); - } - } - if (result2 !== null) { - result0 = [result0, result1, result2]; - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, comb) { return "(" + comb + ")"; })(pos0, result0[1]); - } - if (result0 === null) { - pos = pos0; - } - } - return result0; - } - - function parse_andOp() { - var result0; - - if (input.substr(pos, 3) === "AND") { - result0 = "AND"; - pos += 3; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"AND\""); - } - } - if (result0 === null) { - if (input.substr(pos, 3) === "and") { - result0 = "and"; - pos += 3; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"and\""); - } - } - } - return result0; - } - - function parse_orOp() { - var result0; - - if (input.substr(pos, 2) === "OR") { - result0 = "OR"; - pos += 2; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"OR\""); - } - } - if (result0 === null) { - if (input.substr(pos, 2) === "or") { - result0 = "or"; - pos += 2; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"or\""); - } - } - } - return result0; - } - - function parse_notOp() { - var result0; - - if (input.substr(pos, 3) === "NOT") { - result0 = "NOT"; - pos += 3; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"NOT\""); - } - } - if (result0 === null) { - if (input.substr(pos, 3) === "not") { - result0 = "not"; - pos += 3; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"not\""); - } - } - } - return result0; - } - - function parse_and() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_clause(); - if (result0 !== null) { - result1 = parse_requiredSpace(); - if (result1 !== null) { - result2 = parse_andOp(); - if (result2 !== null) { - result3 = parse_requiredSpace(); - if (result3 !== null) { - result4 = parse_combination(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; + if (this.lexer.showPosition) { + errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + this.lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; } else { - result0 = null; - pos = pos1; + errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; + this.parseError(errStr, { + text: this.lexer.match, + token: this.terminals_[symbol] || symbol, + line: this.lexer.yylineno, + loc: yyloc, + expected: expected + }); } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); } - if (result0 !== null) { - result0 = (function(offset, left, right) { return left + "&&" + right; })(pos0, result0[0], result0[4]); - } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_or() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_clause(); - if (result0 !== null) { - result1 = parse_requiredSpace(); - if (result1 !== null) { - result2 = parse_orOp(); - if (result2 !== null) { - result3 = parse_requiredSpace(); - if (result3 !== null) { - result4 = parse_combination(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; - } else { - result0 = null; - pos = pos1; + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(this.lexer.yytext); + lstack.push(this.lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = this.lexer.yyleng; + yytext = this.lexer.yytext; + yylineno = this.lexer.yylineno; + yyloc = this.lexer.yylloc; + if (recovering > 0) { + recovering--; } - } else { - result0 = null; - pos = pos1; - } } else { - result0 = null; - pos = pos1; + symbol = preErrorSymbol; + preErrorSymbol = null; } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, left, right) { return left + "||" + right; })(pos0, result0[0], result0[4]); - } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_not() { - var result0, result1, result2; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_notOp(); - if (result0 !== null) { - result1 = parse_requiredSpace(); - if (result1 !== null) { - result2 = parse_clause(); - if (result2 !== null) { - result0 = [result0, result1, result2]; - } else { - result0 = null; - pos = pos1; + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }; + if (ranges) { + yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]; } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, clause) { return "!" + clause; })(pos0, result0[2]); - } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_containsOp() { - var result0; - - if (input.substr(pos, 8) === "CONTAINS") { - result0 = "CONTAINS"; - pos += 8; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"CONTAINS\""); - } - } - if (result0 === null) { - if (input.substr(pos, 8) === "contains") { - result0 = "contains"; - pos += 8; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"contains\""); + r = this.performAction.apply(yyval, [ + yytext, + yyleng, + yylineno, + this.yy, + action[1], + vstack, + lstack + ].concat(args)); + if (typeof r !== 'undefined') { + return r; } - } - } - return result0; - } - - function parse_likeOp() { - var result0; - - if (input.substr(pos, 4) === "LIKE") { - result0 = "LIKE"; - pos += 4; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"LIKE\""); - } - } - if (result0 === null) { - if (input.substr(pos, 4) === "like") { - result0 = "like"; - pos += 4; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"like\""); + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); } - } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; } - return result0; - } - - function parse_eqOp() { - var result0; - - if (input.charCodeAt(pos) === 61) { - result0 = "="; - pos++; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"=\""); - } - } - return result0; - } - - function parse_notEqOp() { - var result0; - - if (input.substr(pos, 2) === "<>") { - result0 = "<>"; - pos += 2; + } + return true; +}}; +/* generated by jison-lex 0.2.1 */ +var lexer = (function(){ +var lexer = { + +EOF:1, + +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"<>\""); - } + throw new Error(str); } - return result0; - } - - function parse_numCompOp() { - var result0; - - if (input.charCodeAt(pos) === 62) { - result0 = ">"; - pos++; + }, + +// resets the lexer, sets new input +setInput:function (input) { + this._input = input; + this._more = this._backtrack = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { + first_line: 1, + first_column: 0, + last_line: 1, + last_column: 0 + }; + if (this.options.ranges) { + this.yylloc.range = [0,0]; + } + this.offset = 0; + return this; + }, + +// consumes and returns one char from the input +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\">\""); - } + this.yylloc.last_column++; } - if (result0 === null) { - if (input.charCodeAt(pos) === 60) { - result0 = "<"; - pos++; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"<\""); - } - } - if (result0 === null) { - if (input.substr(pos, 2) === ">=") { - result0 = ">="; - pos += 2; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\">=\""); - } - } - if (result0 === null) { - if (input.substr(pos, 2) === "<=") { - result0 = "<="; - pos += 2; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"<=\""); - } - } - } - } + if (this.options.ranges) { + this.yylloc.range[1]++; } - return result0; - } - - function parse_eq() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_value(); - if (result0 !== null) { - result1 = parse_optionalSpace(); - if (result1 !== null) { - result2 = parse_eqOp(); - if (result2 !== null) { - result3 = parse_optionalSpace(); - if (result3 !== null) { - result4 = parse_value(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } + + this._input = this._input.slice(1); + return ch; + }, + +// unshifts one char (or a string) into the input +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); + + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len - 1); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); + + if (lines.length - 1) { + this.yylineno -= lines.length - 1; + } + var r = this.yylloc.range; + + this.yylloc = { + first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + + oldLines[oldLines.length - lines.length].length - lines[0].length : + this.yylloc.first_column - len + }; + + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + this.yyleng = this.yytext.length; + return this; + }, + +// When called from action, caches matched text and appends it on next action +more:function () { + this._more = true; + return this; + }, + +// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. +reject:function () { + if (this.options.backtrack_lexer) { + this._backtrack = true; } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, left, right) { return left + "==" + right; })(pos0, result0[0], result0[4]); - } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_notEq() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_value(); - if (result0 !== null) { - result1 = parse_optionalSpace(); - if (result1 !== null) { - result2 = parse_notEqOp(); - if (result2 !== null) { - result3 = parse_optionalSpace(); - if (result3 !== null) { - result4 = parse_value(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + + } + return this; + }, + +// retain first n characters of the match +less:function (n) { + this.unput(this.match.slice(n)); + }, + +// displays already matched input, i.e. for error messages +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, + +// displays upcoming input, i.e. for error messages +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, + +// displays the character position where the lexing error occurred, i.e. for error messages +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, + +// test the lexed token: return FALSE when not a match, otherwise return token +test_match:function (match, indexed_rule) { + var token, + lines, + backup; + + if (this.options.backtrack_lexer) { + // save context + backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }; + if (this.options.ranges) { + backup.yylloc.range = this.yylloc.range.slice(0); } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, left, right) { return left + "!=" + right; })(pos0, result0[0], result0[4]); - } - if (result0 === null) { - pos = pos0; } - return result0; - } - - function parse_numComp() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_value(); - if (result0 !== null) { - result1 = parse_optionalSpace(); - if (result1 !== null) { - result2 = parse_numCompOp(); - if (result2 !== null) { - result3 = parse_optionalSpace(); - if (result3 !== null) { - result4 = parse_value(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; + + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno += lines.length; + } + this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? + lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : + this.yylloc.last_column + match[0].length + }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._backtrack = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) { + this.done = false; + } + if (token) { + return token; + } else if (this._backtrack) { + // recover context + for (var k in backup) { + this[k] = backup[k]; } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, left, op, right) { return left + op + right; })(pos0, result0[0], result0[2], result0[4]); + return false; // rule action called reject() implying the next rule should be tested instead. } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_contains() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_value(); - if (result0 !== null) { - result1 = parse_requiredSpace(); - if (result1 !== null) { - result2 = parse_containsOp(); - if (result2 !== null) { - result3 = parse_requiredSpace(); - if (result3 !== null) { - result4 = parse_value(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; - } else { - result0 = null; - pos = pos1; + return false; + }, + +// return next match in input +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) { + this.done = true; + } + + var token, + match, + tempMatch, + index; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (this.options.backtrack_lexer) { + token = this.test_match(tempMatch, rules[i]); + if (token !== false) { + return token; + } else if (this._backtrack) { + match = false; + continue; // rule action called reject() implying a rule MISmatch. + } else { + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + } else if (!this.options.flex) { + break; } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, left, right) { return "(" + left + "&&" + left + ".indexOf(" + right + ")" + " >= 0)"; })(pos0, result0[0], result0[4]); - } - if (result0 === null) { - pos = pos0; } - return result0; - } - - function parse_like() { - var result0, result1, result2, result3, result4; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - result0 = parse_value(); - if (result0 !== null) { - result1 = parse_requiredSpace(); - if (result1 !== null) { - result2 = parse_likeOp(); - if (result2 !== null) { - result3 = parse_requiredSpace(); - if (result3 !== null) { - result4 = parse_value(); - if (result4 !== null) { - result0 = [result0, result1, result2, result3, result4]; - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; + if (match) { + token = this.test_match(match, rules[index]); + if (token !== false) { + return token; } - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, left, right) { return "_helpers['match'](" + left + "," + right + ")"; })(pos0, result0[0], result0[4]); - } - if (result0 === null) { - pos = pos0; + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; } - return result0; - } - - function parse_letter() { - var result0; - - if (/^[A-Za-z]/.test(input.charAt(pos))) { - result0 = input.charAt(pos); - pos++; + if (this._input === "") { + return this.EOF; } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("[A-Za-z]"); - } + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); } - return result0; - } - - function parse_space() { - var result0; - - if (/^[ \n\t\r]/.test(input.charAt(pos))) { - result0 = input.charAt(pos); - pos++; - } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("[ \\n\\t\\r]"); - } - } - return result0; - } - - function parse_optionalSpace() { - var result0, result1; - - result0 = []; - result1 = parse_space(); - while (result1 !== null) { - result0.push(result1); - result1 = parse_space(); - } - return result0; - } - - function parse_requiredSpace() { - var result0, result1; - - result1 = parse_space(); - if (result1 !== null) { - result0 = []; - while (result1 !== null) { - result0.push(result1); - result1 = parse_space(); - } - } else { - result0 = null; - } - return result0; - } - - function parse_value() { - var result0; - - result0 = parse_integer(); - if (result0 === null) { - result0 = parse_string(); - if (result0 === null) { - result0 = parse_var(); - } - } - return result0; - } - - function parse_var() { - var result0, result1; - var pos0; - - pos0 = pos; - result1 = parse_letter(); - if (result1 !== null) { - result0 = []; - while (result1 !== null) { - result0.push(result1); - result1 = parse_letter(); - } - } else { - result0 = null; - } - if (result0 !== null) { - result0 = (function(offset, letters) { return "_env['" + letters.join("") + "']"; })(pos0, result0); - } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_string() { - var result0, result1, result2; - var pos0, pos1; - - pos0 = pos; - pos1 = pos; - if (input.charCodeAt(pos) === 34) { - result0 = "\""; - pos++; + }, + +// return next match that has a token +lex:function lex() { + var r = this.next(); + if (r) { + return r; } else { - result0 = null; - if (reportFailures === 0) { - matchFailed("\"\\\"\""); - } + return this.lex(); } - if (result0 !== null) { - if (/^[^"]/.test(input.charAt(pos))) { - result2 = input.charAt(pos); - pos++; - } else { - result2 = null; - if (reportFailures === 0) { - matchFailed("[^\"]"); - } - } - if (result2 !== null) { - result1 = []; - while (result2 !== null) { - result1.push(result2); - if (/^[^"]/.test(input.charAt(pos))) { - result2 = input.charAt(pos); - pos++; - } else { - result2 = null; - if (reportFailures === 0) { - matchFailed("[^\"]"); - } - } - } - } else { - result1 = null; - } - if (result1 !== null) { - if (input.charCodeAt(pos) === 34) { - result2 = "\""; - pos++; - } else { - result2 = null; - if (reportFailures === 0) { - matchFailed("\"\\\"\""); - } - } - if (result2 !== null) { - result0 = [result0, result1, result2]; - } else { - result0 = null; - pos = pos1; - } - } else { - result0 = null; - pos = pos1; - } + }, + +// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +begin:function begin(condition) { + this.conditionStack.push(condition); + }, + +// pop the previously active lexer condition state off the condition stack +popState:function popState() { + var n = this.conditionStack.length - 1; + if (n > 0) { + return this.conditionStack.pop(); } else { - result0 = null; - pos = pos1; - } - if (result0 !== null) { - result0 = (function(offset, letters) { return "\"" + letters.join("") + "\""; })(pos0, result0[1]); + return this.conditionStack[0]; } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - function parse_integer() { - var result0, result1; - var pos0; - - pos0 = pos; - if (/^[0-9]/.test(input.charAt(pos))) { - result1 = input.charAt(pos); - pos++; + }, + +// produce the lexer rule set which is active for the currently active lexer condition state +_currentRules:function _currentRules() { + if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; } else { - result1 = null; - if (reportFailures === 0) { - matchFailed("[0-9]"); - } + return this.conditions["INITIAL"].rules; } - if (result1 !== null) { - result0 = []; - while (result1 !== null) { - result0.push(result1); - if (/^[0-9]/.test(input.charAt(pos))) { - result1 = input.charAt(pos); - pos++; - } else { - result1 = null; - if (reportFailures === 0) { - matchFailed("[0-9]"); - } - } - } + }, + +// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +topState:function topState(n) { + n = this.conditionStack.length - 1 - Math.abs(n || 0); + if (n >= 0) { + return this.conditionStack[n]; } else { - result0 = null; - } - if (result0 !== null) { - result0 = (function(offset, digits) { return parseInt(digits.join(""), 10); })(pos0, result0); + return "INITIAL"; } - if (result0 === null) { - pos = pos0; - } - return result0; - } - - - function cleanupExpected(expected) { - expected.sort(); - - var lastExpected = null; - var cleanExpected = []; - for (var i = 0; i < expected.length; i++) { - if (expected[i] !== lastExpected) { - cleanExpected.push(expected[i]); - lastExpected = expected[i]; - } - } - return cleanExpected; - } - - function computeErrorPosition() { - /* - * The first idea was to use |String.split| to break the input up to the - * error position along newlines and derive the line and column from - * there. However IE's |split| implementation is so broken that it was - * enough to prevent it. - */ - - var line = 1; - var column = 1; - var seenCR = false; - - for (var i = 0; i < Math.max(pos, rightmostFailuresPos); i++) { - var ch = input.charAt(i); - if (ch === "\n") { - if (!seenCR) { line++; } - column = 1; - seenCR = false; - } else if (ch === "\r" || ch === "\u2028" || ch === "\u2029") { - line++; - column = 1; - seenCR = true; - } else { - column++; - seenCR = false; - } - } - - return { line: line, column: column }; - } - - - var result = parseFunctions[startRule](); - - /* - * The parser is now in one of the following three states: - * - * 1. The parser successfully parsed the whole input. - * - * - |result !== null| - * - |pos === input.length| - * - |rightmostFailuresExpected| may or may not contain something - * - * 2. The parser successfully parsed only a part of the input. - * - * - |result !== null| - * - |pos < input.length| - * - |rightmostFailuresExpected| may or may not contain something - * - * 3. The parser did not successfully parse any part of the input. - * - * - |result === null| - * - |pos === 0| - * - |rightmostFailuresExpected| contains at least one failure - * - * All code following this comment (including called functions) must - * handle these states. - */ - if (result === null || pos !== input.length) { - var offset = Math.max(pos, rightmostFailuresPos); - var found = offset < input.length ? input.charAt(offset) : null; - var errorPosition = computeErrorPosition(); - - throw new this.SyntaxError( - cleanupExpected(rightmostFailuresExpected), - found, - offset, - errorPosition.line, - errorPosition.column - ); - } - - return result; }, - - /* Returns the parser source code. */ - toSource: function() { return this._source; } - }; - - /* Thrown when a parser encounters a syntax error. */ - - result.SyntaxError = function(expected, found, offset, line, column) { - function buildMessage(expected, found) { - var expectedHumanized, foundHumanized; - - switch (expected.length) { - case 0: - expectedHumanized = "end of input"; - break; - case 1: - expectedHumanized = expected[0]; - break; - default: - expectedHumanized = expected.slice(0, expected.length - 1).join(", ") - + " or " - + expected[expected.length - 1]; - } - - foundHumanized = found ? quote(found) : "end of input"; - - return "Expected " + expectedHumanized + " but " + foundHumanized + " found."; - } - - this.name = "SyntaxError"; - this.expected = expected; - this.found = found; - this.message = buildMessage(expected, found); - this.offset = offset; - this.line = line; - this.column = column; - }; - - result.SyntaxError.prototype = Error.prototype; - - return result; + +// alias for begin(condition) +pushState:function pushState(condition) { + this.begin(condition); + }, + +// return the number of states currently on the stack +stateStackSize:function stateStackSize() { + return this.conditionStack.length; + }, +options: {}, +performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { + +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:/* skip whitespace */ +break; +case 1:return 9; +break; +case 2:return 10; +break; +case 3:return 6; +break; +case 4:return 12; +break; +case 5:return 13; +break; +case 6:return 14; +break; +case 7:return 15; +break; +case 8:return 16; +break; +case 9:return 17; +break; +case 10:return 18; +break; +case 11:return 19; +break; +case 12:return 7; +break; +case 13:return 8; +break; +case 14:return 5; +break; +} +}, +rules: [/^(?:\s+)/,/^(?:and|AND\b)/,/^(?:or|OR\b)/,/^(?:not|NOT\b)/,/^(?:=)/,/^(?:<>)/,/^(?:>|<|<=|>=)/,/^(?:contains|CONTAINS\b)/,/^(?:like|LIKE\b)/,/^(?:[0-9]+)/,/^(?:"[^\"]*")/,/^(?:[A-Za-z]+)/,/^(?:\()/,/^(?:\))/,/^(?:$)/], +conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14],"inclusive":true}} +}; +return lexer; })(); +parser.lexer = lexer; +function Parser () { + this.yy = {}; +} +Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})(); + + +if (typeof require !== 'undefined' && typeof exports !== 'undefined') { +exports.parser = zerkelParser; +exports.Parser = zerkelParser.Parser; +exports.parse = function () { return zerkelParser.parse.apply(zerkelParser, arguments); }; +exports.main = function commonjsMain(args) { + if (!args[1]) { + console.log('Usage: '+args[0]+' FILE'); + process.exit(1); + } + var source = require('fs').readFileSync(require('path').normalize(args[1]), "utf8"); + return exports.parser.parse(source); +}; +if (typeof module !== 'undefined' && require.main === module) { + exports.main(process.argv.slice(1)); +} +} \ No newline at end of file diff --git a/src/zerkel-parser.pegjs b/src/zerkel-parser.pegjs deleted file mode 100644 index ba1d1e4..0000000 --- a/src/zerkel-parser.pegjs +++ /dev/null @@ -1,85 +0,0 @@ -prog - = optionalSpace comb:combination optionalSpace { return comb; } - -combination - = and / or / not / clause - -clause - = eq / notEq / numComp / contains / like / primary - -primary - = value / "(" comb:combination ")" { return "(" + comb + ")"; } - -/* Compound Ops */ -andOp - = "AND" / "and" - -orOp - = "OR" / "or" - -notOp - = "NOT" / "not" - -and - = left:clause requiredSpace andOp requiredSpace right:combination { return left + "&&" + right; } - -or - = left:clause requiredSpace orOp requiredSpace right:combination { return left + "||" + right; } - -not - = notOp requiredSpace clause:clause { return "!" + clause; } - -/* Ops */ -containsOp - = "CONTAINS" / "contains" - -likeOp - = "LIKE" / "like" - -eqOp - = "=" - -notEqOp - = "<>" - -numCompOp - = ">" / "<" / ">=" / "<=" -eq - = left:value optionalSpace eqOp optionalSpace right:value { return left + "==" + right; } - -notEq - = left:value optionalSpace notEqOp optionalSpace right:value { return left + "!=" + right; } - -numComp - = left:value optionalSpace op:numCompOp optionalSpace right:value { return left + op + right; } - -contains - = left:value requiredSpace containsOp requiredSpace right:value { return "(" + left + "&&" + left + ".indexOf(" + right + ")" + " >= 0)"; } - -like - = left:value requiredSpace likeOp requiredSpace right:value { return "_helpers['match'](" + left + "," + right + ")"; } - -/* values */ -letter - = [A-Za-z] - -space - = [ \n\t\r] - -optionalSpace - = space* - -requiredSpace - = space+ - -value - = integer / string / var - -var - = letters:letter+ { return "_env['" + letters.join("") + "']"; } - -string - = "\"" letters:[^\"]+ "\"" { return "\"" + letters.join("") + "\""; } - -integer - = digits:[0-9]+ { return parseInt(digits.join(""), 10); } diff --git a/test/mocha.opts b/test/mocha.opts index 547e98a..008b422 100644 --- a/test/mocha.opts +++ b/test/mocha.opts @@ -1,6 +1,6 @@ ---compilers coffee:coffee-script +--compilers coffee:coffee-script/register --ui bdd --reporter spec --recursive --timeout 9999s ---bail \ No newline at end of file +--bail