DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / rpython / mal_types.py
CommitLineData
80320efc 1import sys, copy, types as pytypes
6d3fc1be
JM
2IS_RPYTHON = sys.argv[0].endswith('rpython')
3
4if IS_RPYTHON:
5 from rpython.rlib.listsort import TimSort
6else:
7 import re
80320efc 8
b0a9121d
JM
9# General functions
10
6d3fc1be
JM
11class StringSort(TimSort):
12 def lt(self, a, b):
13 assert isinstance(a, unicode)
14 assert isinstance(b, unicode)
15 return a < b
16
b0a9121d
JM
17def _equal_Q(a, b):
18 assert isinstance(a, MalType) and isinstance(b, MalType)
19 ota, otb = a.__class__, b.__class__
20 if not (ota is otb or (_sequential_Q(a) and _sequential_Q(b))):
21 return False
23fa1b11 22 if isinstance(a, MalSym) and isinstance(b, MalSym):
b0a9121d 23 return a.value == b.value
23fa1b11 24 elif isinstance(a, MalStr) and isinstance(b, MalStr):
b0a9121d 25 return a.value == b.value
23fa1b11 26 elif isinstance(a, MalInt) and isinstance(b, MalInt):
b0a9121d 27 return a.value == b.value
8855a05a 28 elif _list_Q(a) or _vector_Q(a):
b0a9121d
JM
29 if len(a) != len(b): return False
30 for i in range(len(a)):
31 if not _equal_Q(a[i], b[i]): return False
32 return True
6d3fc1be
JM
33 elif _hash_map_Q(a):
34 assert isinstance(a, MalHashMap)
35 assert isinstance(b, MalHashMap)
36 akeys = a.dct.keys()
37 bkeys = b.dct.keys()
38 if len(akeys) != len(bkeys): return False
39
40 StringSort(akeys).sort()
41 StringSort(bkeys).sort()
42 for i in range(len(akeys)):
43 ak, bk = akeys[i], bkeys[i]
44 assert isinstance(ak, unicode)
45 assert isinstance(bk, unicode)
46 if ak != bk: return False
47 av, bv = a.dct[ak], b.dct[bk]
48 if not _equal_Q(av, bv): return False
49 return True
b0a9121d 50 elif a is b:
b0a9121d
JM
51 return True
52 else:
9be6d5a6 53 throw_str("no = op defined for %s" % a.__class__.__name__)
b0a9121d 54
8855a05a 55def _sequential_Q(seq): return _list_Q(seq) or _vector_Q(seq)
b0a9121d 56
7f714804 57def _clone(obj):
7f714804
JM
58 if isinstance(obj, MalFunc):
59 return MalFunc(obj.fn, obj.ast, obj.env, obj.params,
60 obj.EvalFunc, obj.ismacro)
61 elif isinstance(obj, MalList):
ab02c5bb
JM
62 return obj.__class__(obj.values)
63 elif isinstance(obj, MalHashMap):
64 return MalHashMap(obj.dct)
65 elif isinstance(obj, MalAtom):
66 return MalAtom(obj.value)
67 else:
7f714804 68 raise Exception("_clone on invalid type")
80320efc
JM
69
70def _replace(match, sub, old_str):
f0cd1318 71 new_str = u""
80320efc
JM
72 idx = 0
73 while idx < len(old_str):
74 midx = old_str.find(match, idx)
75 if midx < 0: break
76 assert midx >= 0 and midx < len(old_str)
77 new_str = new_str + old_str[idx:midx]
78 new_str = new_str + sub
79 idx = midx + len(match)
80 new_str = new_str + old_str[idx:]
81 return new_str
82
83#
84# Mal Types
85#
86
9be6d5a6
JM
87class MalException(Exception):
88 def __init__(self, object):
89 self.object = object
90
91def throw_str(s):
92 raise MalException(MalStr(unicode(s)))
93
94
7f714804 95### Parent types
80320efc 96class MalType(): pass
ab02c5bb 97class MalMeta(MalType): pass
80320efc
JM
98
99### Scalars
100class MalNil(MalType): pass
101nil = MalNil()
102def _nil_Q(exp):
103 assert isinstance(exp, MalType)
104 return exp is nil
105
106class MalTrue(MalType): pass
107true = MalTrue()
108def _true_Q(exp):
109 assert isinstance(exp, MalType)
110 return exp is true
111
112class MalFalse(MalType): pass
113false = MalFalse()
114def _false_Q(exp):
115 assert isinstance(exp, MalType)
116 return exp is false
117
118# Numbers
119class MalInt(MalType):
120 def __init__(self, value):
121 assert isinstance(value, int)
122 self.value = value
123def _int_Q(exp):
124 assert isinstance(exp, MalType)
125 return exp.__class__ is MalInt
126
127# String
128class MalStr(MalType):
129 def __init__(self, value):
f0cd1318 130 assert isinstance(value, unicode)
80320efc
JM
131 self.value = value
132 def __len__(self):
133 return len(self.value)
134def _string_Q(exp):
135 assert isinstance(exp, MalType)
6791e640 136 return exp.__class__ is MalStr and not _keyword_Q(exp)
80320efc 137
f0cd1318
JM
138# Keywords
139# A specially prefixed string
140def _keyword(mstr):
141 assert isinstance(mstr, MalType)
142 if isinstance(mstr, MalStr):
143 val = mstr.value
144 if val[0] == u"\u029e": return mstr
145 else: return MalStr(u"\u029e" + val)
146 else:
9be6d5a6 147 throw_str("_keyword called on non-string")
f0cd1318
JM
148# Create keyword from unicode string
149def _keywordu(strn):
150 assert isinstance(strn, unicode)
151 return MalStr(u"\u029e" + strn)
152def _keyword_Q(exp):
6791e640 153 if isinstance(exp, MalStr) and len(exp.value) > 0:
9be6d5a6
JM
154 return exp.value[0] == u"\u029e"
155 else:
156 return False
f0cd1318 157
ab02c5bb
JM
158# Symbols
159class MalSym(MalMeta):
160 def __init__(self, value):
161 assert isinstance(value, unicode)
162 self.value = value
163 self.meta = nil
164def _symbol(strn):
165 assert isinstance(strn, unicode)
166 return MalSym(strn)
167def _symbol_Q(exp):
168 assert isinstance(exp, MalType)
169 return exp.__class__ is MalSym
170
80320efc 171# lists
ab02c5bb 172class MalList(MalMeta):
80320efc
JM
173 def __init__(self, vals):
174 assert isinstance(vals, list)
175 self.values = vals
ab02c5bb 176 self.meta = nil
80320efc
JM
177 def append(self, val):
178 self.values.append(val)
b0a9121d 179 def rest(self):
e6cfacb4 180 return MalList(self.values[1:])
2dd89a96
JM
181 def __len__(self):
182 return len(self.values)
e6cfacb4
JM
183 def __getitem__(self, i):
184 assert isinstance(i, int)
185 return self.values[i]
e0529eb2
JM
186 def slice(self, start):
187 return MalList(self.values[start:len(self.values)])
188 def slice2(self, start, end):
189 assert end >= 0
b0a9121d 190 return MalList(self.values[start:end])
80320efc 191def _list(*vals): return MalList(list(vals))
8855a05a 192def _listl(lst): return MalList(lst)
80320efc
JM
193def _list_Q(exp):
194 assert isinstance(exp, MalType)
195 return exp.__class__ is MalList
196
80320efc 197### vectors
8855a05a
JM
198class MalVector(MalList):
199 pass
200def _vector(*vals): return MalVector(list(vals))
201def _vectorl(lst): return MalVector(lst)
202def _vector_Q(exp):
203 assert isinstance(exp, MalType)
204 return exp.__class__ is MalVector
205
206### hash maps
ab02c5bb 207class MalHashMap(MalMeta):
8855a05a 208 def __init__(self, dct):
8855a05a 209 self.dct = dct
ab02c5bb 210 self.meta = nil
8855a05a
JM
211 def append(self, val):
212 self.dct.append(val)
213 def __getitem__(self, k):
214 assert isinstance(k, unicode)
23fa1b11
JM
215 if not isinstance(k, unicode):
216 throw_str("hash-map lookup by non-string/non-keyword")
8855a05a
JM
217 return self.dct[k]
218 def __setitem__(self, k, v):
23fa1b11
JM
219 if not isinstance(k, unicode):
220 throw_str("hash-map key must be string or keyword")
8855a05a
JM
221 assert isinstance(v, MalType)
222 self.dct[k] = v
223 return v
224def _hash_mapl(kvs):
225 dct = {}
226 for i in range(0, len(kvs), 2):
227 k = kvs[i]
23fa1b11
JM
228 if not isinstance(k, MalStr):
229 throw_str("hash-map key must be string or keyword")
8855a05a
JM
230 v = kvs[i+1]
231 dct[k.value] = v
232 return MalHashMap(dct)
8855a05a
JM
233def _hash_map_Q(exp):
234 assert isinstance(exp, MalType)
235 return exp.__class__ is MalHashMap
b0a9121d
JM
236
237# Functions
238# env import must happen after MalSym and MalList definitions to allow
239# circular dependency
240from env import Env
ab02c5bb 241class MalFunc(MalMeta):
b0a9121d 242 def __init__(self, fn, ast=None, env=None, params=None,
c9fe67a8 243 EvalFunc=None, ismacro=False):
b0a9121d 244 if fn is None and EvalFunc is None:
9be6d5a6 245 throw_str("MalFunc requires either fn or EvalFunc")
b0a9121d 246 self.fn = fn
b0a9121d
JM
247 self.ast = ast
248 self.env = env
249 self.params = params
250 self.EvalFunc = EvalFunc
c9fe67a8 251 self.ismacro = ismacro
7f714804 252 self.meta = nil
b0a9121d
JM
253 def apply(self, args):
254 if self.EvalFunc:
255 return self.EvalFunc(self.ast, self.gen_env(args))
256 else:
257 return self.fn(args)
258 def gen_env(self, args):
259 return Env(self.env, self.params, args)
260def _function_Q(exp):
261 assert isinstance(exp, MalType)
262 return exp.__class__ is MalFunc
263
7f714804
JM
264
265# atoms
ab02c5bb
JM
266class MalAtom(MalMeta):
267 def __init__(self, value):
268 self.value = value
269 self.meta = nil
fdf80511
JM
270 def get_value(self):
271 return self.value
7f714804
JM
272def _atom(val): return MalAtom(val)
273def _atom_Q(exp): return exp.__class__ is MalAtom