-
Notifications
You must be signed in to change notification settings - Fork 2
/
convert.py
163 lines (152 loc) · 5.15 KB
/
convert.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
from flatten import *
POS_SIZE = 2**23 - 1
NEG_SIZE = -2**23
OPTIMIZE = True
OPTIMIZERS = {
'set': 'SET',
'setglobal': 'SET_GLOBAL',
'local': 'SET_LOCAL',
'get': 'GET',
'getglobal': 'GET_GLOBAL',
'return': 'RETURN',
'recurse': 'RECURSE',
'drop': 'DROP',
'dup': 'DUP',
'[]': 'NEW_LIST',
'{}': 'NEW_DICT',
'swap': 'SWAP',
'rot': 'ROT',
'over': 'OVER',
'pop-from': 'POP_FROM',
'push-to': 'PUSH_TO',
'push-through': 'PUSH_THROUGH',
'has': 'HAS_DICT',
'get-from': 'GET_DICT',
'set-to': 'SET_DICT',
'raise': 'RAISE',
'reraise': 'RERAISE',
'call': 'CALL',
}
ARGED_OPT = set('SET SET_LOCAL SET_GLOBAL GET GET_GLOBAL'.split())
positional_instructions = set('JMP JMPZ LABDA JMPEQ JMPNE ENTER_ERRHAND'.split())
def convert(filename, flat):
bytecode = [SingleInstruction('SOURCE_FILE', String(None, '"' + filename))]
for k in flat:
if isinstance(k, SingleInstruction):
bytecode.append(k)
elif isinstance(k, Code):
for w in k.words:
if isinstance(w, ProperWord):
if OPTIMIZE and w.value in OPTIMIZERS:
if OPTIMIZERS[w.value] in ARGED_OPT:
if bytecode and bytecode[-1].opcode == 'PUSH_LITERAL' and isinstance(bytecode[-1].ref, Ident):
s = bytecode.pop().ref
else:
bytecode.append(SingleInstruction('PUSH_WORD', w))
continue
else:
s = 0
bytecode.append(SingleInstruction(OPTIMIZERS[w.value], s))
elif w.value == 'for':
mstart = Marker()
mend = Marker()
bytecode.extend([
mstart,
SingleInstruction('DUP', 0),
SingleInstruction('JMPZ', mend),
SingleInstruction('CALL', 0),
SingleInstruction('JMP', mstart),
mend,
SingleInstruction('DROP', 0)
])
elif w.value == '(:split:)':
mparent = Marker()
mchild = Marker()
bytecode.extend([
SingleInstruction('LABDA', mparent),
SingleInstruction('JMP', mchild),
mparent,
SingleInstruction('RETURN', 0),
mchild,
])
elif w.value == '\xce\xbb': #U+03BB GREEK SMALL LETTER LAMDA
for i in range(len(bytecode) - 1, -1, -1):
l = bytecode[i]
if isinstance(l, SingleInstruction) and l.opcode == 'PUSH_WORD' and l.ref.value == ';':
l.opcode = 'LABDA'
l.ref = Marker()
bytecode.extend([
SingleInstruction('RETURN', 0),
l.ref,
])
break
else:
raise DejaSyntaxError('Inline lambda without closing semi-colon.')
elif '!' in w.value:
if w.value.startswith('!'):
w.value = 'eva' + w.value
if w.value.endswith('!'):
w.value = w.value[:-1]
args = w.value.split('!')
base = args.pop(0)
bytecode.extend(SingleInstruction('PUSH_LITERAL', x) for x in reversed(args))
bytecode.extend([
SingleInstruction('PUSH_WORD', base),
SingleInstruction('GET_DICT', 0),
SingleInstruction('CALL', 0)
])
else:
bytecode.append(SingleInstruction('PUSH_WORD', w))
elif isinstance(w, Number) and w.value.is_integer() and w.value <= POS_SIZE and w.value >= NEG_SIZE:
bytecode.append(SingleInstruction('PUSH_INTEGER', int(w.value)))
else:
bytecode.append(SingleInstruction('PUSH_LITERAL', w))
elif isinstance(k, Marker):
bytecode.append(k)
elif isinstance(k, GoTo):
bytecode.append(SingleInstruction('JMP', k.index))
elif isinstance(k, Branch):
bytecode.append(SingleInstruction('JMPZ', k.index))
elif isinstance(k, LabdaNode):
bytecode.append(SingleInstruction('LABDA', k.index))
bytecode.append(SingleInstruction('RETURN', 0))
return bytecode
def is_return(node):
return isinstance(node, SingleInstruction) and node.opcode == 'RETURN'
def is_jump_to(node, marker):
return isinstance(node, SingleInstruction) and node.opcode == 'JMP' and node.ref is marker
def is_pass(node):
return isinstance(node, SingleInstruction) and node.opcode == 'PUSH_WORD' and (node.ref == 'pass' or (isinstance(node.ref, ProperWord) and node.ref.value == 'pass'))
def is_linenr(node):
return isinstance(node, SingleInstruction) and node.opcode == 'LINE_NUMBER'
def get(l, i):
try:
return l[i]
except IndexError:
return None
def optimize(flattened): #optimize away superfluous RETURN statements
for i, instruction in reversed(list(enumerate(flattened))):
if (is_return(instruction) and (is_return(get(flattened, i + 1)) or (isinstance(get(flattened, i + 1), Marker) and is_return(get(flattened, i + 2))))
or isinstance(get(flattened, i + 1), Marker) and is_jump_to(instruction, get(flattened, i + 1))
or isinstance(get(flattened, i + 2), Marker) and isinstance(get(flattened, i + 1), Marker) and is_jump_to(instruction, get(flattened, i + 2))
or is_pass(instruction)
or is_linenr(instruction) and is_linenr(get(flattened, i + 1))
):
flattened.pop(i)
return flattened
def refine(flattened): #removes all markers and replaces them by indices
#first pass: fill dictionary
memo = {}
i = 0
while i < len(flattened):
item = flattened[i]
if isinstance(item, Marker):
memo[item] = i
del flattened[i]
else:
i += 1
#second pass: change all goto and branches
for i, item in enumerate(flattened):
if item.opcode in positional_instructions:
item.ref = memo[item.ref] - i
return flattened