-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathchip.py
executable file
·384 lines (353 loc) · 15 KB
/
chip.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
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
#!/usr/bin/python3 -bb
#coding=utf-8
#author Derek Anderson
#interpreter v0.1.5
VERSION = '0.1.5'
from sys import argv, stdin, stdout, stderr, exit
from getopt import getopt, GetoptError
from argparse import ArgumentParser, ArgumentTypeError, RawDescriptionHelpFormatter
import random, time, termios, tty
import chiplib
class ConfigDict(dict):
def __getattr__(self, name):
return self[name]
def __setattr__(self, name, value):
if name in self:
self[name] = value
else:
raise KeyError(name)
Cfg = ConfigDict(
CUTOFF_BYTES=-1,
ESC_SEQS=tuple(),
GENERATOR=None,
IGNORE_EOF=False,
NEWLINE=False,
NO_BUFFER=False,
STORAGE=None,
VERBOSE=False,
WITHOUT_STDIN=False
)
def prepareGenerator(template):
def inputGenerator():
digits = '0123456789ABCDEF'
age = 0
while(True):
value = list(template.upper())
if value[0] == 'I':
value[0] = digits[(age >> 4) & 15]
elif value[0] == 'J':
value[0] = digits[~((age >> 4) & 15)]
elif value[0] == 'K':
value[0] = random.choice(digits)
if value[1] == 'I':
value[1] = digits[age & 15]
elif value[1] == 'J':
value[1] = digits[~(age & 15)]
elif value[1] == 'K':
value[1] = random.choice(digits)
yield bytes([int(''.join(value), 16)])
age = (age + 1) % 256
return inputGenerator()
def prepareStorage(mode):
valid_modes = {'q', 's'} # 'm' not yet implemented, will need 2 heads. Ditto for 'ss', 'qq', 'qs', and 'sq'.
if not (set(mode) <= valid_modes):
raise ArgumentTypeError("'%s' is not a valid storage mode. Valid modes are: %s" % (mode, str(valid_modes).strip('{}')))
return mode
def init():
"""Perform initialization tasks"""
justify = max(len(cls.__name__) for cls in chiplib.lexmap_r.keys()) + 2
valid_elements = 'supported elements:\n '+'Type'.ljust(justify)+'Lexemes\n'
for cls, lexes in sorted([(cls.__name__, lexes) for cls, lexes in chiplib.lexmap_r.items()]):
valid_elements += ' %s%s\n' % (cls.ljust(justify), ' '.join(sorted(lexes)))
parser = ArgumentParser(usage='%(prog)s [options] <chipspec>', conflict_handler='resolve',
formatter_class=RawDescriptionHelpFormatter, epilog=valid_elements)
# Positional args
parser.add_argument('chipspec', action='store', type=str, nargs='?', metavar='chipspec', help='A Chip specification file.')
# Optional args
parser.add_argument('-c', '--cutoff', action='store', dest='cutoff_bytes', default=-1, type=int, metavar='N', help='Stop '+
'processing and halt after N bytes; applies to both stdin and generated bytes.')
parser.add_argument('-e', '--escape', action='append', dest='esc_seqs', metavar='SEQ', help='Use these characters as escape '+
'sequences for input. A default of ^C and ^D are included in immediate mode (-i) when '+
'stdin is a tty, unless an empty esc sequence is provided. If a sequence is multiple '+
'characters, they must be entered in order. All characters except the last are echoed to '+
'the script. Multiple sequences may be defined.')
parser.add_argument('-g', '--generate', action='store', dest='generator', default='', type=str, metavar='XX', help='When input '+
'is exhausted, instead of terminating, generate values defined by XX. XX is two digits '+
"of base 16, or special characters 'I', 'J', or 'K'. 'I' means count up, 'J' means "+
"count down, 'K' means random value. Place values are respected, so 'I5' means that the "+
'low four bits are always 0101, and the upper four bits will increment every 16 cycles. '+
'Any counting starts at the end of stdin. Case insensitive.')
parser.add_argument('-h', '--help', action='help', help='Show this help message and exit.')
parser.add_argument('-i', '--immediate', action='store_true', dest='no_buffer', default=False, help='Flushes stdout immediately '+
'after each cycle, otherwise, default buffering is used. Also sets input to raw mode, '+
'rather than cbreak mode.')
parser.add_argument('-m', '--storage-mode', action='store', dest='storage', default='s', type=prepareStorage, metavar='MODE',
help="Set the storage to this mode. 's' means stack, 'q' means queue, 'm' "+
'means addressed memory (not yet implemented). Stack is the default mode.')
parser.add_argument('-n', '--extra-newline', action='store_true', dest='extra_newline', default=False, help='Provides an extra '+
'newline to stdout at the end of execution regardless of the method of termination.')
parser.add_argument('-o', '--generate-ones', action='store_const', dest='generator', const='FF', help='When input is exhausted, '+
'instead of terminating, generate one values (0xff) until the circuit terminates '+
'itself. Equivalent to --generate=FF.')
parser.add_argument('-v', '--verbose', action='count', dest='verbose', default=0, help='Enables verbose output; effect is '+
'cumulative. Level 1 shows input/output for each cycle. Level 2 adds the parsed '+
'circuitry and statistics. Level 3 shows a heatmap (using ANSI colors).')
parser.add_argument('-V', '--version', action='version', version=('Chip interpreter v'+VERSION), help="Show interpreter's "+
'version number and exit.')
parser.add_argument('-w', '--without-stdin', action='store_true', dest='without', default=False, help='The program uses the '+
'default value (set by --generate), instead of reading from STDIN. By itself, '+
'implies --generate=00.')
parser.add_argument('-z', '--generate-zeroes', action='store_const', dest='generator', const='00', help='When input is '+
'exhausted, instead of terminating, generate zero values (0x00) until the circuit '+
'terminates itself. Equivalent to --generate=00.')
args = parser.parse_args()
if args.without and not args.generator:
args.generator = '00'
Cfg.CUTOFF_BYTES = args.cutoff_bytes
Cfg.IGNORE_EOF = bool(args.generator)
Cfg.GENERATOR = prepareGenerator(args.generator)
Cfg.NEWLINE = args.extra_newline
Cfg.NO_BUFFER = args.no_buffer
Cfg.STORAGE = args.storage
Cfg.VERBOSE = args.verbose
Cfg.WITHOUT_STDIN = args.without
esc_seqs_str = []
if Cfg.NO_BUFFER and stdin.isatty():
esc_seqs_str = ['\x03', '\x04'] # By default, ^C or ^D will cause exit
if args.esc_seqs:
if '' in args.esc_seqs:
esc_seqs_str = []
esc_seqs_str += [seq for seq in args.esc_seqs if seq]
Cfg.ESC_SEQS = tuple(set([seq.encode('utf-8').decode('unicode_escape').encode('utf-8') for seq in esc_seqs_str]))
if (Cfg.NO_BUFFER and stdin.isatty()) or args.esc_seqs:
stderr.write('Escape sequences are: ' + repr(Cfg.ESC_SEQS) + '\n')
if args.chipspec:
with open(args.chipspec, 'r') as f:
arr = f.readlines()
if len(arr) > 0 and arr[0].startswith("#!"):
# Its a shebang, probably. Remove the whole line.
arr = arr[1:]
return ''.join(arr)
else:
parser.print_help()
exit(2)
def setup(ospec):
"""Prepare the circuitry from the text specification"""
spec = list(ospec)
# Cleanup comments and check symbols
charlist = ''.join(chiplib.lexmap.keys())
blockcomment = False
layercomment = False
for char in range(len(spec)):
if spec[char] == '\n':
layercomment = False
elif blockcomment and spec[char] == ';':
blockcomment = False
spec[char] = ' '
elif spec[char] == '=' and (char == 0 or spec[char-1] == '\n'):
layercomment = True
elif (not layercomment) and spec[char] == ':':
blockcomment = True
spec[char] = ' '
elif blockcomment or layercomment:
spec[char] = ' '
else:
# Check if a valid char
msg = None
if spec[char] == '=':
msg = "'=' must only be found at the beginning of a line, or in a comment"
elif spec[char] == ';':
msg = "';' must only be used to terminate a block comment, or found within a layer comment"
elif spec[char] not in charlist:
msg = "'%s' (%d) is not a valid character" % (spec[char], ord(spec[char]))
if msg:
slice = ''.join(spec[:char])
row = slice.count('\n')+1
col = len(slice) - slice.rfind('\n')
stderr.write("%d:%d WARN: %s\n" % (row, col, msg))
spec[char] = ' '
spec = list(map(lambda x:x.rstrip(), ''.join(spec).split('\n')))
# Cleanup unnecessary lines
layertail = True
for line in range(len(spec)-1, -1, -1):
if spec[line] == '':
if layertail:
spec = spec[:line] + spec[line+1:]
else:
pass
elif spec[line] == '=':
layertail = True
else:
layertail = False
if len(spec) > 0 and spec[0] == '=':
spec = spec[1:]
spec = '\n'.join(spec)
# Convert to final layout
spec2 = list(map(lambda s: s[(1 if len(s) > 0 and s[0] == '\n' else None):].rstrip('\n'), spec.split('=')))
n = max(map(lambda s:s.count('\n'), spec2))
spec2 = list(map(lambda s:(s+('\n'*(n-s.count('\n')))).split('\n'), spec2))
n = max(map(lambda s:max(map(len, s)), spec2))
spec2 = list(map(lambda s:list(map(lambda t:list(t+(' '*(n-len(t)))), s)), spec2))
board = chiplib.Board(Cfg)
board.initialize([[[chiplib.getElementType(char)(board, x, y, z, char) for x,char in enumerate(row)] for y,row in enumerate(layer)] for z,layer in enumerate(spec2)])
if Cfg.VERBOSE > 1:
stderr.write(str(board) + '\n')
def circuit_gen():
"""A generator representing the board's state and function"""
result = chiplib.RunResult(statuscode=0,
outbits=None,
sleep=0,
debug='',
jump=None)
try:
while True:
inbits = yield result
result = board.run(inbits)
except KeyboardInterrupt as e:
if result.debug:
for msg in sorted(result.debug):
stderr.write('\n\t\t\t\t\t%s(%d,%d,%d): %s' % msg)
if Cfg.VERBOSE > 2:
stderr.write('\n' + board.heatmap())
stderr.write('\nStack: ' if Cfg.STORAGE[0] == 's' else '\nQueue: ')
if board.storage:
dir = -1 if Cfg.STORAGE[0] == 's' else 1
if Cfg.VERBOSE > 1 or len(board.storage) < 9:
stderr.write(' '.join(map(lambda v:''.join(map(str, v[::-1])), board.storage[::dir])))
else:
cut = -9 if Cfg.STORAGE[0] == 's' else 8
stderr.write(' '.join(map(lambda v:''.join(map(str, v[::-1])), board.storage[:cut:dir])))
stderr.write(' ... ')
stderr.write(str(len(board.storage)-8))
stderr.write('more')
else:
stderr.write('empty')
stderr.write('\nAge: ')
stderr.write(str(board.age))
if (board.stats):
stderr.write('\nStats: ')
for k,v in sorted(board.stats.items()):
stderr.write('\n%s %s' % (str(v).rjust(24), k))
stderr.write('\n')
#raise e # Uncomment this for a stack trace upon ^C. Usually *very* long.
# Start up the circuit
circuit = circuit_gen()
circuit.send(None)
return circuit, board
def run(circuit, board):
"""Run the circuit for each input byte"""
if Cfg.VERBOSE > 0:
stderr.write(' HGFEDCBA hgfedcba\n')
result = chiplib.EMPTY_RUN_RESULT
total_bytes = 0
inchar = bytes([254])
history = b''
index = 0
try:
while True:
# Read input, plus eof check
if not (result.statuscode & chiplib.Board.READ_HOLD):
if total_bytes >= Cfg.CUTOFF_BYTES > 0:
# we're done here
break
if index < len(history):
inchar = bytes([history[index]]) # need to bytes, otherwise we get an int
else:
if Cfg.WITHOUT_STDIN:
inchar = next(Cfg.GENERATOR)
else:
try:
if Cfg.NO_BUFFER and stdin.isatty():
orig_settings = termios.tcgetattr(stdin)
tty.setraw(stdin)
inchar = stdin.buffer.read(1)
finally:
if Cfg.NO_BUFFER and stdin.isatty():
termios.tcsetattr(stdin, termios.TCSADRAIN, orig_settings)
if len(inchar) == 0:
# EOF (optimization: switch to without stdin mode for future)
if Cfg.IGNORE_EOF:
inchar = next(Cfg.GENERATOR)
Cfg.WITHOUT_STDIN = True
else:
break
history += inchar
if history.endswith(Cfg.ESC_SEQS):
break
index += 1
total_bytes += 1
inbin = bin(ord(inchar))[2:]
inbits = list(map(int, '0'*(8-len(inbin)) + inbin))[::-1]
if Cfg.VERBOSE > 0:
if not (result.statuscode & chiplib.Board.READ_HOLD):
if 0 <= inchar[0] < 32 or inchar[0] == 127:
inc = '�'
else:
inc = inchar.decode('utf-8', 'replace')
stderr.write(' %s\t%s →' % (inc, ''.join(map(str, inbits[::-1]))))
else:
stderr.write(' →')
# Execute a clock cycle
result = circuit.send(inbits)
# Output
outchar = bytes([int(''.join(map(str, result.outbits[::-1])), 2)])
if Cfg.VERBOSE > 0:
if not (result.statuscode & chiplib.Board.WRITE_HOLD):
if 0 <= outchar[0] < 32 or outchar[0] == 127:
outc = '�'
else:
outc = outchar.decode('utf-8', 'replace')
stderr.write(' %s\t%s' % (outc, ''.join(map(str, result.outbits[::-1]))))
else:
stderr.write(' ')
if Cfg.VERBOSE > 1:
if result.debug:
for msg in sorted(result.debug):
stderr.write('\n\t\t\t\t\t%s(%d,%d,%d): %s' % msg)
if board.storage:
stderr.write('\n\t\t\t\t\tStack: ' if Cfg.STORAGE[0] == 's' else '\n\t\t\t\t\tQueue: ')
dir = -1 if Cfg.STORAGE[0] == 's' else 1
if len(board.storage) < 9 or Cfg.VERBOSE > 2:
stderr.write(' '.join(map(lambda v:''.join(map(str, v[::-1])), board.storage[::dir])))
else:
cut = -9 if Cfg.STORAGE[0] == 's' else 8
stderr.write(' '.join(map(lambda v:''.join(map(str, v[::-1])), board.storage[:cut:dir])))
stderr.write(' ... ')
stderr.write(str(len(board.storage)-8))
stderr.write('more')
stderr.write('\n')
if not (result.statuscode & chiplib.Board.WRITE_HOLD):
stdout.buffer.write(outchar)
if Cfg.NO_BUFFER:
stdout.flush()
# Early termination
if (result.statuscode & chiplib.Board.TERMINATE):
break
# Sleep
if (result.sleep):
time.sleep(result.sleep)
# Jump
if (result.jump is not None):
if result.jump >= 0:
index = result.jump
else:
index += result.jump
if Cfg.VERBOSE > 1:
if Cfg.VERBOSE > 2:
stderr.write('\n')
stderr.write(board.heatmap())
stderr.write('\nAge: ')
stderr.write(str(board.age))
if (board.stats):
stderr.write('\nStats: ')
for k,v in sorted(board.stats.items()):
stderr.write('\n%s %s' % (str(v).rjust(24), k))
stderr.write('\n')
except StopIteration as e:
stderr.write('Execution halted\n')
if Cfg.NEWLINE:
stdout.buffer.write(b'\n')
if __name__ == '__main__':
spec = init()
circuit, board = setup(spec)
run(circuit, board)