All: TCO let* and quasiquote.
[jackhill/mal.git] / js / types.js
1 // Node vs browser behavior
2 var types = {};
3 if (typeof module === 'undefined') {
4 var exports = types;
5 }
6
7 // General fucnctions
8
9 function _obj_type(obj) {
10 if (_symbol_Q(obj)) { return 'symbol'; }
11 else if (_list_Q(obj)) { return 'list'; }
12 else if (_vector_Q(obj)) { return 'vector'; }
13 else if (_hash_map_Q(obj)) { return 'hash-map'; }
14 else if (_nil_Q(obj)) { return 'nil'; }
15 else if (_true_Q(obj)) { return 'true'; }
16 else if (_false_Q(obj)) { return 'false'; }
17 else if (_atom_Q(obj)) { return 'atom'; }
18 else {
19 switch (typeof(obj)) {
20 case 'number': return 'number';
21 case 'function': return 'function';
22 case 'string': return 'string';
23 default: throw new Error("Unknown type '" + typeof(obj) + "'");
24 }
25 }
26 }
27
28 function _sequential_Q(lst) { return _list_Q(lst) || _vector_Q(lst); }
29
30
31 function _equal_Q (a, b) {
32 var ota = _obj_type(a), otb = _obj_type(b);
33 if (!(ota === otb || (_sequential_Q(a) && _sequential_Q(b)))) {
34 return false;
35 }
36 switch (ota) {
37 case 'symbol': return a.value === b.value;
38 case 'list':
39 case 'vector':
40 if (a.length !== b.length) { return false; }
41 for (var i=0; i<a.length; i++) {
42 if (! _equal_Q(a[i], b[i])) { return false; }
43 }
44 return true;
45 case 'hash-map':
46 var akeys = Object.keys(a).sort(),
47 bkeys = Object.keys(b).sort();
48 if (akeys.length !== bkeys.length) { return false; }
49 for (var i=0; i<akeys.length; i++) {
50 if (akeys[i] !== bkeys[i]) { return false; }
51 if (! equal_Q(a[akeys[i]], b[bkeys[i]])) { return false; }
52 }
53 return true;
54 default:
55 return a === b;
56 }
57 }
58
59
60 function _clone (obj) {
61 var new_obj;
62 switch (_obj_type(obj)) {
63 case 'list':
64 new_obj = obj.slice(0);
65 break;
66 case 'vector':
67 new_obj = obj.slice(0);
68 new_obj.__isvector__ = true;
69 break;
70 case 'hash-map':
71 new_obj = {};
72 for (var k in obj) {
73 if (obj.hasOwnProperty(k)) { new_obj[k] = obj[k]; }
74 }
75 break;
76 case 'function':
77 new_obj = obj.clone();
78 break;
79 default:
80 throw new Error("clone of non-collection: " + _obj_type(obj));
81 }
82 return new_obj;
83 }
84
85
86 // Scalars
87 function _nil_Q(a) { return a === null ? true : false; }
88 function _true_Q(a) { return a === true ? true : false; }
89 function _false_Q(a) { return a === false ? true : false; }
90
91
92 // Symbols
93 function Symbol(name) {
94 this.value = name;
95 return this;
96 }
97 Symbol.prototype.toString = function() { return this.value; }
98 function _symbol(name) { return new Symbol(name); }
99 function _symbol_Q(obj) { return obj instanceof Symbol; }
100
101
102 // Functions
103 function _function(Eval, Env, ast, env, params) {
104 var fn = function() {
105 return Eval(ast, new Env(env, params, arguments));
106 };
107 fn.__meta__ = null;
108 fn.__ast__ = ast;
109 fn.__gen_env__ = function(args) { return new Env(env, params, args); };
110 fn._ismacro_ = false;
111 return fn;
112 }
113 function _function_Q(obj) { return typeof obj == "function"; }
114 Function.prototype.clone = function() {
115 var that = this;
116 var temp = function () { return that.apply(this, arguments); };
117 for( key in this ) {
118 temp[key] = this[key];
119 }
120 return temp;
121 };
122
123
124 // Lists
125 function _list() { return Array.prototype.slice.call(arguments, 0); }
126 function _list_Q(obj) { return Array.isArray(obj) && !obj.__isvector__; }
127
128
129 // Vectors
130 function _vector() {
131 var v = Array.prototype.slice.call(arguments, 0);
132 v.__isvector__ = true;
133 return v;
134 }
135 function _vector_Q(obj) { return Array.isArray(obj) && !!obj.__isvector__; }
136
137
138
139 // Hash Maps
140 function _hash_map() {
141 if (arguments.length % 2 === 1) {
142 throw new Error("Odd number of hash map arguments");
143 }
144 var args = [{}].concat(Array.prototype.slice.call(arguments, 0));
145 return _assoc_BANG.apply(null, args);
146 }
147 function _hash_map_Q(hm) {
148 return typeof hm === "object" &&
149 !Array.isArray(hm) &&
150 !(hm === null) &&
151 !(hm instanceof Atom);
152 }
153 function _assoc_BANG(hm) {
154 if (arguments.length % 2 !== 1) {
155 throw new Error("Odd number of assoc arguments");
156 }
157 for (var i=1; i<arguments.length; i+=2) {
158 var ktoken = arguments[i],
159 vtoken = arguments[i+1];
160 // TODO: support more than string keys
161 //if (list_Q(ktoken) && hash_map_Q(ktoken)) {
162 // throw new Error("expected hash-map key atom, got collection");
163 //}
164 if (typeof ktoken !== "string") {
165 throw new Error("expected hash-map key string, got: " + (typeof ktoken));
166 }
167 hm[ktoken] = vtoken;
168 }
169 return hm;
170 }
171 function _dissoc_BANG(hm) {
172 for (var i=1; i<arguments.length; i++) {
173 var ktoken = arguments[i];
174 delete hm[ktoken];
175 }
176 return hm;
177 }
178
179
180 // Atoms
181 function Atom(val) { this.val = val; }
182 function _atom(val) { return new Atom(val); }
183 function _atom_Q(atm) { return atm instanceof Atom; }
184
185
186 // Exports
187 exports._obj_type = types._obj_type = _obj_type;
188 exports._sequential_Q = types._sequential_Q = _sequential_Q;
189 exports._equal_Q = types._equal_Q = _equal_Q;
190 exports._clone = types._clone = _clone;
191 exports._nil_Q = types._nil_Q = _nil_Q;
192 exports._true_Q = types._true_Q = _true_Q;
193 exports._false_Q = types._false_Q = _false_Q;
194 exports._symbol = types._symbol = _symbol;
195 exports._symbol_Q = types._symbol_Q = _symbol_Q;
196 exports._function = types._function = _function;
197 exports._function_Q = types._function_Q = _function_Q;
198 exports._list = types._list = _list;
199 exports._list_Q = types._list_Q = _list_Q;
200 exports._vector = types._vector = _vector;
201 exports._vector_Q = types._vector_Q = _vector_Q;
202 exports._hash_map = types._hash_map = _hash_map;
203 exports._hash_map_Q = types._hash_map_Q = _hash_map_Q;
204 exports._assoc_BANG = types._assoc_BANG = _assoc_BANG;
205 exports._dissoc_BANG = types._dissoc_BANG = _dissoc_BANG;
206 exports._atom = types._atom = _atom;
207 exports._atom_Q = types._atom_Q = _atom_Q;