Question

I'm trying to use Jison.

Here's my grammar:

var grammar = {
lex:{
    rules:[
        ["\\s+",            ""],
        ["then",            "return 'newline';"],
        ["goto",            "return 'goto';"],
        ["http[^\\s]*",     "return 'url';"],
        ["search",          "return 'search';"],
        ["should_exist",    "return 'exist';"],
        //["#(.)*",           "return 'elementById';"],
        //["$",               "return 'EOF';"]
    ]
},
bnf:{
    lines:[
        ['lines line',          "console.log('big expression is ',$3);  return ['l2',$1, $2];"],
        ['line',                "console.log('expression is ',$1); return ['l1',$1]"],
    ],
    line:[
        ["line newline",        "console.log('line newline', $1); $$ = $1"],
        ["goto url",            "console.log('goto', $2); $$ = {cmd:'goto', url: $2 } "],
        ["elementById exist",   "$$ = {cmd:'assert', elId: $1} "]
    ]
}
};

When I try to parse goto http://www.google.com then goto http://www.bing.com I only ever get [ 'l1', { cmd: 'goto', url: 'http://www.google.com' } ] returned.

I'm expecting to get both goto commands returned.

Any help with me figuring out my grammar?

Was it helpful?

Solution

The main problem in your code is the premature use of return. Using return will end the parsing right there. So if you use it in a rule that is not meant to terminate parsing, you're in trouble. I like to have one rule which is the entry point of the whole system and whose job is only to call return with something sensible.

Here's something that works more like what you want. I've not changed anything to lex.

    bnf:{
        top: [
            ['lines', "console.log('top is ', $1);  return $1;"]
        ],
        lines:[
            ['lines line',          "console.log('big expression is ', $1);  $$ = ['l2', $1, $2];"],
            ['line',                "console.log('expression is ',$1); $$ = ['l1',$1]"],
        ],
        line:[
            ["line newline",        "console.log('line newline', $1); $$ = $1"],
            ["goto url",            "console.log('goto', $2); $$ = {cmd:'goto', url: $2 } "],
            ["elementById exist",   "$$ = {cmd:'assert', elId: $1} "]
        ]
    }

The output I get with the above is:

goto http://www.google.com
line newline { cmd: 'goto', url: 'http://www.google.com' }
expression is  { cmd: 'goto', url: 'http://www.google.com' }
goto http://www.bing.com
big expression is  [ 'l1', { cmd: 'goto', url: 'http://www.google.com' } ]
top is  [ 'l2',
  [ 'l1', { cmd: 'goto', url: 'http://www.google.com' } ],
  { cmd: 'goto', url: 'http://www.bing.com' } ]

Here's a diff between what you originally had and what I suggest:

--- original.js 2014-02-23 08:10:37.605989877 -0500
+++ parser.js   2014-02-23 08:35:06.674952990 -0500
@@ -14,9 +14,12 @@
         ]
     },
     bnf:{
+        top: [
+            ['lines', "console.log('top is ', $1);  return $1;"]
+        ],
         lines:[
-            ['lines line',          "console.log('big expression is ',$3);  return ['l2',$1, $2];"],
-            ['line',                "console.log('expression is ',$1); return ['l1',$1]"],
+            ['lines line',          "console.log('big expression is ', $1);  $$ = ['l2', $1, $2];"],
+            ['line',                "console.log('expression is ',$1); $$ = ['l1',$1]"],
         ],
         line:[
             ["line newline",        "console.log('line newline', $1); $$ = $1"],

OTHER TIPS

I would contest your need for a full-fledged parser for this purpose. For example you could do the same thing simply using a lexer and a handwritten recursive descent parser:

var lexer = new Lexer;

lexer.addRule(/\s+/, function () {
    // skip whitespace
});

lexer.addRule(/then|goto|search/, function (lexeme) {
    return lexeme.toUpperCase();
});

lexer.addRule(/https?:\/\/[^\s]+/, function (lexeme) {
    this.yytext = lexeme;
    return "URL";
});

lexer.addRule(/$/, function () {
    return "EOF";
});

Now that we have a lexical analyzer we'll create the recursive descent parser:

function parse(input) {
    lexer.setInput(input);
    return statement();
}

function statement() {
    var condition = command();
    match("THEN");
    var branch = command();
    match("EOF");

    return {
        condition: condition,
        branch: branch
    };
}

function command() {
    match("GOTO");
    var url = match("URL");

    return {
        command: "GOTO",
        url: url
    };
}

function match(expected) {
    var token = lexer.lex();
    if (token === expected) return lexer.yytext;
    else throw new Error("Unexpected token: " + token);
}

Now all you need to do is call parse:

var output = parse("goto http://www.google.com/ then goto http://www.bing.com/");

alert(JSON.stringify(output, null, 4));

See the demo for yourself: http://jsfiddle.net/r4RH2/1/

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top