on d760cbf help sign in
bridge/src/nockjs/compiler.js
726 lines 20 kB history raw
1var noun = require('./noun.js'),
2 NounMap = require('./hamt.js').NounMap,
3 list = require('./list.js'),
4 Noun = noun.Noun,
5 Atom = noun.Atom.Atom,
6 Cell = noun.Cell,
7 MEMO = noun.dwim("memo"),
8 SLOG = noun.dwim("slog"),
9 FAST = noun.dwim("fast"),
10 SPOT = noun.dwim("spot"),
11 MEAN = noun.dwim("mean"),
12 HUNK = noun.dwim("hunk"),
13 LOSE = noun.dwim("lose");
15function Statement() {
18function Block() {
19 Statement.call(this);
20 this.statements = [];
22Block.prototype = Object.create(Statement.prototype);
23Block.prototype.constructor = Block;
25Block.prototype.append = function(st) {
26 this.statements.push(st);
29Block.prototype.toJs = function() {
30 var sts = this.statements;
31 var parts = new Array(sts.length);
32 for ( var i = 0; i < sts.length; ++i) {
33 parts[i] = sts[i].toJs();
34 }
35 return parts.join('');
36};
38function Assignment(name, expr) {
39 Statement.call(this);
40 this.name = name;
41 this.expr = expr;
43Assignment.prototype = Object.create(Statement.prototype);
44Assignment.prototype.constructor = Assignment;
46Assignment.prototype.toJs = function() {
47 return "var " + this.name + " = " + this.expr.toJs() + ";";
50function Expression() {
53function Cons(head, tail) {
54 Expression.call(this);
55 this.head = head;
56 this.tail = tail;
58Cons.prototype = Object.create(Expression.prototype);
59Cons.prototype.constructor = Cons;
61Cons.prototype.toJs = function() {
62 return "context.cons(" + this.head + ", " + this.tail + ")";
63};
65function Frag(axis, name) {
66 Expression.call(this);
67 this.axis = axis;
68 this.name = name;
70Frag.prototype = Object.create(Expression.prototype);
71Frag.prototype.constructor = Frag;
73Frag.prototype.toJs = function() {
74 var parts = [this.name];
75 for ( var ax = this.axis; ax > 1; ax = ax.mas() ) {
76 parts.push( ( 2 === ax.cap().valueOf() ) ? "head" : "tail" );
77 }
78 return parts.join(".");
79};
81function Bail() {
82 Statement.call(this);
84Bail.prototype = Object.create(Statement.prototype);
85Bail.prototype.constructor = Bail;
87Bail.prototype.toJs = function() {
88 return "throw new Error(\"Bail\")";
89};
91function Identity(name) {
92 Expression.call(this);
93 this.name = name;
95Identity.prototype = Object.create(Expression.prototype);
96Identity.prototype.constructor = Identity;
98Identity.prototype.toJs = function() {
99 return this.name;
100};
102function Constant(index) {
103 Expression.call(this);
104 this.index = index;
106Constant.prototype = Object.create(Expression.prototype);
107Constant.prototype.constructor = Constant;
109Constant.prototype.toJs = function() {
110 return "constants[" + this.index + "]";
111};
113function Nock(subject, formula, tail) {
114 Expression.call(this);
115 this.subject = subject;
116 this.formula = formula;
117 this.tail = tail;
119Nock.prototype = Object.create(Expression.prototype);
120Nock.prototype.constructor = Nock;
122Nock.prototype.toJs = function() {
123 var f = this.formula;
124 var targetCode = "(" + f + ".hasOwnProperty('target') ? " + f +
125 ".target : (" + f + ".target = context.compile(" + this.formula + ")))";
126 return this.tail ?
127 "context.trampoline(" + targetCode + ", " + this.subject + ")" :
128 targetCode + "(" + this.subject + ")";
129};
131function Deep(name) {
132 Expression.call(this);
133 this.name = name;
135Deep.prototype = Object.create(Expression.prototype);
136Deep.prototype.constructor = Deep;
138Deep.prototype.toJs = function() {
139 return this.name +".deep ? context.yes : context.no";
140};
142function Bump(name) {
143 Expression.call(this);
144 this.name = name;
146Bump.prototype = Object.create(Expression.prototype);
147Bump.prototype.constructor = Bump;
149Bump.prototype.toJs = function() {
150 return this.name + ".bump()";
151};
153function Same(one, two) {
154 Expression.call(this);
155 this.one = one;
156 this.two = two;
158Same.prototype = Object.create(Expression.prototype);
159Same.prototype.constructor = Same;
161Same.prototype.toJs = function() {
162 return "(" + this.one + ".equals(" + this.two + ")" + " ? context.yes : context.no)";
163};
165function If(test, yes, no) {
166 Statement.call(this);
167 this.test = test;
168 this.yes = yes;
169 this.no = no;
171If.prototype = Object.create(Statement.prototype);
172If.prototype.constructor = If;
174If.prototype.toJs = function() {
175 return "if(" + this.test + ".loob()){" +
176 this.yes.toJs() + "}else{" + this.no.toJs() + "}";
177};
179function Kick(axis, core, tail) {
180 Expression.call(this)
181 this.axis = axis;
182 this.core = core;
183 this.tail = tail;
185Kick.prototype = Object.create(Expression.prototype);
186Kick.prototype.constructor = Kick;
188Kick.prototype.toJs = function() {
189 var axis = this.axis.shortCode();
191 return "(function (cor) {" +
192 "var pro, tgt, bus, arms, bat = cor.head, has = false;" +
193 "if ( bat.hasOwnProperty('loc') && (tgt = bat.loc.jets[" + axis + "]) && bat.loc.fine(cor) ) {" +
194 "return tgt(cor);" +
195 "}" +
196 "if ( bat.hasOwnProperty('arms') ) {" +
197 "arms = bat.arms;" +
198 "has = arms.hasOwnProperty('" + axis + "');" +
199 "}" +
200 "else arms = bat.arms = {};" +
201 "tgt = (has ? arms['" + axis + "'] : (arms['" + axis + "'] = context.compile(" +
202 new Frag(this.axis, "bat").toJs() + ")));" +
203 "bus = cor;" +
204 (this.tail ? "pro = context.trampoline(tgt, bus);" :
205 "while (true) {" +
206 "pro = tgt(bus);" +
207 "if ( context.isTrampoline(pro) ) {" +
208 "tgt = pro.target;" +
209 "bus = pro.subject;" +
210 "}" +
211 "else break;" +
212 "}") +
213 "return pro;" +
214 "})(" + this.core + ")";
215};
217function GetMemo(name) {
218 Expression.call(this);
219 this.name = name;
221GetMemo.prototype = Object.create(Expression.prototype);
222GetMemo.prototype.constructor = GetMemo;
224GetMemo.prototype.toJs = function() {
225 return "context.getMemo(" + this.name + ")";
226};
228function PutMemo(key, val) {
229 Statement.call(this);
230 this.key = key;
231 this.val = val;
233PutMemo.prototype = Object.create(Statement.prototype);
234PutMemo.prototype.constructor = PutMemo;
236function Push(name) {
237 Statement.call(this);
238 this.name = name;
240Push.prototype = Object.create(Statement.prototype);
241Push.prototype.constructor = Push;
243Push.prototype.toJs = function() {
244 return "context.stackPush(" + this.name + ");";
245};
247function Pop() {
248 Statement.call(this);
250Pop.prototype = Object.create(Statement.prototype);
251Pop.prototype.constructor = Pop;
253Pop.prototype.toJs = function() {
254 return "context.stackPop()";
255};
257function Fast(clue, core) {
258 Statement.call(this);
259 this.clue = clue;
260 this.core = core;
263Fast.prototype.toJs = function() {
264 return "context.register(" + this.core + ", " + this.clue + ");";
267function Slog(name) {
268 Statement.call(this);
269 this.name = name;
272Slog.prototype.toJs = function() {
273 return "context.slog(" + this.name + ")";
274};
276function compile(formula, subject, product, fresh, constants, block, tail) {
277 var op, arg, one, two, odd;
278 if ( !(formula instanceof Cell )) {
279 throw new Error("invalid formula");
280 }
281 op = formula.head;
282 arg = formula.tail;
283 if ( op instanceof Cell ) {
284 one = fresh();
285 two = fresh();
286 compile(op, subject, one, fresh, constants, block, false);
287 compile(arg, subject, two, fresh, constants, block, false);
288 block.append(new Assignment(product, new Cons(one, two)));
289 }
290 else switch ( op.valueOf() ) {
291 case 0:
292 if ( 0 === arg ) {
293 block.append(new Bail());
294 }
295 else if ( 1 === arg ) {
296 block.append(new Identity(subject));
297 }
298 else {
299 block.append(new Assignment(product, new Frag(arg, subject)));
300 }
301 break;
302 case 1:
303 constants.push(arg);
304 block.append(new Assignment(product, new Constant(constants.length - 1)));
305 break;
306 case 2:
307 one = fresh();
308 two = fresh();
309 compile(arg.head, subject, one, fresh, constants, block, false);
310 compile(arg.tail, subject, two, fresh, constants, block, false);
311 block.append(new Assignment(product, new Nock(one, two, tail)));
312 break;
313 case 3:
314 one = fresh();
315 compile(arg, subject, one, fresh, constants, block, false);
316 block.append(new Assignment(product, new Deep(one)));
317 break;
318 case 4:
319 one = fresh();
320 compile(arg, subject, one, fresh, constants, block, false);
321 block.append(new Assignment(product, new Bump(one)));
322 break;
323 case 5:
324 one = fresh();
325 two = fresh();
326 compile(arg.head, subject, one, fresh, constants, block, false);
327 compile(arg.tail, subject, two, fresh, constants, block, false);
328 block.append(new Assignment(product, new Same(one, two)));
329 break;
330 case 6:
331 odd = fresh();
332 one = new Block();
333 two = new Block();
334 compile(arg.head, subject, odd, fresh, constants, block, false);
335 compile(arg.tail.head, subject, product, fresh, constants, one, tail);
336 compile(arg.tail.tail, subject, product, fresh, constants, two, tail);
337 block.append(new If(odd, one, two));
338 break;
339 case 7:
340 one = fresh();
341 compile(arg.head, subject, one, fresh, constants, block, false);
342 compile(arg.tail, one, product, fresh, constants, block, tail);
343 break;
344 case 8:
345 one = fresh();
346 two = fresh();
347 compile(arg.head, subject, one, fresh, constants, block, false);
348 block.append(new Assignment(two, new Cons(one, subject)));
349 compile(arg.tail, two, product, fresh, constants, block, tail);
350 break;
351 case 9:
352 odd = arg.head;
353 if ( 2 === odd.cap().valueOf() ) {
354 one = fresh();
355 two = odd.mas();
356 compile(arg.tail, subject, one, fresh, constants, block, false);
357 block.append(new Assignment(product, new Kick(two, one, tail)));
358 }
359 else {
360 compile(noun.dwim([7, arg.tail, 2, [0, 1], 0, odd]),
361 subject, product, fresh, constants, block, tail);
362 }
363 break;
364 case 10:
365 var hint = arg.head;
366 if ( !(arg.head instanceof Cell) ) {
367 // no recognized static hints
368 compile(arg.tail, subject, product, fresh, constants, block, tail);
369 }
370 else {
371 var zep = hint.head;
372 var clu = fresh();
373 compile(hint.tail, subject, clu, fresh, constants, block, false);
374 if ( zep.equals(MEMO) ) {
375 var key = fresh();
376 var got = fresh();
377 odd = fresh();
378 one = new Block();
379 two = new Block();
380 var konst = fresh();
381 block.append(new Assignment(konst, new Constant(hint.tail)));
382 block.append(new Assignment(key, new Cons(subject, konst)));
383 block.append(new Assignment(got, new GetMemo(two)));
384 block.append(new Assignment(odd, new Deep(got)));
385 one.append(new Assignment(product, new Frag(noun.dwim(3), got)));
386 compile(arg.tail, subject, product, fresh, two, false);
387 two.append(new PutMemo(key, product));
388 block.append(new If(odd, one, two));
389 }
390 else if ( zep.equals(SLOG) ) {
391 block.append(new Slog(clu));
392 compile(arg.tail, subject, product, fresh, constants, block, tail);
393 }
394 else if ( zep.equals(FAST) ) {
395 compile(arg.tail, subject, product, fresh, constants, block, false);
396 block.append(new Fast(clu, product));
397 }
398 else if ( zep.equals(SPOT) ||
399 zep.equals(MEAN) ||
400 zep.equals(HUNK) ||
401 zep.equals(LOSE) ) {
402 one = fresh();
403 two = fresh();
404 block.append(new Assignment(one, new Constant(zep)));
405 block.append(new Assignment(two, new Cons(one, clu)));
406 block.append(new Push(two));
407 compile(arg.tail, subject, product, fresh, constants, block, false);
408 block.append(new Pop());
409 }
410 else {
411 // unrecognized
412 compile(arg.tail, subject, product, fresh, constants, block, tail);
413 }
414 }
415 break;
416 case 11:
417 one = fresh();
418 two = fresh();
419 compile(arg.head, subject, one, fresh, constants, block, false);
420 compile(arg.tail, subject, two, fresh, constants, block, false);
421 block.append(new Assignment(product, new Esc(one, two)));
422 break;
423 default:
424 throw new Error("invalid opcode");
425 }
428function Trampoline(target, subject) {
429 this.target = target;
430 this.subject = subject;
433function genFine(loc) {
434 var constants = [], out = [], i;
435 for ( i = 0; !loc.isStatic; ++i ) {
436 out.push("if(!constants[" + i + "].equals(a.head)){return false;}");
437 constants.push(loc.noun);
438 out.push("a=" + new Frag(loc.axisToParent, "a").toJs() + ";");
439 loc = loc.parentLoc;
440 }
441 out.push("return constants[" + i + "].equals(a);");
442 constants.push(loc.noun);
443 var body = 'return function(a){' + out.join('') + 'return true;};';
444 var builder = new Function('constants', body);
445 return builder(constants);
448var three = noun.dwim(3);
449function Location(context, name, label, axisToParent, hooks, noun, parentLoc) {
450 this.name = name;
451 this.label = label;
452 this.parentLoc = parentLoc;
453 this.axisToParent = axisToParent;
454 this.fragToParent = Noun.fragmenter(axisToParent);
455 this.nameToAxis = hooks;
456 this.axisToName = {};
457 this.isStatic = ( null === parentLoc || (three.equals(axisToParent) && parentLoc.isStatic) );
458 if ( this.isStatic ) {
459 this.noun = noun;
460 }
461 else {
462 this.noun = noun.head;
463 this.noun.mug();
464 }
465 for ( var k in hooks ) {
466 if ( hooks.hasOwnProperty(k) ) {
467 this.axisToName[hooks[k].shortCode()] = k;
468 }
469 }
470 this.jets = {};
471 var drivers = context.drivers[label];
472 if ( drivers && drivers.length > 0 ) {
473 for ( var i = 0; i < drivers.length; ++i ) {
474 var d = drivers[i];
475 if ( d instanceof AxisArm ) {
476 this.jets[d.axis.mas().shortCode()] = d.fn;
477 }
478 else {
479 this.jets[nameToAxis[d.name].mas().shortCode()] = d.fn;
480 }
481 }
482 }
483 this.fine = genFine(this);
486function Clue(name, parentAxis, hooks) {
487 this.name = name;
488 this.parentAxis = parentAxis;
489 this.hooks = hooks;
492function JetDriver(label, fn) {
493 this.label = label;
494 this.fn = fn;
497function AxisArm(label, axis, fn) {
498 JetDriver.call(this, label, fn);
499 this.axis = axis;
501AxisArm.prototype = Object.create(JetDriver.prototype);
502AxisArm.prototype.constructor = AxisArm;
504function NamedArm(label, name, fn) {
505 JetDriver.call(this, label, fn);
506 this.name = name;
508NamedArm.prototype = Object.create(JetDriver.prototype);
509NamedArm.prototype.constructor = NamedArm;
511var two = noun.dwim(2);
512function collectFromCore(prefix, spec, out) {
513 var name = spec[0], arms = spec[1], children = spec[2],
514 labl = prefix + "/" + name;
515 if ( arms instanceof Function ) {
516 out[labl] = [new AxisArm(labl, two, arms)];
517 }
518 else {
519 var all = [];
520 for ( var k in arms ) {
521 if ( arms.hasOwnProperty(k) ) {
522 all.push(( 'number' === typeof(k) ) ?
523 new AxisArm(labl, noun.dwim(k), arms[k]) :
524 new NamedArm(labl, k, arms[k]));
525 }
526 }
527 out[labl] = all;
528 }
529 if ( children ) {
530 for ( var i = 0; i < children.length; ++i ) {
531 collectFromCore(labl, children[i], out);
532 }
533 }
536function Context(drivers) {
537 this.memo = new NounMap();
538 this.clues = new NounMap();
539 this.dash = new NounMap();
540 this.tax = noun.Atom.yes;
541 this.drivers = {};
542 if ( drivers ) {
543 collectFromCore('', drivers, this.drivers);
544 }
547Context.prototype.yes = noun.Atom.yes;
548Context.prototype.no = noun.Atom.no;
549Context.prototype.cons = function (h, t) {
550 return new Cell(h, t);
551};
553Context.prototype.trampoline = function(tgt, bus) {
554 return new Trampoline(tgt, bus);
555};
557Context.prototype.isTrampoline = function(a) {
558 return (a instanceof Trampoline);
559};
561Context.prototype.compile = function(cell) {
562 var i = 0;
563 var fresh = function() {
564 return "v" + ++i;
565 };
566 var body = new Block();
567 var constants = [];
568 compile(cell, "subject", "product", fresh, constants, body, true);
569 var text = "return function(subject){" + body.toJs() + "return product;}";
570 var builder = new Function("context", "constants", text);
571 return cell.target = builder(this, constants);
572};
574Context.prototype.nock = function(subject, formula) {
575 var product, target;
576 if ( !formula.hasOwnProperty("target") ) {
577 this.compile(formula);
578 }
579 target = formula.target;
580 while ( true ) {
581 product = target(subject);
582 if ( product instanceof Trampoline ) {
583 subject = product.subject;
584 target = product.target;
585 }
586 else {
587 return product;
588 }
589 }
590};
592Context.prototype.getMemo = function(key) {
593 return this.memo.get(key);
596Context.prototype.putMemo = function(key, val) {
597 this.memo.insert(key, val);
598};
600Context.prototype.stackPush = function(item) {
601 this.tax = new Cell(item, this.tax);
602};
604Context.prototype.stackPop = function() {
605 this.tax = this.tax.tail;
608Context.prototype.slog = function(item) {
609 // TODO: don't rewrite ++wash again, just call the kernel
610 console.log(item);
611};
613function chum(n) {
614 if ( n.deep ) {
615 return Atom.cordToString(n.head) + n.tail.number.shortValue().toString(10);
616 }
617 else {
618 return Atom.cordToString(n);
619 }
622var ten = noun.dwim(10);
623function skipHints(formula) {
624 while ( true ) {
625 if ( formula.deep ) {
626 if ( ten.equals(formula.head) ) {
627 formula = formula.tail.tail;
628 continue;
629 }
630 }
631 return formula;
632 }
635var zero = noun.dwim(0), constant_zero = noun.dwim(1,0);
636function parseParentAxis(noun) {
637 var f = skipHints(noun);
638 if ( constant_zero.equals(f) ) {
639 return zero;
640 }
641 else if ( !zero.equals(f.head) ) {
642 throw new Error("weird formula head");
643 }
644 else if ( 3 != f.tail.cap().valueOf() ) {
645 throw new Error("weird parent axis");
646 }
647 return f.tail;
650var nine = noun.dwim(9), constant_frag = noun.dwim(0,1);
651function parseHookAxis(nock) {
652 var f = skipHints(nock),
653 op = f.head;
654 if ( !op.deep ) {
655 if ( zero.equals(op) ) {
656 if ( !f.tail.deep ) {
657 return f.tail;
658 }
659 }
660 else if ( nine.equals(op) ) {
661 var rest = f.tail;
662 if ( !rest.head.deep && constant_frag.equals(rest.tail) ) {
663 return rest.head;
664 }
665 }
666 }
667 return null;
670function parseHooks(noun) {
671 var o = {};
672 list.forEach(noun, function(c) {
673 var term = Atom.cordToString(c.head),
674 axis = parseHookAxis(c.tail);
675 if ( null != axis ) {
676 o[term] = axis;
677 }
678 });
679 return o;
682Context.prototype.parseClue = function(raw) {
683 var clue = this.clues.get(raw);
684 if ( clue === undefined ) {
685 var name = chum(raw.head),
686 parentAxis = parseParentAxis(raw.tail.head),
687 hooks = parseHooks(raw.tail.tail);
688 clue = new Clue(name, parentAxis, hooks);
689 this.clues.insert(raw, clue);
690 }
691 return clue;
694Context.prototype.register = function(core, raw) {
695 var bat = core.head;
696 var loc = this.dash.get(bat);
697 if ( undefined === loc ) {
698 try {
699 var clue = this.parseClue(raw);
700 if ( zero.equals(clue.parentAxis) ) {
701 loc = new Location(this, clue.name, '/' + clue.name, zero, clue.hooks, core, null);
702 }
703 else {
704 var parentCore = core.at(clue.parentAxis),
705 parentBattery = parentCore.head,
706 parentLoc = this.dash.get(parentBattery);
707 if ( undefined === parentLoc ) {
708 console.log('register: invalid parent for ' + clue.name);
709 }
710 else {
711 var label = parentLoc.label + "/" + clue.name;
712 loc = new Location(this, clue.name, label, clue.parentAxis, clue.hooks, core, parentLoc);
713 }
714 }
715 bat.loc = loc;
716 this.dash.insert(bat, loc);
717 }
718 catch (e) {
719 console.log(e);
720 }
721 }
722};
724module.exports = {
725 Context: Context,