1
2 /*
3
4 This is the Hoot parser. It turns Hootscripts into Hoot games.
5
6 It is predictive (see hoot-grammar.js for creation of predictions)
7 and iterative rather than recursive. It's orders of magnitude faster than the
8 previous recursive PEG-style parser.
9 This could probably do with some refactoring and beautification, but
10 I need some time away from it before I start that.
11
12 */
13
14
15 function parser(script, grammar){
16 var stack = [grammar["script"]];
17 var success = true;
18 var tree = [];
19 var matchedrule, atom, newatom, ruleatom; // used later
20
21 var treestack = [tree];
22
23 script = script.replace(/^\s+/, ''); // trim leading space
24
25 while(stack.length > 0 && success){
26 //console.log((function(){var s = script.replace(/\n/,''); var elipsis = (script.length>30 ? '...' : ''); var s = "'"+s.substring(0,30)+elipsis+"' <--- "; var i = 0; for(; i<stack.length; i++){ s+=stack[i].operator+stack[i].name+(stack[i].popstack?'(pop)':'')+" "; } return s; })() );
27
28 atom = stack.pop();
29
30 if(atom.extract){ // terminal
31 matches = atom.extract.exec(script);
32 if(matches != null){
33 var len = matches[0].length;
34 script = script.slice(len);
35 if(atom.keep){
36 treestack[treestack.length-1].push([atom.name, matches[1]]);
37 }
38 if(atom.operator == "*" || atom.operator == "+"){
39 atom.operator = "*";
40 stack.push(cloneAtom(atom));
41 }
42 }
43 else{
44 if(atom.operator != "*"){
45 success = false;
46 }
47 else{
48 if(atom.popstack){
49 treestack.pop(); // because this was an optional item!
50 }
51 }
52 }
53 }
54
55 else{ // non-terminal
56
57 rule = function(pr){
58 for(p in pr){ if(pr[p].test(script)){ return p; } }
59 return false;
60 }(atom.predictions);
61
62 if(rule !== false){ // no rule matched the incoming script!
63 if(atom.operator == "*" || atom.operator == "+"){
64 atom.operator = "*";
65 stack.push(cloneAtom(atom));
66 }
67 matchedrule = atom.rules[rule];
68 for(var i=matchedrule.length-1; i>=0; i--){
69 ruleatom = cloneAtom(grammar[matchedrule[i].name]);
70 ruleatom.operator = matchedrule[i].operator;
71 ruleatom.popstack = false; // by default, we don't pop the script stack after a match
72 if(i==matchedrule.length-1 && atom.keep){
73 ruleatom.popstack = true; // last atom in rule pops the script stack if this is an atom that we keep
74 }
75 stack.push(ruleatom);
76 }
77 newatom = [atom.name];
78 if(atom.keep){
79 treestack[treestack.length-1].push(newatom);
80 treestack.push(newatom);
81 }
82 }
83 else{
84 if(atom.operator != "*"){
85 success = false;
86 }
87 else{
88 if(atom.popstack){
89 treestack.pop(); // because this was an optional item!
90 }
91 }
92 }
93 }
94
95
96 if(atom.popstack && atom.operator != "*" && atom.operator != "+"){
97 treestack.pop();
98 }
99
100 }
101
102 // we're out of atoms on the stack, there better not be any script left!
103 success &= (/\S/.test(script) ? false : true );
104
105 if(!success){
106 return {"success":false, "script":script, "stack":stack, "atom":atom.name};
107 }
108
109 return {"success":true, "tree":tree};
110
111 function cloneAtom(a){
112 var c = {};
113 for(attr in a){ // since we are in control of the atom object, it's safe to copy this way
114 c[attr] = a[attr];
115 }
116 return c;
117 }
118 }