DISABLE FDs (REMOVE ME).
[jackhill/mal.git] / rpython / mal_types.py
1 import sys, copy, types as pytypes
2 IS_RPYTHON = sys.argv[0].endswith('rpython')
3
4 if IS_RPYTHON:
5 from rpython.rlib.listsort import TimSort
6 else:
7 import re
8
9 # General functions
10
11 class StringSort(TimSort):
12 def lt(self, a, b):
13 assert isinstance(a, unicode)
14 assert isinstance(b, unicode)
15 return a < b
16
17 def _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
22 if isinstance(a, MalSym) and isinstance(b, MalSym):
23 return a.value == b.value
24 elif isinstance(a, MalStr) and isinstance(b, MalStr):
25 return a.value == b.value
26 elif isinstance(a, MalInt) and isinstance(b, MalInt):
27 return a.value == b.value
28 elif _list_Q(a) or _vector_Q(a):
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
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
50 elif a is b:
51 return True
52 else:
53 throw_str("no = op defined for %s" % a.__class__.__name__)
54
55 def _sequential_Q(seq): return _list_Q(seq) or _vector_Q(seq)
56
57 def _clone(obj):
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):
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:
68 raise Exception("_clone on invalid type")
69
70 def _replace(match, sub, old_str):
71 new_str = u""
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
87 class MalException(Exception):
88 def __init__(self, object):
89 self.object = object
90
91 def throw_str(s):
92 raise MalException(MalStr(unicode(s)))
93
94
95 ### Parent types
96 class MalType(): pass
97 class MalMeta(MalType): pass
98
99 ### Scalars
100 class MalNil(MalType): pass
101 nil = MalNil()
102 def _nil_Q(exp):
103 assert isinstance(exp, MalType)
104 return exp is nil
105
106 class MalTrue(MalType): pass
107 true = MalTrue()
108 def _true_Q(exp):
109 assert isinstance(exp, MalType)
110 return exp is true
111
112 class MalFalse(MalType): pass
113 false = MalFalse()
114 def _false_Q(exp):
115 assert isinstance(exp, MalType)
116 return exp is false
117
118 # Numbers
119 class MalInt(MalType):
120 def __init__(self, value):
121 assert isinstance(value, int)
122 self.value = value
123 def _int_Q(exp):
124 assert isinstance(exp, MalType)
125 return exp.__class__ is MalInt
126
127 # String
128 class MalStr(MalType):
129 def __init__(self, value):
130 assert isinstance(value, unicode)
131 self.value = value
132 def __len__(self):
133 return len(self.value)
134 def _string_Q(exp):
135 assert isinstance(exp, MalType)
136 return exp.__class__ is MalStr and not _keyword_Q(exp)
137
138 # Keywords
139 # A specially prefixed string
140 def _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:
147 throw_str("_keyword called on non-string")
148 # Create keyword from unicode string
149 def _keywordu(strn):
150 assert isinstance(strn, unicode)
151 return MalStr(u"\u029e" + strn)
152 def _keyword_Q(exp):
153 if isinstance(exp, MalStr) and len(exp.value) > 0:
154 return exp.value[0] == u"\u029e"
155 else:
156 return False
157
158 # Symbols
159 class MalSym(MalMeta):
160 def __init__(self, value):
161 assert isinstance(value, unicode)
162 self.value = value
163 self.meta = nil
164 def _symbol(strn):
165 assert isinstance(strn, unicode)
166 return MalSym(strn)
167 def _symbol_Q(exp):
168 assert isinstance(exp, MalType)
169 return exp.__class__ is MalSym
170
171 # lists
172 class MalList(MalMeta):
173 def __init__(self, vals):
174 assert isinstance(vals, list)
175 self.values = vals
176 self.meta = nil
177 def append(self, val):
178 self.values.append(val)
179 def rest(self):
180 return MalList(self.values[1:])
181 def __len__(self):
182 return len(self.values)
183 def __getitem__(self, i):
184 assert isinstance(i, int)
185 return self.values[i]
186 def slice(self, start):
187 return MalList(self.values[start:len(self.values)])
188 def slice2(self, start, end):
189 assert end >= 0
190 return MalList(self.values[start:end])
191 def _list(*vals): return MalList(list(vals))
192 def _listl(lst): return MalList(lst)
193 def _list_Q(exp):
194 assert isinstance(exp, MalType)
195 return exp.__class__ is MalList
196
197 ### vectors
198 class MalVector(MalList):
199 pass
200 def _vector(*vals): return MalVector(list(vals))
201 def _vectorl(lst): return MalVector(lst)
202 def _vector_Q(exp):
203 assert isinstance(exp, MalType)
204 return exp.__class__ is MalVector
205
206 ### hash maps
207 class MalHashMap(MalMeta):
208 def __init__(self, dct):
209 self.dct = dct
210 self.meta = nil
211 def append(self, val):
212 self.dct.append(val)
213 def __getitem__(self, k):
214 assert isinstance(k, unicode)
215 if not isinstance(k, unicode):
216 throw_str("hash-map lookup by non-string/non-keyword")
217 return self.dct[k]
218 def __setitem__(self, k, v):
219 if not isinstance(k, unicode):
220 throw_str("hash-map key must be string or keyword")
221 assert isinstance(v, MalType)
222 self.dct[k] = v
223 return v
224 def _hash_mapl(kvs):
225 dct = {}
226 for i in range(0, len(kvs), 2):
227 k = kvs[i]
228 if not isinstance(k, MalStr):
229 throw_str("hash-map key must be string or keyword")
230 v = kvs[i+1]
231 dct[k.value] = v
232 return MalHashMap(dct)
233 def _hash_map_Q(exp):
234 assert isinstance(exp, MalType)
235 return exp.__class__ is MalHashMap
236
237 # Functions
238 # env import must happen after MalSym and MalList definitions to allow
239 # circular dependency
240 from env import Env
241 class MalFunc(MalMeta):
242 def __init__(self, fn, ast=None, env=None, params=None,
243 EvalFunc=None, ismacro=False):
244 if fn is None and EvalFunc is None:
245 throw_str("MalFunc requires either fn or EvalFunc")
246 self.fn = fn
247 self.ast = ast
248 self.env = env
249 self.params = params
250 self.EvalFunc = EvalFunc
251 self.ismacro = ismacro
252 self.meta = nil
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)
260 def _function_Q(exp):
261 assert isinstance(exp, MalType)
262 return exp.__class__ is MalFunc
263
264
265 # atoms
266 class MalAtom(MalMeta):
267 def __init__(self, value):
268 self.value = value
269 self.meta = nil
270 def get_value(self):
271 return self.value
272 def _atom(val): return MalAtom(val)
273 def _atom_Q(exp): return exp.__class__ is MalAtom