-
Notifications
You must be signed in to change notification settings - Fork 0
/
gen-mips
executable file
·242 lines (204 loc) · 5.93 KB
/
gen-mips
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
#!/usr/bin/python3
# Run this script from the directory corresponding to the root of your
# code repo. In other words, when your TA pulls the code from your repo,
# goes to its root directory, and runs this script, it should work.
#
# Output is logged in the file "run.output", WHICH WILL BE OVERWRITTEN
# EACH TIME THIS SCRIPT IS RUN.
#
# The filenames "out.c", "out.s", and "a.out" may also be used, depending
# on the version of this script, and ALSO WILL BE OVERWRITTEN EACH TIME
# THIS SCRIPT IS RUN.
#
# Output from the standard output and standard error streams is collected
# separately, and as a result won't be intermingled as might be seen when
# running a command directly from the terminal.
#
# To best show off your program's construction, be sure to remove all
# object/executable/class/jar files before running this script (or, ideally,
# you'll have a "clean" target in your Makefile and you can just "make clean"
# first.
#
# Needs Python 3.8 or higher.
import os
import sys
import time
import signal
import socket
import subprocess
MILESTONE = 'final'
TESTPATH = os.getenv( 'GOLF_TEST_PREFIX' ) + MILESTONE
INPUTPATH = f'{TESTPATH}/input'
LOGFILE = MILESTONE + os.getenv( 'GOLF_LOG_SUFFIX' )
JARCLASS = 'golf'
JAR = 'golf.jar'
EXE = './golf'
TIMEOUT = 7 # was 5s, increased 24 March 2023
JAVA = '/usr/bin/java'
MAKE = '/usr/bin/make'
WIDTH = 79
#
# Test files
#
TESTS = (
( 'gen.t1', 'hello, world' ),
( 'gen.t2', 'var init in loop block' ),
( 'gen.t3', 'zero values' ),
( 'gen.t4', 'nested function calls' ),
( 'gen.t5', 'recursive Fibonacci' ),
( 'gen.t6', 'iterative Fibonacci' ),
( 'gen.t7', 'string reversal' ),
( 'gen.t8', 'hunting minint' ),
( 'gen.t9', 'negating minint' ),
( 'gen.t10', 'minint div and mod' ),
( 'gen.t11', 'division by zero' ),
( 'gen.t12', 'basic Booleans' ),
( 'gen.t13', 'string escapes' ),
( 'gen.t14', 'all your base are belong to us' ),
( 'gen.t15', 'short-circuiting' ),
( 'gen.t16', 'dynamic semantic check' ),
( 'gen.t17', 'less obvious division by zero' ),
( 'gen.t18', 'recursive-descent calculator' ),
( 'gen.t19', 'string operations' ),
( 'gen.t20', 'redefining built-ins' ),
( 'gen.t21', 'register blow-out' ),
( 'gen.t22', 'nested "for" loops' ),
( 'gen.t23', 'EOF and scope' ),
( 'gen.t24', 'banners' ),
( 'gen.t25', 'div and mod table' ),
( 'gen.entombed', 'basic maze generation from Entombed (1982)' ),
( 'gen.life', 'the Game of Life' ),
( 'gen.select', 'selection sort' ),
( 'gen.sieve', 'prime number sieve' ),
)
#
# Utility routines
#
f = None
def log(s):
global f
if f is None:
try:
# ensure Python doesn't try to alter output
f = open(LOGFILE, 'w', encoding='iso-8859-1')
except IOError as e:
print(f'{LOGFILE}: {e.strerror}', file=sys.stderr)
sys.exit(1)
print(s, file=f)
print(file=f)
def die(s):
log(f'*** ABNORMAL TERMINATION: {s}')
print(f'{sys.argv[0]}: {s}', file=sys.stderr)
sys.exit(1)
def timestamp(reason):
log(f'Run {reason} on {time.ctime()}')
def banner(title):
# his friends call him Bruce
s = '-' * WIDTH + '\n'
s += title + ' |\n'
s += '-' * (len(title) + 2)
log(s)
# make progress visible to user
print(title, file=sys.stderr)
def run(cmd, stdin=None, stdout=None):
redir = ''
if stdin:
redir += f' < {stdin}'
if stdout:
redir += f' > {stdout}'
log('% ' + ' '.join(cmd) + redir)
fstdin=None
if stdin:
try:
fstdin = open(stdin, 'rb')
log('STDIN:\n' +
str(fstdin.read(), encoding='iso-8859-1'))
fstdin.seek(0)
except IOError as e:
die(f'unexpected error with stdin file: {e.strerror}')
try:
cp = subprocess.run(cmd, capture_output=True,
timeout=TIMEOUT, stdin=fstdin)
except subprocess.TimeoutExpired:
log('*** TERMINATION VIA TIMEOUT')
return False
except subprocess.SubprocessError:
die('unexpected exception running command')
except IOError as e:
die(f'unexpected I/O error running command: {e.strerror}')
# encoding here should effectively act as a passthrough with no
# possibility of encoding errors
if stdout:
try:
fstdout = open(stdout, 'wb')
fstdout.write(cp.stdout)
fstdout.close()
except IOError as e:
die(f'unexpected error with stdout file: {e.strerror}')
else:
log('STDOUT:\n' + str(cp.stdout, encoding='iso-8859-1'))
log('STDERR:\n' + str(cp.stderr, encoding='iso-8859-1'))
if cp.returncode < 0:
# rarely a good sign, let's be honest
which = signal.strsignal(-cp.returncode)
log(f'*** TERMINATION VIA SIGNAL: {which}')
return False
else:
log(f'RETURN CODE: {cp.returncode}')
if cp.returncode == 0:
return True
return False
#
# Figure out what form the executable code's in
#
cmd = []
def findcompiler():
global cmd
is_exe = is_jar = False
if os.access(EXE, os.X_OK):
is_exe = True
cmd = [ EXE ]
if os.access(JAR, os.R_OK):
is_jar = True
cmd = [ JAVA, '-cp', JAR, JARCLASS ]
if not is_exe and not is_jar:
die('no compiler found with name per spec')
if is_exe and is_jar:
die('multiple compiler candidates found')
log('Compiler will be run as ' + ' '.join(cmd))
#
# Testing, one, two, three, testing
#
def main():
timestamp('started')
# must be run on CPSC Linux machine
log(f'Hostname: {socket.gethostname()}')
# build it; this'll also permit variants on "Makefile" like "makefile"
banner('Running make')
run([ MAKE ])
# figure out how to run the compiler
banner('Locating GoLF compiler')
findcompiler()
# no-argument case
banner('Test: too few arguments')
run(cmd)
# extra-argument case
banner('Test: too many arguments')
run(cmd + [ '/dev/null', '/dev/zero' ])
# nonexistent case
banner('Test: nonexistent file')
run(cmd + [ '/i/do/not/exist' ])
# now the fun begins
for file, description in TESTS:
banner(f'Test: {description}')
# run compiler
ok = run(cmd + [ f'{TESTPATH}/{file}' ])
# if not ok:
# log('(GoLF compiler unsuccessful, skipping test)')
# continue
timestamp('ended')
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
die('terminated via keyboard interrupt')