2
// Logo Interpreter in Javascript
5
// Copyright 2009 Joshua Bell
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
11
// http://www.apache.org/licenses/LICENSE-2.0
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.
19
/*jslint browser: true, sub: true, undef: true */
22
// Based on: http://www.jbouchard.net/chris/blog/2008/01/currying-in-javascript-fun-for-whole.html
23
if (!Function.prototype.toArity) {
25
Function.prototype.toArity = function(arity) {
29
if (func.length === arity) {
33
for (var i = 0; i < arity; i += 1) {
37
/*jslint evil: true */
38
var f = eval('(function (' + parms.join(',') + ') { return func.apply(this, arguments); })');
39
/*jslint evil: false */
44
//----------------------------------------------------------------------
45
function LogoInterpreter(turtle, stream)
46
//----------------------------------------------------------------------
50
var UNARY_MINUS = '<UNARYMINUS>'; // Must not match regexIdentifier
52
//----------------------------------------------------------------------
56
//----------------------------------------------------------------------
64
//----------------------------------------------------------------------
68
//----------------------------------------------------------------------
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; };
75
// Used to stop processing cleanly
80
if (typeof atom === 'undefined') {
83
else if (typeof atom === 'string') {
86
else if (typeof atom === 'number') {
89
else if (Array.isArray(atom)) {
96
throw "Unknown type!";
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 = /^(\+|\-|\*|\/|%|\^|>=|<=|<>|=|<|>)$/;
109
// Construct a parse tree
112
// Output: atom list (e.g. "to", "jump", "repeat", "random", 10, [ "fd", 10, "rt" 10 ], "end"
114
// TODO: Move this into expression parsing; should be '[' operator's job!
116
//----------------------------------------------------------------------
117
function parse(string)
118
//----------------------------------------------------------------------
120
if (typeof string === 'undefined') {
121
return undefined; // TODO: Replace this with ...?
127
// Filter out comments
128
string = string.replace(/;.*\n/g, '');
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+$/, '');
134
while (typeof string !== 'undefined' && string !== '') {
137
// Ignore (but track) leading space - needed for unary minus disambiguation
138
var leading_space = /^\s+/.test(string);
139
string = string.replace(/^\s+/, '');
141
if (string.match(regexIdentifier) ||
142
string.match(regexStringLiteral) ||
143
string.match(regexVariableLiteral)) {
148
else if (string.match(regexNumberLiteral)) {
153
// The following dirties RegExp.$n so it is kept separate
154
atom = parseFloat(atom.replace(/\s+/g, ''), 10);
156
else if (string.match(regexListDelimiter)) {
158
if (RegExp.$1 === '[') {
159
// Start of list - recurse!
160
var r = parse(RegExp.$2);
161
if (!r.list) { throw "Expected end-of-list"; }
165
else { // (RegExp.$1 === ']')
166
// End of list - return list and the remaining input
167
return { list: atoms, string: RegExp.$2 };
171
else if (string.match(regexOperator)) {
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.
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.
188
var trailing_space = /^\s+/.test(string);
190
if (typeof prev === 'undefined' ||
191
(Type(prev) === 'word' && regexInfix.test(prev)) ||
192
(Type(prev) === 'word' && prev === '(') ||
193
(leading_space && !trailing_space)
201
throw "Couldn't parse: '" + string + "'";
214
//----------------------------------------------------------------------
215
self.getvar = function(name)
216
//----------------------------------------------------------------------
218
name = name.toLowerCase();
220
for (var i = 0; i < self.scopes.length; ++i) {
221
if (self.scopes[i].hasOwnProperty(name)) {
222
return self.scopes[i][name];
225
throw "Don't know about variable " + name;
228
//----------------------------------------------------------------------
229
self.setvar = function(name, value)
230
//----------------------------------------------------------------------
232
name = name.toLowerCase();
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;
242
// Otherwise, define a global
243
self.scopes[self.scopes.length - 1][name] = value;
246
//----------------------------------------------------------------------
248
// Expression Evaluation
250
//----------------------------------------------------------------------
252
// Expression := RelationalExpression
253
// RelationalExpression := AdditiveExpression [ ( '=' | '<' | '>' | '<=' | '>=' | '<>' ) AdditiveExpression ... ]
254
// AdditiveExpression := MultiplicativeExpression [ ( '+' | '-' ) MultiplicativeExpression ... ]
255
// MultiplicativeExpression := PowerExpression [ ( '*' | '/' | '%' ) PowerExpression ... ]
256
// PowerExpression := UnaryExpression [ '^' UnaryExpression ]
257
// UnaryExpression := ( '-' ) UnaryExpression
259
// FinalExpression := string-literal
262
// | variable-reference
264
// | '(' Expression ')'
266
//----------------------------------------------------------------------
267
// Peek at the list to see if there are additional atoms from a set
269
//----------------------------------------------------------------------
270
function peek(list, options)
271
//----------------------------------------------------------------------
273
if (list.length < 1) { return false; }
275
return options.some(function(x) { return next === x; });
279
//----------------------------------------------------------------------
280
self.evaluateExpression = function(list)
281
//----------------------------------------------------------------------
283
return (self.expression(list))();
287
//----------------------------------------------------------------------
288
self.expression = function(list)
289
//----------------------------------------------------------------------
291
return self.relationalExpression(list);
295
//----------------------------------------------------------------------
296
self.relationalExpression = function(list)
297
//----------------------------------------------------------------------
299
var lhs = self.additiveExpression(list);
301
while (peek(list, ['=', '<', '>', '<=', '>=', '<>'])) {
304
lhs = function(lhs) {
305
var rhs = self.additiveExpression(list);
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; };
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; };
322
//----------------------------------------------------------------------
323
self.additiveExpression = function(list)
324
//----------------------------------------------------------------------
326
var lhs = self.multiplicativeExpression(list);
328
while (peek(list, ['+', '-'])) {
331
lhs = function(lhs) {
332
var rhs = self.multiplicativeExpression(list);
334
case "+": return function() { return aexpr(lhs()) + aexpr(rhs()); };
335
case "-": return function() { return aexpr(lhs()) - aexpr(rhs()); };
343
//----------------------------------------------------------------------
344
self.multiplicativeExpression = function(list)
345
//----------------------------------------------------------------------
347
var lhs = self.powerExpression(list);
349
while (peek(list, ['*', '/', '%'])) {
352
lhs = function(lhs) {
353
var rhs = self.powerExpression(list);
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"; }
361
case "%": return function() {
362
var n = aexpr(lhs()), d = aexpr(rhs());
363
if (d === 0) { throw "Division by zero"; }
373
//----------------------------------------------------------------------
374
self.powerExpression = function(list)
375
//----------------------------------------------------------------------
377
var lhs = self.unaryExpression(list);
379
while (peek(list, ['^'])) {
381
lhs = function(lhs) {
382
var rhs = self.unaryExpression(list);
383
return function() { return Math.pow(aexpr(lhs()), aexpr(rhs())); };
390
//----------------------------------------------------------------------
391
self.unaryExpression = function(list)
392
//----------------------------------------------------------------------
396
if (peek(list, [UNARY_MINUS])) {
398
rhs = self.unaryExpression(list);
399
return function() { return -aexpr(rhs()); };
402
return self.finalExpression(list);
407
//----------------------------------------------------------------------
408
self.finalExpression = function(list)
409
//----------------------------------------------------------------------
412
throw "Unexpected end of instructions";
415
var atom = list.shift();
417
var args, i, routine, result;
418
var literal, varname;
420
switch (Type(atom)) {
423
return function() { return atom; };
426
if (atom.charAt(0) === '"') {
428
literal = atom.substring(1);
429
return function() { return literal; };
431
else if (atom.charAt(0) === ':') {
433
varname = atom.substring(1);
434
return function() { return self.getvar(varname); };
436
else if (atom === '(') {
437
// parenthesized expression/procedure call
438
if (list.length && Type(list[0]) === 'word' &&
439
self.routines[list[0].toString().toLowerCase()]) {
441
// Lisp-style (procedure input ...) calling syntax
443
return self.dispatch(atom, list, false);
446
// Standard parenthesized expression
447
result = self.expression(list);
449
if (!peek(list, [')'])) {
450
throw "Expected ')', saw " + list.shift();
457
// Procedure dispatch
458
return self.dispatch(atom, list, true);
463
throw "Unexpected: " + atom;
467
//----------------------------------------------------------------------
468
self.dispatch = function(name, tokenlist, natural)
469
//----------------------------------------------------------------------
471
var procedure = self.routines[name.toLowerCase()];
472
if (!procedure) { throw "Don't know how to " + name; }
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() { };
483
// Natural arity of the function
484
for (var i = 0; i < procedure.length; ++i) {
485
args.push(self.expression(tokenlist));
489
// Caller specified argument count
490
while (tokenlist.length && !peek(tokenlist, [')'])) {
491
args.push(self.expression(tokenlist));
493
tokenlist.shift(); // Consume ')'
496
if (procedure.noeval) {
498
return procedure.apply(procedure, args);
503
return procedure.apply(procedure, args.map(function(a) { return a(); }));
508
//----------------------------------------------------------------------
509
// Arithmetic expression convenience function
510
//----------------------------------------------------------------------
512
//----------------------------------------------------------------------
514
if (Type(atom) === 'number') { return atom; }
515
if (Type(atom) === 'word') { return parseFloat(atom); } // coerce
517
throw "Expected number";
520
//----------------------------------------------------------------------
521
// String expression convenience function
522
//----------------------------------------------------------------------
524
//----------------------------------------------------------------------
526
if (Type(atom) === 'word') { return atom; }
527
if (Type(atom) === 'number') { return atom.toString(); } // coerce
529
throw "Expected string";
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; }
541
throw "Expected list";
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
//----------------------------------------------------------------------
551
if (Array.isArray(a)) {
552
if (!Array.isArray(b)) {
555
if (a.length !== b.length) {
558
for (var i = 0; i < a.length; i += 1) {
559
if (!self.equal(a[i], b[i])) {
565
else if (typeof a !== typeof b) {
568
else if (typeof epsilon !== 'undefined' && typeof a === 'number') {
569
return Math.abs(a - b) < epsilon;
576
//----------------------------------------------------------------------
580
//----------------------------------------------------------------------
582
//----------------------------------------------------------------------
583
// Execute a sequence of statements
584
//----------------------------------------------------------------------
585
self.execute = function(statements)
586
//----------------------------------------------------------------------
588
// Operate on a copy so the original is not destroyed
589
statements = statements.slice();
592
while (statements.length) {
593
result = self.evaluateExpression(statements);
596
// Return last result
602
//----------------------------------------------------------------------
603
self.run = function(string)
604
//----------------------------------------------------------------------
606
if (self.turtle) { self.turtle.begin(); }
610
var atoms = parse(string);
613
return self.execute(atoms);
616
if (e instanceof Bye) {
625
if (self.turtle) { self.turtle.end(); }
630
//----------------------------------------------------------------------
632
// Built-In Proceedures
634
//----------------------------------------------------------------------
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
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)
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)
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);
663
return Array.prototype.reduce.call(Array.prototype.map.call(list, mapfunc), reducefunc, initial);
667
function stringify(thing) {
669
if (Type(thing) === 'list') {
670
return "[ " + thing.map(stringify).join(" ") + " ]";
677
function stringify_nodecorate(thing) {
679
if (Type(thing) === 'list') {
680
return thing.map(stringify).join(" ");
683
return stringify(thing);
688
// Procedures and Flow Control
690
self.routines["to"] = function(list) {
691
var name = list.shift();
692
if (!name.match(regexIdentifier)) {
693
throw "Expected identifier";
695
name = name.toLowerCase();
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') {
707
else if (state_inputs && Type(atom) === 'word' && atom.charAt(0) === ':') {
708
inputs.push(atom.substring(1));
711
state_inputs = false;
716
// Closure over inputs and block to handle scopes, arguments and outputs
717
var func = function() {
719
// Define a new scope
721
for (var i = 0; i < inputs.length && i < arguments.length; i += 1) {
722
scope[inputs[i]] = arguments[i];
724
self.scopes.unshift(scope);
729
return self.execute(block);
733
if (e instanceof Output) {
747
self.routines[name] = func.toArity(inputs.length);
749
// For DEF de-serialization
750
self.routines[name].inputs = inputs;
751
self.routines[name].block = block;
753
self.routines["to"].special = true;
755
self.routines["def"] = function(list) {
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(' ') + ' ]';
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; }
770
var def = "to " + name + " ";
771
if (proc.inputs.length) {
772
def += proc.inputs.map(function(a) { return ":" + a; }).join(" ");
775
def += proc.block.map(defn).join(" ").replace(new RegExp(UNARY_MINUS + ' ', 'g'), '-');
782
//----------------------------------------------------------------------
784
// 2. Data Structure Primitives
786
//----------------------------------------------------------------------
792
self.routines["word"] = function(word1, word2) {
793
return arguments.length ? mapreduce(arguments, sexpr, function(a, b) { return a + " " + b; }) : "";
796
self.routines["list"] = function(thing1, thing2) {
797
return Array.prototype.map.call(arguments, function(x) { return x; }); // Make a copy
800
self.routines["sentence"] = self.routines["se"] = function(thing1, thing2) {
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);
815
self.routines["fput"] = function(thing, list) { list = lexpr(list); list.unshift(thing); return list; };
817
self.routines["lput"] = function(thing, list) { list = lexpr(list); list.push(thing); return list; };
819
// Not Supported: array
820
// Not Supported: mdarray
821
// Not Supported: listtoarray
822
// Not Supported: arraytolist
824
self.routines["combine"] = function(thing1, thing2) {
825
if (Type(thing2) !== 'list') {
826
return self.routines['word'](thing1, thing2);
829
return self.routines['fput'](thing1, thing2);
833
self.routines["reverse"] = function(list) { return lexpr(list).slice().reverse(); };
835
var gensym_index = 0;
836
self.routines["gensym"] = function() {
838
return 'G' + gensym_index;
842
// 2.2 Data Selectors
845
self.routines["first"] = function(list) { return lexpr(list)[0]; };
847
self.routines["firsts"] = function(list) {
848
return lexpr(list).map(function(x) { return x[0]; });
851
self.routines["last"] = function(list) { list = lexpr(list); return list[list.length - 1]; };
853
self.routines["butfirst"] = self.routines["bf"] = function(list) { return lexpr(list).slice(1); };
855
self.routines["butfirsts"] = self.routines["bfs"] = function(list) {
856
return lexpr(list).map(function(x) { return lexpr(x).slice(1); });
859
self.routines["butlast"] = self.routines["bl"] = function(list) { return lexpr(list).slice(0, -1); };
861
self.routines["item"] = function(index, list) {
862
index = aexpr(index);
863
if (index < 1 || index > list.length) {
864
throw "index out of bounds";
866
return lexpr(list)[index - 1];
869
// Not Supported: mditem
871
self.routines["pick"] = function(list) {
873
var i = Math.floor(Math.random() * list.length);
877
self.routines["remove"] = function(thing, list) {
878
return lexpr(list).filter(function(x) { return x !== thing; });
881
self.routines["remdup"] = function(list) {
883
return lexpr(list).filter(function(x) { if (!dict[x]) { dict[x] = true; return true; } else { return false; } });
892
// Not Supported: setitem
893
// Not Supported: mdsetitem
894
// Not Supported: .setfirst
895
// Not Supported: .setbf
896
// Not Supported: .setitem
898
self.routines["push"] = function(stackname, thing) {
899
var stack = lexpr(self.getvar(stackname));
900
stack.unshift(thing);
901
self.setvar(stackname, stack);
904
self.routines["pop"] = function(stackname) {
905
return self.getvar(stackname).shift();
908
self.routines["queue"] = function(stackname, thing) {
909
var stack = lexpr(self.getvar(stackname));
911
self.setvar(stackname, stack);
914
// NOTE: Same as "pop" (!?!)
915
self.routines["dequeue"] = function(stackname) {
916
return self.getvar(stackname).shift();
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; };
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; };
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; };
936
// Not Supported: .eq
937
// Not Supported: vbarredp
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;
945
self.routines["substringp"] = self.routines["substring?"] =
946
function(word1, word2) {
947
return sexpr(word2).indexOf(sexpr(word1)) !== -1 ? 1 : 0;
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
964
//----------------------------------------------------------------------
968
//----------------------------------------------------------------------
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");
977
self.routines["type"] = function(thing) {
978
var s = Array.prototype.map.call(arguments, stringify_nodecorate).join("");
979
self.stream.write(s);
982
self.routines["show"] = function(thing) {
983
var s = Array.prototype.map.call(arguments, stringify).join(" ");
984
self.stream.write(s, "\n");
990
// Not Supported: readlist
992
self.routines["readword"] = function() {
993
if (arguments.length > 0) {
994
return stream.read(sexpr(arguments[0]));
997
return stream.read();
1002
// Not Supported: readrawline
1003
// Not Supported: readchar
1004
// Not Supported: readchars
1005
// Not Supported: shell
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
1032
// 3.4 Terminal Access
1034
// Not Supported: keyp
1036
self.routines["cleartext"] = self.routines["ct"] = function() {
1037
self.stream.clear();
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
1050
//----------------------------------------------------------------------
1054
//----------------------------------------------------------------------
1055
// 4.1 Numeric Operations
1058
self.routines["sum"] = function(a, b) {
1059
return mapreduce(arguments, aexpr, function(a, b) { return a + b; }, 0);
1062
self.routines["difference"] = function(a, b) {
1063
return aexpr(a) - aexpr(b);
1066
self.routines["minus"] = function(a) { return -aexpr(a); };
1068
self.routines["product"] = function(a, b) {
1069
return mapreduce(arguments, aexpr, function(a, b) { return a * b; }, 1);
1072
self.routines["quotient"] = function(a, b) {
1073
if (typeof b !== 'undefined') {
1074
return aexpr(a) / aexpr(b);
1077
return 1 / aexpr(a);
1081
self.routines["remainder"] = function(num1, num2) {
1082
return aexpr(num1) % aexpr(num2);
1084
self.routines["modulo"] = function(num1, num2) {
1087
return Math.abs(num1 % num2) * (num2 < 0 ? -1 : 1);
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)); };
1097
function deg2rad(d) { return d / 180 * Math.PI; }
1098
function rad2deg(r) { return r * 180 / Math.PI; }
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));
1107
return rad2deg(Math.atan(aexpr(a)));
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))); };
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);
1122
return Math.atan(aexpr(a));
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)); };
1130
self.routines["abs"] = function(a) { return Math.abs(aexpr(a)); };
1133
function truncate(x) { return parseInt(x, 10); }
1135
self.routines["int"] = function(a) { return truncate(aexpr(a)); };
1136
self.routines["round"] = function(a) { return Math.round(aexpr(a)); };
1138
self.routines["iseq"] = function(a, b) {
1139
a = truncate(aexpr(a));
1140
b = truncate(aexpr(b));
1141
var step = (a < b) ? 1 : -1;
1143
for (var i = a; (step > 0) ? (i <= b) : (i >= b); i += step) {
1150
self.routines["rseq"] = function(from, to, count) {
1153
count = truncate(aexpr(count));
1154
var step = (to - from) / (count - 1);
1156
for (var i = from; (step > 0) ? (i <= to) : (i >= to); i += step) {
1162
// 4.2 Numeric Predicates
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; };
1169
// 4.3 Random Numbers
1171
self.routines["random"] = function(max) {
1173
return Math.floor(Math.random() * max);
1176
// Not Supported: rerandom
1178
// 4.4 Print Formatting
1180
// Not Supported: form
1182
// 4.5 Bitwise Operations
1185
self.routines["bitand"] = function(num1, num2) {
1186
return mapreduce(arguments, aexpr, function(a, b) { return a & b; }, -1);
1188
self.routines["bitor"] = function(num1, num2) {
1189
return mapreduce(arguments, aexpr, function(a, b) { return a | b; }, 0);
1191
self.routines["bitxor"] = function(num1, num2) {
1192
return mapreduce(arguments, aexpr, function(a, b) { return a ^ b; }, 0);
1194
self.routines["bitnot"] = function(num) {
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;
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;
1212
//----------------------------------------------------------------------
1214
// 5. Logical Operations
1216
//----------------------------------------------------------------------
1218
self.routines["true"] = function() { return 1; };
1219
self.routines["false"] = function() { return 0; };
1221
self.routines["and"] = function(a, b) {
1222
return Array.prototype.every.call(arguments, function(f) { return f(); }) ? 1 : 0;
1224
self.routines["and"].noeval = true;
1226
self.routines["or"] = function(a, b) {
1227
return Array.prototype.some.call(arguments, function(f) { return f(); }) ? 1 : 0;
1229
self.routines["or"].noeval = true;
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;
1235
self.routines["not"] = function(a) {
1236
return !aexpr(a) ? 1 : 0;
1239
//----------------------------------------------------------------------
1243
//----------------------------------------------------------------------
1244
// 6.1 Turtle Motion
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)); };
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)); };
1257
self.routines["home"] = function() { turtle.home(); };
1259
// Not Supported: arc
1262
// 6.2 Turtle Motion Queries
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])); };
1271
// Not Supported: scrunch
1274
// 6.3 Turtle and Window Control
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(); };
1282
// Not Supported: wrap
1283
// Not Supported: window
1284
// Not Supported: fence
1285
// Not Supported: fill
1286
// Not Supported: filled
1288
self.routines["label"] = function(a) {
1289
var s = Array.prototype.map.call(arguments, stringify_nodecorate).join(" ");
1294
self.routines["setlabelheight"] = function(a) { turtle.setfontsize(aexpr(a)); };
1296
// Not Supported: testscreen
1297
// Not Supported: fullscreen
1298
// Not Supported: splitscreen
1299
// Not Supported: setcrunch
1300
// Not Supported: refresh
1301
// Not Supported: norefresh
1304
// 6.4 Turtle and Window Queries
1307
self.routines["shownp"] = self.routines["shown?"] = function() {
1308
return turtle.isturtlevisible() ? 1 : 0;
1311
// Not Supported: screenmode
1312
// Not Supported: turtlemode
1314
self.routines["labelsize"] = function() {
1315
return [turtle.getfontsize(), turtle.getfontsize()];
1319
// 6.5 Pen and Background Control
1321
self.routines["pendown"] = self.routines["pd"] = function() { turtle.pendown(); };
1322
self.routines["penup"] = self.routines["pu"] = function() { turtle.penup(); };
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'); };
1330
// Not Supported: penpaint
1331
// Not Supported: penerase
1332
// Not Supported: penreverse
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);
1345
turtle.setcolor(sexpr(a));
1349
// Not Supported: setpallete
1351
self.routines["setpensize"] = self.routines["setwidth"] = self.routines["setpw"] = function(a) {
1352
if (Type(a) === 'list') {
1353
turtle.setwidth(aexpr(a[0]));
1356
turtle.setwidth(aexpr(a));
1360
// Not Supported: setpenpattern
1361
// Not Supported: setpen
1362
// Not Supported: setbackground
1368
self.routines["pendownp"] = self.routines["pendown?"] = function() {
1369
return turtle.ispendown() ? 1 : 0;
1372
self.routines["penmode"] = self.routines["pc"] = function() {
1373
return turtle.getpenmode().toUpperCase();
1376
self.routines["pencolor"] = self.routines["pc"] = function() {
1377
return turtle.getcolor();
1380
// Not Supported: palette
1382
self.routines["pensize"] = function() {
1383
return [turtle.getwidth(), turtle.getwidth()];
1386
// Not Supported: pen
1387
// Not Supported: background
1389
// 6.7 Saving and Loading Pictures
1391
// Not Supported: savepict
1392
// Not Supported: loadpict
1393
// Not Supported: epspict
1395
// 6.8 Mouse Queries
1397
// Not Supported: mousepos
1398
// Not Supported: clickpos
1399
// Not Supported: buttonp
1400
// Not Supported: button
1402
//----------------------------------------------------------------------
1404
// 7. Workspace Management
1406
//----------------------------------------------------------------------
1407
// 7.1 Procedure Definition
1408
// 7.2 Variable Definition
1410
self.routines["make"] = function(varname, value) {
1411
self.setvar(sexpr(varname), value);
1414
self.routines["name"] = function(value, varname) {
1415
self.setvar(sexpr(varname), value);
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; });
1423
self.routines["localmake"] = function(varname, value) {
1424
var localscope = self.scopes[0];
1425
localscope[sexpr(varname).toLowerCase()] = value;
1428
self.routines["thing"] = function(varname) {
1429
return self.getvar(sexpr(varname));
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; });
1437
// 7.3 Property Lists
1440
// 7.4 Workspace Predicates
1443
self.routines["procedurep"] = self.routines["procedure?"] = function(name) {
1444
name = sexpr(name).toLowerCase();
1445
return typeof self.routines[name] === 'function' ? 1 : 0;
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;
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;
1460
self.routines["namep"] = self.routines["name?"] = function(varname) {
1462
return typeof self.getvar(sexpr(varname)) !== 'undefined' ? 1 : 0;
1469
// Not Supported: plistp
1472
// 7.5 Workspace Queries
1475
self.routines["contents"] = function() {
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)); }, [])
1482
// Not Supported: buried
1483
// Not Supported: traced
1484
// Not Supported: stepped
1486
self.routines["procedures"] = function() {
1487
return Object.keys(self.routines).filter(function(x) { return !self.routines[x].primitive; });
1490
self.routines["primitives"] = function() {
1491
return Object.keys(self.routines).filter(function(x) { return self.routines[x].primitive; });
1494
self.routines["globals"] = function() {
1495
var globalscope = self.scopes[self.scopes.length - 1];
1496
return Object.keys(globalscope);
1499
self.routines["names"] = function() {
1500
return [[], self.scopes.reduce(function(list, scope) { return list.concat(Object.keys(scope)); }, [])];
1503
// Not Supported: plists
1504
// Not Supported: namelist
1505
// Not Supported: pllist
1506
// Not Supported: arity
1507
// Not Supported: nodes
1509
// 7.6 Workspace Inspection
1512
// 7.7 Workspace Control
1515
self.routines["erase"] = self.routines["erase"] = function(list) {
1518
// Delete procedures
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];
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)) {
1543
self.routines["erall"] = function() {
1545
Object.keys(self.routines).forEach(function(name) {
1546
if (!self.routines[name].primitive) {
1547
delete self.routines[name];
1551
self.scopes.forEach(function(scope) {
1552
Object.keys(scope).forEach(function(name) { delete scope[name]; });
1556
//----------------------------------------------------------------------
1558
// 8. Control Structures
1560
//----------------------------------------------------------------------
1567
self.routines["run"] = function(statements) {
1568
statements = lexpr(statements);
1569
return self.execute(statements);
1572
self.routines["runresult"] = function(statements) {
1573
statements = lexpr(statements);
1574
var result = self.execute(statements);
1575
if (typeof result !== 'undefined') {
1583
self.routines["repeat"] = function(count, statements) {
1584
count = aexpr(count);
1585
statements = lexpr(statements);
1587
for (var i = 1; i <= count; ++i) {
1588
var old_repcount = self.repcount;
1591
last = self.execute(statements);
1593
self.repcount = old_repcount;
1599
self.routines["forever"] = function(statements) {
1600
statements = lexpr(statements);
1601
for (var i = 1; true; ++i) {
1602
var old_repcount = self.repcount;
1605
self.execute(statements);
1608
self.repcount = old_repcount;
1613
self.routines["repcount"] = function() {
1614
return self.repcount;
1617
self.routines["if"] = function(test, statements) {
1619
statements = lexpr(statements);
1621
return test ? self.execute(statements) : test;
1624
self.routines["ifelse"] = function(test, statements1, statements2) {
1626
statements1 = lexpr(statements1);
1627
statements2 = lexpr(statements2);
1629
return self.execute(test ? statements1 : statements2);
1632
self.routines["test"] = function(tf) {
1634
self.scopes[0]._test = tf;
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;
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;
1649
self.routines["stop"] = function() {
1653
self.routines["output"] = self.routines["op"] = function(atom) {
1654
throw new Output(atom);
1660
// Not Supported: pause
1661
// Not Supported: continue
1662
// Not Supported: wait
1664
self.routines["bye"] = function() {
1668
self.routines[".maybeoutput"] = function(value) {
1669
if (typeof value !== 'undefined') {
1670
throw new Output(value);
1676
// Not Supported: goto
1677
// Not Supported: tag
1679
self.routines["ignore"] = function(value) {
1684
self.routines["for"] = function(control, statements) {
1685
control = lexpr(control);
1686
statements = lexpr(statements);
1688
function sign(x) { return x < 0 ? -1 : x > 0 ? 1 : 0; }
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);
1696
for (var current = start; sign(current - limit) !== sign(step); current += step) {
1697
self.setvar(varname, current);
1698
last = self.execute(statements);
1704
function checkevalblock(block) {
1706
if (Type(block) === 'list') { return block; }
1707
throw "Expected block";
1710
self.routines["do.while"] = function(block, tf) {
1711
block = checkevalblock(block);
1714
self.execute(block);
1717
self.routines["do.while"].noeval = true;
1719
self.routines["while"] = function(tf, block) {
1720
block = checkevalblock(block);
1723
self.execute(block);
1726
self.routines["while"].noeval = true;
1728
self.routines["do.until"] = function(block, tf) {
1729
block = checkevalblock(block);
1732
self.execute(block);
1735
self.routines["do.until"].noeval = true;
1737
self.routines["until"] = function(tf, block) {
1738
block = checkevalblock(block);
1741
self.execute(block);
1744
self.routines["until"].noeval = true;
1746
// Not Supported: case
1747
// Not Supported: cond
1751
// 8.2 Template-based Iteration
1756
// Higher order functions
1759
// TODO: multiple inputs
1761
self.routines["apply"] = function(procname, list) {
1762
procname = sexpr(procname).toLowerCase();
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; }
1769
return routine.apply(null, lexpr(list));
1772
self.routines["invoke"] = function(procname) {
1773
procname = sexpr(procname).toLowerCase();
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; }
1781
for (var i = 1; i < arguments.length; i += 1) {
1782
args.push(arguments[i]);
1785
return routine.apply(null, args);
1788
self.routines["foreach"] = function(procname, list) {
1789
procname = sexpr(procname).toLowerCase();
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; }
1796
return lexpr(list).forEach(routine);
1800
self.routines["map"] = function(procname, list) {
1801
procname = sexpr(procname).toLowerCase();
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; }
1808
return lexpr(list).map(routine);
1811
// Not Supported: map.se
1813
self.routines["filter"] = function(procname, list) {
1814
procname = sexpr(procname).toLowerCase();
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; }
1821
return lexpr(list).filter(function(x) { return routine(x); });
1824
self.routines["find"] = function(procname, list) {
1825
procname = sexpr(procname).toLowerCase();
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; }
1833
for (var i = 0; i < list.length; i += 1) {
1835
if (routine(item)) {
1842
self.routines["reduce"] = function(procname, list) {
1843
procname = sexpr(procname).toLowerCase();
1845
var value = typeof arguments[2] !== 'undefined' ? arguments[2] : list.shift();
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; }
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);
1857
// Not Supported: crossmap
1858
// Not Supported: cascade
1859
// Not Supported: cascade.2
1860
// Not Supported: transfer
1862
//----------------------------------------------------------------------
1863
// Mark built-ins as such
1864
//----------------------------------------------------------------------
1866
Object.keys(self.routines).forEach(function(x) { self.routines[x].primitive = true; });
1868
} // LogoInterpreter