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 }