DISABLE FDs (REMOVE ME).
[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.match(/^"(?:\\.|[^\\"])*"$/)) {
35 return token.slice(1,token.length-1)
36 .replace(/\\(.)/g, function (_, c) { return c === "n" ? "\n" : c})
37 } else if (token[0] === "\"") {
38 throw new Error("expected '\"', got EOF");
39 } else if (token[0] === ":") {
40 return types._keyword(token.slice(1));
41 } else if (token === "nil") {
42 return null;
43 } else if (token === "true") {
44 return true;
45 } else if (token === "false") {
46 return false;
47 } else {
48 return types._symbol(token); // symbol
49 }
50 }
51
52 // read list of tokens
53 function read_list(reader, start, end) {
54 start = start || '(';
55 end = end || ')';
56 var ast = [];
57 var token = reader.next();
58 if (token !== start) {
59 throw new Error("expected '" + start + "'");
60 }
61 while ((token = reader.peek()) !== end) {
62 if (!token) {
63 throw new Error("expected '" + end + "', got EOF");
64 }
65 ast.push(read_form(reader));
66 }
67 reader.next();
68 return ast;
69 }
70
71 // read vector of tokens
72 function read_vector(reader) {
73 var lst = read_list(reader, '[', ']');
74 return types._vector.apply(null, lst);
75 }
76
77 // read hash-map key/value pairs
78 function read_hash_map(reader) {
79 var lst = read_list(reader, '{', '}');
80 return types._hash_map.apply(null, lst);
81 }
82
83 function read_form(reader) {
84 var token = reader.peek();
85 switch (token) {
86 // reader macros/transforms
87 case ';': return null; // Ignore comments
88 case '\'': reader.next();
89 return [types._symbol('quote'), read_form(reader)];
90 case '`': reader.next();
91 return [types._symbol('quasiquote'), read_form(reader)];
92 case '~': reader.next();
93 return [types._symbol('unquote'), read_form(reader)];
94 case '~@': reader.next();
95 return [types._symbol('splice-unquote'), read_form(reader)];
96 case '^': reader.next();
97 var meta = read_form(reader);
98 return [types._symbol('with-meta'), read_form(reader), meta];
99 case '@': reader.next();
100 return [types._symbol('deref'), read_form(reader)];
101
102 // list
103 case ')': throw new Error("unexpected ')'");
104 case '(': return read_list(reader);
105
106 // vector
107 case ']': throw new Error("unexpected ']'");
108 case '[': return read_vector(reader);
109
110 // hash-map
111 case '}': throw new Error("unexpected '}'");
112 case '{': return read_hash_map(reader);
113
114 // atom
115 default: return read_atom(reader);
116 }
117 }
118
119 function BlankException(msg) {
120 }
121
122 function read_str(str) {
123 var tokens = tokenize(str);
124 if (tokens.length === 0) { throw new BlankException(); }
125 return read_form(new Reader(tokens))
126 }
127
128 exports.Reader = reader.Reader = Reader;
129 exports.BlankException = reader.BlankException = BlankException;
130 exports.tokenize = reader.tokenize = tokenize;
131 exports.read_form = reader.read_form = read_form;
132 exports.read_str = reader.read_str = read_str;