├── .gitignore ├── es3.js ├── es3_tests.js ├── example1.js ├── example2.js ├── example3.js ├── jsparse.js ├── readme.txt └── tests.js /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | -------------------------------------------------------------------------------- /es3.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2007 Chris Double. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, 7 | // this list of conditions and the following disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | // DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 18 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 19 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 21 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | 25 | // Forward Declarations 26 | var SourceElement = 27 | function(input) { return SourceElement(input); } 28 | var AssignmentExpression = 29 | function(input) { return AssignmentExpression(input); } 30 | var Expression = 31 | function(input) { return Expression(input); } 32 | var Statement = 33 | function(input) { return Statement(input); } 34 | var LeftHandSideExpression = 35 | function(input) { return LeftHandSideExpression(input); } 36 | 37 | var Whitespace = choice("\t", " "); 38 | var LineTerminator = choice(ch("\r"), ch("\n")); 39 | 40 | var SingleLineCommentChars = join_action(repeat1(negate(LineTerminator)), ""); 41 | var SingleLineComment = sequence("//", optional(SingleLineCommentChars), LineTerminator); 42 | 43 | var Comment = 44 | choice(SingleLineComment); 45 | 46 | var NullLiteral = token("null"); 47 | var BooleanLiteral = choice("true", "false"); 48 | var Zero = action("0", function(ast) { return 0; }); 49 | var DecimalDigit = action(range("0", "9"), function(ast) { return parseInt(ast); }); 50 | var NonZeroDigit = action(range("1", "9"), function(ast) { return parseInt(ast); }); 51 | var DecimalDigits = repeat1(DecimalDigit); 52 | var DecimalIntegerLiteral = choice(Zero, sequence(NonZeroDigit, optional(DecimalDigits))); 53 | var SignedInteger = choice(DecimalDigits, sequence("+", DecimalDigits), sequence("-", DecimalDigits)); 54 | var ExponentIndicator = choice("e", "E"); 55 | var ExponentPart = sequence(ExponentIndicator, SignedInteger); 56 | var DecimalLiteral = choice(sequence(DecimalIntegerLiteral, ".", optional(DecimalDigits), optional(ExponentPart)), 57 | sequence(".", DecimalDigits, optional(ExponentPart)), 58 | sequence(DecimalIntegerLiteral, optional(ExponentPart))); 59 | 60 | var HexDigit = choice(range("0", "9"), range("a", "f"), range("A", "F")); 61 | var HexIntegerLiteral = sequence(choice("0x", "0X"), repeat1(HexDigit)); 62 | var NumericLiteral = choice(HexIntegerLiteral, DecimalLiteral); 63 | var SingleEscapeCharacter = choice("'", "\"", "\\", "b", "f", "n", "r", "t", "v"); 64 | var NonEscapeCharacter = negate(SingleEscapeCharacter); 65 | 66 | var CharacterEscapeSequence = choice(SingleEscapeCharacter, NonEscapeCharacter); 67 | var HexEscapeSequence = sequence("x", HexDigit, HexDigit); 68 | var UnicodeEscapeSequence = sequence("u", HexDigit, HexDigit, HexDigit, HexDigit); 69 | var EscapeSequence = choice(HexEscapeSequence, UnicodeEscapeSequence, CharacterEscapeSequence); 70 | var SingleStringCharacter = choice(negate(choice("\'", "\\", "\r", "\n")), 71 | sequence("\\", EscapeSequence)); 72 | var DoubleStringCharacter = choice(negate(choice("\"", "\\", "\r", "\n")), 73 | sequence("\\", EscapeSequence)); 74 | var SingleStringCharacters = repeat1(SingleStringCharacter); 75 | var DoubleStringCharacters = repeat1(DoubleStringCharacter); 76 | 77 | var StringLiteral = choice(sequence("\"", optional(DoubleStringCharacters), "\""), 78 | sequence("'", optional(SingleStringCharacters), "'")); 79 | 80 | var Literal = choice(NullLiteral, BooleanLiteral, NumericLiteral, StringLiteral); 81 | 82 | var Keyword = 83 | choice("break", 84 | "case", 85 | "catch", 86 | "continue", 87 | "default", 88 | "delete", 89 | "do", 90 | "else", 91 | "finally", 92 | "for", 93 | "function", 94 | "if", 95 | "in", 96 | "instanceof", 97 | "new", 98 | "return", 99 | "switch", 100 | "this", 101 | "throw", 102 | "try", 103 | "typeof", 104 | "var", 105 | "void", 106 | "while", 107 | "with"); 108 | 109 | var ReservedWord = choice(Keyword, NullLiteral, BooleanLiteral); 110 | 111 | var HexDigit = choice(range("0", "9"), range("a", "f"), range("A", "F")); 112 | var IdentifierLetter = choice(range("a", "z"), range("A", "Z")); 113 | var IdentifierStart = choice(IdentifierLetter, "$", "_"); 114 | var IdentifierPart = choice(IdentifierStart, range("0-9")); 115 | var IdentifierName = 116 | action(sequence(IdentifierStart, join_action(repeat0(IdentifierPart), "")), 117 | function(ast) { 118 | return ast[0].concat(ast[1]); 119 | }); 120 | var Identifier = butnot(IdentifierName, ReservedWord); 121 | 122 | var StatementList = repeat1(Statement); 123 | var Block = wsequence("{", optional(StatementList), "}"); 124 | var Initialiser = wsequence("=", AssignmentExpression); 125 | var VariableDeclaration = wsequence(Identifier, optional(Initialiser)); 126 | var VariableDeclarationList = wlist(VariableDeclaration, ","); 127 | var VariableStatement = 128 | wsequence("var", VariableDeclarationList); 129 | 130 | var EmptyStatement = token(";"); 131 | 132 | var IfStatement = 133 | choice(wsequence("if", "(", Expression, ")", Statement, "else", Statement), 134 | wsequence("if", "(", Expression, ")", Statement)); 135 | 136 | var IterationStatement = 137 | choice(wsequence("do", Statement, "while", "(", Expression, ")", ";"), 138 | wsequence("while", "(", Expression, ")", Statement), 139 | wsequence("for", "(", optional(Expression), ";", optional(Expression), ";", optional(Expression), ")", Statement), 140 | wsequence("for", "(", "var", VariableDeclarationList, ";", optional(Expression), ";", optional(Expression), ")", Statement), 141 | wsequence("for", "(", LeftHandSideExpression, "in", Expression, ")", Statement), 142 | wsequence("for", "(", "var", VariableDeclaration, "in", Expression, ")", Statement)); 143 | 144 | var ContinueStatement = wsequence("continue", optional(Identifier), ";"); 145 | var BreakStatement = wsequence("break", optional(Identifier), ";"); 146 | var ReturnStatement = wsequence("return", optional(Expression), ";"); 147 | var WithStatement = wsequence("with", "(", Expression, ")", Statement); 148 | 149 | 150 | var CaseClause = 151 | wsequence("case", Expression, ":", optional(StatementList)); 152 | var DefaultClause = 153 | wsequence("default", ":", optional(StatementList)); 154 | var CaseBlock = 155 | choice(wsequence("{", repeat0(CaseClause), "}"), 156 | wsequence("{", repeat0(CaseClause), DefaultClause, repeat0(CaseClause), "}")); 157 | 158 | var SwitchStatement = wsequence("switch", "(", Expression, ")", CaseBlock); 159 | var LabelledStatement = wsequence(Identifier, ":", Statement); 160 | var ThrowStatement = wsequence("throw", Expression, ";"); 161 | 162 | var Catch = wsequence("catch", "(", Identifier, ")", Block); 163 | var Finally = wsequence("finally", Block); 164 | var TryStatement = 165 | choice(wsequence("try", Block, Catch), 166 | wsequence("try", Block, Finally), 167 | wsequence("try", Block, Catch, Finally)); 168 | 169 | var ExpressionStatement = 170 | choice(sequence(choice("{", "function"), nothing_p), 171 | Expression); 172 | var Statement = 173 | choice(Block, 174 | VariableStatement, 175 | EmptyStatement, 176 | ExpressionStatement, 177 | IfStatement, 178 | IterationStatement, 179 | ContinueStatement, 180 | BreakStatement, 181 | ReturnStatement, 182 | WithStatement, 183 | SwitchStatement, 184 | LabelledStatement, 185 | ThrowStatement, 186 | TryStatement); 187 | 188 | var FunctionDeclaration = 189 | function(input) { return FunctionDeclaration(input); } 190 | 191 | var FunctionBody = repeat0(SourceElement); 192 | var FormalParameterList = wlist(Identifier, ","); 193 | var FunctionExpression = 194 | wsequence("function", optional(Identifier), "(", optional(FormalParameterList), ")", "{", FunctionBody, "}"); 195 | 196 | var FunctionDeclaration = 197 | wsequence("function", Identifier, "(", optional(FormalParameterList), ")", "{", FunctionBody, "}"); 198 | 199 | 200 | var PrimaryExpression = 201 | function(input) { return PrimaryExpression(input); } 202 | 203 | var ArgumentList = list(AssignmentExpression, ","); 204 | var Arguments = 205 | choice(wsequence("(", ")"), 206 | wsequence("(", ArgumentList, ")")); 207 | 208 | var MemberExpression = function(input) { return MemberExpression(input); } 209 | var MemberExpression = 210 | left_factor_action(sequence(choice(wsequence("new", MemberExpression, Arguments), 211 | PrimaryExpression, 212 | FunctionExpression), 213 | repeat0(choice(wsequence("[", Expression, "]"), 214 | wsequence(".", Identifier))))); 215 | 216 | var NewExpression = 217 | choice(MemberExpression, 218 | wsequence("new", NewExpression)); 219 | var CallExpression = 220 | left_factor_action(wsequence(wsequence(MemberExpression, Arguments), 221 | repeat0(choice(Arguments, 222 | wsequence("[", Expression, "]"), 223 | wsequence(".", Identifier))))); 224 | 225 | var LeftHandSideExpression = choice(CallExpression, NewExpression); 226 | 227 | var AssignmentOperator = 228 | choice("=", 229 | "*=", 230 | "/=", 231 | "%=", 232 | "+=", 233 | "-=", 234 | "<<=", 235 | ">>=", 236 | ">>>=", 237 | "&=", 238 | "^=", 239 | "|="); 240 | 241 | var LogicalORExpression = 242 | function(input) { return LogicalORExpression(input); } 243 | var LogicalANDExpression = 244 | function(input) { return LogicalANDExpression(input); } 245 | var BitwiseORExpression = 246 | function(input) { return BitwiseORExpression(input); } 247 | var BitwiseXORExpression = 248 | function(input) { return BitwiseXORExpression(input); } 249 | var BitwiseANDExpression = 250 | function(input) { return BitwiseANDExpression(input); } 251 | var EqualityExpression = 252 | function(input) { return EqualityExpression(input); } 253 | var RelationalExpression = 254 | function(input) { return RelationalExpression(input); } 255 | var ShiftExpression = 256 | function(input) { return ShiftExpression(input); } 257 | var AdditiveExpression = 258 | function(input) { return AdditiveExpression(input); } 259 | var MultiplicativeExpression = 260 | function(input) { return MultiplicativeExpression(input); } 261 | var UnaryExpression = 262 | function(input) { return UnaryExpression(input); } 263 | var PostfixExpression = 264 | function(input) { return PostfixExpression(input); } 265 | 266 | var PostfixExpression = 267 | choice(wsequence(LeftHandSideExpression, "++"), 268 | wsequence(LeftHandSideExpression, "--"), 269 | LeftHandSideExpression); 270 | 271 | var UnaryExpression = 272 | choice(PostfixExpression, 273 | wsequence("delete", UnaryExpression), 274 | wsequence("void", UnaryExpression), 275 | wsequence("typeof", UnaryExpression), 276 | wsequence("++", UnaryExpression), 277 | wsequence("--", UnaryExpression), 278 | wsequence("+", UnaryExpression), 279 | wsequence("-", UnaryExpression), 280 | wsequence("~", UnaryExpression), 281 | wsequence("!", UnaryExpression)); 282 | 283 | var MultiplicativeExpression = 284 | wsequence(UnaryExpression, 285 | repeat0(choice(wsequence("*", UnaryExpression), 286 | wsequence("/", UnaryExpression), 287 | wsequence("%", UnaryExpression)))); 288 | 289 | var AdditiveExpression = 290 | wsequence(MultiplicativeExpression, 291 | repeat0(choice(wsequence("+", MultiplicativeExpression), 292 | wsequence("-", MultiplicativeExpression)))); 293 | 294 | var ShiftExpression = 295 | wsequence(AdditiveExpression, 296 | repeat0(choice(wsequence("<<", AdditiveExpression), 297 | wsequence(">>", AdditiveExpression), 298 | wsequence(">>>", AdditiveExpression)))); 299 | 300 | var RelationalExpression = 301 | wsequence(ShiftExpression, 302 | repeat0(choice(wsequence("<", ShiftExpression), 303 | wsequence(">", ShiftExpression), 304 | wsequence("<=", ShiftExpression), 305 | wsequence(">=", ShiftExpression), 306 | wsequence("instanceof", ShiftExpression)))); 307 | 308 | var EqualityExpression = 309 | wsequence(RelationalExpression, 310 | repeat0(choice(wsequence("==", RelationalExpression), 311 | wsequence("!==", RelationalExpression), 312 | wsequence("===", RelationalExpression), 313 | wsequence("!==", RelationalExpression)))); 314 | 315 | var BitwiseANDExpression = 316 | wsequence(EqualityExpression, repeat0(wsequence("&", EqualityExpression))); 317 | var BitwiseXORExpression = 318 | wsequence(BitwiseANDExpression, repeat0(wsequence("^", BitwiseANDExpression))); 319 | var BitwiseORExpression = 320 | wsequence(BitwiseXORExpression, repeat0(wsequence("|", BitwiseXORExpression))); 321 | var LogicalANDExpression = 322 | wsequence(BitwiseORExpression, repeat0(wsequence("&&", BitwiseORExpression))); 323 | 324 | var LogicalORExpression = 325 | wsequence(LogicalANDExpression, repeat0(wsequence("||", LogicalANDExpression))); 326 | 327 | var ConditionalExpression = 328 | choice(LogicalORExpression, 329 | wsequence(LogicalORExpression, "?", AssignmentExpression, ":", AssignmentExpression)); 330 | 331 | var AssignmentExpression = 332 | choice(wsequence(LeftHandSideExpression, AssignmentOperator, AssignmentExpression), 333 | ConditionalExpression); 334 | 335 | var Expression = list(AssignmentExpression, ","); 336 | 337 | var Elision = repeat1(","); 338 | var ElementList = list(wsequence(optional(Elision), AssignmentExpression), ","); 339 | var ArrayLiteral = 340 | choice(wsequence("[", optional(Elision), "]"), 341 | wsequence("[", ElementList, "]"), 342 | wsequence("[", ElementList, optional(Elision), "]")); 343 | 344 | var PropertyName = choice(Identifier, StringLiteral, NumericLiteral); 345 | var PropertyNameAndValueList = 346 | list(wsequence(PropertyName, ":", AssignmentExpression), ","); 347 | var ObjectLiteral = 348 | choice(wsequence("{", "}"), 349 | wsequence("{", PropertyNameAndValueList, "}")); 350 | 351 | var PrimaryExpression = 352 | choice("this", 353 | wsequence("(", Expression, ")"), 354 | Identifier, 355 | ArrayLiteral, 356 | ObjectLiteral, 357 | Literal); 358 | var SourceElement = choice(Comment, Statement, FunctionDeclaration); 359 | var Program = repeat0(SourceElement); 360 | -------------------------------------------------------------------------------- /es3_tests.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2007 Chris Double. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, 7 | // this list of conditions and the following disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | // DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 18 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 19 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 21 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | load("jsparse.js"); 25 | load("es3.js"); 26 | load("tests.js"); 27 | 28 | function WhitespaceTest() { 29 | assertTrue("Whitespace failed to parse space", Whitespace(ps(" "))); 30 | assertTrue("Whitespace failed to parse tab", Whitespace(ps("\t"))); 31 | assertFalse("Whitespace parsed non-space", Whitespace(ps("abcd"))); 32 | } 33 | 34 | function LineTerminatorTest() { 35 | assertTrue("LineTerminator failed to parse carriage return", LineTerminator(ps("\n"))); 36 | assertTrue("LineTerminator failed to parse newline", LineTerminator(ps("\n"))); 37 | assertFalse("LineTerminator parsed incorrect data", LineTerminator(ps("abcd"))); 38 | } 39 | 40 | function SingleLineCommentTest() { 41 | assertTrue("SingleLineComment failed to parse comment with no space", SingleLineComment(ps("//foo\n"))); 42 | assertTrue("SingleLineComment failed to parse comment with space", SingleLineComment(ps("// foo\n"))); 43 | } 44 | 45 | function CommentTest() { 46 | assertFullyParsed("Comment", "// foo\r"); 47 | assertFullyParsed("Comment", "//foo\n"); 48 | assertFullyParsed("Comment", "/* foo */"); 49 | assertFullyParsed("Comment", "/* /* foo */ */"); 50 | assertFullyParsed("Comment", "/* foo \n * bar */"); 51 | } 52 | 53 | function NullLiteralTest() { 54 | assertTrue("NullLiterator failed to parse null", NullLiteral(ps("null"))); 55 | assertFalse("NullLiterator parsed invalid data", NullLiteral(ps("xnull"))); 56 | } 57 | 58 | function DecimalDigitTest() { 59 | assertEqual("DecimalDigit failed to parse 0", DecimalDigit(ps("0")).ast, 0); 60 | assertEqual("DecimalDigit failed to parse 1", DecimalDigit(ps("1")).ast, 1); 61 | assertEqual("DecimalDigit failed to parse 2", DecimalDigit(ps("2")).ast, 2); 62 | assertEqual("DecimalDigit failed to parse 3", DecimalDigit(ps("3")).ast, 3); 63 | assertEqual("DecimalDigit failed to parse 4", DecimalDigit(ps("4")).ast, 4); 64 | assertEqual("DecimalDigit failed to parse 5", DecimalDigit(ps("5")).ast, 5); 65 | assertEqual("DecimalDigit failed to parse 6", DecimalDigit(ps("6")).ast, 6); 66 | assertEqual("DecimalDigit failed to parse 7", DecimalDigit(ps("7")).ast, 7); 67 | assertEqual("DecimalDigit failed to parse 8", DecimalDigit(ps("8")).ast, 8); 68 | assertEqual("DecimalDigit failed to parse 9", DecimalDigit(ps("9")).ast, 9); 69 | assertFalse("DecimalDigit parsed invalid data", DecimalDigit(ps("a"))); 70 | } 71 | 72 | function NonZeroDigitTest() { 73 | assertEqual("NonZeroDigit failed to parse 1", NonZeroDigit(ps("1")).ast, 1); 74 | assertEqual("NonZeroDigit failed to parse 2", NonZeroDigit(ps("2")).ast, 2); 75 | assertEqual("NonZeroDigit failed to parse 3", NonZeroDigit(ps("3")).ast, 3); 76 | assertEqual("NonZeroDigit failed to parse 4", NonZeroDigit(ps("4")).ast, 4); 77 | assertEqual("NonZeroDigit failed to parse 5", NonZeroDigit(ps("5")).ast, 5); 78 | assertEqual("NonZeroDigit failed to parse 6", NonZeroDigit(ps("6")).ast, 6); 79 | assertEqual("NonZeroDigit failed to parse 7", NonZeroDigit(ps("7")).ast, 7); 80 | assertEqual("NonZeroDigit failed to parse 8", NonZeroDigit(ps("8")).ast, 8); 81 | assertEqual("NonZeroDigit failed to parse 9", NonZeroDigit(ps("9")).ast, 9); 82 | assertFalse("NonZeroDigit parsed zero", NonZeroDigit(ps("0"))); 83 | assertFalse("NonZeroDigit parsed invalid data", NonZeroDigit(ps("a"))); 84 | } 85 | 86 | function IdentifierTest() { 87 | assertFullyParsed("Identifier", "abcd"); 88 | assertFalse("Identifier('while')", Identifier(ps('while'))); 89 | assertTrue("Identifier('abcd').ast=='abcd'", Identifier(ps('abcd')).ast=='abcd'); 90 | } 91 | 92 | function DecimalDigitsTest() { 93 | assertEqual("DecimalDigits failed to parse 123", DecimalDigits(ps("123")).ast.toString(), "1,2,3"); 94 | } 95 | 96 | function AssignmentExpressionTest() { 97 | assertFullyParsed("AssignmentExpression", "a=1"); 98 | assertFullyParsed("AssignmentExpression", "a=b"); 99 | assertFullyParsed("AssignmentExpression", "a"); 100 | assertFullyParsed("AssignmentExpression", "12"); 101 | } 102 | 103 | function ExpressionTest() { 104 | assertFullyParsed("Expression", "1"); 105 | assertFullyParsed("Expression", "'ddf'"); 106 | assertFullyParsed("Expression", "\"ddf\""); 107 | assertFullyParsed("Expression", "foo"); 108 | assertFullyParsed("Expression", "foo.bar"); 109 | } 110 | 111 | function VariableDeclarationTest() { 112 | assertFullyParsed("VariableDeclaration", "a"); 113 | assertFullyParsed("VariableDeclaration", "a=1"); 114 | } 115 | 116 | function VariableStatementTest() { 117 | assertFullyParsed("VariableStatement", "var a"); 118 | assertFullyParsed("VariableStatement", "var a,b"); 119 | assertFullyParsed("VariableStatement", "var a=1"); 120 | assertFullyParsed("VariableStatement", "var a = 1, b = 2,c=3"); 121 | } 122 | 123 | function IfStatementTest() { 124 | assertFullyParsed("IfStatement", "if(a) { }"); 125 | assertFullyParsed("IfStatement", "if(a) { var a=2; var b=3; print('foo') }"); 126 | } 127 | 128 | function ArrayLiteralTest() { 129 | assertFullyParsed("ArrayLiteral", "[]"); 130 | assertFullyParsed("ArrayLiteral", "[ ]"); 131 | assertFullyParsed("ArrayLiteral", "[ 1,2,3 ]"); 132 | assertFullyParsed("ArrayLiteral", "[ 1,,3 ]"); 133 | assertFullyParsed("ArrayLiteral", "[ 'hello' ]"); 134 | assertFullyParsed("ArrayLiteral", "[ 1,[2,3],4 ]"); 135 | } 136 | 137 | function ObjectLiteralTest() { 138 | assertFullyParsed("ObjectLiteral", "{}"); 139 | assertFullyParsed("ObjectLiteral", "{ }"); 140 | assertFullyParsed("ObjectLiteral", "{ one: 1 }"); 141 | assertFullyParsed("ObjectLiteral", "{ one: 1, two: 'two' }"); 142 | assertFullyParsed("ObjectLiteral", "{ one: 1, two: {three:3}, four:4 }"); 143 | } 144 | 145 | function IterationTest() { 146 | assertFullyParsed("IterationStatement", "for(;;) ;"); 147 | assertFullyParsed("IterationStatement", "for(;;) { }"); 148 | assertFullyParsed("IterationStatement", "for(i=0;i<5;++i) ;"); 149 | assertFullyParsed("IterationStatement", "for(i=0;i<5;++i) {}"); 150 | assertFullyParsed("IterationStatement", "for(var i=0;i<5;++i) ;"); 151 | assertFullyParsed("IterationStatement", "for(i=0;i= s.length && state.substring(0,s.length) == s; 115 | if(r) 116 | cached = { remaining: state.from(s.length), matched: s, ast: s }; 117 | else 118 | cached = false; 119 | savedState.putCached(pid, cached); 120 | return cached; 121 | }; 122 | } 123 | 124 | // Like 'token' but for a single character. Returns a parser that given a string 125 | // containing a single character, parses that character value. 126 | function ch(c) { 127 | var pid = parser_id++; 128 | return function(state) { 129 | var savedState = state; 130 | var cached = savedState.getCached(pid); 131 | if(cached) 132 | return cached; 133 | var r = state.length >= 1 && state.at(0) == c; 134 | if(r) 135 | cached = { remaining: state.from(1), matched: c, ast: c }; 136 | else 137 | cached = false; 138 | savedState.putCached(pid, cached); 139 | return cached; 140 | }; 141 | } 142 | 143 | // 'range' is a parser combinator that returns a single character parser 144 | // (similar to 'ch'). It parses single characters that are in the inclusive 145 | // range of the 'lower' and 'upper' bounds ("a" to "z" for example). 146 | function range(lower, upper) { 147 | var pid = parser_id++; 148 | return function(state) { 149 | var savedState = state; 150 | var cached = savedState.getCached(pid); 151 | if(cached) 152 | return cached; 153 | 154 | if(state.length < 1) 155 | cached = false; 156 | else { 157 | var ch = state.at(0); 158 | if(ch >= lower && ch <= upper) 159 | cached = { remaining: state.from(1), matched: ch, ast: ch }; 160 | else 161 | cached = false; 162 | } 163 | savedState.putCached(pid, cached); 164 | return cached; 165 | }; 166 | } 167 | 168 | // Helper function to convert string literals to token parsers 169 | // and perform other implicit parser conversions. 170 | function toParser(p) { 171 | return (typeof(p) == "string") ? token(p) : p; 172 | } 173 | 174 | // Parser combinator that returns a parser that 175 | // skips whitespace before applying parser. 176 | function whitespace(p) { 177 | var p = toParser(p); 178 | var pid = parser_id++; 179 | return function(state) { 180 | var savedState = state; 181 | var cached = savedState.getCached(pid); 182 | if(cached) 183 | return cached; 184 | 185 | cached = p(state.trimLeft()); 186 | savedState.putCached(pid, cached); 187 | return cached; 188 | }; 189 | } 190 | 191 | // Parser combinator that passes the AST generated from the parser 'p' 192 | // to the function 'f'. The result of 'f' is used as the AST in the result. 193 | function action(p, f) { 194 | var p = toParser(p); 195 | var pid = parser_id++; 196 | return function(state) { 197 | var savedState = state; 198 | var cached = savedState.getCached(pid); 199 | if(cached) 200 | return cached; 201 | 202 | var x = p(state); 203 | if(x) { 204 | x.ast = f(x.ast); 205 | cached = x; 206 | } 207 | else { 208 | cached = false; 209 | } 210 | savedState.putCached(pid, cached); 211 | return cached; 212 | }; 213 | } 214 | 215 | // Given a parser that produces an array as an ast, returns a 216 | // parser that produces an ast with the array joined by a separator. 217 | function join_action(p, sep) { 218 | return action(p, function(ast) { return ast.join(sep); }); 219 | } 220 | 221 | // Given an ast of the form [ Expression, [ a, b, ...] ], convert to 222 | // [ [ [ Expression [ a ] ] b ] ... ] 223 | // This is used for handling left recursive entries in the grammar. e.g. 224 | // MemberExpression: 225 | // PrimaryExpression 226 | // FunctionExpression 227 | // MemberExpression [ Expression ] 228 | // MemberExpression . Identifier 229 | // new MemberExpression Arguments 230 | function left_factor(ast) { 231 | return foldl(function(v, action) { 232 | return [ v, action ]; 233 | }, 234 | ast[0], 235 | ast[1]); 236 | } 237 | 238 | // Return a parser that left factors the ast result of the original 239 | // parser. 240 | function left_factor_action(p) { 241 | return action(p, left_factor); 242 | } 243 | 244 | // 'negate' will negate a single character parser. So given 'ch("a")' it will successfully 245 | // parse any character except for 'a'. Or 'negate(range("a", "z"))' will successfully parse 246 | // anything except the lowercase characters a-z. 247 | function negate(p) { 248 | var p = toParser(p); 249 | var pid = parser_id++; 250 | return function(state) { 251 | var savedState = state; 252 | var cached = savedState.getCached(pid); 253 | if(cached) 254 | return cached; 255 | 256 | if(state.length >= 1) { 257 | var r = p(state); 258 | if(!r) 259 | cached = make_result(state.from(1), state.at(0), state.at(0)); 260 | else 261 | cached = false; 262 | } 263 | else { 264 | cached = false; 265 | } 266 | savedState.putCached(pid, cached); 267 | return cached; 268 | }; 269 | } 270 | 271 | // 'end_p' is a parser that is successful if the input string is empty (ie. end of parse). 272 | function end_p(state) { 273 | if(state.length == 0) 274 | return make_result(state, undefined, undefined); 275 | else 276 | return false; 277 | } 278 | 279 | // 'nothing_p' is a parser that always fails. 280 | function nothing_p(state) { 281 | return false; 282 | } 283 | 284 | // 'sequence' is a parser combinator that processes a number of parsers in sequence. 285 | // It can take any number of arguments, each one being a parser. The parser that 'sequence' 286 | // returns succeeds if all the parsers in the sequence succeeds. It fails if any of them fail. 287 | function sequence() { 288 | var parsers = []; 289 | for(var i = 0; i < arguments.length; ++i) 290 | parsers.push(toParser(arguments[i])); 291 | var pid = parser_id++; 292 | return function(state) { 293 | var savedState = state; 294 | var cached = savedState.getCached(pid); 295 | if(cached) { 296 | return cached; 297 | } 298 | 299 | var ast = []; 300 | var matched = ""; 301 | var i; 302 | for(i=0; i< parsers.length; ++i) { 303 | var parser = parsers[i]; 304 | var result = parser(state); 305 | if(result) { 306 | state = result.remaining; 307 | if(result.ast != undefined) { 308 | ast.push(result.ast); 309 | matched = matched + result.matched; 310 | } 311 | } 312 | else { 313 | break; 314 | } 315 | } 316 | if(i == parsers.length) { 317 | cached = make_result(state, matched, ast); 318 | } 319 | else 320 | cached = false; 321 | savedState.putCached(pid, cached); 322 | return cached; 323 | }; 324 | } 325 | 326 | // Like sequence, but ignores whitespace between individual parsers. 327 | function wsequence() { 328 | var parsers = []; 329 | for(var i=0; i < arguments.length; ++i) { 330 | parsers.push(whitespace(toParser(arguments[i]))); 331 | } 332 | return sequence.apply(null, parsers); 333 | } 334 | 335 | // 'choice' is a parser combinator that provides a choice between other parsers. 336 | // It takes any number of parsers as arguments and returns a parser that will try 337 | // each of the given parsers in order. The first one that succeeds results in a 338 | // successfull parse. It fails if all parsers fail. 339 | function choice() { 340 | var parsers = []; 341 | for(var i = 0; i < arguments.length; ++i) 342 | parsers.push(toParser(arguments[i])); 343 | var pid = parser_id++; 344 | return function(state) { 345 | var savedState = state; 346 | var cached = savedState.getCached(pid); 347 | if(cached) { 348 | return cached; 349 | } 350 | var i; 351 | for(i=0; i< parsers.length; ++i) { 352 | var parser=parsers[i]; 353 | var result = parser(state); 354 | if(result) { 355 | break; 356 | } 357 | } 358 | if(i == parsers.length) 359 | cached = false; 360 | else 361 | cached = result; 362 | savedState.putCached(pid, cached); 363 | return cached; 364 | } 365 | } 366 | 367 | // 'butnot' is a parser combinator that takes two parsers, 'p1' and 'p2'. 368 | // It returns a parser that succeeds if 'p1' matches and 'p2' does not, or 369 | // 'p1' matches and the matched text is longer that p2's. 370 | // Useful for things like: butnot(IdentifierName, ReservedWord) 371 | function butnot(p1,p2) { 372 | var p1 = toParser(p1); 373 | var p2 = toParser(p2); 374 | var pid = parser_id++; 375 | 376 | // match a but not b. if both match and b's matched text is shorter 377 | // than a's, a failed match is made 378 | return function(state) { 379 | var savedState = state; 380 | var cached = savedState.getCached(pid); 381 | if(cached) 382 | return cached; 383 | 384 | var br = p2(state); 385 | if(!br) { 386 | cached = p1(state); 387 | } else { 388 | var ar = p1(state); 389 | 390 | if (ar) { 391 | if(ar.matched.length > br.matched.length) 392 | cached = ar; 393 | else 394 | cached = false; 395 | } 396 | else { 397 | cached = false; 398 | } 399 | } 400 | savedState.putCached(pid, cached); 401 | return cached; 402 | } 403 | } 404 | 405 | // 'difference' is a parser combinator that takes two parsers, 'p1' and 'p2'. 406 | // It returns a parser that succeeds if 'p1' matches and 'p2' does not. If 407 | // both match then if p2's matched text is shorter than p1's it is successfull. 408 | function difference(p1,p2) { 409 | var p1 = toParser(p1); 410 | var p2 = toParser(p2); 411 | var pid = parser_id++; 412 | 413 | // match a but not b. if both match and b's matched text is shorter 414 | // than a's, a successfull match is made 415 | return function(state) { 416 | var savedState = state; 417 | var cached = savedState.getCached(pid); 418 | if(cached) 419 | return cached; 420 | 421 | var br = p2(state); 422 | if(!br) { 423 | cached = p1(state); 424 | } else { 425 | var ar = p1(state); 426 | if(ar.matched.length >= br.matched.length) 427 | cached = br; 428 | else 429 | cached = ar; 430 | } 431 | savedState.putCached(pid, cached); 432 | return cached; 433 | } 434 | } 435 | 436 | 437 | // 'xor' is a parser combinator that takes two parsers, 'p1' and 'p2'. 438 | // It returns a parser that succeeds if 'p1' or 'p2' match but fails if 439 | // they both match. 440 | function xor(p1, p2) { 441 | var p1 = toParser(p1); 442 | var p2 = toParser(p2); 443 | var pid = parser_id++; 444 | 445 | // match a or b but not both 446 | return function(state) { 447 | var savedState = state; 448 | var cached = savedState.getCached(pid); 449 | if(cached) 450 | return cached; 451 | 452 | var ar = p1(state); 453 | var br = p2(state); 454 | if(ar && br) 455 | cached = false; 456 | else 457 | cached = ar || br; 458 | savedState.putCached(pid, cached); 459 | return cached; 460 | } 461 | } 462 | 463 | // A parser combinator that takes one parser. It returns a parser that 464 | // looks for zero or more matches of the original parser. 465 | function repeat0(p) { 466 | var p = toParser(p); 467 | var pid = parser_id++; 468 | 469 | return function(state) { 470 | var savedState = state; 471 | var cached = savedState.getCached(pid); 472 | if(cached) { 473 | return cached; 474 | } 475 | 476 | var ast = []; 477 | var matched = ""; 478 | var result; 479 | while(result = p(state)) { 480 | ast.push(result.ast); 481 | matched = matched + result.matched; 482 | if(result.remaining.index == state.index) 483 | break; 484 | state = result.remaining; 485 | } 486 | cached = make_result(state, matched, ast); 487 | savedState.putCached(pid, cached); 488 | return cached; 489 | } 490 | } 491 | 492 | // A parser combinator that takes one parser. It returns a parser that 493 | // looks for one or more matches of the original parser. 494 | function repeat1(p) { 495 | var p = toParser(p); 496 | var pid = parser_id++; 497 | 498 | return function(state) { 499 | var savedState = state; 500 | var cached = savedState.getCached(pid); 501 | if(cached) 502 | return cached; 503 | 504 | var ast = []; 505 | var matched = ""; 506 | var result= p(state); 507 | if(!result) 508 | cached = false; 509 | else { 510 | while(result) { 511 | ast.push(result.ast); 512 | matched = matched + result.matched; 513 | if(result.remaining.index == state.index) 514 | break; 515 | state = result.remaining; 516 | result = p(state); 517 | } 518 | cached = make_result(state, matched, ast); 519 | } 520 | savedState.putCached(pid, cached); 521 | return cached; 522 | } 523 | } 524 | 525 | // A parser combinator that takes one parser. It returns a parser that 526 | // matches zero or one matches of the original parser. 527 | function optional(p) { 528 | var p = toParser(p); 529 | var pid = parser_id++; 530 | return function(state) { 531 | var savedState = state; 532 | var cached = savedState.getCached(pid); 533 | if(cached) 534 | return cached; 535 | var r = p(state); 536 | cached = r || make_result(state, "", false); 537 | savedState.putCached(pid, cached); 538 | return cached; 539 | } 540 | } 541 | 542 | // A parser combinator that ensures that the given parser succeeds but 543 | // ignores its result. This can be useful for parsing literals that you 544 | // don't want to appear in the ast. eg: 545 | // sequence(expect("("), Number, expect(")")) => ast: Number 546 | function expect(p) { 547 | return action(p, function(ast) { return undefined; }); 548 | } 549 | 550 | function chain(p, s, f) { 551 | var p = toParser(p); 552 | 553 | return action(sequence(p, repeat0(action(sequence(s, p), f))), 554 | function(ast) { return [ast[0]].concat(ast[1]); }); 555 | } 556 | 557 | // A parser combinator to do left chaining and evaluation. Like 'chain', it expects a parser 558 | // for an item and for a seperator. The seperator parser's AST result should be a function 559 | // of the form: function(lhs,rhs) { return x; } 560 | // Where 'x' is the result of applying some operation to the lhs and rhs AST's from the item 561 | // parser. 562 | function chainl(p, s) { 563 | var p = toParser(p); 564 | return action(sequence(p, repeat0(sequence(s, p))), 565 | function(ast) { 566 | return foldl(function(v, action) { return action[0](v, action[1]); }, ast[0], ast[1]); 567 | }); 568 | } 569 | 570 | // A parser combinator that returns a parser that matches lists of things. The parser to 571 | // match the list item and the parser to match the seperator need to 572 | // be provided. The AST is the array of matched items. 573 | function list(p, s) { 574 | return chain(p, s, function(ast) { return ast[1]; }); 575 | } 576 | 577 | // Like list, but ignores whitespace between individual parsers. 578 | function wlist() { 579 | var parsers = []; 580 | for(var i=0; i < arguments.length; ++i) { 581 | parsers.push(whitespace(arguments[i])); 582 | } 583 | return list.apply(null, parsers); 584 | } 585 | 586 | // A parser that always returns a zero length match 587 | function epsilon_p(state) { 588 | return make_result(state, "", undefined); 589 | } 590 | 591 | // Allows attaching of a function anywhere in the grammer. If the function returns 592 | // true then parse succeeds otherwise it fails. Can be used for testing if a symbol 593 | // is in the symbol table, etc. 594 | function semantic(f) { 595 | var pid = parser_id++; 596 | return function(state) { 597 | var savedState = state; 598 | var cached = savedState.getCached(pid); 599 | if(cached) 600 | return cached; 601 | cached = f() ? make_result(state, "", undefined) : false; 602 | savedState.putCached(pid, cached); 603 | return cached; 604 | } 605 | } 606 | 607 | // The and predicate asserts that a certain conditional 608 | // syntax is satisfied before evaluating another production. Eg: 609 | // sequence(and("0"), oct_p) 610 | // (if a leading zero, then parse octal) 611 | // It succeeds if 'p' succeeds and fails if 'p' fails. It never 612 | // consume any input however, and doesn't put anything in the resulting 613 | // AST. 614 | function and(p) { 615 | var p = toParser(p); 616 | var pid = parser_id++; 617 | return function(state) { 618 | var savedState = state; 619 | var cached = savedState.getCached(pid); 620 | if(cached) 621 | return cached; 622 | var r = p(state); 623 | cached = r ? make_result(state, "", undefined) : false; 624 | savedState.putCached(pid, cached); 625 | return cached; 626 | } 627 | } 628 | 629 | // The opposite of 'and'. It fails if 'p' succeeds and succeeds if 630 | // 'p' fails. It never consumes any input. This combined with 'and' can 631 | // be used for 'lookahead' and disambiguation of cases. 632 | // 633 | // Compare: 634 | // sequence("a",choice("+","++"),"b") 635 | // parses a+b 636 | // but not a++b because the + matches the first part and peg's don't 637 | // backtrack to other choice options if they succeed but later things fail. 638 | // 639 | // sequence("a",choice(sequence("+", not("+")),"++"),"b") 640 | // parses a+b 641 | // parses a++b 642 | // 643 | function not(p) { 644 | var p = toParser(p); 645 | var pid = parser_id++; 646 | return function(state) { 647 | var savedState = state; 648 | var cached = savedState.getCached(pid); 649 | if(cached) 650 | return cached; 651 | cached = p(state) ? false : make_result(state, "", undefined); 652 | savedState.putCached(pid, cached); 653 | return cached; 654 | } 655 | } 656 | 657 | 658 | -------------------------------------------------------------------------------- /readme.txt: -------------------------------------------------------------------------------- 1 | jsparse 2 | ======= 3 | 4 | This is a simple library of parser combinators for Javascript based on 5 | Packrat parsers [1] and Parsing expression grammars [2]. 6 | 7 | [1] http://pdos.csail.mit.edu/~baford/packrat/ 8 | [2] http://en.wikipedia.org/wiki/Parsing_expression_grammar 9 | 10 | The only documentation currently available in these blog entries: 11 | 12 | http://www.bluishcoder.co.nz/2007/10/javascript-packrat-parser.html 13 | http://www.bluishcoder.co.nz/2007/10/javascript-parser-combinators.html 14 | 15 | Examples: 16 | 17 | tests.js 18 | Various tests to ensure things are working 19 | 20 | example1.js 21 | Simple expression example from wikipedia article on PEGs. 22 | 23 | example2.js 24 | Expression example with actions used to produce AST. 25 | 26 | example3.js 27 | Expression example with actions used to evaluate as it parses. 28 | 29 | es3.js 30 | Incomplete/work-in-progress ECMAScript 3 parser 31 | 32 | es3_tests.js 33 | Tests for ECMAScript 3 parser 34 | 35 | I use it from within the Mozilla Rhino environment but it also works 36 | in the browser. 37 | -------------------------------------------------------------------------------- /tests.js: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2007 Chris Double. 2 | // 3 | // Redistribution and use in source and binary forms, with or without 4 | // modification, are permitted provided that the following conditions are met: 5 | // 6 | // 1. Redistributions of source code must retain the above copyright notice, 7 | // this list of conditions and the following disclaimer. 8 | // 9 | // 2. Redistributions in binary form must reproduce the above copyright notice, 10 | // this list of conditions and the following disclaimer in the documentation 11 | // and/or other materials provided with the distribution. 12 | // 13 | // THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, 14 | // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND 15 | // FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE 16 | // DEVELOPERS AND CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 17 | // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 18 | // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; 19 | // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 20 | // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR 21 | // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 22 | // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | // 24 | 25 | var passed = []; 26 | var failed = []; 27 | function assertTrue(msg, test) { 28 | if(test) 29 | passed.push(msg); 30 | else 31 | failed.push(msg); 32 | } 33 | 34 | function assertTrue2(test) { 35 | if(eval(test)) 36 | passed.push(test); 37 | else 38 | failed.push(test); 39 | } 40 | 41 | function assertFalse(msg, test) { 42 | if(test) 43 | failed.push(msg); 44 | else 45 | passed.push(msg); 46 | } 47 | 48 | function assertEqual(msg, value1, value2) { 49 | if(value1 == value2) 50 | passed.push(msg); 51 | else 52 | failed.push(msg); 53 | } 54 | 55 | function assertNotEqual(msg, value1, value2) { 56 | if(value1 != value2) 57 | passed.push(msg); 58 | else 59 | failed.push(msg); 60 | } 61 | 62 | function assertFullyParsed(parser, string) { 63 | var msg = parser + " did not fully parse: " + string; 64 | try { 65 | var result = eval(parser)(ps(string)); 66 | if(result && result.remaining.length == 0) 67 | passed.push(msg); 68 | else 69 | failed.push(msg); 70 | } 71 | catch(e) { 72 | failed.push(msg); 73 | } 74 | } 75 | 76 | function assertParseFailed(parser, string) { 77 | var msg = parser + " succeeded but should have failed: " + string; 78 | try { 79 | var result = eval(parser)(ps(string)); 80 | if(!result) 81 | passed.push(msg); 82 | else 83 | failed.push(msg); 84 | } 85 | catch(e) { 86 | failed.push(msg); 87 | } 88 | } 89 | 90 | function assertParseMatched(parser, string, expected) { 91 | var msg = parser + " parse did not match: " + string; 92 | try { 93 | var result = eval(parser)(ps(string)); 94 | if(result && result.matched == expected) 95 | passed.push(msg); 96 | else 97 | failed.push(msg + " got [" + result.matched + "] expected [" + expected + "]"); 98 | } 99 | catch(e) { 100 | failed.push(msg); 101 | } 102 | } 103 | 104 | function time(func) { 105 | var start = java.lang.System.currentTimeMillis(); 106 | var r = func(); 107 | var end = java.lang.System.currentTimeMillis(); 108 | print("Time: " + (end-start) + "ms"); 109 | return r; 110 | } 111 | 112 | function runTests(func) { 113 | passed = []; 114 | failed = []; 115 | func(); 116 | var total = passed.length + failed.length; 117 | for(var i=0; i < failed.length; ++i) 118 | print(failed[i]); 119 | print(total + " tests: " + passed.length + " passed, " + failed.length + " failed"); 120 | } 121 | 122 | function ParserTests() { 123 | // Token 124 | assertFullyParsed("token('a')", "a"); 125 | assertFullyParsed("token('abcd')", "abcd"); 126 | assertParseMatched("token('abcd')", "abcdef", "abcd"); 127 | assertParseFailed("token('a')", "b"); 128 | 129 | // ch 130 | assertParseMatched("ch('a')", "abcd", "a"); 131 | assertParseFailed("ch('a')", "bcd"); 132 | 133 | // range 134 | for(var i=0; i < 10; ++i) { 135 | assertParseMatched("range('0','9')", "" + i, i); 136 | } 137 | assertParseFailed("range('0','9')", "a"); 138 | 139 | // whitespace 140 | assertFullyParsed("whitespace(token('ab'))", "ab"); 141 | assertFullyParsed("whitespace(token('ab'))", " ab"); 142 | assertFullyParsed("whitespace(token('ab'))", " ab"); 143 | assertFullyParsed("whitespace(token('ab'))", " ab"); 144 | 145 | // negate 146 | assertFullyParsed("negate(ch('a'))", "b"); 147 | assertParseFailed("negate(ch('a'))", "a"); 148 | 149 | // end_p 150 | assertParseFailed("end_p", "ab"); 151 | assertFullyParsed("end_p", ""); 152 | 153 | // nothing_p 154 | assertParseFailed("nothing_p", "abcd"); 155 | assertParseFailed("nothing_p", ""); 156 | 157 | // sequence 158 | assertFullyParsed("sequence('a', 'b')", "ab"); 159 | assertParseFailed("sequence('a', 'b')", "b"); 160 | assertParseFailed("sequence('a', 'b')", "a"); 161 | assertParseMatched("sequence('a', whitespace('b'))", "a b", "ab"); 162 | assertParseMatched("sequence('a', whitespace('b'))", "a b", "ab"); 163 | assertParseMatched("sequence('a', whitespace('b'))", "ab", "ab"); 164 | 165 | // choice 166 | assertFullyParsed("choice('a', 'b')", "a"); 167 | assertFullyParsed("choice('a', 'b')", "b"); 168 | assertParseMatched("choice('a', 'b')", "ab", "a"); 169 | assertParseMatched("choice('a', 'b')", "bc", "b"); 170 | 171 | // repeat0 172 | assertParseMatched("repeat0(choice('a','b'))", "adef", "a"); 173 | assertParseMatched("repeat0(choice('a','b'))", "bdef", "b"); 174 | assertParseMatched("repeat0(choice('a','b'))", "aabbabadef", "aabbaba"); 175 | assertParseMatched("repeat0(choice('a','b'))", "daabbabadef", ""); 176 | 177 | // repeat1 178 | assertParseMatched("repeat1(choice('a','b'))", "adef", "a"); 179 | assertParseMatched("repeat1(choice('a','b'))", "bdef", "b"); 180 | assertParseMatched("repeat1(choice('a','b'))", "aabbabadef", "aabbaba"); 181 | assertParseFailed("repeat1(choice('a','b'))", "daabbabadef"); 182 | 183 | // optional 184 | assertParseMatched("sequence('a', optional(choice('b','c')), 'd')", "abd", "abd"); 185 | assertParseMatched("sequence('a', optional(choice('b','c')), 'd')", "acd", "acd"); 186 | assertParseMatched("sequence('a', optional(choice('b','c')), 'd')", "ad", "ad"); 187 | assertParseFailed("sequence('a', optional(choice('b','c')), 'd')", "aed"); 188 | assertParseFailed("sequence('a', optional(choice('b','c')), 'd')", "ab"); 189 | assertParseFailed("sequence('a', optional(choice('b','c')), 'd')", "ac"); 190 | 191 | // list 192 | assertParseMatched("list(choice('1','2','3'),',')", "1,2,3", "1,2,3"); 193 | assertParseMatched("list(choice('1','2','3'),',')", "1,3,2", "1,3,2"); 194 | assertParseMatched("list(choice('1','2','3'),',')", "1,3", "1,3"); 195 | assertParseMatched("list(choice('1','2','3'),',')", "3", "3"); 196 | assertParseFailed("list(choice('1','2','3'),',')", "5,6,7"); 197 | 198 | // and 199 | assertParseMatched("sequence(and('0'), '0')", "0", "0"); 200 | assertParseFailed("sequence(and('0'), '1')", "0"); 201 | assertParseMatched("sequence('1',and('2'))", "12", "1"); 202 | 203 | // not 204 | assertParseMatched("sequence('a',choice('+','++'),'b')", "a+b", "a+b"); 205 | assertParseFailed("sequence('a',choice('+','++'),'b')", "a++b"); 206 | assertParseMatched("sequence('a',choice(sequence('+',not('+')),'++'),'b')", "a+b", "a+b"); 207 | assertParseMatched("sequence('a',choice(sequence('+',not('+')),'++'),'b')", "a++b", "a++b"); 208 | 209 | // butnot 210 | assertFullyParsed("butnot(range('0','9'), '6')", "1"); 211 | assertParseFailed("butnot(range('0','9'), '6')", "6"); 212 | assertParseFailed("butnot(range('0','9'), 'x')", "x"); 213 | assertParseFailed("butnot(range('0','9'), 'y')", "x"); 214 | } 215 | 216 | 217 | time(function() { runTests(ParserTests); }); 218 | --------------------------------------------------------------------------------