diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..8e2e6a6 --- /dev/null +++ b/AUTHORS @@ -0,0 +1,6 @@ +# actually wrote jsparse. +Chris Double + +# just this random guy who came along and packaged it for node. +Peter Burns + diff --git a/es3.js b/examples/es3.js similarity index 89% rename from es3.js rename to examples/es3.js index 6065322..4d953a3 100644 --- a/es3.js +++ b/examples/es3.js @@ -1,15 +1,15 @@ // Copyright (C) 2007 Chris Double. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE @@ -22,16 +22,18 @@ // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +//This has not been updated to use jsparse.* + // Forward Declarations -var SourceElement = +var SourceElement = function(input) { return SourceElement(input); } -var AssignmentExpression = +var AssignmentExpression = function(input) { return AssignmentExpression(input); } -var Expression = +var Expression = function(input) { return Expression(input); } -var Statement = +var Statement = function(input) { return Statement(input); } -var LeftHandSideExpression = +var LeftHandSideExpression = function(input) { return LeftHandSideExpression(input); } var Whitespace = choice("\t", " "); @@ -48,7 +50,7 @@ var BooleanLiteral = choice("true", "false"); var Zero = action("0", function(ast) { return 0; }); var DecimalDigit = action(range("0", "9"), function(ast) { return parseInt(ast); }); var NonZeroDigit = action(range("1", "9"), function(ast) { return parseInt(ast); }); -var DecimalDigits = repeat1(DecimalDigit); +var DecimalDigits = repeat1(DecimalDigit); var DecimalIntegerLiteral = choice(Zero, sequence(NonZeroDigit, optional(DecimalDigits))); var SignedInteger = choice(DecimalDigits, sequence("+", DecimalDigits), sequence("-", DecimalDigits)); var ExponentIndicator = choice("e", "E"); @@ -77,9 +79,9 @@ var DoubleStringCharacters = repeat1(DoubleStringCharacter); var StringLiteral = choice(sequence("\"", optional(DoubleStringCharacters), "\""), sequence("'", optional(SingleStringCharacters), "'")); -var Literal = choice(NullLiteral, BooleanLiteral, NumericLiteral, StringLiteral); +var Literal = choice(NullLiteral, BooleanLiteral, NumericLiteral, StringLiteral); -var Keyword = +var Keyword = choice("break", "case", "catch", @@ -112,10 +114,10 @@ var HexDigit = choice(range("0", "9"), range("a", "f"), range("A", "F")); var IdentifierLetter = choice(range("a", "z"), range("A", "Z")); var IdentifierStart = choice(IdentifierLetter, "$", "_"); var IdentifierPart = choice(IdentifierStart, range("0-9")); -var IdentifierName = +var IdentifierName = action(sequence(IdentifierStart, join_action(repeat0(IdentifierPart), "")), - function(ast) { - return ast[0].concat(ast[1]); + function(ast) { + return ast[0].concat(ast[1]); }); var Identifier = butnot(IdentifierName, ReservedWord); @@ -123,13 +125,13 @@ var StatementList = repeat1(Statement); var Block = wsequence("{", optional(StatementList), "}"); var Initialiser = wsequence("=", AssignmentExpression); var VariableDeclaration = wsequence(Identifier, optional(Initialiser)); -var VariableDeclarationList = wlist(VariableDeclaration, ","); -var VariableStatement = +var VariableDeclarationList = wlist(VariableDeclaration, ","); +var VariableStatement = wsequence("var", VariableDeclarationList); var EmptyStatement = token(";"); -var IfStatement = +var IfStatement = choice(wsequence("if", "(", Expression, ")", Statement, "else", Statement), wsequence("if", "(", Expression, ")", Statement)); @@ -161,15 +163,15 @@ var ThrowStatement = wsequence("throw", Expression, ";"); var Catch = wsequence("catch", "(", Identifier, ")", Block); var Finally = wsequence("finally", Block); -var TryStatement = +var TryStatement = choice(wsequence("try", Block, Catch), wsequence("try", Block, Finally), wsequence("try", Block, Catch, Finally)); -var ExpressionStatement = +var ExpressionStatement = choice(sequence(choice("{", "function"), nothing_p), Expression); -var Statement = +var Statement = choice(Block, VariableStatement, EmptyStatement, @@ -185,27 +187,27 @@ var Statement = ThrowStatement, TryStatement); -var FunctionDeclaration = +var FunctionDeclaration = function(input) { return FunctionDeclaration(input); } var FunctionBody = repeat0(SourceElement); -var FormalParameterList = wlist(Identifier, ","); -var FunctionExpression = +var FormalParameterList = wlist(Identifier, ","); +var FunctionExpression = wsequence("function", optional(Identifier), "(", optional(FormalParameterList), ")", "{", FunctionBody, "}"); -var FunctionDeclaration = +var FunctionDeclaration = wsequence("function", Identifier, "(", optional(FormalParameterList), ")", "{", FunctionBody, "}"); -var PrimaryExpression = +var PrimaryExpression = function(input) { return PrimaryExpression(input); } -var ArgumentList = list(AssignmentExpression, ","); -var Arguments = +var ArgumentList = list(AssignmentExpression, ","); +var Arguments = choice(wsequence("(", ")"), wsequence("(", ArgumentList, ")")); -var MemberExpression = function(input) { return MemberExpression(input); } +var MemberExpression = function(input) { return MemberExpression(input); } var MemberExpression = left_factor_action(sequence(choice(wsequence("new", MemberExpression, Arguments), PrimaryExpression, @@ -213,18 +215,18 @@ var MemberExpression = repeat0(choice(wsequence("[", Expression, "]"), wsequence(".", Identifier))))); -var NewExpression = +var NewExpression = choice(MemberExpression, wsequence("new", NewExpression)); -var CallExpression = +var CallExpression = left_factor_action(wsequence(wsequence(MemberExpression, Arguments), repeat0(choice(Arguments, wsequence("[", Expression, "]"), wsequence(".", Identifier))))); - + var LeftHandSideExpression = choice(CallExpression, NewExpression); -var AssignmentOperator = +var AssignmentOperator = choice("=", "*=", "/=", @@ -238,29 +240,29 @@ var AssignmentOperator = "^=", "|="); -var LogicalORExpression = +var LogicalORExpression = function(input) { return LogicalORExpression(input); } -var LogicalANDExpression = +var LogicalANDExpression = function(input) { return LogicalANDExpression(input); } -var BitwiseORExpression = +var BitwiseORExpression = function(input) { return BitwiseORExpression(input); } -var BitwiseXORExpression = +var BitwiseXORExpression = function(input) { return BitwiseXORExpression(input); } -var BitwiseANDExpression = +var BitwiseANDExpression = function(input) { return BitwiseANDExpression(input); } -var EqualityExpression = +var EqualityExpression = function(input) { return EqualityExpression(input); } -var RelationalExpression = +var RelationalExpression = function(input) { return RelationalExpression(input); } -var ShiftExpression = +var ShiftExpression = function(input) { return ShiftExpression(input); } -var AdditiveExpression = +var AdditiveExpression = function(input) { return AdditiveExpression(input); } -var MultiplicativeExpression = +var MultiplicativeExpression = function(input) { return MultiplicativeExpression(input); } -var UnaryExpression = +var UnaryExpression = function(input) { return UnaryExpression(input); } -var PostfixExpression = +var PostfixExpression = function(input) { return PostfixExpression(input); } var PostfixExpression = @@ -290,8 +292,8 @@ var AdditiveExpression = wsequence(MultiplicativeExpression, repeat0(choice(wsequence("+", MultiplicativeExpression), wsequence("-", MultiplicativeExpression)))); - -var ShiftExpression = + +var ShiftExpression = wsequence(AdditiveExpression, repeat0(choice(wsequence("<<", AdditiveExpression), wsequence(">>", AdditiveExpression), @@ -306,37 +308,37 @@ var RelationalExpression = wsequence("instanceof", ShiftExpression)))); var EqualityExpression = - wsequence(RelationalExpression, + wsequence(RelationalExpression, repeat0(choice(wsequence("==", RelationalExpression), wsequence("!==", RelationalExpression), wsequence("===", RelationalExpression), wsequence("!==", RelationalExpression)))); -var BitwiseANDExpression = +var BitwiseANDExpression = wsequence(EqualityExpression, repeat0(wsequence("&", EqualityExpression))); -var BitwiseXORExpression = +var BitwiseXORExpression = wsequence(BitwiseANDExpression, repeat0(wsequence("^", BitwiseANDExpression))); -var BitwiseORExpression = +var BitwiseORExpression = wsequence(BitwiseXORExpression, repeat0(wsequence("|", BitwiseXORExpression))); -var LogicalANDExpression = +var LogicalANDExpression = wsequence(BitwiseORExpression, repeat0(wsequence("&&", BitwiseORExpression))); -var LogicalORExpression = +var LogicalORExpression = wsequence(LogicalANDExpression, repeat0(wsequence("||", LogicalANDExpression))); -var ConditionalExpression = +var ConditionalExpression = choice(LogicalORExpression, wsequence(LogicalORExpression, "?", AssignmentExpression, ":", AssignmentExpression)); -var AssignmentExpression = +var AssignmentExpression = choice(wsequence(LeftHandSideExpression, AssignmentOperator, AssignmentExpression), ConditionalExpression); var Expression = list(AssignmentExpression, ","); -var Elision = repeat1(","); +var Elision = repeat1(","); var ElementList = list(wsequence(optional(Elision), AssignmentExpression), ","); -var ArrayLiteral = +var ArrayLiteral = choice(wsequence("[", optional(Elision), "]"), wsequence("[", ElementList, "]"), wsequence("[", ElementList, optional(Elision), "]")); @@ -344,11 +346,11 @@ var ArrayLiteral = var PropertyName = choice(Identifier, StringLiteral, NumericLiteral); var PropertyNameAndValueList = list(wsequence(PropertyName, ":", AssignmentExpression), ","); -var ObjectLiteral = +var ObjectLiteral = choice(wsequence("{", "}"), wsequence("{", PropertyNameAndValueList, "}")); -var PrimaryExpression = +var PrimaryExpression = choice("this", wsequence("(", Expression, ")"), Identifier, diff --git a/es3_tests.js b/examples/es3_tests.js similarity index 98% rename from es3_tests.js rename to examples/es3_tests.js index 03fdb59..758cf30 100644 --- a/es3_tests.js +++ b/examples/es3_tests.js @@ -1,15 +1,15 @@ // Copyright (C) 2007 Chris Double. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE @@ -21,6 +21,9 @@ // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // + +//This has NOT been updated to use jsparse.* + load("jsparse.js"); load("es3.js"); load("tests.js"); @@ -36,7 +39,7 @@ function LineTerminatorTest() { assertTrue("LineTerminator failed to parse newline", LineTerminator(ps("\n"))); assertFalse("LineTerminator parsed incorrect data", LineTerminator(ps("abcd"))); } - + function SingleLineCommentTest() { assertTrue("SingleLineComment failed to parse comment with no space", SingleLineComment(ps("//foo\n"))); assertTrue("SingleLineComment failed to parse comment with space", SingleLineComment(ps("// foo\n"))); @@ -86,7 +89,7 @@ function NonZeroDigitTest() { function IdentifierTest() { assertFullyParsed("Identifier", "abcd"); assertFalse("Identifier('while')", Identifier(ps('while'))); - assertTrue("Identifier('abcd').ast=='abcd'", Identifier(ps('abcd')).ast=='abcd'); + assertTrue("Identifier('abcd').ast=='abcd'", Identifier(ps('abcd')).ast=='abcd'); } function DecimalDigitsTest() { @@ -167,7 +170,7 @@ function FunctionDeclarationTest() { assertFullyParsed("FunctionBody", "return 123;"); assertFullyParsed("FunctionBody", "return function() { };"); } - + function allTests() { WhitespaceTest(); LineTerminatorTest(); @@ -189,4 +192,4 @@ function allTests() { FunctionDeclarationTest(); } -time(function() { runTests(allTests); }); \ No newline at end of file +time(function() { runTests(allTests); }); diff --git a/example1.js b/examples/example1.js similarity index 73% rename from example1.js rename to examples/example1.js index 8feda80..f3ffae0 100644 --- a/example1.js +++ b/examples/example1.js @@ -1,15 +1,15 @@ // Copyright (C) 2007 Chris Double. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE @@ -27,14 +27,23 @@ // Value := [0-9]+ / '(' Expr ')' // Product := Value (('*' / '/') Value)* // Sum := Product (('+' / '-') Product)* -// Expr := Sum +// Expr := Sum // -// Forward definitions required due to lack of laziness in JS + +//to run in both node and browser +try { + var jp = require("../jsparse") +} catch(e) { + var jp = jsparse; +} + +// Forward definitions required due to lack of laziness in JS var Expr = function(state) { return Expr(state); } -var Value = choice(repeat1(range('0','9')), Expr); -var Product = sequence(Value, repeat0(sequence(choice('*', '/'), Value))); -var Sum = sequence(Product, repeat0(sequence(choice('+', '-'), Product))); +var Value = jp.choice(jp.repeat1(jp.range('0','9')), Expr); +var Product = jp.sequence(Value, jp.repeat0(jp.sequence(jp.choice('*', '/'), Value))); +var Sum = jp.sequence(Product, jp.repeat0(jp.sequence(jp.choice('+', '-'), Product))); var Expr = Sum; -// Usage: Expr(ps("1+2*3-4")) \ No newline at end of file +console.log(JSON.stringify(Expr(jp.ps("1+2*3-4")).ast)) +//Yields: [[["1"],[]],[["+",[["2"],[["*",["3"]]]]],["-",[["4"],[]]]]] diff --git a/example2.js b/examples/example2.js similarity index 71% rename from example2.js rename to examples/example2.js index 455efd1..6df5559 100644 --- a/example2.js +++ b/examples/example2.js @@ -1,15 +1,15 @@ // Copyright (C) 2007 Chris Double. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE @@ -28,9 +28,18 @@ // Value := [0-9]+ / '(' Expr ')' // Product := Value (('*' / '/') Value)* // Sum := Product (('+' / '-') Product)* -// Expr := Sum +// Expr := Sum // -// Forward definitions required due to lack of laziness in JS + +//to run in both node and browser +try { + var jp = require("../jsparse") +} catch(e) { + var jp = jsparse; +} + + +// Forward definitions required due to lack of laziness in JS var Expr = function(state) { return Expr(state); } // AST objects @@ -44,22 +53,37 @@ Operator.prototype.toString = function() { return uneval(this); } -var Number = - action(repeat1(range('0','9')), +var Number = + jp.action(jp.repeat1(jp.range('0','9')), function(ast) { return parseInt(ast.join("")); }); -var Value = choice(Number, Expr); +var Value = jp.choice(Number, Expr); function operator_action(p) { - return action(p, - function(ast) { + return jp.action(p, + function(ast) { return function(lhs,rhs) { return new Operator(ast, lhs, rhs); }; }); } -var Product = chainl(Value, operator_action(choice('*', '/'))); -var Sum = chainl(Product, operator_action(choice('+', '-'))); +var Product = jp.chainl(Value, operator_action(jp.choice('*', '/'))); +var Sum = jp.chainl(Product, operator_action(jp.choice('+', '-'))); var Expr = Sum; -// Usage: Expr(ps("1+2*3-4")) \ No newline at end of file +var example_result = Expr(jp.ps("1+2*3-4")).ast; +console.log(JSON.stringify(example_result, null, 2)) +//Yields: +// { +// "symbol": "-", +// "lhs": { +// "symbol": "+", +// "lhs": 1, +// "rhs": { +// "symbol": "*", +// "lhs": 2, +// "rhs": 3 +// } +// }, +// "rhs": 4 +// } diff --git a/example3.js b/examples/example3.js similarity index 77% rename from example3.js rename to examples/example3.js index a2912c1..c122400 100644 --- a/example3.js +++ b/examples/example3.js @@ -1,15 +1,15 @@ // Copyright (C) 2007 Chris Double. -// +// // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: -// +// // 1. Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. -// +// // 2. Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. -// +// // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE @@ -28,21 +28,30 @@ // Value := [0-9]+ / '(' Expr ')' // Product := Value (('*' / '/') Value)* // Sum := Product (('+' / '-') Product)* -// Expr := Sum +// Expr := Sum // -// Forward definitions required due to lack of laziness in JS + +//to run in both node and browser +try { + var jp = require("../jsparse") +} catch(e) { + var jp = jsparse; +} + + +// Forward definitions required due to lack of laziness in JS var Expr = function(state) { return Expr(state); } -var Number = - action(repeat1(range('0','9')), +var Number = + jp.action(jp.repeat1(jp.range('0','9')), function(ast) { return parseInt(ast.join("")); }); -var Value = choice(Number, Expr); +var Value = jp.choice(Number, Expr); -function operator_action(p, func) +function operator_action(p, func) { - return action(p, function(ast) { return func; }); + return jp.action(p, function(ast) { return func; }); } var Times = operator_action('*', function(lhs,rhs) { return lhs*rhs; }); @@ -50,8 +59,9 @@ var Divides = operator_action('/', function(lhs,rhs) { return lhs/rhs; }); var Plus = operator_action('+', function(lhs,rhs) { return lhs+rhs; }); var Minus = operator_action('-', function(lhs,rhs) { return lhs-rhs; }); -var Product = chainl(Value, choice(Times, Divides)); -var Sum = chainl(Product, choice(Plus, Minus)); +var Product = jp.chainl(Value, jp.choice(Times, Divides)); +var Sum = jp.chainl(Product, jp.choice(Plus, Minus)); var Expr = Sum; -// Usage: Expr(ps("1+2*3-4")) \ No newline at end of file +console.log(Expr(jp.ps("1+2*3-4")).ast) +//Yields: 3 diff --git a/jsparse.js b/jsparse.js index d62f22b..e50efac 100644 --- a/jsparse.js +++ b/jsparse.js @@ -22,89 +22,99 @@ // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // -function foldl(f, initial, seq) { +var jsparse; +try { + jsparse = exports; +} catch(e) { + jsparse = {}; +} + +jsparse.foldl = function foldl(f, initial, seq) { for(var i=0; i< seq.length; ++i) initial = f(initial, seq[i]); return initial; } -var memoize = true; +jsparse.memoize = true; -function ParseState(input, index) { - this.input = input; - this.index = index || 0; - this.length = input.length - this.index; - this.cache = { }; - return this; -} +jsparse.ParseState = (function() { + function ParseState(input, index) { + this.input = input; + this.index = index || 0; + this.length = input.length - this.index; + this.cache = { }; + return this; + } -ParseState.prototype.from = function(index) { - var r = new ParseState(this.input, this.index + index); - r.cache = this.cache; - r.length = this.length - index; - return r; -} + ParseState.prototype.from = function(index) { + var r = new ParseState(this.input, this.index + index); + r.cache = this.cache; + r.length = this.length - index; + return r; + } -ParseState.prototype.substring = function(start, end) { - return this.input.substring(start + this.index, (end || this.length) + this.index); -} + ParseState.prototype.substring = function(start, end) { + return this.input.substring(start + this.index, (end || this.length) + this.index); + } -ParseState.prototype.trimLeft = function() { - var s = this.substring(0); - var m = s.match(/^\s+/); - return m ? this.from(m[0].length) : this; -} + ParseState.prototype.trimLeft = function() { + var s = this.substring(0); + var m = s.match(/^\s+/); + return m ? this.from(m[0].length) : this; + } -ParseState.prototype.at = function(index) { - return this.input.charAt(this.index + index); -} + ParseState.prototype.at = function(index) { + return this.input.charAt(this.index + index); + } -ParseState.prototype.toString = function() { - return 'PS"' + this.substring(0) + '"'; -} + ParseState.prototype.toString = function() { + return 'PS"' + this.substring(0) + '"'; + } -ParseState.prototype.getCached = function(pid) { - if(!memoize) - return false; + ParseState.prototype.getCached = function(pid) { + if(!jsparse.memoize) + return false; - var p = this.cache[pid]; - if(p) - return p[this.index]; - else - return false; -} + var p = this.cache[pid]; + if(p) + return p[this.index]; + else + return false; + } -ParseState.prototype.putCached = function(pid, cached) { - if(!memoize) - return false; + ParseState.prototype.putCached = function(pid, cached) { + if(!jsparse.memoize) + return false; - var p = this.cache[pid]; - if(p) - p[this.index] = cached; - else { - p = this.cache[pid] = { }; - p[this.index] = cached; + var p = this.cache[pid]; + if(p) + p[this.index] = cached; + else { + p = this.cache[pid] = { }; + p[this.index] = cached; + } } -} + return ParseState; +})() -function ps(str) { - return new ParseState(str); +jsparse.ps = function ps(str) { + return new jsparse.ParseState(str); } // 'r' is the remaining string to be parsed. // 'matched' is the portion of the string that // was successfully matched by the parser. // 'ast' is the AST returned by the successfull parse. -function make_result(r, matched, ast) { - return { remaining: r, matched: matched, ast: ast }; +jsparse.make_result = function make_result(r, matched, ast) { + return { remaining: r, matched: matched, ast: ast }; } -var parser_id = 0; +jsparse.parser_id = 0; // 'token' is a parser combinator that given a string, returns a parser // that parses that string value. The AST contains the string that was parsed. -function token(s) { - var pid = parser_id++; +jsparse.token = function token(s) { + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -123,8 +133,8 @@ function token(s) { // Like 'token' but for a single character. Returns a parser that given a string // containing a single character, parses that character value. -function ch(c) { - var pid = parser_id++; +jsparse.ch = function ch(c) { + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -143,8 +153,8 @@ function ch(c) { // 'range' is a parser combinator that returns a single character parser // (similar to 'ch'). It parses single characters that are in the inclusive // range of the 'lower' and 'upper' bounds ("a" to "z" for example). -function range(lower, upper) { - var pid = parser_id++; +jsparse.range = function range(lower, upper) { + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -167,15 +177,15 @@ function range(lower, upper) { // Helper function to convert string literals to token parsers // and perform other implicit parser conversions. -function toParser(p) { - return (typeof(p) == "string") ? token(p) : p; +jsparse.toParser = function toParser(p) { + return (typeof(p) == "string") ? jsparse.token(p) : p; } // Parser combinator that returns a parser that // skips whitespace before applying parser. -function whitespace(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.whitespace = function whitespace(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -190,9 +200,9 @@ function whitespace(p) { // Parser combinator that passes the AST generated from the parser 'p' // to the function 'f'. The result of 'f' is used as the AST in the result. -function action(p, f) { - var p = toParser(p); - var pid = parser_id++; +jsparse.action = function action(p, f) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -214,8 +224,8 @@ function action(p, f) { // Given a parser that produces an array as an ast, returns a // parser that produces an ast with the array joined by a separator. -function join_action(p, sep) { - return action(p, function(ast) { return ast.join(sep); }); +jsparse.join_action = function join_action(p, sep) { + return jsparse.action(p, function(ast) { return ast.join(sep); }); } // Given an ast of the form [ Expression, [ a, b, ...] ], convert to @@ -227,8 +237,8 @@ function join_action(p, sep) { // MemberExpression [ Expression ] // MemberExpression . Identifier // new MemberExpression Arguments -function left_factor(ast) { - return foldl(function(v, action) { +jsparse.left_factor = function left_factor(ast) { + return jsparse.foldl(function(v, action) { return [ v, action ]; }, ast[0], @@ -237,16 +247,16 @@ function left_factor(ast) { // Return a parser that left factors the ast result of the original // parser. -function left_factor_action(p) { - return action(p, left_factor); +jsparse.left_factor_action = function left_factor_action(p) { + return jsparse.action(p, jsparse.left_factor); } // 'negate' will negate a single character parser. So given 'ch("a")' it will successfully // parse any character except for 'a'. Or 'negate(range("a", "z"))' will successfully parse // anything except the lowercase characters a-z. -function negate(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.negate = function negate(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -256,7 +266,7 @@ function negate(p) { if(state.length >= 1) { var r = p(state); if(!r) - cached = make_result(state.from(1), state.at(0), state.at(0)); + cached = jsparse.make_result(state.from(1), state.at(0), state.at(0)); else cached = false; } @@ -268,27 +278,29 @@ function negate(p) { }; } -// 'end_p' is a parser that is successful if the input string is empty (ie. end of parse). -function end_p(state) { +// 'end' is a parser that is successful if the input string is empty (ie. end of parse). +jsparse.end = function end(state) { if(state.length == 0) - return make_result(state, undefined, undefined); + return jsparse.make_result(state, undefined, undefined); else return false; } +jsparse.end_p = jsparse.end; -// 'nothing_p' is a parser that always fails. -function nothing_p(state) { +// 'nothing' is a parser that always fails. +jsparse.nothing = function nothing(state) { return false; } +jsparse.nothing_p = jsparse.nothing; // 'sequence' is a parser combinator that processes a number of parsers in sequence. // It can take any number of arguments, each one being a parser. The parser that 'sequence' // returns succeeds if all the parsers in the sequence succeeds. It fails if any of them fail. -function sequence() { +jsparse.sequence = function sequence() { var parsers = []; for(var i = 0; i < arguments.length; ++i) - parsers.push(toParser(arguments[i])); - var pid = parser_id++; + parsers.push(jsparse.toParser(arguments[i])); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -314,7 +326,7 @@ function sequence() { } } if(i == parsers.length) { - cached = make_result(state, matched, ast); + cached = jsparse.make_result(state, matched, ast); } else cached = false; @@ -324,23 +336,23 @@ function sequence() { } // Like sequence, but ignores whitespace between individual parsers. -function wsequence() { +jsparse.wsequence = function wsequence() { var parsers = []; for(var i=0; i < arguments.length; ++i) { - parsers.push(whitespace(toParser(arguments[i]))); + parsers.push(jsparse.whitespace(jsparse.toParser(arguments[i]))); } - return sequence.apply(null, parsers); + return jsparse.sequence.apply(null, parsers); } // 'choice' is a parser combinator that provides a choice between other parsers. // It takes any number of parsers as arguments and returns a parser that will try // each of the given parsers in order. The first one that succeeds results in a // successfull parse. It fails if all parsers fail. -function choice() { +jsparse.choice = function choice() { var parsers = []; for(var i = 0; i < arguments.length; ++i) - parsers.push(toParser(arguments[i])); - var pid = parser_id++; + parsers.push(jsparse.toParser(arguments[i])); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); @@ -368,10 +380,10 @@ function choice() { // It returns a parser that succeeds if 'p1' matches and 'p2' does not, or // 'p1' matches and the matched text is longer that p2's. // Useful for things like: butnot(IdentifierName, ReservedWord) -function butnot(p1,p2) { - var p1 = toParser(p1); - var p2 = toParser(p2); - var pid = parser_id++; +jsparse.butnot = function butnot(p1,p2) { + var p1 = jsparse.toParser(p1); + var p2 = jsparse.toParser(p2); + var pid = jsparse.parser_id++; // match a but not b. if both match and b's matched text is shorter // than a's, a failed match is made @@ -405,10 +417,10 @@ function butnot(p1,p2) { // 'difference' is a parser combinator that takes two parsers, 'p1' and 'p2'. // It returns a parser that succeeds if 'p1' matches and 'p2' does not. If // both match then if p2's matched text is shorter than p1's it is successfull. -function difference(p1,p2) { - var p1 = toParser(p1); - var p2 = toParser(p2); - var pid = parser_id++; +jsparse.difference = function difference(p1,p2) { + var p1 = jsparse.toParser(p1); + var p2 = jsparse.toParser(p2); + var pid = jsparse.parser_id++; // match a but not b. if both match and b's matched text is shorter // than a's, a successfull match is made @@ -437,10 +449,10 @@ function difference(p1,p2) { // 'xor' is a parser combinator that takes two parsers, 'p1' and 'p2'. // It returns a parser that succeeds if 'p1' or 'p2' match but fails if // they both match. -function xor(p1, p2) { - var p1 = toParser(p1); - var p2 = toParser(p2); - var pid = parser_id++; +jsparse.xor = function xor(p1, p2) { + var p1 = jsparse.toParser(p1); + var p2 = jsparse.toParser(p2); + var pid = jsparse.parser_id++; // match a or b but not both return function(state) { @@ -462,9 +474,9 @@ function xor(p1, p2) { // A parser combinator that takes one parser. It returns a parser that // looks for zero or more matches of the original parser. -function repeat0(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.repeat0 = function repeat0(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; @@ -483,7 +495,7 @@ function repeat0(p) { break; state = result.remaining; } - cached = make_result(state, matched, ast); + cached = jsparse.make_result(state, matched, ast); savedState.putCached(pid, cached); return cached; } @@ -491,9 +503,9 @@ function repeat0(p) { // A parser combinator that takes one parser. It returns a parser that // looks for one or more matches of the original parser. -function repeat1(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.repeat1 = function repeat1(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; @@ -515,7 +527,7 @@ function repeat1(p) { state = result.remaining; result = p(state); } - cached = make_result(state, matched, ast); + cached = jsparse.make_result(state, matched, ast); } savedState.putCached(pid, cached); return cached; @@ -524,16 +536,16 @@ function repeat1(p) { // A parser combinator that takes one parser. It returns a parser that // matches zero or one matches of the original parser. -function optional(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.optional = function optional(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); if(cached) return cached; var r = p(state); - cached = r || make_result(state, "", false); + cached = r || jsparse.make_result(state, "", false); savedState.putCached(pid, cached); return cached; } @@ -543,14 +555,14 @@ function optional(p) { // ignores its result. This can be useful for parsing literals that you // don't want to appear in the ast. eg: // sequence(expect("("), Number, expect(")")) => ast: Number -function expect(p) { - return action(p, function(ast) { return undefined; }); +jsparse.expect = function expect(p) { + return jsparse.action(p, function(ast) { return undefined; }); } -function chain(p, s, f) { - var p = toParser(p); +jsparse.chain = function chain(p, s, f) { + var p = jsparse.toParser(p); - return action(sequence(p, repeat0(action(sequence(s, p), f))), + return jsparse.action(jsparse.sequence(p, jsparse.repeat0(jsparse.action(jsparse.sequence(s, p), f))), function(ast) { return [ast[0]].concat(ast[1]); }); } @@ -559,46 +571,46 @@ function chain(p, s, f) { // of the form: function(lhs,rhs) { return x; } // Where 'x' is the result of applying some operation to the lhs and rhs AST's from the item // parser. -function chainl(p, s) { - var p = toParser(p); - return action(sequence(p, repeat0(sequence(s, p))), +jsparse.chainl = function chainl(p, s) { + var p = jsparse.toParser(p); + return jsparse.action(jsparse.sequence(p, jsparse.repeat0(jsparse.sequence(s, p))), function(ast) { - return foldl(function(v, action) { return action[0](v, action[1]); }, ast[0], ast[1]); + return jsparse.foldl(function(v, action) { return action[0](v, action[1]); }, ast[0], ast[1]); }); } // A parser combinator that returns a parser that matches lists of things. The parser to // match the list item and the parser to match the seperator need to // be provided. The AST is the array of matched items. -function list(p, s) { - return chain(p, s, function(ast) { return ast[1]; }); +jsparse.list = function list(p, s) { + return jsparse.chain(p, s, function(ast) { return ast[1]; }); } // Like list, but ignores whitespace between individual parsers. -function wlist() { +jsparse.wlist = function wlist() { var parsers = []; for(var i=0; i < arguments.length; ++i) { - parsers.push(whitespace(arguments[i])); + parsers.push(jsparse.whitespace(arguments[i])); } - return list.apply(null, parsers); + return jsparse.list.apply(null, parsers); } // A parser that always returns a zero length match -function epsilon_p(state) { - return make_result(state, "", undefined); +jsparse.epsilon_p = function epsilon_p(state) { + return jsparse.make_result(state, "", undefined); } // Allows attaching of a function anywhere in the grammer. If the function returns // true then parse succeeds otherwise it fails. Can be used for testing if a symbol // is in the symbol table, etc. -function semantic(f) { - var pid = parser_id++; +jsparse.semantic = function semantic(f) { + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); if(cached) return cached; - cached = f() ? make_result(state, "", undefined) : false; + cached = f() ? jsparse.make_result(state, "", undefined) : false; savedState.putCached(pid, cached); return cached; } @@ -611,16 +623,16 @@ function semantic(f) { // It succeeds if 'p' succeeds and fails if 'p' fails. It never // consume any input however, and doesn't put anything in the resulting // AST. -function and(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.and = function and(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); if(cached) return cached; var r = p(state); - cached = r ? make_result(state, "", undefined) : false; + cached = r ? jsparse.make_result(state, "", undefined) : false; savedState.putCached(pid, cached); return cached; } @@ -640,18 +652,37 @@ function and(p) { // parses a+b // parses a++b // -function not(p) { - var p = toParser(p); - var pid = parser_id++; +jsparse.not = function not(p) { + var p = jsparse.toParser(p); + var pid = jsparse.parser_id++; return function(state) { var savedState = state; var cached = savedState.getCached(pid); if(cached) return cached; - cached = p(state) ? false : make_result(state, "", undefined); + cached = p(state) ? false : jsparse.make_result(state, "", undefined); savedState.putCached(pid, cached); return cached; } } +// For ease of use, it's sometimes nice to be able to not have to prefix all +// of the jsparse functions with `jsparse.` and since the original version of +// this library put everything in the toplevel namespace, this makes it easy +// to use this version with old code. +// +// The only caveat there is that changing `memoize` MUST be done on +// jsparse.memoize +// +// Typical usage: +// jsparse.inject_into(window) +// +jsparse.inject_into = function inject_into(into) { + for (var key in jsparse) { + if (typeof jsparse[key] === 'function') { + into[key] = jsparse[key]; + } + } +} + diff --git a/package.json b/package.json new file mode 100644 index 0000000..1b565e6 --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "jsparse", + "description": "A parser combinator library", + "version": "0.1.0", + "homepage": "https://github.com/rictic/jsparse", + "repository": { + "type": "git", + "url": "git://github.com/rictic/jsparse.git" + }, + "main": "jsparse.js", + "scripts": { + "test": "node tests.js" + }, + "engines": { + "node": ">0.1.0" + }, + "dependencies": {}, + "devDependencies": {}, + "optionalDependencies": {} +} diff --git a/readme.txt b/readme.txt index cffc187..a86d541 100644 --- a/readme.txt +++ b/readme.txt @@ -2,7 +2,7 @@ jsparse ======= This is a simple library of parser combinators for Javascript based on -Packrat parsers [1] and Parsing expression grammars [2]. +Packrat parsers [1] and Parsing expression grammars [2]. [1] http://pdos.csail.mit.edu/~baford/packrat/ [2] http://en.wikipedia.org/wiki/Parsing_expression_grammar @@ -17,20 +17,19 @@ Examples: tests.js Various tests to ensure things are working -example1.js +examples/example1.js Simple expression example from wikipedia article on PEGs. -example2.js +examples/example2.js Expression example with actions used to produce AST. -example3.js +examples/example3.js Expression example with actions used to evaluate as it parses. -es3.js - Incomplete/work-in-progress ECMAScript 3 parser +examples/es3.js + Incomplete/work-in-progress ECMAScript 3 parser (currently broken) -es3_tests.js +examples/es3_tests.js Tests for ECMAScript 3 parser -I use it from within the Mozilla Rhino environment but it also works -in the browser. +It has been updated to work in nodejs as well as in the browser. diff --git a/tests.html b/tests.html new file mode 100644 index 0000000..53f61a1 --- /dev/null +++ b/tests.html @@ -0,0 +1,28 @@ + + + Tests + + +

+  
+  
+  
+  
+
+
diff --git a/tests.js b/tests.js
index 84eb706..c0da643 100644
--- a/tests.js
+++ b/tests.js
@@ -1,15 +1,15 @@
 // Copyright (C) 2007 Chris Double.
-// 
+//
 // Redistribution and use in source and binary forms, with or without
 // modification, are permitted provided that the following conditions are met:
-// 
+//
 // 1. Redistributions of source code must retain the above copyright notice,
 //    this list of conditions and the following disclaimer.
-// 
+//
 // 2. Redistributions in binary form must reproduce the above copyright notice,
 //    this list of conditions and the following disclaimer in the documentation
 //    and/or other materials provided with the distribution.
-// 
+//
 // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES,
 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
@@ -22,8 +22,18 @@
 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 //
 
+try {
+    var jsparse = require("./jsparse");
+} catch (e) {}
+
+
 var passed = [];
 var failed = [];
+
+var print = print || function print(str) {
+    console.log(str);
+}
+
 function assertTrue(msg, test) {
     if(test)
 	passed.push(msg);
@@ -46,14 +56,14 @@ function assertFalse(msg, test) {
 }
 
 function assertEqual(msg, value1, value2) {
-    if(value1 == value2) 
+    if(value1 == value2)
 	passed.push(msg);
     else
 	failed.push(msg);
 }
 
 function assertNotEqual(msg, value1, value2) {
-    if(value1 != value2) 
+    if(value1 != value2)
 	passed.push(msg);
     else
 	failed.push(msg);
@@ -62,49 +72,51 @@ function assertNotEqual(msg, value1, value2) {
 function assertFullyParsed(parser, string) {
     var msg = parser + " did not fully parse: " + string;
     try {
-	var result = eval(parser)(ps(string));
-	if(result && result.remaining.length == 0) 
-	    passed.push(msg);
-	else
-	    failed.push(msg);
+    	var result = eval(parser)(jsparse.ps(string));
+    	if(result && result.remaining.length == 0)
+    	    passed.push(msg);
+    	else
+    	    failed.push(msg);
     }
     catch(e) {
-	failed.push(msg);
+        console.log(e);
+    	failed.push(msg);
     }
 }
 
 function assertParseFailed(parser, string) {
     var msg = parser + " succeeded but should have failed: " + string;
     try {
-	var result = eval(parser)(ps(string));
-	if(!result) 
-	    passed.push(msg);
-	else
-	    failed.push(msg);
-    }
+    	var result = eval(parser)(jsparse.ps(string));
+    	if(!result)
+    	    passed.push(msg);
+    	else
+    	    failed.push(msg);
+        }
     catch(e) {
-	failed.push(msg);
+        console.log(e);
+    	failed.push(msg);
     }
 }
 
 function assertParseMatched(parser, string, expected) {
     var msg = parser + " parse did not match: " + string;
     try {
-	var result = eval(parser)(ps(string));
-	if(result && result.matched == expected) 
-	    passed.push(msg);
-	else
-	    failed.push(msg + " got [" + result.matched + "] expected [" + expected + "]");
+    	var result = eval(parser)(jsparse.ps(string));
+    	if(result && result.matched == expected)
+    	    passed.push(msg);
+    	else
+    	    failed.push(msg + " got [" + result.matched + "] expected [" + expected + "]");
     }
     catch(e) {
-	failed.push(msg);
+    	failed.push(msg);
     }
 }
 
 function time(func) {
-    var start = java.lang.System.currentTimeMillis();
+    var start = +new Date();
     var r =  func();
-    var end = java.lang.System.currentTimeMillis();
+    var end = +new Date();
     print("Time: " + (end-start) + "ms");
     return r;
 }
@@ -114,104 +126,109 @@ function runTests(func) {
     failed = [];
     func();
     var total = passed.length + failed.length;
-    for(var i=0; i < failed.length; ++i) 
+    for(var i=0; i < failed.length; ++i)
 	print(failed[i]);
     print(total + " tests: " + passed.length + " passed, " + failed.length + " failed");
 }
 
 function ParserTests() {
     // Token
-    assertFullyParsed("token('a')", "a");
-    assertFullyParsed("token('abcd')", "abcd");
-    assertParseMatched("token('abcd')", "abcdef", "abcd");
-    assertParseFailed("token('a')", "b");
+    assertFullyParsed("jsparse.token('a')", "a");
+    assertFullyParsed("jsparse.token('abcd')", "abcd");
+    assertParseMatched("jsparse.token('abcd')", "abcdef", "abcd");
+    assertParseFailed("jsparse.token('a')", "b");
 
     // ch
-    assertParseMatched("ch('a')", "abcd", "a");
-    assertParseFailed("ch('a')", "bcd");
+    assertParseMatched("jsparse.ch('a')", "abcd", "a");
+    assertParseFailed("jsparse.ch('a')", "bcd");
 
-    // range
+ //    // range
     for(var i=0; i < 10; ++i) {
-	assertParseMatched("range('0','9')", "" + i, i);
+    	assertParseMatched("jsparse.range('0','9')", "" + i, i);
     }
-    assertParseFailed("range('0','9')", "a");
-
-    // whitespace
-    assertFullyParsed("whitespace(token('ab'))", "ab");
-    assertFullyParsed("whitespace(token('ab'))", " ab");
-    assertFullyParsed("whitespace(token('ab'))", "  ab");
-    assertFullyParsed("whitespace(token('ab'))", "   ab");
-
-    // negate
-    assertFullyParsed("negate(ch('a'))", "b");
-    assertParseFailed("negate(ch('a'))", "a");
-
-    // end_p
-    assertParseFailed("end_p", "ab");
-    assertFullyParsed("end_p", "");
-
-    // nothing_p
-    assertParseFailed("nothing_p", "abcd");
-    assertParseFailed("nothing_p", "");
-
-    // sequence
-    assertFullyParsed("sequence('a', 'b')", "ab");
-    assertParseFailed("sequence('a', 'b')", "b");
-    assertParseFailed("sequence('a', 'b')", "a");
-    assertParseMatched("sequence('a', whitespace('b'))", "a b", "ab");
-    assertParseMatched("sequence('a', whitespace('b'))", "a  b", "ab");
-    assertParseMatched("sequence('a', whitespace('b'))", "ab", "ab");
-
-    // choice
-    assertFullyParsed("choice('a', 'b')", "a");
-    assertFullyParsed("choice('a', 'b')", "b");
-    assertParseMatched("choice('a', 'b')", "ab", "a");
-    assertParseMatched("choice('a', 'b')", "bc", "b");
-
-    // repeat0
-    assertParseMatched("repeat0(choice('a','b'))", "adef", "a");
-    assertParseMatched("repeat0(choice('a','b'))", "bdef", "b");
-    assertParseMatched("repeat0(choice('a','b'))", "aabbabadef", "aabbaba");
-    assertParseMatched("repeat0(choice('a','b'))", "daabbabadef", "");
-
-    // repeat1
-    assertParseMatched("repeat1(choice('a','b'))", "adef", "a");
-    assertParseMatched("repeat1(choice('a','b'))", "bdef", "b");
-    assertParseMatched("repeat1(choice('a','b'))", "aabbabadef", "aabbaba");
-    assertParseFailed("repeat1(choice('a','b'))", "daabbabadef");
-
-    // optional
-    assertParseMatched("sequence('a', optional(choice('b','c')), 'd')", "abd", "abd");
-    assertParseMatched("sequence('a', optional(choice('b','c')), 'd')", "acd", "acd");
-    assertParseMatched("sequence('a', optional(choice('b','c')), 'd')", "ad", "ad");
-    assertParseFailed("sequence('a', optional(choice('b','c')), 'd')", "aed");
-    assertParseFailed("sequence('a', optional(choice('b','c')), 'd')", "ab");
-    assertParseFailed("sequence('a', optional(choice('b','c')), 'd')", "ac");
-
-    // list
-    assertParseMatched("list(choice('1','2','3'),',')", "1,2,3", "1,2,3");
-    assertParseMatched("list(choice('1','2','3'),',')", "1,3,2", "1,3,2");
-    assertParseMatched("list(choice('1','2','3'),',')", "1,3", "1,3");
-    assertParseMatched("list(choice('1','2','3'),',')", "3", "3");
-    assertParseFailed("list(choice('1','2','3'),',')", "5,6,7");
-
-    // and
-    assertParseMatched("sequence(and('0'), '0')", "0", "0");
-    assertParseFailed("sequence(and('0'), '1')", "0");
-    assertParseMatched("sequence('1',and('2'))", "12", "1");
-
-    // not
-    assertParseMatched("sequence('a',choice('+','++'),'b')", "a+b", "a+b");
-    assertParseFailed("sequence('a',choice('+','++'),'b')", "a++b");
-    assertParseMatched("sequence('a',choice(sequence('+',not('+')),'++'),'b')", "a+b", "a+b");
-    assertParseMatched("sequence('a',choice(sequence('+',not('+')),'++'),'b')", "a++b", "a++b");
-    
-    // butnot
-    assertFullyParsed("butnot(range('0','9'), '6')", "1");
-    assertParseFailed("butnot(range('0','9'), '6')", "6");
-    assertParseFailed("butnot(range('0','9'), 'x')", "x");
-    assertParseFailed("butnot(range('0','9'), 'y')", "x");
+    assertParseFailed("jsparse.range('0','9')", "a");
+
+ //    // whitespace
+    assertFullyParsed("jsparse.whitespace(jsparse.token('ab'))", "ab");
+    assertFullyParsed("jsparse.whitespace(jsparse.token('ab'))", " ab");
+    assertFullyParsed("jsparse.whitespace(jsparse.token('ab'))", "  ab");
+    assertFullyParsed("jsparse.whitespace(jsparse.token('ab'))", "   ab");
+
+ //    // negate
+    assertFullyParsed("jsparse.negate(jsparse.ch('a'))", "b");
+    assertParseFailed("jsparse.negate(jsparse.ch('a'))", "a");
+
+ //    // end
+    assertParseFailed("jsparse.end", "ab");
+    assertFullyParsed("jsparse.end", "");
+
+ //    // nothing
+    assertParseFailed("jsparse.nothing", "abcd");
+    assertParseFailed("jsparse.nothing", "");
+
+ //    // sequence
+    assertFullyParsed("jsparse.sequence('a', 'b')", "ab");
+    assertParseFailed("jsparse.sequence('a', 'b')", "b");
+    assertParseFailed("jsparse.sequence('a', 'b')", "a");
+    assertParseMatched("jsparse.sequence('a', jsparse.whitespace('b'))", "a b", "ab");
+    assertParseMatched("jsparse.sequence('a', jsparse.whitespace('b'))", "a  b", "ab");
+    assertParseMatched("jsparse.sequence('a', jsparse.whitespace('b'))", "ab", "ab");
+
+ //    // choice
+    assertFullyParsed("jsparse.choice('a', 'b')", "a");
+    assertFullyParsed("jsparse.choice('a', 'b')", "b");
+    assertParseMatched("jsparse.choice('a', 'b')", "ab", "a");
+    assertParseMatched("jsparse.choice('a', 'b')", "bc", "b");
+
+ //    // repeat0
+    assertParseMatched("jsparse.repeat0(jsparse.choice('a','b'))", "adef", "a");
+    assertParseMatched("jsparse.repeat0(jsparse.choice('a','b'))", "bdef", "b");
+    assertParseMatched("jsparse.repeat0(jsparse.choice('a','b'))", "aabbabadef", "aabbaba");
+    assertParseMatched("jsparse.repeat0(jsparse.choice('a','b'))", "daabbabadef", "");
+
+ //    // repeat1
+    assertParseMatched("jsparse.repeat1(jsparse.choice('a','b'))", "adef", "a");
+    assertParseMatched("jsparse.repeat1(jsparse.choice('a','b'))", "bdef", "b");
+    assertParseMatched("jsparse.repeat1(jsparse.choice('a','b'))", "aabbabadef", "aabbaba");
+    assertParseFailed("jsparse.repeat1(jsparse.choice('a','b'))", "daabbabadef");
+
+ //    // optional
+    assertParseMatched("jsparse.sequence('a', jsparse.optional(jsparse.choice('b','c')), 'd')", "abd", "abd");
+    assertParseMatched("jsparse.sequence('a', jsparse.optional(jsparse.choice('b','c')), 'd')", "acd", "acd");
+    assertParseMatched("jsparse.sequence('a', jsparse.optional(jsparse.choice('b','c')), 'd')", "ad", "ad");
+    assertParseFailed("jsparse.sequence('a', jsparse.optional(jsparse.choice('b','c')), 'd')", "aed");
+    assertParseFailed("jsparse.sequence('a', jsparse.optional(jsparse.choice('b','c')), 'd')", "ab");
+    assertParseFailed("jsparse.sequence('a', jsparse.optional(jsparse.choice('b','c')), 'd')", "ac");
+
+ //    // list
+    assertParseMatched("jsparse.list(jsparse.choice('1','2','3'),',')", "1,2,3", "1,2,3");
+    assertParseMatched("jsparse.list(jsparse.choice('1','2','3'),',')", "1,3,2", "1,3,2");
+    assertParseMatched("jsparse.list(jsparse.choice('1','2','3'),',')", "1,3", "1,3");
+    assertParseMatched("jsparse.list(jsparse.choice('1','2','3'),',')", "3", "3");
+    assertParseFailed("jsparse.list(jsparse.choice('1','2','3'),',')", "5,6,7");
+
+ //    // and
+    assertParseMatched("jsparse.sequence(jsparse.and('0'), '0')", "0", "0");
+    assertParseFailed("jsparse.sequence(jsparse.and('0'), '1')", "0");
+    assertParseMatched("jsparse.sequence('1',jsparse.and('2'))", "12", "1");
+
+ //    // not
+    assertParseMatched("jsparse.sequence('a',jsparse.choice('+','++'),'b')", "a+b", "a+b");
+    assertParseFailed("jsparse.sequence('a',jsparse.choice('+','++'),'b')", "a++b");
+    assertParseMatched("jsparse.sequence('a',jsparse.choice(jsparse.sequence('+',jsparse.not('+')),'++'),'b')", "a+b", "a+b");
+    assertParseMatched("jsparse.sequence('a',jsparse.choice(jsparse.sequence('+',jsparse.not('+')),'++'),'b')", "a++b", "a++b");
+
+ //    // butnot
+    assertFullyParsed("jsparse.butnot(jsparse.range('0','9'), '6')", "1");
+    assertParseFailed("jsparse.butnot(jsparse.range('0','9'), '6')", "6");
+    assertParseFailed("jsparse.butnot(jsparse.range('0','9'), 'x')", "x");
+    assertParseFailed("jsparse.butnot(jsparse.range('0','9'), 'y')", "x");
 }
 
 
 time(function() { runTests(ParserTests); });
+
+try {
+  process.exit(failed.length)
+} catch(e) {}
+