Mercurial > repos > tabletprog
comparison parser.js @ 6:554602d4cbc6
Javascript compiler backend
author | Mike Pavone <pavone@retrodev.com> |
---|---|
date | Wed, 21 Mar 2012 08:42:12 -0700 |
parents | ed5b563147ec |
children | 8af72f11714e |
comparison
equal
deleted
inserted
replaced
5:ed5b563147ec | 6:554602d4cbc6 |
---|---|
1 var mainModule; | |
2 function toobj(val) | |
3 { | |
4 switch(typeof val) | |
5 { | |
6 case 'boolean': | |
7 if(val) { | |
8 return mainModule.strue; | |
9 } else { | |
10 return mainModule.sfalse; | |
11 } | |
12 case 'number': | |
13 return mainModule.snumber(val); | |
14 } | |
15 throw new Error("can't make val into object"); | |
16 } | |
17 | |
18 function indent(str) | |
19 { | |
20 return str.split('\n').join('\n\t'); | |
21 } | |
22 | |
23 function osymbols(parent) | |
24 { | |
25 this.parent = parent; | |
26 this.names = {}; | |
27 this.lastname = null; | |
28 } | |
29 osymbols.prototype.find = function(name) { | |
30 if (name in this.names) { | |
31 return { | |
32 type: 'self', | |
33 def: this.names[name], | |
34 }; | |
35 } else if(this.parent) { | |
36 var ret = this.parent.find(name); | |
37 if (ret) { | |
38 if(ret.type == 'self') { | |
39 ret.type = 'parent'; | |
40 ret.depth = 1; | |
41 } else if(ret.type == 'parent') { | |
42 ret.depth++; | |
43 } | |
44 } | |
45 return ret; | |
46 } | |
47 return null; | |
48 }; | |
49 osymbols.prototype.defineMsg = function(name, def) { | |
50 this.lastname = name; | |
51 this.names[name] = def; | |
52 } | |
53 osymbols.prototype.parentObject = function() { | |
54 if (!this.parent) { | |
55 return 'null'; | |
56 } | |
57 return 'this'; | |
58 } | |
59 | |
60 function lsymbols(parent) | |
61 { | |
62 this.parent = parent; | |
63 this.names = {}; | |
64 this.lastname = null; | |
65 this.needsSelfVar = false; | |
66 } | |
67 lsymbols.prototype.find = function(name) { | |
68 if (name in this.names) { | |
69 return { | |
70 type: 'local', | |
71 def: this.names[name] | |
72 }; | |
73 } else if(this.parent) { | |
74 var ret = this.parent.find(name); | |
75 if (ret && ret.type == 'local') { | |
76 ret.type = 'upvar'; | |
77 } | |
78 return ret; | |
79 } | |
80 return null; | |
81 }; | |
82 lsymbols.prototype.defineVar = function(name, def) { | |
83 this.lastname = name; | |
84 this.names[name] = def; | |
85 }; | |
86 lsymbols.prototype.selfVar = function() { | |
87 if (this.parent && this.parent instanceof lsymbols) { | |
88 this.parent.needsSelf(); | |
89 return 'self'; | |
90 } else { | |
91 return 'this'; | |
92 } | |
93 }; | |
94 lsymbols.prototype.needsSelf = function() { | |
95 if (this.parent && this.parent instanceof lsymbols) { | |
96 this.parent.needsSelf(); | |
97 } else { | |
98 this.needsSelfVar = true; | |
99 } | |
100 }; | |
1 | 101 |
2 function op(left, op, right) | 102 function op(left, op, right) |
3 { | 103 { |
4 this.left = left; | 104 this.left = left; |
5 this.op = op; | 105 this.op = op; |
6 this.right = right; | 106 this.right = right; |
7 } | 107 } |
108 op.prototype.toJS = function(symbols, isReceiver) { | |
109 var ret = '(' + this.left.toJS(symbols) +' '+ (this.op == '=' ? '==' : this.op) +' '+ this.right.toJS(symbols) + ')'; | |
110 if (isReceiver) { | |
111 ret = 'toobj' + ret; | |
112 } | |
113 return ret; | |
114 }; | |
8 | 115 |
9 function symbol(name) | 116 function symbol(name) |
10 { | 117 { |
11 this.name = name; | 118 this.name = name; |
12 } | 119 } |
120 symbol.prototype.toJS = function(symbols) { | |
121 var name = this.cleanName(); | |
122 return name == 'self' ? symbols.selfVar() : 's' + name.replace("_", "UN_").replace(":", "CN_").replace("!", "EX_").replace('?', 'QS_').replace('@', 'AT_'); | |
123 } | |
124 symbol.prototype.cleanName = function() { | |
125 return this.name[0] == ':' ? this.name.substr(1) : this.name; | |
126 } | |
13 | 127 |
14 function intlit(val) | 128 function intlit(val) |
15 { | 129 { |
16 this.val = val; | 130 this.val = val; |
17 } | 131 } |
132 intlit.prototype.toJS = function(symbols) { | |
133 return this.val.toString(); | |
134 } | |
18 | 135 |
19 function floatlit(val) | 136 function floatlit(val) |
20 { | 137 { |
21 this.val = val; | 138 this.val = val; |
22 } | 139 } |
140 floatlit.prototype.toJS = function(symbols) { | |
141 return this.val.toString(); | |
142 } | |
23 | 143 |
24 function strlit(val) | 144 function strlit(val) |
25 { | 145 { |
26 this.val = val; | 146 this.val = val; |
147 } | |
148 strlit.prototype.toJS = function(symbols) { | |
149 return '"' + this.val.replace('\\', '\\\\').replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r') + '"'; | |
27 } | 150 } |
28 | 151 |
29 function funcall(name, args) | 152 function funcall(name, args) |
30 { | 153 { |
31 this.name = name; | 154 this.name = name; |
32 this.args = args; | 155 this.args = args; |
33 this.receiver = null; | 156 this.receiver = null; |
34 } | 157 } |
158 funcall.prototype.toJS = function(symbols) { | |
159 var name = this.name[this.name.length-1] == ':' ? this.name.substr(0, this.name.length-1) : this.name; | |
160 var args = this.args.slice(0, this.args.length); | |
161 if (this.receiver) { | |
162 args.splice(0, 0, this.receiver); | |
163 } | |
164 var funinfo = symbols.find(name); | |
165 if (!funinfo) { | |
166 var receiver = args[0]; | |
167 args.splice(0, 1); | |
168 for (var i in args) { | |
169 args[i] = args[i].toJS(symbols); | |
170 } | |
171 return receiver.toJS(symbols, true) + '.' + (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
172 } | |
173 switch(funinfo.type) | |
174 { | |
175 case 'self': | |
176 if (args.length < funinfo.def.args.length || funinfo.def.args[0].name != 'self') { | |
177 var receiver = new symbol('self'); | |
178 } else { | |
179 var receiver = args[0]; | |
180 args.splice(0, 1); | |
181 } | |
182 for (var i in args) { | |
183 args[i] = args[i].toJS(symbols); | |
184 } | |
185 return receiver.toJS(symbols, true) + '.' + (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
186 case 'parent': | |
187 var ret = 'this'; | |
188 for (var i = 0; i < funinfo.depth; ++i) { | |
189 ret += '.parent'; | |
190 } | |
191 ret += (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
192 return ret; | |
193 case 'local': | |
194 case 'upvar': | |
195 return (new symbol(name)).toJS(symbols) + '(' + args.join(', ') + ')'; | |
196 } | |
197 } | |
35 | 198 |
36 function object(messages) | 199 function object(messages) |
37 { | 200 { |
38 this.messages = messages; | 201 this.messages = messages; |
202 } | |
203 object.prototype.toJS = function(symbols) { | |
204 var messages = this.messages.slice(0, this.messages.length); | |
205 symbols = new osymbols(symbols); | |
206 for (var i in messages) { | |
207 messages[i] = indent(messages[i].toJSObject(symbols)); | |
208 } | |
209 return '{\n\tparent: ' + symbols.parentObject() + ',\n\t' + messages.join(',\n\t') + '\n}'; | |
210 } | |
211 | |
212 object.prototype.toJSModule = function() { | |
213 return '(function () {\n\tvar module = ' + indent(this.toJS(null)) + ';\n\treturn module;\n})' | |
39 } | 214 } |
40 | 215 |
41 function lambda(args, expressions) | 216 function lambda(args, expressions) |
42 { | 217 { |
43 this.args = args; | 218 this.args = args; |
44 this.expressions = expressions; | 219 this.expressions = expressions; |
45 } | 220 } |
221 lambda.prototype.toJS = function(symbols) { | |
222 var args = this.args ? this.args.slice(0, this.args.length) : []; | |
223 if (args.length && args[0].cleanName() == 'self') { | |
224 args.splice(0, 1); | |
225 } | |
226 var exprs = this.expressions.slice(0, this.expressions.length); | |
227 symbols = new lsymbols(symbols); | |
228 for (var i in args) { | |
229 symbols.defineVar(args[i].cleanName(), null); | |
230 args[i] = args[i].toJS(symbols); | |
231 } | |
232 for (var i in exprs) { | |
233 exprs[i] = indent(exprs[i].toJS(symbols)); | |
234 } | |
235 if (exprs.length) { | |
236 exprs[exprs.length-1] = 'return ' + exprs[exprs.length-1] + ';'; | |
237 } | |
238 return 'function (' + args.join(', ') + ') {\n\t' + (symbols.needsSelfVar ? 'var self = this;\n\t' : '') + exprs.join(';\n\t') + '\n}' | |
239 }; | |
240 lambda.prototype.toJSModule = lambda.prototype.toJS | |
46 | 241 |
47 function assignment(sym, expr) | 242 function assignment(sym, expr) |
48 { | 243 { |
49 this.symbol = sym; | 244 this.symbol = sym; |
50 this.expression = expr; | 245 this.expression = expr; |
51 } | 246 } |
247 assignment.prototype.toJS = function(symbols) { | |
248 var existing = symbols.find(this.symbol.name); | |
249 var prefix = ''; | |
250 if (!existing) { | |
251 symbols.defineVar(this.symbol.name, this.expression); | |
252 prefix = 'var '; | |
253 } else { | |
254 switch (existing.type) | |
255 { | |
256 case 'self': | |
257 prefix = 'this.'; | |
258 break; | |
259 case 'parent': | |
260 prefix = 'this.'; | |
261 for (var i = 0; i < existing.depth; ++i) { | |
262 prefix += 'parent.'; | |
263 } | |
264 break; | |
265 } | |
266 } | |
267 return prefix + this.symbol.toJS(symbols) + ' = ' + this.expression.toJS(symbols); | |
268 }; | |
269 assignment.prototype.toJSObject = function(symbols) { | |
270 symbols.defineMsg(this.symbol.name, this.expression); | |
271 return this.symbol.toJS(symbols) + ': ' + this.expression.toJS(symbols); | |
272 }; | |
52 | 273 |
53 var grammar = | 274 var grammar = |
54 'start = ws module:(object / lambda) ws { return module; };' + | 275 'start = ws module:(object / lambda) ws { return module; };' + |
55 'ws = ([ \\t\\n\\r] / "//" [^\\n]* "\\n")*;' + | 276 'ws = ([ \\t\\n\\r] / "//" [^\\n]* "\\n")*;' + |
56 'hws = ([ \\t] / "/*" ([^*] / "*" ! "/")* "*/" )*;' + | 277 'hws = ([ \\t] / "/*" ([^*] / "*" ! "/")* "*/" )*;' + |
57 'expr = e:(funcall / methcall / opexpr) ws { return e; };' + | 278 'expr = e:(funcall / methcall / opexpr) ws { return e; };' + |
58 'opexpr = left:addsub hws opn:("<=" / ">=" / "<" / ">" / "=") hws right:opexpr { return new op(left, opn, right); } / addsub;' + | 279 'opexpr = left:addsub hws opn:("<=" / ">=" / "<" / ">" / "=") hws right:opexpr { return new op(left, opn, right); } / addsub;' + |
59 'addsub = left:muldiv hws opn:("+"/"-") hws right:addsub { return new op(left, opn, right); } / muldiv;'+ | 280 'addsub = left:muldiv hws opn:("+"/"-") hws right:addsub { return new op(left, opn, right); } / muldiv;'+ |
60 'muldiv = left:primlitsym hws opn:("*"/"/") hws right:muldiv { return new op(left, opn, right); } / primlitsym;'+ | 281 'muldiv = left:primlitsym hws opn:("*"/"/") hws right:muldiv { return new op(left, opn, right); } / primlitsym;'+ |
61 'primlitsym = hws val:(float / int / string / symbol / object / lambda / "(" expr:expr hws ")" { return expr; }) { return val; };' + | 282 'primlitsym = hws val:(float / int / string / symbol / object / lambda / "(" expr:expr hws ")" { return expr; }) { return val; };' + |
62 'symbol = chars:[a-zA-Z_!?@]+ trailing:(":"? [a-zA-Z_!?@0-9])* ! ":" { return new symbol(chars.join("") + trailing.join("")); };' + | 283 'symbol = chars:[a-zA-Z_!?@]+ trailing:(":"? [a-zA-Z_!?@0-9])* ! ":" { for (var i in trailing) { trailing[i] = trailing[i].join(""); } return new symbol(chars.join("") + trailing.join("")); };' + |
63 'float = digits:[0-9]+ "." decimals:[0-9]+ { return new floatlit(parseFloat(digits.join("") + "." + decimals.join(""))); };' + | 284 'float = digits:[0-9]+ "." decimals:[0-9]+ { return new floatlit(parseFloat(digits.join("") + "." + decimals.join(""))); };' + |
64 'int = digits:[0-9]+ { return new intlit(parseInt(digits.join(""), 10)); };' + | 285 'int = digits:[0-9]+ { return new intlit(parseInt(digits.join(""), 10)); };' + |
65 'string = "\\"" text:[^\\"]* "\\"" { return new strlit(text.join("")); };' + | 286 'string = "\\"" text:[^\\"]* "\\"" { return new strlit(text.join("")); };' + |
66 'object = "#{" ws messages:assignment* "}" { return new object(messages); };' + | 287 'object = "#{" ws messages:assignment* "}" { return new object(messages); };' + |
67 'assignment = hws sym:symbol hws "<-" expr:expr ws { return new assignment(sym, expr); }' + | 288 'assignment = hws sym:symbol hws "<-" expr:expr ws { return new assignment(sym, expr); }' + |
70 'funcall = hws parts: funcallpart+ { var fun = ""; var args = []; for (var i in parts) { fun += parts[i].name; args = args.concat(parts[i].args); } return new funcall(fun, args); };' + | 291 'funcall = hws parts: funcallpart+ { var fun = ""; var args = []; for (var i in parts) { fun += parts[i].name; args = args.concat(parts[i].args); } return new funcall(fun, args); };' + |
71 'funcallpart = fun:funpart args:opexpr* hws { return { name: fun, args: args}; };' + | 292 'funcallpart = fun:funpart args:opexpr* hws { return { name: fun, args: args}; };' + |
72 'funpart = chars:[a-zA-Z_!?@]+ middle:[a-zA-Z_!?@0-9]* ":" & [ \\t\\n\\r] { return chars.join("") + middle.join("") + ":"; };' + | 293 'funpart = chars:[a-zA-Z_!?@]+ middle:[a-zA-Z_!?@0-9]* ":" & [ \\t\\n\\r] { return chars.join("") + middle.join("") + ":"; };' + |
73 'methcall = receiver:opexpr hws info:methcallrest { info.receiver = receiver; return info; };' + | 294 'methcall = receiver:opexpr hws info:methcallrest { info.receiver = receiver; return info; };' + |
74 'methcallrest = funcall / unarymeth;' + | 295 'methcallrest = funcall / unarymeth;' + |
75 'unarymeth = name:symbol { return new funcall(name, []); };'; | 296 'unarymeth = name:symbol { return new funcall(name.name, []); };'; |
76 var parser = PEG.buildParser(grammar); | 297 var parser = PEG.buildParser(grammar); |
77 | 298 |
78 //var parser = PEG.buildParser('start = expr; expr = int; int = digits:[0-9]+ { return parseInt(digits.join(""), 10); }'); | 299 //var parser = PEG.buildParser('start = expr; expr = int; int = digits:[0-9]+ { return parseInt(digits.join(""), 10); }'); |
79 | |
80 onReady(function() { | 300 onReady(function() { |
81 q('input[type=button]').onclick = function() { | 301 q('#parse').onclick = function() { |
82 var text = q('textarea').value; | 302 var text = q('textarea').value; |
83 try { | 303 try { |
84 var parsed = parser.parse(text); | 304 var parsed = parser.parse(text); |
85 q('div').innerHTML = text + "<br><br>" + JSON.stringify(parsed); | 305 q('pre').innerHTML = text + "\n\n" + JSON.stringify(parsed); |
86 console.log(parsed); | 306 console.log(parsed); |
87 } catch(e) { | 307 } catch(e) { |
88 q('div').innerHTML = e.message + '<br>Line: ' + e.line + '<br>Col: ' + e.column; | 308 q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column; |
89 } | 309 } |
310 } | |
311 q('#tojs').onclick = function() { | |
312 var text = q('textarea').value; | |
313 //try { | |
314 var parsed = parser.parse(text); | |
315 var js = parsed.toJSModule(); | |
316 q('pre').innerHTML = js; | |
317 console.log(parsed); | |
318 /*} catch(e) { | |
319 q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column; | |
320 }*/ | |
321 } | |
322 q('#run').onclick = function() { | |
323 var text = q('textarea').value; | |
324 //try { | |
325 var parsed = parser.parse(text); | |
326 var js = parsed.toJSModule(); | |
327 mainModule = eval(js)(); | |
328 q('pre').innerHTML = mainModule.smain(); | |
329 /*} catch(e) { | |
330 q('pre').innerHTML = e.message + '\nLine: ' + e.line + '\nCol: ' + e.column; | |
331 }*/ | |
90 } | 332 } |
91 }); | 333 }); |
92 | 334 |