Add symbol and structure support
[fur] / interpreter.py
1 import collections
2 import sys
3
4 import crossplatform_ir_generation
5
6 Symbol = collections.namedtuple(
7     'Symbol',
8     (
9         'name',
10     ),
11 )
12
13 SYMBOL_TABLE = {}
14
15 class Environment(object):
16     def __init__(self, parent):
17         self.symbols = {}
18         self.parent = parent
19
20     def __getitem__(self, symbol):
21         if symbol in self.symbols:
22             return self.symbols[symbol]
23
24         if self.parent is None:
25             raise Exception('Symbol "{}" not found'.format(symbol))
26
27         return self.parent[symbol]
28
29     def __setitem__(self, symbol, value):
30         self.symbols[symbol] = value
31
32 def builtin_print(*outputs):
33     outputs = list(outputs)
34
35     for i in range(len(outputs)):
36         output = outputs[i]
37
38         if isinstance(output, bool):
39             outputs[i] = str(output).lower()
40
41     print(*outputs, end='')
42
43 BUILTIN_ENVIRONMENT = Environment(None)
44 BUILTIN_ENVIRONMENT.symbols = {
45     'false': False,
46     'pow': lambda base, exponent: pow(base, exponent),
47     'print': builtin_print,
48     'true': True,
49 }
50
51 def interpret(program):
52     environment = Environment(BUILTIN_ENVIRONMENT)
53
54     label_indices = {}
55
56     for i in range(len(program.entry_list)):
57         if isinstance(program.entry_list[i], crossplatform_ir_generation.CIRLabel):
58             if program.entry_list[i].label in label_indices:
59                 raise Exception('Label already in labels')
60
61             label_indices[program.entry_list[i].label] = i
62
63     program_counter = label_indices['__main__'] + 1
64     stack = []
65
66     while True:
67         while isinstance(program.entry_list[program_counter], crossplatform_ir_generation.CIRLabel):
68             program_counter += 1
69
70         instruction = program.entry_list[program_counter].instruction
71         argument = program.entry_list[program_counter].argument
72
73         if instruction in ('add', 'idiv', 'mod', 'mul', 'sub'):
74             assert argument == 2
75             right = stack.pop()
76             left = stack.pop()
77             assert isinstance(left, int)
78             assert isinstance(right, int)
79
80             if instruction == 'add':
81                 result = left + right
82             elif instruction == 'idiv':
83                 result = left // right
84             elif instruction == 'mod':
85                 result = left % right
86             elif instruction == 'mul':
87                 result = left * right
88             elif instruction == 'sub':
89                 result = left - right
90
91             stack.append(result)
92
93         elif instruction in ('gt', 'gte', 'lt', 'lte'):
94             assert argument == 2
95             right = stack.pop()
96             left = stack.pop()
97             assert isinstance(left, int)
98             assert isinstance(right, int)
99
100             if instruction == 'gt':
101                 result = left > right
102             elif instruction == 'gte':
103                 result = left >= right
104             elif instruction == 'lt':
105                 result = left < right
106             elif instruction == 'lte':
107                 result = left <= right
108
109             stack.append(result)
110
111         elif instruction in ('eq', 'neq'):
112             assert argument == 2
113             right = stack.pop()
114             left = stack.pop()
115
116             if instruction == 'eq':
117                 result = left == right
118             elif instruction == 'neq':
119                 result = left != right
120
121             stack.append(result)
122
123         elif instruction == 'call':
124             assert isinstance(argument, int)
125             args = []
126
127             f = stack.pop()
128
129             for i in range(argument):
130                 args.append(stack.pop())
131
132             args = list(reversed(args))
133
134             stack.append(f(*args))
135
136         elif instruction == 'concat':
137             assert argument == 2
138             right = stack.pop()
139             left = stack.pop()
140             assert isinstance(left, str)
141             assert isinstance(right, str)
142
143             stack.append(left + right)
144
145         elif instruction == 'drop':
146             assert argument is None
147             stack.pop()
148
149         elif instruction == 'end':
150             assert argument is None
151             sys.exit(0)
152
153         elif instruction == 'field':
154             key = stack.pop()
155             structure = stack.pop()
156             sentinel = object()
157             result = getattr(structure, key.name, sentinel)
158
159             assert result is not sentinel
160
161             stack.append(result)
162
163         elif instruction == 'get':
164             index = stack.pop()
165             assert isinstance(argument, int)
166             xs = stack.pop()
167             assert isinstance(xs, list)
168             stack.append(xs[index])
169
170         elif instruction == 'jump_if_false':
171             program_counter = label_indices[argument]
172
173         elif instruction == 'list':
174             assert isinstance(argument, int)
175
176             result = []
177
178             for i in range(argument):
179                 result.append(stack.pop())
180
181             stack.append(list(reversed(result)))
182
183         elif instruction == 'neg':
184             stack.append(-stack.pop())
185
186         elif instruction == 'pop':
187             assert argument.startswith('sym(')
188             assert argument.endswith(')')
189             environment[argument[4:-1]] = stack.pop()
190
191         elif instruction == 'push':
192             assert argument.startswith('sym(')
193             assert argument.endswith(')')
194             stack.append(environment[argument[4:-1]])
195
196         elif instruction == 'push_integer':
197             assert isinstance(argument, int)
198             stack.append(argument)
199
200         elif instruction == 'push_string':
201             assert argument.startswith('"')
202             assert argument.endswith('"')
203             stack.append(argument[1:-1].encode('utf-8').decode('unicode_escape'))
204
205         elif instruction == 'push_symbol':
206             assert argument.startswith('sym(')
207             assert argument.endswith(')')
208
209             result = SYMBOL_TABLE.get(argument)
210             if not result:
211                 result = Symbol(name=argument[4:-1])
212                 SYMBOL_TABLE[argument] = result
213
214             stack.append(result)
215
216         elif instruction == 'structure':
217             kvps = []
218
219             for i in range(argument):
220                 key = stack.pop()
221                 value = stack.pop()
222                 kvps.append((key.name, value))
223
224             keys = tuple(reversed(list(k for k,v in kvps)))
225             result = collections.namedtuple('Structure', keys)(**dict(kvps))
226
227             stack.append(result)
228
229         else:
230             raise Exception('Instruction "{}" not supported (argument {}).'.format(
231                 instruction,
232                 argument,
233             ))
234
235         program_counter += 1