-
Notifications
You must be signed in to change notification settings - Fork 1
/
parser.js
88 lines (84 loc) · 1.99 KB
/
parser.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
function isWhitespace(ch) {
return [" ", "\t", "\r", "\n", ",", ":"].includes(ch);
}
function isSymbolChar(ch) {
return ch && !["\"", "(", ")", ";"].includes(ch) && !isWhitespace(ch);
}
function tokenize(input) {
const tokens = [];
let pos = 0;
while (pos < input.length) {
const ch = input[pos];
if (ch === "(") {
tokens.push({type: "startList"});
pos += 1;
}
else if (ch === ")") {
tokens.push({type: "endList"});
pos += 1;
}
else if (ch === ";") { // skip line comment
while (input[pos] && input[pos] !== "\n") {
pos += 1;
}
pos += 1; // advance past the newline
}
else if (isWhitespace(ch)) { // skip whitespace
while (isWhitespace(input[pos])) {
pos += 1;
}
}
else if (ch === "\"") { // tokenize string
let escapeNext = false;
let string = "";
pos += 1;
while (pos < input.length) {
if (escapeNext) {
string += input[pos];
escapeNext = false;
}
else if (input[pos] === "\\") {
escapeNext = true;
}
else if (input[pos] === "\"") {
tokens.push({type: "string", text: string});
pos += 1; // don't double-count this char as the start of a new quote
break;
}
else {
string += input[pos];
}
pos += 1;
}
}
else { // tokenize symbol
let symbol = "";
while (isSymbolChar(input[pos])) {
symbol += input[pos];
pos += 1;
}
tokens.push({type: "symbol", text: symbol});
}
}
return tokens;
}
function doParse(tokens) {
const form = [];
while (tokens.length > 0) {
const token = tokens.shift();
if (token.type === "startList") {
form.push(doParse(tokens));
}
else if (token.type === "endList") {
return form;
}
else {
form.push(token);
}
}
return form;
}
function parse(input) {
const tokens = tokenize(input);
return doParse(tokens);
}