colorful rat Ratfactor.com > Dave's Repos

hoot

Silly HTML game building engine
git clone http://ratfactor.com/repos/hoot/hoot.git

hoot/js/hoot-parser.js

Download raw file: js/hoot-parser.js

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 }