-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathlogo.js
114 lines (107 loc) · 3.23 KB
/
logo.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
'use strict';
function leading_ws(str) {
var chars = {
space: '',
tab: '',
};
for (var ch of str) {
switch(ch) {
case ' ': {
chars.space += ' ';
} break;
case '\t': {
chars.tab += '\t';
} break;
default: break;
}
}
if (chars.space && chars.tab) {
throw new Error("Inconsitent indentation. Both tabs and spaces were found.");
}
return chars.space || chars.tab || '';
}
function tokenise(script) {
var tokens = [];
var indent_str = '';
for (var line of script.split("\n")) {
indent_str = leading_ws(line)
var tkns = line.trim().split(/\s/);
if (tkns.join('')) {
tokens.push([indent_str].concat(tkns));
}
}
return tokens;
}
function parse(tokens) {
var stack = new Stack();
stack.push({
op: null,
indent: '',
body: [],
});
for (var line of tokens) {
var indent = line[0];
var op = line[1];
switch(op) {
case 'to': {
if (stack.backtrace(op)) {
throw new Error("Parser error: Already defining a procedure");
}
const name = line[2];
if (!name) {
throw new Error("Parser error: Procedure definition - no name given.");
}
const body = [];
stack.last().body.push([op, name, line.slice(3), body]);
stack.push({
op: 'to',
indent,
body,
});
} break;
case 'repeat': {
const count = +line[2];
if (Number.isNaN(count)) {
throw new Error("Parser error: Repeat expects a number as its first argument.");
}
const body = [];
stack.last().body.push([op, count, body]);
stack.push({
op: 'repeat',
indent,
body,
});
} break;
default: {
if (stack.length > 1) {
if (stack.last().indent.length < indent.length) {
stack.last().body.push(line.slice(1));
} else {
const prev_frame = stack.pop();
const cur_frame = stack.last();
if (stack.length) {
cur_frame.body.push(prev_frame.body);
cur_frame.body.push(line.slice(1));
} else {
prev_frame && cur_frame.body.push(prev_frame.body);
cur_frame.body.push(line.slice(1));
}
}
} else {
stack.last().body.push(line.slice(1));
}
}
}
}
return stack[0].body;
}
function Logo(turtle) {
return {
parse(script) {
return parse(tokenise(script.toLowerCase()));
},
eval(script) {
return turtle.perform(this.parse(script));
}
}
}