JS web: use/import to Himera style web REPL.
[jackhill/mal.git] / js / reader.js
1 // Node vs browser behavior
2 var reader = {};
3 if (typeof module !== 'undefined') {
4 var types = require('./types');
5 } else {
6 var exports = reader;
7 }
8
9 function Reader(tokens) {
10 // copy
11 this.tokens = tokens.map(function (a) { return a; });
12 this.position = 0;
13 }
14 Reader.prototype.next = function() { return this.tokens[this.position++]; }
15 Reader.prototype.peek = function() { return this.tokens[this.position]; }
16
17 function tokenize(str) {
18 var re = /[\s,]*(~@|[\[\]{}()'`~^@]|"(?:\\.|[^\\"])*"|;.*|[^\s\[\]{}('"`,;)]*)/g;
19 var results = [];
20 while ((match = re.exec(str)[1]) != '') {
21 if (match[0] === ';') { continue; }
22 results.push(match);
23 }
24 return results;
25 }
26
27 function read_atom (reader) {
28 var token = reader.next();
29 //console.log("read_atom:", token);
30 if (token.match(/^-?[0-9]+$/)) {
31 return parseInt(token,10) // integer
32 } else if (token.match(/^-?[0-9][0-9.]*$/)) {
33 return parseFloat(token,10); // float
34 } else if (token[0] === "\"") {
35 return token.slice(1,token.length-1)
36 .replace(/\\"/g, '"')
37 .replace(/\\n/g, "\n"); // string
38 } else if (token === "nil") {
39 return null;
40 } else if (token === "true") {
41 return true;
42 } else if (token === "false") {
43 return false;
44 } else {
45 return types._symbol(token); // symbol
46 }
47 }
48
49 // read list of tokens
50 function read_list(reader, start, end) {
51 start = start || '(';
52 end = end || ')';
53 var ast = [];
54 var token = reader.next();
55 if (token !== start) {
56 throw new Error("expected '" + start + "'");
57 }
58 while ((token = reader.peek()) !== end) {
59 if (!token) {
60 throw new Error("expected '" + end + "', got EOF");
61 }
62 ast.push(read_form(reader));
63 }
64 reader.next();
65 return ast;
66 }
67
68 // read vector of tokens
69 function read_vector(reader) {
70 var lst = read_list(reader, '[', ']');
71 return types._vector.apply(null, lst);
72 }
73
74 // read hash-map key/value pairs
75 function read_hash_map(reader) {
76 var lst = read_list(reader, '{', '}');
77 return types._hash_map.apply(null, lst);
78 }
79
80 function read_form(reader) {
81 var token = reader.peek();
82 switch (token) {
83 // reader macros/transforms
84 case ';': return null; // Ignore comments
85 case '\'': reader.next();
86 return [types._symbol('quote'), read_form(reader)];
87 case '`': reader.next();
88 return [types._symbol('quasiquote'), read_form(reader)];
89 case '~': reader.next();
90 return [types._symbol('unquote'), read_form(reader)];
91 case '~@': reader.next();
92 return [types._symbol('splice-unquote'), read_form(reader)];
93 case '^': reader.next();
94 var meta = read_form(reader);
95 return [types._symbol('with-meta'), read_form(reader), meta];
96 case '@': reader.next();
97 return [types._symbol('deref'), read_form(reader)];
98
99 // list
100 case ')': throw new Error("unexpected ')'");
101 case '(': return read_list(reader);
102
103 // vector
104 case ']': throw new Error("unexpected ']'");
105 case '[': return read_vector(reader);
106
107 // hash-map
108 case '}': throw new Error("unexpected '}'");
109 case '{': return read_hash_map(reader);
110
111 // atom
112 default: return read_atom(reader);
113 }
114 }
115
116 function BlankException(msg) {
117 }
118
119 function read_str(str) {
120 var tokens = tokenize(str);
121 if (tokens.length === 0) { throw new BlankException(); }
122 return read_form(new Reader(tokens))
123 }
124
125 exports.Reader = reader.Reader = Reader;
126 exports.BlankException = reader.BlankException = BlankException;
127 exports.tokenize = reader.tokenize = tokenize;
128 exports.read_form = reader.read_form = read_form;
129 exports.read_str = reader.read_str = read_str;