~crc-x/+junk/chrome-extension-logo

« back to all changes in this revision

Viewing changes to logo.js

  • Committer: Charles Childers
  • Date: 2009-12-12 13:45:52 UTC
  • Revision ID: git-v1:38ec3a57c2dbddb37f0d3178cccd579746346400
initial checkin

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
//
 
2
// Logo Interpreter in Javascript
 
3
//
 
4
 
 
5
// Copyright 2009 Joshua Bell
 
6
//
 
7
// Licensed under the Apache License, Version 2.0 (the "License");
 
8
// you may not use this file except in compliance with the License.
 
9
// You may obtain a copy of the License at
 
10
//
 
11
// http://www.apache.org/licenses/LICENSE-2.0
 
12
//
 
13
// Unless required by applicable law or agreed to in writing, software
 
14
// distributed under the License is distributed on an "AS IS" BASIS,
 
15
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 
16
// See the License for the specific language governing permissions and
 
17
// limitations under the License.
 
18
 
 
19
/*jslint browser: true, sub: true, undef: true */
 
20
/*global window */
 
21
 
 
22
// Based on: http://www.jbouchard.net/chris/blog/2008/01/currying-in-javascript-fun-for-whole.html
 
23
if (!Function.prototype.toArity) {
 
24
 
 
25
    Function.prototype.toArity = function(arity) {
 
26
        var func = this;
 
27
        var parms = [];
 
28
 
 
29
        if (func.length === arity) {
 
30
            return func;
 
31
        }
 
32
 
 
33
        for (var i = 0; i < arity; i += 1) {
 
34
            parms.push('a' + i);
 
35
        }
 
36
 
 
37
        /*jslint evil: true */
 
38
        var f = eval('(function (' + parms.join(',') + ') { return func.apply(this, arguments); })');
 
39
        /*jslint evil: false */
 
40
        return f;
 
41
    };
 
42
}
 
43
 
 
44
//----------------------------------------------------------------------
 
45
function LogoInterpreter(turtle, stream)
 
46
//----------------------------------------------------------------------
 
47
{
 
48
    var self = this;
 
49
 
 
50
    var UNARY_MINUS = '<UNARYMINUS>'; // Must not match regexIdentifier
 
51
 
 
52
    //----------------------------------------------------------------------
 
53
    //
 
54
    // Interpreter State
 
55
    //
 
56
    //----------------------------------------------------------------------
 
57
 
 
58
    self.turtle = turtle;
 
59
    self.stream = stream;
 
60
    self.routines = {};
 
61
    self.scopes = [{}];
 
62
 
 
63
 
 
64
    //----------------------------------------------------------------------
 
65
    //
 
66
    // Parsing
 
67
    //
 
68
    //----------------------------------------------------------------------
 
69
 
 
70
    // Used to return values from routines (thrown/caught)
 
71
    function Output(output) { this.output = output; }
 
72
    Output.prototype.toString = function() { return this.output; };
 
73
    Output.prototype.valueOf = function() { return this.output; };
 
74
 
 
75
    // Used to stop processing cleanly
 
76
    function Bye() { }
 
77
 
 
78
 
 
79
    function Type(atom) {
 
80
        if (typeof atom === 'undefined') {
 
81
            throw "Undefined";
 
82
        }
 
83
        else if (typeof atom === 'string') {
 
84
            return 'word';
 
85
        }
 
86
        else if (typeof atom === 'number') {
 
87
            return 'number';
 
88
        }
 
89
        else if (Array.isArray(atom)) {
 
90
            return 'list';
 
91
        }
 
92
        else if (!atom) {
 
93
            throw "Null type!";
 
94
        }
 
95
        else {
 
96
            throw "Unknown type!";
 
97
        }
 
98
    } // Type
 
99
 
 
100
    var regexIdentifier = /^(\.?[A-Za-z][A-Za-z0-9_.\?]*)(.*?)$/;
 
101
    var regexStringLiteral = /^("[^ \[\]\(\)]*)(.*?)$/;
 
102
    var regexVariableLiteral = /^(:[A-Za-z][A-Za-z0-9]*)(.*?)$/;
 
103
    var regexNumberLiteral = /^([0-9]*\.?[0-9]+(?:[eE]\s*[\-+]?\s*[0-9]+)?)(.*?)$/;
 
104
    var regexListDelimiter = /^(\[|\])(.*?)$/;
 
105
    var regexOperator = /^(\+|\-|\*|\/|%|\^|>=|<=|<>|=|<|>|\[|\]|\(|\))(.*?)$/;
 
106
    var regexInfix = /^(\+|\-|\*|\/|%|\^|>=|<=|<>|=|<|>)$/;
 
107
 
 
108
    //
 
109
    // Construct a parse tree
 
110
    //
 
111
    // Input: string
 
112
    // Output: atom list (e.g. "to", "jump", "repeat", "random", 10, [ "fd", 10, "rt" 10 ], "end"
 
113
    //
 
114
    // TODO: Move this into expression parsing; should be '[' operator's job!
 
115
 
 
116
    //----------------------------------------------------------------------
 
117
    function parse(string)
 
118
    //----------------------------------------------------------------------
 
119
    {
 
120
        if (typeof string === 'undefined') {
 
121
            return undefined; // TODO: Replace this with ...?
 
122
        }
 
123
 
 
124
        var atoms = [];
 
125
        var prev;
 
126
 
 
127
        // Filter out comments
 
128
        string = string.replace(/;.*\n/g, '');
 
129
 
 
130
        // Treat newlines as whitespace (so \s will match)
 
131
        string = string.replace(/\r/g, '').replace(/\n/g, ' ');
 
132
        string = string.replace(/^\s+/, '').replace(/\s+$/, '');
 
133
 
 
134
        while (typeof string !== 'undefined' && string !== '') {
 
135
            var atom;
 
136
 
 
137
            // Ignore (but track) leading space - needed for unary minus disambiguation
 
138
            var leading_space = /^\s+/.test(string);
 
139
            string = string.replace(/^\s+/, '');
 
140
 
 
141
            if (string.match(regexIdentifier) ||
 
142
                string.match(regexStringLiteral) ||
 
143
                string.match(regexVariableLiteral)) {
 
144
                // Word
 
145
                atom = RegExp.$1;
 
146
                string = RegExp.$2;
 
147
            }
 
148
            else if (string.match(regexNumberLiteral)) {
 
149
                // Number literal
 
150
                atom = RegExp.$1;
 
151
                string = RegExp.$2;
 
152
 
 
153
                // The following dirties RegExp.$n so it is kept separate
 
154
                atom = parseFloat(atom.replace(/\s+/g, ''), 10);
 
155
            }
 
156
            else if (string.match(regexListDelimiter)) {
 
157
 
 
158
                if (RegExp.$1 === '[') {
 
159
                    // Start of list - recurse!
 
160
                    var r = parse(RegExp.$2);
 
161
                    if (!r.list) { throw "Expected end-of-list"; }
 
162
                    atom = r.list;
 
163
                    string = r.string;
 
164
                }
 
165
                else { // (RegExp.$1 === ']')
 
166
                    // End of list - return list and the remaining input
 
167
                    return { list: atoms, string: RegExp.$2 };
 
168
                }
 
169
 
 
170
            }
 
171
            else if (string.match(regexOperator)) {
 
172
 
 
173
                atom = RegExp.$1;
 
174
                string = RegExp.$2;
 
175
 
 
176
                // From UCB Logo:
 
177
 
 
178
                // Minus sign means infix difference in ambiguous contexts
 
179
                // (when preceded by a complete expression), unless it is
 
180
                // preceded by a space and followed by a nonspace.
 
181
 
 
182
                // Minus sign means unary minus if the previous token is an
 
183
                // infix operator or open parenthesis, or it is preceded by
 
184
                // a space and followed by a nonspace.
 
185
 
 
186
                if (atom === '-') {
 
187
 
 
188
                    var trailing_space = /^\s+/.test(string);
 
189
 
 
190
                    if (typeof prev === 'undefined' ||
 
191
                            (Type(prev) === 'word' && regexInfix.test(prev)) ||
 
192
                            (Type(prev) === 'word' && prev === '(') ||
 
193
                            (leading_space && !trailing_space)
 
194
                           ) {
 
195
                        atom = UNARY_MINUS;
 
196
                    }
 
197
 
 
198
                }
 
199
            }
 
200
            else {
 
201
                throw "Couldn't parse: '" + string + "'";
 
202
            }
 
203
 
 
204
 
 
205
            atoms.push(atom);
 
206
            prev = atom;
 
207
        }
 
208
 
 
209
        return atoms;
 
210
 
 
211
    } // parse
 
212
 
 
213
 
 
214
    //----------------------------------------------------------------------
 
215
    self.getvar = function(name)
 
216
    //----------------------------------------------------------------------
 
217
    {
 
218
        name = name.toLowerCase();
 
219
 
 
220
        for (var i = 0; i < self.scopes.length; ++i) {
 
221
            if (self.scopes[i].hasOwnProperty(name)) {
 
222
                return self.scopes[i][name];
 
223
            }
 
224
        }
 
225
        throw "Don't know about variable " + name;
 
226
    };
 
227
 
 
228
    //----------------------------------------------------------------------
 
229
    self.setvar = function(name, value)
 
230
    //----------------------------------------------------------------------
 
231
    {
 
232
        name = name.toLowerCase();
 
233
 
 
234
        // Find the variable in existing scope
 
235
        for (var i = 0; i < self.scopes.length; ++i) {
 
236
            if (self.scopes[i].hasOwnProperty(name)) {
 
237
                self.scopes[i][name] = value;
 
238
                return;
 
239
            }
 
240
        }
 
241
 
 
242
        // Otherwise, define a global
 
243
        self.scopes[self.scopes.length - 1][name] = value;
 
244
    };
 
245
 
 
246
    //----------------------------------------------------------------------
 
247
    //
 
248
    // Expression Evaluation
 
249
    //
 
250
    //----------------------------------------------------------------------
 
251
 
 
252
    // Expression               := RelationalExpression
 
253
    // RelationalExpression     := AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '>=' | '<>' ) AdditiveExpression ... ]
 
254
    // AdditiveExpression       := MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]
 
255
    // MultiplicativeExpression := PowerExpression [ ( '*' | '/' | '%' ) PowerExpression ... ]
 
256
    // PowerExpression          := UnaryExpression [ '^' UnaryExpression ]
 
257
    // UnaryExpression          := ( '-' ) UnaryExpression
 
258
    //                           | FinalExpression
 
259
    // FinalExpression          := string-literal
 
260
    //                           | number-literal
 
261
    //                           | list
 
262
    //                           | variable-reference
 
263
    //                           | procedure-call
 
264
    //                           | '(' Expression ')'
 
265
 
 
266
    //----------------------------------------------------------------------
 
267
    // Peek at the list to see if there are additional atoms from a set 
 
268
    // of options. 
 
269
    //----------------------------------------------------------------------
 
270
    function peek(list, options)
 
271
    //----------------------------------------------------------------------
 
272
    {
 
273
        if (list.length < 1) { return false; }
 
274
        var next = list[0];
 
275
        return options.some(function(x) { return next === x; });
 
276
 
 
277
    } // peek
 
278
 
 
279
    //----------------------------------------------------------------------
 
280
    self.evaluateExpression = function(list)
 
281
    //----------------------------------------------------------------------
 
282
    {
 
283
        return (self.expression(list))();
 
284
 
 
285
    };
 
286
 
 
287
    //----------------------------------------------------------------------
 
288
    self.expression = function(list)
 
289
    //----------------------------------------------------------------------
 
290
    {
 
291
        return self.relationalExpression(list);
 
292
 
 
293
    };
 
294
 
 
295
    //----------------------------------------------------------------------
 
296
    self.relationalExpression = function(list)
 
297
    //----------------------------------------------------------------------
 
298
    {
 
299
        var lhs = self.additiveExpression(list);
 
300
        var op;
 
301
        while (peek(list, ['=', '<', '>', '<=', '>=', '<>'])) {
 
302
            op = list.shift();
 
303
 
 
304
            lhs = function(lhs) {
 
305
                var rhs = self.additiveExpression(list);
 
306
 
 
307
                switch (op) {
 
308
                    case "<": return function() { return (aexpr(lhs()) < aexpr(rhs())) ? 1 : 0; };
 
309
                    case ">": return function() { return (aexpr(lhs()) > aexpr(rhs())) ? 1 : 0; };
 
310
                    case "=": return function() { return self.equal(lhs(), rhs()) ? 1 : 0; };
 
311
 
 
312
                    case "<=": return function() { return (aexpr(lhs()) <= aexpr(rhs())) ? 1 : 0; };
 
313
                    case ">=": return function() { return (aexpr(lhs()) >= aexpr(rhs())) ? 1 : 0; };
 
314
                    case "<>": return function() { return !self.equal(lhs(), rhs()) ? 1 : 0; };
 
315
                }
 
316
            } (lhs);
 
317
        }
 
318
 
 
319
        return lhs;
 
320
    };
 
321
 
 
322
    //----------------------------------------------------------------------
 
323
    self.additiveExpression = function(list)
 
324
    //----------------------------------------------------------------------
 
325
    {
 
326
        var lhs = self.multiplicativeExpression(list);
 
327
        var op;
 
328
        while (peek(list, ['+', '-'])) {
 
329
            op = list.shift();
 
330
 
 
331
            lhs = function(lhs) {
 
332
                var rhs = self.multiplicativeExpression(list);
 
333
                switch (op) {
 
334
                    case "+": return function() { return aexpr(lhs()) + aexpr(rhs()); };
 
335
                    case "-": return function() { return aexpr(lhs()) - aexpr(rhs()); };
 
336
                }
 
337
            } (lhs);
 
338
        }
 
339
 
 
340
        return lhs;
 
341
    };
 
342
 
 
343
    //----------------------------------------------------------------------
 
344
    self.multiplicativeExpression = function(list)
 
345
    //----------------------------------------------------------------------
 
346
    {
 
347
        var lhs = self.powerExpression(list);
 
348
        var op;
 
349
        while (peek(list, ['*', '/', '%'])) {
 
350
            op = list.shift();
 
351
 
 
352
            lhs = function(lhs) {
 
353
                var rhs = self.powerExpression(list);
 
354
                switch (op) {
 
355
                    case "*": return function() { return aexpr(lhs()) * aexpr(rhs()); };
 
356
                    case "/": return function() {
 
357
                        var n = aexpr(lhs()), d = aexpr(rhs());
 
358
                        if (d === 0) { throw "Division by zero"; }
 
359
                        return n / d;
 
360
                    };
 
361
                    case "%": return function() {
 
362
                        var n = aexpr(lhs()), d = aexpr(rhs());
 
363
                        if (d === 0) { throw "Division by zero"; }
 
364
                        return n % d;
 
365
                    };
 
366
                }
 
367
            } (lhs);
 
368
        }
 
369
 
 
370
        return lhs;
 
371
    };
 
372
 
 
373
    //----------------------------------------------------------------------
 
374
    self.powerExpression = function(list)
 
375
    //----------------------------------------------------------------------
 
376
    {
 
377
        var lhs = self.unaryExpression(list);
 
378
        var op;
 
379
        while (peek(list, ['^'])) {
 
380
            op = list.shift();
 
381
            lhs = function(lhs) {
 
382
                var rhs = self.unaryExpression(list);
 
383
                return function() { return Math.pow(aexpr(lhs()), aexpr(rhs())); };
 
384
            } (lhs);
 
385
        }
 
386
 
 
387
        return lhs;
 
388
    };
 
389
 
 
390
    //----------------------------------------------------------------------
 
391
    self.unaryExpression = function(list)
 
392
    //----------------------------------------------------------------------
 
393
    {
 
394
        var rhs, op;
 
395
 
 
396
        if (peek(list, [UNARY_MINUS])) {
 
397
            op = list.shift();
 
398
            rhs = self.unaryExpression(list);
 
399
            return function() { return -aexpr(rhs()); };
 
400
        }
 
401
        else {
 
402
            return self.finalExpression(list);
 
403
        }
 
404
    };
 
405
 
 
406
 
 
407
    //----------------------------------------------------------------------
 
408
    self.finalExpression = function(list)
 
409
    //----------------------------------------------------------------------
 
410
    {
 
411
        if (!list.length) {
 
412
            throw "Unexpected end of instructions";
 
413
        }
 
414
 
 
415
        var atom = list.shift();
 
416
 
 
417
        var args, i, routine, result;
 
418
        var literal, varname;
 
419
 
 
420
        switch (Type(atom)) {
 
421
            case 'number':
 
422
            case 'list':
 
423
                return function() { return atom; };
 
424
 
 
425
            case 'word':
 
426
                if (atom.charAt(0) === '"') {
 
427
                    // string literal
 
428
                    literal = atom.substring(1);
 
429
                    return function() { return literal; };
 
430
                }
 
431
                else if (atom.charAt(0) === ':') {
 
432
                    // variable
 
433
                    varname = atom.substring(1);
 
434
                    return function() { return self.getvar(varname); };
 
435
                }
 
436
                else if (atom === '(') {
 
437
                    // parenthesized expression/procedure call
 
438
                    if (list.length && Type(list[0]) === 'word' && 
 
439
                        self.routines[list[0].toString().toLowerCase()]) {
 
440
 
 
441
                        // Lisp-style (procedure input ...) calling syntax
 
442
                        atom = list.shift();
 
443
                        return self.dispatch(atom, list, false);
 
444
                    }
 
445
                    else {
 
446
                        // Standard parenthesized expression
 
447
                        result = self.expression(list);
 
448
 
 
449
                        if (!peek(list, [')'])) {
 
450
                            throw "Expected ')', saw " + list.shift();
 
451
                        }
 
452
                        list.shift();
 
453
                        return result;
 
454
                    }
 
455
                }
 
456
                else {
 
457
                    // Procedure dispatch
 
458
                    return self.dispatch(atom, list, true);
 
459
                }
 
460
                break;
 
461
 
 
462
            default:
 
463
                throw "Unexpected: " + atom;
 
464
        }
 
465
    };
 
466
 
 
467
    //----------------------------------------------------------------------
 
468
    self.dispatch = function(name, tokenlist, natural)
 
469
    //----------------------------------------------------------------------
 
470
    {
 
471
        var procedure = self.routines[name.toLowerCase()];
 
472
        if (!procedure) { throw "Don't know how to " + name; }
 
473
 
 
474
        if (procedure.special) {
 
475
            // Special routines are built-ins that get handed the token list:
 
476
            // * workspace modifiers like TO that special-case varnames
 
477
            procedure(tokenlist);
 
478
            return function() { };
 
479
        }
 
480
 
 
481
        var args = [];
 
482
        if (natural) {
 
483
            // Natural arity of the function
 
484
            for (var i = 0; i < procedure.length; ++i) {
 
485
                args.push(self.expression(tokenlist));
 
486
            }
 
487
        }
 
488
        else {
 
489
            // Caller specified argument count
 
490
            while (tokenlist.length && !peek(tokenlist, [')'])) {
 
491
                args.push(self.expression(tokenlist));
 
492
            }
 
493
            tokenlist.shift(); // Consume ')'
 
494
        }
 
495
 
 
496
        if (procedure.noeval) {
 
497
            return function() {
 
498
                return procedure.apply(procedure, args);
 
499
            };
 
500
        }
 
501
        else {
 
502
            return function() {
 
503
                return procedure.apply(procedure, args.map(function(a) { return a(); }));
 
504
            };
 
505
        }
 
506
    };
 
507
 
 
508
    //----------------------------------------------------------------------
 
509
    // Arithmetic expression convenience function
 
510
    //----------------------------------------------------------------------
 
511
    function aexpr(atom)
 
512
    //----------------------------------------------------------------------
 
513
    {
 
514
        if (Type(atom) === 'number') { return atom; }
 
515
        if (Type(atom) === 'word') { return parseFloat(atom); } // coerce
 
516
 
 
517
        throw "Expected number";
 
518
    }
 
519
 
 
520
    //----------------------------------------------------------------------
 
521
    // String expression convenience function
 
522
    //----------------------------------------------------------------------
 
523
    function sexpr(atom)
 
524
    //----------------------------------------------------------------------
 
525
    {
 
526
        if (Type(atom) === 'word') { return atom; }
 
527
        if (Type(atom) === 'number') { return atom.toString(); } // coerce
 
528
 
 
529
        throw "Expected string";
 
530
    }
 
531
 
 
532
    //----------------------------------------------------------------------
 
533
    // List expression convenience function
 
534
    //----------------------------------------------------------------------
 
535
    function lexpr(atom) {
 
536
        // TODO: If this is an input, output needs to be re-stringified
 
537
        if (Type(atom) === 'number') { return Array.prototype.map.call(atom.toString(), function(x) { return x; }); }
 
538
        if (Type(atom) === 'word') { return Array.prototype.map.call(atom, function(x) { return x; }); }
 
539
        if (Type(atom) === 'list') { return atom; }
 
540
 
 
541
        throw "Expected list";
 
542
    }
 
543
 
 
544
    //----------------------------------------------------------------------
 
545
    // Deep compare of values (numbers, strings, lists)
 
546
    // (with optional epsilon compare for numbers)
 
547
    //----------------------------------------------------------------------
 
548
    self.equal = function(a, b, epsilon)
 
549
    //----------------------------------------------------------------------
 
550
    {
 
551
        if (Array.isArray(a)) {
 
552
            if (!Array.isArray(b)) {
 
553
                return false;
 
554
            }
 
555
            if (a.length !== b.length) {
 
556
                return false;
 
557
            }
 
558
            for (var i = 0; i < a.length; i += 1) {
 
559
                if (!self.equal(a[i], b[i])) {
 
560
                    return false;
 
561
                }
 
562
            }
 
563
            return true;
 
564
        }
 
565
        else if (typeof a !== typeof b) {
 
566
            return false;
 
567
        }
 
568
        else if (typeof epsilon !== 'undefined' && typeof a === 'number') {
 
569
            return Math.abs(a - b) < epsilon;
 
570
        }
 
571
        else {
 
572
            return a === b;
 
573
        }
 
574
    };
 
575
 
 
576
    //----------------------------------------------------------------------
 
577
    //
 
578
    // Execute a script
 
579
    //
 
580
    //----------------------------------------------------------------------
 
581
 
 
582
    //----------------------------------------------------------------------
 
583
    // Execute a sequence of statements
 
584
    //----------------------------------------------------------------------
 
585
    self.execute = function(statements)
 
586
    //----------------------------------------------------------------------
 
587
    {
 
588
        // Operate on a copy so the original is not destroyed
 
589
        statements = statements.slice();
 
590
 
 
591
        var result;
 
592
        while (statements.length) {
 
593
            result = self.evaluateExpression(statements);
 
594
        }
 
595
 
 
596
        // Return last result
 
597
        return result;
 
598
 
 
599
    };
 
600
 
 
601
 
 
602
    //----------------------------------------------------------------------
 
603
    self.run = function(string)
 
604
    //----------------------------------------------------------------------
 
605
    {
 
606
        if (self.turtle) { self.turtle.begin(); }
 
607
 
 
608
        try {
 
609
            // Parse it
 
610
            var atoms = parse(string);
 
611
 
 
612
            // And execute it!
 
613
            return self.execute(atoms);
 
614
        }
 
615
        catch (e) {
 
616
            if (e instanceof Bye) {
 
617
                // clean exit
 
618
                return;
 
619
            }
 
620
            else {
 
621
                throw e;
 
622
            }
 
623
        }
 
624
        finally {
 
625
            if (self.turtle) { self.turtle.end(); }
 
626
        }
 
627
    };
 
628
 
 
629
 
 
630
    //----------------------------------------------------------------------
 
631
    //
 
632
    // Built-In Proceedures
 
633
    //
 
634
    //----------------------------------------------------------------------
 
635
 
 
636
    // Basic form:
 
637
    //
 
638
    //  self.routines["procname"] = function(input1, input2, ...) { ... return output; }
 
639
    //   * inputs are JavaScript strings, numbers, or Arrays
 
640
    //   * output is string, number, Array or undefined/no output
 
641
    //
 
642
    // Special forms:
 
643
    //
 
644
    //   self.routines["procname"] = function(tokenlist) { ... }
 
645
    //   self.routines["procname"].special = true
 
646
    //    * input is Array (list) of tokens (words, numbers, Arrays)
 
647
    //    * used for implementation of special forms (e.g. TO inputs... statements... END)
 
648
    //
 
649
    //   self.routines["procname"] = function(finput1, finput2, ...) { ... return output; }
 
650
    //   self.routines["procname"].noeval = true
 
651
    //    * inputs are arity-0 functions that evaluate to string, number Array
 
652
    //    * used for short-circuiting evaluation (AND, OR)
 
653
    //    * used for repeat evaluation (DO.WHILE, WHILE, DO.UNTIL, UNTIL)
 
654
    //
 
655
 
 
656
 
 
657
    function mapreduce(list, mapfunc, reducefunc, initial) {
 
658
        // NOTE: Uses Array.XXX format to handle array-like types: arguments and strings
 
659
        if (typeof initial === 'undefined') {
 
660
            return Array.prototype.reduce.call(Array.prototype.map.call(list, mapfunc), reducefunc);
 
661
        }
 
662
        else {
 
663
            return Array.prototype.reduce.call(Array.prototype.map.call(list, mapfunc), reducefunc, initial);
 
664
        }
 
665
    }
 
666
 
 
667
    function stringify(thing) {
 
668
 
 
669
        if (Type(thing) === 'list') {
 
670
            return "[ " + thing.map(stringify).join(" ") + " ]";
 
671
        }
 
672
        else {
 
673
            return sexpr(thing);
 
674
        }
 
675
    }
 
676
 
 
677
    function stringify_nodecorate(thing) {
 
678
 
 
679
        if (Type(thing) === 'list') {
 
680
            return thing.map(stringify).join(" ");
 
681
        }
 
682
        else {
 
683
            return stringify(thing);
 
684
        }
 
685
    }
 
686
 
 
687
    //
 
688
    // Procedures and Flow Control
 
689
    //
 
690
    self.routines["to"] = function(list) {
 
691
        var name = list.shift();
 
692
        if (!name.match(regexIdentifier)) {
 
693
            throw "Expected identifier";
 
694
        }
 
695
        name = name.toLowerCase();
 
696
 
 
697
        var inputs = [];
 
698
        var block = [];
 
699
 
 
700
        // Process inputs, then the statements of the block
 
701
        var state_inputs = true;
 
702
        while (list.length) {
 
703
            var atom = list.shift();
 
704
            if (Type(atom) === 'word' && atom === 'end') {
 
705
                break;
 
706
            }
 
707
            else if (state_inputs && Type(atom) === 'word' && atom.charAt(0) === ':') {
 
708
                inputs.push(atom.substring(1));
 
709
            }
 
710
            else {
 
711
                state_inputs = false;
 
712
                block.push(atom);
 
713
            }
 
714
        }
 
715
 
 
716
        // Closure over inputs and block to handle scopes, arguments and outputs
 
717
        var func = function() {
 
718
 
 
719
            // Define a new scope
 
720
            var scope = {};
 
721
            for (var i = 0; i < inputs.length && i < arguments.length; i += 1) {
 
722
                scope[inputs[i]] = arguments[i];
 
723
            }
 
724
            self.scopes.unshift(scope);
 
725
 
 
726
            try {
 
727
                // Execute the block
 
728
                try {
 
729
                    return self.execute(block);
 
730
                }
 
731
                catch (e) {
 
732
                    // From OUTPUT
 
733
                    if (e instanceof Output) {
 
734
                        return e.output;
 
735
                    }
 
736
                    else {
 
737
                        throw e;
 
738
                    }
 
739
                }
 
740
            }
 
741
            finally {
 
742
                // Close the scope
 
743
                self.scopes.shift();
 
744
            }
 
745
        };
 
746
 
 
747
        self.routines[name] = func.toArity(inputs.length);
 
748
 
 
749
        // For DEF de-serialization
 
750
        self.routines[name].inputs = inputs;
 
751
        self.routines[name].block = block;
 
752
    };
 
753
    self.routines["to"].special = true;
 
754
 
 
755
    self.routines["def"] = function(list) {
 
756
 
 
757
        function defn(atom) {
 
758
            switch (Type(atom)) {
 
759
                case 'word': return atom;
 
760
                case 'number': return atom.toString();
 
761
                case 'list': return '[ ' + atom.map(defn).join(' ') + ' ]';
 
762
            }
 
763
        }
 
764
 
 
765
        var name = sexpr(list);
 
766
        var proc = self.routines[name.toLowerCase()];
 
767
        if (!proc) { throw "Don't know how to " + name; }
 
768
        if (!proc.inputs) { throw "Can't show definition of intrinsic " + name; }
 
769
 
 
770
        var def = "to " + name + " ";
 
771
        if (proc.inputs.length) {
 
772
            def += proc.inputs.map(function(a) { return ":" + a; }).join(" ");
 
773
            def += " ";
 
774
        }
 
775
        def += proc.block.map(defn).join(" ").replace(new RegExp(UNARY_MINUS + ' ', 'g'), '-');
 
776
        def += " end";
 
777
 
 
778
        return def;
 
779
    };
 
780
 
 
781
 
 
782
    //----------------------------------------------------------------------
 
783
    //
 
784
    // 2. Data Structure Primitives
 
785
    //
 
786
    //----------------------------------------------------------------------
 
787
 
 
788
    //
 
789
    // 2.1 Constructors
 
790
    //
 
791
 
 
792
    self.routines["word"] = function(word1, word2) {
 
793
        return arguments.length ? mapreduce(arguments, sexpr, function(a, b) { return a + " " + b; }) : "";
 
794
    };
 
795
 
 
796
    self.routines["list"] = function(thing1, thing2) {
 
797
        return Array.prototype.map.call(arguments, function(x) { return x; }); // Make a copy
 
798
    };
 
799
 
 
800
    self.routines["sentence"] = self.routines["se"] = function(thing1, thing2) {
 
801
        var list = [];
 
802
        for (var i = 0; i < arguments.length; i += 1) {
 
803
            var thing = arguments[i];
 
804
            if (Type(thing) === 'list') {
 
805
                thing = lexpr(thing);
 
806
                list = list.concat(thing);
 
807
            }
 
808
            else {
 
809
                list.push(thing);
 
810
            }
 
811
        }
 
812
        return list;
 
813
    };
 
814
 
 
815
    self.routines["fput"] = function(thing, list) { list = lexpr(list); list.unshift(thing); return list; };
 
816
 
 
817
    self.routines["lput"] = function(thing, list) { list = lexpr(list); list.push(thing); return list; };
 
818
 
 
819
    // Not Supported: array
 
820
    // Not Supported: mdarray
 
821
    // Not Supported: listtoarray
 
822
    // Not Supported: arraytolist
 
823
 
 
824
    self.routines["combine"] = function(thing1, thing2) {
 
825
        if (Type(thing2) !== 'list') {
 
826
            return self.routines['word'](thing1, thing2);
 
827
        }
 
828
        else {
 
829
            return self.routines['fput'](thing1, thing2);
 
830
        }
 
831
    };
 
832
 
 
833
    self.routines["reverse"] = function(list) { return lexpr(list).slice().reverse(); };
 
834
 
 
835
    var gensym_index = 0;
 
836
    self.routines["gensym"] = function() {
 
837
        gensym_index += 1;
 
838
        return 'G' + gensym_index;
 
839
    };
 
840
 
 
841
    //
 
842
    // 2.2 Data Selectors
 
843
    //
 
844
 
 
845
    self.routines["first"] = function(list) { return lexpr(list)[0]; };
 
846
 
 
847
    self.routines["firsts"] = function(list) {
 
848
        return lexpr(list).map(function(x) { return x[0]; });
 
849
    };
 
850
 
 
851
    self.routines["last"] = function(list) { list = lexpr(list); return list[list.length - 1]; };
 
852
 
 
853
    self.routines["butfirst"] = self.routines["bf"] = function(list) { return lexpr(list).slice(1); };
 
854
 
 
855
    self.routines["butfirsts"] = self.routines["bfs"] = function(list) {
 
856
        return lexpr(list).map(function(x) { return lexpr(x).slice(1); });
 
857
    };
 
858
 
 
859
    self.routines["butlast"] = self.routines["bl"] = function(list) { return lexpr(list).slice(0, -1); };
 
860
 
 
861
    self.routines["item"] = function(index, list) {
 
862
        index = aexpr(index);
 
863
        if (index < 1 || index > list.length) {
 
864
            throw "index out of bounds";
 
865
        }
 
866
        return lexpr(list)[index - 1];
 
867
    };
 
868
 
 
869
    // Not Supported: mditem
 
870
 
 
871
    self.routines["pick"] = function(list) {
 
872
        list = lexpr(list);
 
873
        var i = Math.floor(Math.random() * list.length);
 
874
        return list[i];
 
875
    };
 
876
 
 
877
    self.routines["remove"] = function(thing, list) {
 
878
        return lexpr(list).filter(function(x) { return x !== thing; });
 
879
    };
 
880
 
 
881
    self.routines["remdup"] = function(list) {
 
882
        var dict = {};
 
883
        return lexpr(list).filter(function(x) { if (!dict[x]) { dict[x] = true; return true; } else { return false; } });
 
884
    };
 
885
 
 
886
    // TODO: quoted
 
887
 
 
888
    //
 
889
    // 2.3 Data Mutators
 
890
    //
 
891
 
 
892
    // Not Supported: setitem
 
893
    // Not Supported: mdsetitem
 
894
    // Not Supported: .setfirst
 
895
    // Not Supported: .setbf
 
896
    // Not Supported: .setitem
 
897
 
 
898
    self.routines["push"] = function(stackname, thing) {
 
899
        var stack = lexpr(self.getvar(stackname));
 
900
        stack.unshift(thing);
 
901
        self.setvar(stackname, stack);
 
902
    };
 
903
 
 
904
    self.routines["pop"] = function(stackname) {
 
905
        return self.getvar(stackname).shift();
 
906
    };
 
907
 
 
908
    self.routines["queue"] = function(stackname, thing) {
 
909
        var stack = lexpr(self.getvar(stackname));
 
910
        stack.push(thing);
 
911
        self.setvar(stackname, stack);
 
912
    };
 
913
 
 
914
    // NOTE: Same as "pop" (!?!)
 
915
    self.routines["dequeue"] = function(stackname) {
 
916
        return self.getvar(stackname).shift();
 
917
    };
 
918
 
 
919
    //
 
920
    // 2.4 Predicates
 
921
    //
 
922
 
 
923
 
 
924
    self.routines["wordp"] = self.routines["word?"] = function(thing) { return Type(thing) === 'word' ? 1 : 0; };
 
925
    self.routines["listp"] = self.routines["list?"] = function(thing) { return Type(thing) === 'list' ? 1 : 0; };
 
926
    // Not Supported: arrayp
 
927
    self.routines["numberp"] = self.routines["number?"] = function(thing) { return Type(thing) === 'number' ? 1 : 0; };
 
928
    self.routines["numberwang"] = function(thing) { return Math.random() < 0.5 ? 1 : 0; };
 
929
 
 
930
    self.routines["equalp"] = self.routines["equal?"] = function(a, b) { return self.equal(a, b) ? 1 : 0; };
 
931
    self.routines["notequalp"] = self.routines["notequal?"] = function(a, b) { return !self.equal(a, b) ? 1 : 0; };
 
932
 
 
933
    self.routines["emptyp"] = self.routines["empty?"] = function(thing) { return lexpr(thing).length === 0 ? 1 : 0; };
 
934
    self.routines["beforep"] = self.routines["before?"] = function(word1, word2) { return sexpr(word1) < sexpr(word2) ? 1 : 0; };
 
935
 
 
936
    // Not Supported: .eq
 
937
    // Not Supported: vbarredp
 
938
 
 
939
    self.routines["memberp"] = self.routines["member?"] =
 
940
        function(thing, list) {
 
941
            return lexpr(list).some(function(x) { return self.equal(x, thing); }) ? 1 : 0;
 
942
        };
 
943
 
 
944
 
 
945
    self.routines["substringp"] = self.routines["substring?"] =
 
946
        function(word1, word2) {
 
947
            return sexpr(word2).indexOf(sexpr(word1)) !== -1 ? 1 : 0;
 
948
        };
 
949
 
 
950
    //
 
951
    // 2.5 Queries
 
952
    //
 
953
 
 
954
    self.routines["count"] = function(thing) { return lexpr(thing).length; };
 
955
    self.routines["ascii"] = function(chr) { return sexpr(chr).charCodeAt(0); };
 
956
    // Not Supported: rawascii
 
957
    self.routines["char"] = function(integer) { return String.fromCharCode(aexpr(integer)); };
 
958
    self.routines["lowercase"] = function(word) { return sexpr(word).toLowerCase(); };
 
959
    self.routines["uppercase"] = function(word) { return sexpr(word).toUpperCase(); };
 
960
    self.routines["standout"] = function(word) { return sexpr(word); }; // For compat
 
961
    // Not Supported: parse
 
962
    // Not Supported: runparse
 
963
 
 
964
    //----------------------------------------------------------------------
 
965
    //
 
966
    // 3. Communication
 
967
    //
 
968
    //----------------------------------------------------------------------
 
969
 
 
970
    // 3.1 Transmitters
 
971
 
 
972
    self.routines["print"] = self.routines["pr"] = function(thing) {
 
973
        var s = Array.prototype.map.call(arguments, stringify_nodecorate).join(" ");
 
974
        self.stream.write(s, "\n");
 
975
        return s;
 
976
    };
 
977
    self.routines["type"] = function(thing) {
 
978
        var s = Array.prototype.map.call(arguments, stringify_nodecorate).join("");
 
979
        self.stream.write(s);
 
980
        return s;
 
981
    };
 
982
    self.routines["show"] = function(thing) {
 
983
        var s = Array.prototype.map.call(arguments, stringify).join(" ");
 
984
        self.stream.write(s, "\n");
 
985
        return s;
 
986
    };
 
987
 
 
988
    // 3.2 Receivers
 
989
 
 
990
    // Not Supported: readlist
 
991
 
 
992
    self.routines["readword"] = function() {
 
993
        if (arguments.length > 0) {
 
994
            return stream.read(sexpr(arguments[0]));
 
995
        }
 
996
        else {
 
997
            return stream.read();
 
998
        }
 
999
    };
 
1000
 
 
1001
 
 
1002
    // Not Supported: readrawline
 
1003
    // Not Supported: readchar
 
1004
    // Not Supported: readchars
 
1005
    // Not Supported: shell
 
1006
 
 
1007
    // 3.3 File Access
 
1008
 
 
1009
    // Not Supported: setprefix
 
1010
    // Not Supported: prefix
 
1011
    // Not Supported: openread
 
1012
    // Not Supported: openwrite
 
1013
    // Not Supported: openappend
 
1014
    // Not Supported: openupdate
 
1015
    // Not Supported: close
 
1016
    // Not Supported: allopen
 
1017
    // Not Supported: closeall
 
1018
    // Not Supported: erasefile
 
1019
    // Not Supported: dribble
 
1020
    // Not Supported: nodribble
 
1021
    // Not Supported: setread
 
1022
    // Not Supported: setwrite
 
1023
    // Not Supported: reader
 
1024
    // Not Supported: writer
 
1025
    // Not Supported: setreadpos
 
1026
    // Not Supported: setwritepos
 
1027
    // Not Supported: readpos
 
1028
    // Not Supported: writepos
 
1029
    // Not Supported: eofp
 
1030
    // Not Supported: filep
 
1031
 
 
1032
    // 3.4 Terminal Access
 
1033
 
 
1034
    // Not Supported: keyp
 
1035
 
 
1036
    self.routines["cleartext"] = self.routines["ct"] = function() {
 
1037
        self.stream.clear();
 
1038
    };
 
1039
 
 
1040
    // Not Supported: setcursor
 
1041
    // Not Supported: cursor
 
1042
    // Not Supported: setmargins
 
1043
    // Not Supported: settextcolor
 
1044
    // Not Supported: increasefont
 
1045
    // Not Supported: settextsize
 
1046
    // Not Supported: textsize
 
1047
    // Not Supported: setfont
 
1048
    // Not Supported: font
 
1049
 
 
1050
    //----------------------------------------------------------------------
 
1051
    //
 
1052
    // 4. Arithmetic
 
1053
    //
 
1054
    //----------------------------------------------------------------------
 
1055
    // 4.1 Numeric Operations
 
1056
 
 
1057
 
 
1058
    self.routines["sum"] = function(a, b) {
 
1059
        return mapreduce(arguments, aexpr, function(a, b) { return a + b; }, 0);
 
1060
    };
 
1061
 
 
1062
    self.routines["difference"] = function(a, b) {
 
1063
        return aexpr(a) - aexpr(b);
 
1064
    };
 
1065
 
 
1066
    self.routines["minus"] = function(a) { return -aexpr(a); };
 
1067
 
 
1068
    self.routines["product"] = function(a, b) {
 
1069
        return mapreduce(arguments, aexpr, function(a, b) { return a * b; }, 1);
 
1070
    };
 
1071
 
 
1072
    self.routines["quotient"] = function(a, b) {
 
1073
        if (typeof b !== 'undefined') {
 
1074
            return aexpr(a) / aexpr(b);
 
1075
        }
 
1076
        else {
 
1077
            return 1 / aexpr(a);
 
1078
        }
 
1079
    };
 
1080
 
 
1081
    self.routines["remainder"] = function(num1, num2) {
 
1082
        return aexpr(num1) % aexpr(num2);
 
1083
    };
 
1084
    self.routines["modulo"] = function(num1, num2) {
 
1085
        num1 = aexpr(num1);
 
1086
        num2 = aexpr(num2);
 
1087
        return Math.abs(num1 % num2) * (num2 < 0 ? -1 : 1);
 
1088
    };
 
1089
 
 
1090
    self.routines["power"] = function(a, b) { return Math.pow(aexpr(a), aexpr(b)); };
 
1091
    self.routines["sqrt"] = function(a) { return Math.sqrt(aexpr(a)); };
 
1092
    self.routines["exp"] = function(a) { return Math.exp(aexpr(a)); };
 
1093
    self.routines["log10"] = function(a) { return Math.log(aexpr(a)) / Math.LN10; };
 
1094
    self.routines["ln"] = function(a) { return Math.log(aexpr(a)); };
 
1095
 
 
1096
 
 
1097
    function deg2rad(d) { return d / 180 * Math.PI; }
 
1098
    function rad2deg(r) { return r * 180 / Math.PI; }
 
1099
 
 
1100
    self.routines["arctan"] = function(a) {
 
1101
        if (arguments.length > 1) {
 
1102
            var x = aexpr(arguments[0]);
 
1103
            var y = aexpr(arguments[1]);
 
1104
            return rad2deg(Math.atan2(y, x));
 
1105
        }
 
1106
        else {
 
1107
            return rad2deg(Math.atan(aexpr(a)));
 
1108
        }
 
1109
    };
 
1110
 
 
1111
    self.routines["sin"] = function(a) { return Math.sin(deg2rad(aexpr(a))); };
 
1112
    self.routines["cos"] = function(a) { return Math.cos(deg2rad(aexpr(a))); };
 
1113
    self.routines["tan"] = function(a) { return Math.tan(deg2rad(aexpr(a))); };
 
1114
 
 
1115
    self.routines["radarctan"] = function(a) {
 
1116
        if (arguments.length > 1) {
 
1117
            var x = aexpr(arguments[0]);
 
1118
            var y = aexpr(arguments[1]);
 
1119
            return Math.atan2(y, x);
 
1120
        }
 
1121
        else {
 
1122
            return Math.atan(aexpr(a));
 
1123
        }
 
1124
    };
 
1125
 
 
1126
    self.routines["radsin"] = function(a) { return Math.sin(aexpr(a)); };
 
1127
    self.routines["radcos"] = function(a) { return Math.cos(aexpr(a)); };
 
1128
    self.routines["radtan"] = function(a) { return Math.tan(aexpr(a)); };
 
1129
 
 
1130
    self.routines["abs"] = function(a) { return Math.abs(aexpr(a)); };
 
1131
 
 
1132
 
 
1133
    function truncate(x) { return parseInt(x, 10); }
 
1134
 
 
1135
    self.routines["int"] = function(a) { return truncate(aexpr(a)); };
 
1136
    self.routines["round"] = function(a) { return Math.round(aexpr(a)); };
 
1137
 
 
1138
    self.routines["iseq"] = function(a, b) {
 
1139
        a = truncate(aexpr(a));
 
1140
        b = truncate(aexpr(b));
 
1141
        var step = (a < b) ? 1 : -1;
 
1142
        var list = [];
 
1143
        for (var i = a; (step > 0) ? (i <= b) : (i >= b); i += step) {
 
1144
            list.push(i);
 
1145
        }
 
1146
        return list;
 
1147
    };
 
1148
 
 
1149
 
 
1150
    self.routines["rseq"] = function(from, to, count) {
 
1151
        from = aexpr(from);
 
1152
        to = aexpr(to);
 
1153
        count = truncate(aexpr(count));
 
1154
        var step = (to - from) / (count - 1);
 
1155
        var list = [];
 
1156
        for (var i = from; (step > 0) ? (i <= to) : (i >= to); i += step) {
 
1157
            list.push(i);
 
1158
        }
 
1159
        return list;
 
1160
    };
 
1161
 
 
1162
    // 4.2 Numeric Predicates
 
1163
 
 
1164
    self.routines["greaterp"] = self.routines["greater?"] = function(a, b) { return aexpr(a) > aexpr(b) ? 1 : 0; };
 
1165
    self.routines["greaterequalp"] = self.routines["greaterequal?"] = function(a, b) { return aexpr(a) >= aexpr(b) ? 1 : 0; };
 
1166
    self.routines["lessp"] = self.routines["less?"] = function(a, b) { return aexpr(a) < aexpr(b) ? 1 : 0; };
 
1167
    self.routines["lessequalp"] = self.routines["lessequal?"] = function(a, b) { return aexpr(a) <= aexpr(b) ? 1 : 0; };
 
1168
 
 
1169
    // 4.3 Random Numbers
 
1170
 
 
1171
    self.routines["random"] = function(max) {
 
1172
        max = aexpr(max);
 
1173
        return Math.floor(Math.random() * max);
 
1174
    };
 
1175
 
 
1176
    // Not Supported: rerandom
 
1177
 
 
1178
    // 4.4 Print Formatting
 
1179
 
 
1180
    // Not Supported: form
 
1181
 
 
1182
    // 4.5 Bitwise Operations
 
1183
 
 
1184
 
 
1185
    self.routines["bitand"] = function(num1, num2) {
 
1186
        return mapreduce(arguments, aexpr, function(a, b) { return a & b; }, -1);
 
1187
    };
 
1188
    self.routines["bitor"] = function(num1, num2) {
 
1189
        return mapreduce(arguments, aexpr, function(a, b) { return a | b; }, 0);
 
1190
    };
 
1191
    self.routines["bitxor"] = function(num1, num2) {
 
1192
        return mapreduce(arguments, aexpr, function(a, b) { return a ^ b; }, 0);
 
1193
    };
 
1194
    self.routines["bitnot"] = function(num) {
 
1195
        return ~aexpr(num);
 
1196
    };
 
1197
 
 
1198
 
 
1199
    self.routines["ashift"] = function(num1, num2) {
 
1200
        num1 = truncate(aexpr(num1));
 
1201
        num2 = truncate(aexpr(num2));
 
1202
        return num2 >= 0 ? num1 << num2 : num1 >> -num2;
 
1203
    };
 
1204
 
 
1205
    self.routines["lshift"] = function(num1, num2) {
 
1206
        num1 = truncate(aexpr(num1));
 
1207
        num2 = truncate(aexpr(num2));
 
1208
        return num2 >= 0 ? num1 << num2 : num1 >>> -num2;
 
1209
    };
 
1210
 
 
1211
 
 
1212
    //----------------------------------------------------------------------
 
1213
    //
 
1214
    // 5. Logical Operations
 
1215
    //
 
1216
    //----------------------------------------------------------------------
 
1217
 
 
1218
    self.routines["true"] = function() { return 1; };
 
1219
    self.routines["false"] = function() { return 0; };
 
1220
 
 
1221
    self.routines["and"] = function(a, b) {
 
1222
        return Array.prototype.every.call(arguments, function(f) { return f(); }) ? 1 : 0;
 
1223
    };
 
1224
    self.routines["and"].noeval = true;
 
1225
 
 
1226
    self.routines["or"] = function(a, b) {
 
1227
        return Array.prototype.some.call(arguments, function(f) { return f(); }) ? 1 : 0;
 
1228
    };
 
1229
    self.routines["or"].noeval = true;
 
1230
 
 
1231
    self.routines["xor"] = function(a, b) {
 
1232
        function bool(x) { return !!x; }
 
1233
        return mapreduce(arguments, aexpr, function(a, b) { return bool(a) !== bool(b); }, 0) ? 1 : 0;
 
1234
    };
 
1235
    self.routines["not"] = function(a) {
 
1236
        return !aexpr(a) ? 1 : 0;
 
1237
    };
 
1238
 
 
1239
    //----------------------------------------------------------------------
 
1240
    //
 
1241
    // 6. Graphics
 
1242
    //
 
1243
    //----------------------------------------------------------------------
 
1244
    // 6.1 Turtle Motion
 
1245
 
 
1246
    self.routines["forward"] = self.routines["fd"] = function(a) { turtle.move(aexpr(a)); };
 
1247
    self.routines["back"] = self.routines["bk"] = function(a) { turtle.move(-aexpr(a)); };
 
1248
    self.routines["left"] = self.routines["lt"] = function(a) { turtle.turn(-aexpr(a)); };
 
1249
    self.routines["right"] = self.routines["rt"] = function(a) { turtle.turn(aexpr(a)); };
 
1250
 
 
1251
    self.routines["setpos"] = function(l) { l = lexpr(l); turtle.setposition(aexpr(l[0]), aexpr(l[1])); };
 
1252
    self.routines["setxy"] = function(x, y) { turtle.setposition(aexpr(x), aexpr(y)); };
 
1253
    self.routines["setx"] = function(x) { turtle.setposition(aexpr(x), undefined); }; // TODO: Replace with ...?
 
1254
    self.routines["sety"] = function(y) { turtle.setposition(undefined, aexpr(y)); };
 
1255
    self.routines["setheading"] = self.routines["seth"] = function(a) { turtle.setheading(aexpr(a)); };
 
1256
 
 
1257
    self.routines["home"] = function() { turtle.home(); };
 
1258
 
 
1259
    // Not Supported: arc
 
1260
 
 
1261
    //
 
1262
    // 6.2 Turtle Motion Queries
 
1263
    //
 
1264
 
 
1265
    self.routines["pos"] = function() { var l = turtle.getxy(); return [l[0], l[1]]; };
 
1266
    self.routines["xcor"] = function() { var l = turtle.getxy(); return l[0]; };
 
1267
    self.routines["ycor"] = function() { var l = turtle.getxy(); return l[1]; };
 
1268
    self.routines["heading"] = function() { return turtle.getheading(); };
 
1269
    self.routines["towards"] = function(l) { l = lexpr(l); return turtle.towards(aexpr(l[0]), aexpr(l[1])); };
 
1270
 
 
1271
    // Not Supported: scrunch
 
1272
 
 
1273
    //
 
1274
    // 6.3 Turtle and Window Control
 
1275
    //
 
1276
 
 
1277
    self.routines["showturtle"] = self.routines["st"] = function() { turtle.showturtle(); };
 
1278
    self.routines["hideturtle"] = self.routines["ht"] = function() { turtle.hideturtle(); };
 
1279
    self.routines["clean"] = function() { turtle.clear(); };
 
1280
    self.routines["clearscreen"] = self.routines["cs"] = function() { turtle.clearscreen(); };
 
1281
 
 
1282
    // Not Supported: wrap
 
1283
    // Not Supported: window
 
1284
    // Not Supported: fence
 
1285
    // Not Supported: fill
 
1286
    // Not Supported: filled
 
1287
 
 
1288
    self.routines["label"] = function(a) {
 
1289
        var s = Array.prototype.map.call(arguments, stringify_nodecorate).join(" ");
 
1290
        turtle.drawtext(s);
 
1291
        return s;
 
1292
    };
 
1293
 
 
1294
    self.routines["setlabelheight"] = function(a) { turtle.setfontsize(aexpr(a)); };
 
1295
 
 
1296
    // Not Supported: testscreen
 
1297
    // Not Supported: fullscreen
 
1298
    // Not Supported: splitscreen
 
1299
    // Not Supported: setcrunch
 
1300
    // Not Supported: refresh
 
1301
    // Not Supported: norefresh
 
1302
 
 
1303
    //
 
1304
    // 6.4 Turtle and Window Queries
 
1305
    //
 
1306
 
 
1307
    self.routines["shownp"] = self.routines["shown?"] = function() {
 
1308
        return turtle.isturtlevisible() ? 1 : 0;
 
1309
    };
 
1310
 
 
1311
    // Not Supported: screenmode
 
1312
    // Not Supported: turtlemode
 
1313
 
 
1314
    self.routines["labelsize"] = function() {
 
1315
        return [turtle.getfontsize(), turtle.getfontsize()];
 
1316
    };
 
1317
 
 
1318
    //
 
1319
    // 6.5 Pen and Background Control
 
1320
    //
 
1321
    self.routines["pendown"] = self.routines["pd"] = function() { turtle.pendown(); };
 
1322
    self.routines["penup"] = self.routines["pu"] = function() { turtle.penup(); };
 
1323
 
 
1324
    self.routines["penpaint"] = self.routines["ppt"] = function() { turtle.setpenmode('paint'); };
 
1325
    self.routines["penerase"] = self.routines["pe"] = function() { turtle.setpenmode('erase'); };
 
1326
    self.routines["penreverse"] = self.routines["px"] = function() { turtle.setpenmode('reverse'); };
 
1327
 
 
1328
 
 
1329
 
 
1330
    // Not Supported: penpaint
 
1331
    // Not Supported: penerase
 
1332
    // Not Supported: penreverse
 
1333
 
 
1334
    self.routines["setpencolor"] = self.routines["setpc"] = self.routines["setcolor"] = function(a) {
 
1335
        if (arguments.length === 3) {
 
1336
            var r = Math.round(aexpr(arguments[0]) * 255 / 99);
 
1337
            var g = Math.round(aexpr(arguments[1]) * 255 / 99);
 
1338
            var b = Math.round(aexpr(arguments[2]) * 255 / 99);
 
1339
            var rr = (r < 16 ? "0" : "") + r.toString(16);
 
1340
            var gg = (g < 16 ? "0" : "") + g.toString(16);
 
1341
            var bb = (b < 16 ? "0" : "") + b.toString(16);
 
1342
            turtle.setcolor('#' + rr + gg + bb);
 
1343
        }
 
1344
        else {
 
1345
            turtle.setcolor(sexpr(a));
 
1346
        }
 
1347
    };
 
1348
 
 
1349
    // Not Supported: setpallete
 
1350
 
 
1351
    self.routines["setpensize"] = self.routines["setwidth"] = self.routines["setpw"] = function(a) {
 
1352
        if (Type(a) === 'list') {
 
1353
            turtle.setwidth(aexpr(a[0]));
 
1354
        }
 
1355
        else {
 
1356
            turtle.setwidth(aexpr(a));
 
1357
        }
 
1358
    };
 
1359
 
 
1360
    // Not Supported: setpenpattern
 
1361
    // Not Supported: setpen
 
1362
    // Not Supported: setbackground
 
1363
 
 
1364
    //
 
1365
    // 6.6 Pen Queries
 
1366
    //
 
1367
 
 
1368
    self.routines["pendownp"] = self.routines["pendown?"] = function() {
 
1369
        return turtle.ispendown() ? 1 : 0;
 
1370
    };
 
1371
 
 
1372
    self.routines["penmode"] = self.routines["pc"] = function() {
 
1373
        return turtle.getpenmode().toUpperCase();
 
1374
    };
 
1375
 
 
1376
    self.routines["pencolor"] = self.routines["pc"] = function() {
 
1377
        return turtle.getcolor();
 
1378
    };
 
1379
 
 
1380
    // Not Supported: palette
 
1381
 
 
1382
    self.routines["pensize"] = function() {
 
1383
        return [turtle.getwidth(), turtle.getwidth()];
 
1384
    };
 
1385
 
 
1386
    // Not Supported: pen
 
1387
    // Not Supported: background
 
1388
 
 
1389
    // 6.7 Saving and Loading Pictures
 
1390
 
 
1391
    // Not Supported: savepict
 
1392
    // Not Supported: loadpict
 
1393
    // Not Supported: epspict
 
1394
 
 
1395
    // 6.8 Mouse Queries
 
1396
 
 
1397
    // Not Supported: mousepos
 
1398
    // Not Supported: clickpos
 
1399
    // Not Supported: buttonp
 
1400
    // Not Supported: button
 
1401
 
 
1402
    //----------------------------------------------------------------------
 
1403
    //
 
1404
    // 7. Workspace Management
 
1405
    //
 
1406
    //----------------------------------------------------------------------
 
1407
    // 7.1 Procedure Definition
 
1408
    // 7.2 Variable Definition
 
1409
 
 
1410
    self.routines["make"] = function(varname, value) {
 
1411
        self.setvar(sexpr(varname), value);
 
1412
    };
 
1413
 
 
1414
    self.routines["name"] = function(value, varname) {
 
1415
        self.setvar(sexpr(varname), value);
 
1416
    };
 
1417
 
 
1418
    self.routines["local"] = function(varname) {
 
1419
        var localscope = self.scopes[0];
 
1420
        Array.prototype.forEach.call(arguments, function(name) { localscope[sexpr(name).toLowerCase()] = undefined; });
 
1421
    };
 
1422
 
 
1423
    self.routines["localmake"] = function(varname, value) {
 
1424
        var localscope = self.scopes[0];
 
1425
        localscope[sexpr(varname).toLowerCase()] = value;
 
1426
    };
 
1427
 
 
1428
    self.routines["thing"] = function(varname) {
 
1429
        return self.getvar(sexpr(varname));
 
1430
    };
 
1431
 
 
1432
    self.routines["global"] = function(varname) {
 
1433
        var globalscope = self.scopes[self.scopes.length - 1];
 
1434
        Array.prototype.forEach.call(arguments, function(name) { globalscope[sexpr(name).toLowerCase()] = undefined; });
 
1435
    };
 
1436
 
 
1437
    // 7.3 Property Lists
 
1438
 
 
1439
    //
 
1440
    // 7.4 Workspace Predicates
 
1441
    //
 
1442
 
 
1443
    self.routines["procedurep"] = self.routines["procedure?"] = function(name) {
 
1444
        name = sexpr(name).toLowerCase();
 
1445
        return typeof self.routines[name] === 'function' ? 1 : 0;
 
1446
    };
 
1447
 
 
1448
    self.routines["primitivep"] = self.routines["primitive?"] = function(name) {
 
1449
        name = sexpr(name).toLowerCase();
 
1450
        return (typeof self.routines[name] === 'function' &&
 
1451
            self.routines[name].primitive) ? 1 : 0;
 
1452
    };
 
1453
 
 
1454
    self.routines["definedp"] = self.routines["defined?"] = function(name) {
 
1455
        name = sexpr(name).toLowerCase();
 
1456
        return (typeof self.routines[name] === 'function' &&
 
1457
            !self.routines[name].primitive) ? 1 : 0;
 
1458
    };
 
1459
 
 
1460
    self.routines["namep"] = self.routines["name?"] = function(varname) {
 
1461
        try {
 
1462
            return typeof self.getvar(sexpr(varname)) !== 'undefined' ? 1 : 0;
 
1463
        }
 
1464
        catch (e) {
 
1465
            return 0;
 
1466
        }
 
1467
    };
 
1468
 
 
1469
    // Not Supported: plistp
 
1470
 
 
1471
    //
 
1472
    // 7.5 Workspace Queries
 
1473
    //
 
1474
 
 
1475
    self.routines["contents"] = function() {
 
1476
        return [
 
1477
            Object.keys(self.routines).filter(function(x) { return !self.routines[x].primitive; }),
 
1478
            self.scopes.reduce(function(list, scope) { return list.concat(Object.keys(scope)); }, [])
 
1479
            ];
 
1480
    };
 
1481
 
 
1482
    // Not Supported: buried
 
1483
    // Not Supported: traced
 
1484
    // Not Supported: stepped
 
1485
 
 
1486
    self.routines["procedures"] = function() {
 
1487
        return Object.keys(self.routines).filter(function(x) { return !self.routines[x].primitive; });
 
1488
    };
 
1489
 
 
1490
    self.routines["primitives"] = function() {
 
1491
        return Object.keys(self.routines).filter(function(x) { return self.routines[x].primitive; });
 
1492
    };
 
1493
 
 
1494
    self.routines["globals"] = function() {
 
1495
        var globalscope = self.scopes[self.scopes.length - 1];
 
1496
        return Object.keys(globalscope);
 
1497
    };
 
1498
 
 
1499
    self.routines["names"] = function() {
 
1500
        return [[], self.scopes.reduce(function(list, scope) { return list.concat(Object.keys(scope)); }, [])];
 
1501
    };
 
1502
 
 
1503
    // Not Supported: plists
 
1504
    // Not Supported: namelist
 
1505
    // Not Supported: pllist
 
1506
    // Not Supported: arity
 
1507
    // Not Supported: nodes
 
1508
 
 
1509
    // 7.6 Workspace Inspection
 
1510
 
 
1511
    //
 
1512
    // 7.7 Workspace Control
 
1513
    //
 
1514
 
 
1515
    self.routines["erase"] = self.routines["erase"] = function(list) {
 
1516
        list = lexpr(list);
 
1517
 
 
1518
        // Delete procedures
 
1519
        if (list.length) {
 
1520
            var procs = lexpr(list.shift());
 
1521
            procs.forEach(function(name) {
 
1522
                name = name.toLowerCase();
 
1523
                if (self.routines.hasOwnProperty(name) && !self.routines[name].primitive) {
 
1524
                    delete self.routines[name];
 
1525
                }
 
1526
            });
 
1527
        }
 
1528
 
 
1529
        // Delete variables
 
1530
        if (list.length) {
 
1531
            var vars = lexpr(list.shift());
 
1532
            // TODO: global only?
 
1533
            self.scopes.forEach(function(scope) {
 
1534
                vars.forEach(function(name) {
 
1535
                    if (scope.hasOwnProperty(name)) {
 
1536
                        delete scope[name];
 
1537
                    }
 
1538
                });
 
1539
            });
 
1540
        }
 
1541
    };
 
1542
 
 
1543
    self.routines["erall"] = function() {
 
1544
 
 
1545
        Object.keys(self.routines).forEach(function(name) {
 
1546
            if (!self.routines[name].primitive) {
 
1547
                delete self.routines[name];
 
1548
            } 
 
1549
        });
 
1550
 
 
1551
        self.scopes.forEach(function(scope) {
 
1552
            Object.keys(scope).forEach(function(name) { delete scope[name]; });
 
1553
        });
 
1554
    };
 
1555
 
 
1556
    //----------------------------------------------------------------------
 
1557
    //
 
1558
    // 8. Control Structures
 
1559
    //
 
1560
    //----------------------------------------------------------------------
 
1561
 
 
1562
    //
 
1563
    // 8.1 Control
 
1564
    //
 
1565
 
 
1566
 
 
1567
    self.routines["run"] = function(statements) {
 
1568
        statements = lexpr(statements);
 
1569
        return self.execute(statements);
 
1570
    };
 
1571
 
 
1572
    self.routines["runresult"] = function(statements) {
 
1573
        statements = lexpr(statements);
 
1574
        var result = self.execute(statements);
 
1575
        if (typeof result !== 'undefined') {
 
1576
            return [result];
 
1577
        }
 
1578
        else {
 
1579
            return [];
 
1580
        }
 
1581
    };
 
1582
 
 
1583
    self.routines["repeat"] = function(count, statements) {
 
1584
        count = aexpr(count);
 
1585
        statements = lexpr(statements);
 
1586
        var last;
 
1587
        for (var i = 1; i <= count; ++i) {
 
1588
            var old_repcount = self.repcount;
 
1589
            self.repcount = i;
 
1590
            try {
 
1591
                last = self.execute(statements);
 
1592
            } finally {
 
1593
                self.repcount = old_repcount;
 
1594
            }
 
1595
        }
 
1596
        return last;
 
1597
    };
 
1598
 
 
1599
    self.routines["forever"] = function(statements) {
 
1600
        statements = lexpr(statements);
 
1601
        for (var i = 1; true; ++i) {
 
1602
            var old_repcount = self.repcount;
 
1603
            self.repcount = i;
 
1604
            try {
 
1605
                self.execute(statements);
 
1606
            }
 
1607
            finally {
 
1608
                self.repcount = old_repcount;
 
1609
            }
 
1610
        }
 
1611
    };
 
1612
 
 
1613
    self.routines["repcount"] = function() {
 
1614
        return self.repcount;
 
1615
    };
 
1616
 
 
1617
    self.routines["if"] = function(test, statements) {
 
1618
        test = aexpr(test);
 
1619
        statements = lexpr(statements);
 
1620
 
 
1621
        return test ? self.execute(statements) : test;
 
1622
    };
 
1623
 
 
1624
    self.routines["ifelse"] = function(test, statements1, statements2) {
 
1625
        test = aexpr(test);
 
1626
        statements1 = lexpr(statements1);
 
1627
        statements2 = lexpr(statements2);
 
1628
 
 
1629
        return self.execute(test ? statements1 : statements2);
 
1630
    };
 
1631
 
 
1632
    self.routines["test"] = function(tf) {
 
1633
        tf = aexpr(tf);
 
1634
        self.scopes[0]._test = tf;
 
1635
        return tf;
 
1636
    };
 
1637
 
 
1638
    self.routines["iftrue"] = self.routines["ift"] = function(statements) {
 
1639
        statements = lexpr(statements);
 
1640
        return self.scopes[0]._test ? self.execute(statements) : self.scopes[0]._test;
 
1641
    };
 
1642
 
 
1643
    self.routines["iffalse"] = self.routines["iff"] = function(statements) {
 
1644
        statements = lexpr(statements);
 
1645
        return !self.scopes[0]._test ? self.execute(statements) : self.scopes[0]._test;
 
1646
    };
 
1647
 
 
1648
 
 
1649
    self.routines["stop"] = function() {
 
1650
        throw new Output();
 
1651
    };
 
1652
 
 
1653
    self.routines["output"] = self.routines["op"] = function(atom) {
 
1654
        throw new Output(atom);
 
1655
    };
 
1656
 
 
1657
    // TODO: catch
 
1658
    // TODO: throw
 
1659
    // TODO: error
 
1660
    // Not Supported: pause
 
1661
    // Not Supported: continue
 
1662
    // Not Supported: wait
 
1663
 
 
1664
    self.routines["bye"] = function() {
 
1665
        throw new Bye();
 
1666
    };
 
1667
 
 
1668
    self.routines[".maybeoutput"] = function(value) {
 
1669
        if (typeof value !== 'undefined') {
 
1670
            throw new Output(value);
 
1671
        } else {
 
1672
            throw new Output();
 
1673
        }
 
1674
    };
 
1675
 
 
1676
    // Not Supported: goto
 
1677
    // Not Supported: tag
 
1678
 
 
1679
    self.routines["ignore"] = function(value) {
 
1680
    };
 
1681
 
 
1682
    // Not Supported: `
 
1683
 
 
1684
    self.routines["for"] = function(control, statements) {
 
1685
        control = lexpr(control);
 
1686
        statements = lexpr(statements);
 
1687
 
 
1688
        function sign(x) { return x < 0 ? -1 : x > 0 ? 1 : 0; }
 
1689
 
 
1690
        var varname = sexpr(control.shift());
 
1691
        var start = aexpr(self.evaluateExpression(control));
 
1692
        var limit = aexpr(self.evaluateExpression(control));
 
1693
        var step = (control.length) ? aexpr(self.evaluateExpression(control)) : sign(limit - start);
 
1694
 
 
1695
        var last;
 
1696
        for (var current = start; sign(current - limit) !== sign(step); current += step) {
 
1697
            self.setvar(varname, current);
 
1698
            last = self.execute(statements);
 
1699
        }
 
1700
 
 
1701
        return last;
 
1702
    };
 
1703
 
 
1704
    function checkevalblock(block) {
 
1705
        block = block();
 
1706
        if (Type(block) === 'list') { return block; }
 
1707
        throw "Expected block";
 
1708
    }
 
1709
 
 
1710
    self.routines["do.while"] = function(block, tf) {
 
1711
        block = checkevalblock(block);
 
1712
 
 
1713
        do {
 
1714
            self.execute(block);
 
1715
        } while (tf());
 
1716
    };
 
1717
    self.routines["do.while"].noeval = true;
 
1718
 
 
1719
    self.routines["while"] = function(tf, block) {
 
1720
        block = checkevalblock(block);
 
1721
 
 
1722
        while (tf()) {
 
1723
            self.execute(block);
 
1724
        }
 
1725
    };
 
1726
    self.routines["while"].noeval = true;
 
1727
 
 
1728
    self.routines["do.until"] = function(block, tf) {
 
1729
        block = checkevalblock(block);
 
1730
 
 
1731
        do {
 
1732
            self.execute(block);
 
1733
        } while (!tf());
 
1734
    };
 
1735
    self.routines["do.until"].noeval = true;
 
1736
 
 
1737
    self.routines["until"] = function(tf, block) {
 
1738
        block = checkevalblock(block);
 
1739
 
 
1740
        while (!tf()) {
 
1741
            self.execute(block);
 
1742
        }
 
1743
    };
 
1744
    self.routines["until"].noeval = true;
 
1745
 
 
1746
    // Not Supported: case
 
1747
    // Not Supported: cond
 
1748
 
 
1749
 
 
1750
    //
 
1751
    // 8.2 Template-based Iteration
 
1752
    //
 
1753
 
 
1754
 
 
1755
    //
 
1756
    // Higher order functions
 
1757
    //
 
1758
 
 
1759
    // TODO: multiple inputs
 
1760
 
 
1761
    self.routines["apply"] = function(procname, list) {
 
1762
        procname = sexpr(procname).toLowerCase();
 
1763
 
 
1764
        var routine = self.routines[procname];
 
1765
        if (!routine) { throw "Don't know how to " + procname; }
 
1766
        if (routine.special) { throw "Can't apply over special " + procname; }
 
1767
        if (routine.noeval) { throw "Can't apply over special " + procname; }
 
1768
 
 
1769
        return routine.apply(null, lexpr(list));
 
1770
    };
 
1771
 
 
1772
    self.routines["invoke"] = function(procname) {
 
1773
        procname = sexpr(procname).toLowerCase();
 
1774
 
 
1775
        var routine = self.routines[procname];
 
1776
        if (!routine) { throw "Don't know how to " + procname; }
 
1777
        if (routine.special) { throw "Can't invoke over special " + procname; }
 
1778
        if (routine.noeval) { throw "Can't invoke over special " + procname; }
 
1779
 
 
1780
        var args = [];
 
1781
        for (var i = 1; i < arguments.length; i += 1) {
 
1782
            args.push(arguments[i]);
 
1783
        }
 
1784
 
 
1785
        return routine.apply(null, args);
 
1786
    };
 
1787
 
 
1788
    self.routines["foreach"] = function(procname, list) {
 
1789
        procname = sexpr(procname).toLowerCase();
 
1790
 
 
1791
        var routine = self.routines[procname];
 
1792
        if (!routine) { throw "Don't know how to " + procname; }
 
1793
        if (routine.special) { throw "Can't foreach over special " + procname; }
 
1794
        if (routine.noeval) { throw "Can't foreach over special " + procname; }
 
1795
 
 
1796
        return lexpr(list).forEach(routine);
 
1797
    };
 
1798
 
 
1799
 
 
1800
    self.routines["map"] = function(procname, list) {
 
1801
        procname = sexpr(procname).toLowerCase();
 
1802
 
 
1803
        var routine = self.routines[procname];
 
1804
        if (!routine) { throw "Don't know how to " + procname; }
 
1805
        if (routine.special) { throw "Can't map over special " + procname; }
 
1806
        if (routine.noeval) { throw "Can't map over special " + procname; }
 
1807
 
 
1808
        return lexpr(list).map(routine);
 
1809
    };
 
1810
 
 
1811
    // Not Supported: map.se
 
1812
 
 
1813
    self.routines["filter"] = function(procname, list) {
 
1814
        procname = sexpr(procname).toLowerCase();
 
1815
 
 
1816
        var routine = self.routines[procname];
 
1817
        if (!routine) { throw "Don't know how to " + procname; }
 
1818
        if (routine.special) { throw "Can't filter over special " + procname; }
 
1819
        if (routine.noeval) { throw "Can't filter over special " + procname; }
 
1820
 
 
1821
        return lexpr(list).filter(function(x) { return routine(x); });
 
1822
    };
 
1823
 
 
1824
    self.routines["find"] = function(procname, list) {
 
1825
        procname = sexpr(procname).toLowerCase();
 
1826
 
 
1827
        var routine = self.routines[procname];
 
1828
        if (!routine) { throw "Don't know how to " + procname; }
 
1829
        if (routine.special) { throw "Can't filter over special " + procname; }
 
1830
        if (routine.noeval) { throw "Can't filter over special " + procname; }
 
1831
 
 
1832
        list = lexpr(list);
 
1833
        for (var i = 0; i < list.length; i += 1) {
 
1834
            var item = list[i];
 
1835
            if (routine(item)) {
 
1836
                return item;
 
1837
            }
 
1838
        }
 
1839
        return [];
 
1840
    };
 
1841
 
 
1842
    self.routines["reduce"] = function(procname, list) {
 
1843
        procname = sexpr(procname).toLowerCase();
 
1844
        list = lexpr(list);
 
1845
        var value = typeof arguments[2] !== 'undefined' ? arguments[2] : list.shift();
 
1846
 
 
1847
        var procedure = self.routines[procname];
 
1848
        if (!procedure) { throw "Don't know how to " + procname; }
 
1849
        if (procedure.special) { throw "Can't reduce over special " + procname; }
 
1850
        if (procedure.noeval) { throw "Can't reduce over special " + procname; }
 
1851
 
 
1852
        // NOTE: Can't use procedure directly as reduce calls 
 
1853
        // targets w/ additional args and defaults initial value to undefined
 
1854
        return list.reduce(function(a, b) { return procedure(a, b); }, value);
 
1855
    };
 
1856
 
 
1857
    // Not Supported: crossmap
 
1858
    // Not Supported: cascade
 
1859
    // Not Supported: cascade.2
 
1860
    // Not Supported: transfer
 
1861
 
 
1862
    //----------------------------------------------------------------------
 
1863
    // Mark built-ins as such
 
1864
    //----------------------------------------------------------------------
 
1865
 
 
1866
    Object.keys(self.routines).forEach(function(x) { self.routines[x].primitive = true; });
 
1867
 
 
1868
} // LogoInterpreter
 
1869