├── LICENSE ├── codegen.py ├── repl.py ├── secd.py └── test.py /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Willem Yarbrough 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /codegen.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | def lex(stream): 4 | stream = stream.replace('(',' ( ') 5 | stream = stream.replace(')',' ) ') 6 | return stream.split() 7 | 8 | def parse(program): 9 | tokens = lex(program) 10 | return buildast(tokens) 11 | 12 | def buildast(tokens): 13 | token = tokens.pop(0) 14 | if token == '(': 15 | L = [] 16 | while tokens[0] != ')': 17 | L.append(buildast(tokens)) 18 | tokens.pop(0) 19 | return L 20 | elif token == ')': 21 | raise SyntaxError('mismatched parens.') 22 | else: 23 | return atom(token) 24 | 25 | def atom(token): 26 | try: return int(token) 27 | except ValueError: 28 | try: return float(token) 29 | except ValueError: 30 | return str(token) 31 | 32 | operators = { 33 | '+': 'add', 34 | '*': 'mul', 35 | '-': 'sub', 36 | '/': 'div', 37 | } 38 | 39 | builtins = ['if','null','nil','lambda','let','letrec','list','and2'] 40 | 41 | def index(e, n): 42 | def indx2(e, n, j): 43 | if len(n) == 0: 44 | return [] 45 | elif n[0] == e: 46 | return j 47 | else: 48 | return indx2(e, n[1:], j + 1) 49 | 50 | def indx(e, n, i): 51 | if len(n) == 0: 52 | return [] 53 | 54 | j = indx2(e, n[0], 1) 55 | 56 | if j == []: 57 | return indx(e, n[1:], i + 1) 58 | else: 59 | return [j, i] 60 | 61 | rval = indx(e, n, 1) 62 | 63 | if rval == []: 64 | return rval 65 | else: 66 | assert len(rval) == 2 67 | assert type(rval[0]) == int 68 | assert type(rval[1]) == int 69 | return rval 70 | 71 | def isAtom(e): 72 | return e == [] or type(e) == int or type(e) == str 73 | 74 | def isBuiltin(e): 75 | return e in ['+','-','*','/','car','cdr','zero','atom','eq','leq'] 76 | 77 | def isOperator(e): 78 | return e in operators 79 | 80 | def codegen(expr,names,code): 81 | # print("expr =", expr, ", names =", names, ", code =", code) 82 | 83 | if isAtom(expr): 84 | if expr == []: 85 | # print("1 appending nil to", code) 86 | code.append('nil') 87 | return code 88 | else: 89 | ij = index(expr,names) 90 | if ij == []: 91 | code.append(expr) 92 | code.append('ldc') 93 | else: 94 | code.append(ij) 95 | code.append('ld') 96 | return code 97 | else: 98 | fn = expr[0] 99 | args = expr[1:] 100 | if isAtom(fn): 101 | if isBuiltin(fn): 102 | if isOperator(fn): 103 | fn = operators[fn] 104 | code.append(fn) 105 | return genBuiltin(args, names, code) 106 | elif fn == "list": 107 | xs = [] 108 | # print("looping through", args) 109 | for item in args: 110 | x = codegen(item,names,['cons']) 111 | xs.extend(x[::-1]) 112 | xs = xs[::-1] 113 | code.extend(xs) 114 | # print("2 appending nil to", code) 115 | code.append('nil') 116 | return code 117 | elif fn == "lambda": 118 | # print("generating lambda w/",args[1],args[0],code) 119 | return genLambda(args[1], [args[0]]+names, code) 120 | elif fn == "if": 121 | return genIf(args[0],args[1],args[2],n,code) 122 | elif fn == 'let' or fn == 'letrec': 123 | new = [args[0]] + names 124 | vals = args[1] 125 | body = args[2] 126 | 127 | if fn == 'let': 128 | code.append('ap') 129 | lamb = genLambda(body,new,code) 130 | app = genApp(vals,names,lamb) 131 | # print("3 appending nil to", code) 132 | app.append('nil') 133 | return app 134 | elif fn == 'letrec': 135 | code.append('rap') 136 | lamb = genLambda(body,new,code) 137 | app = genApp(vals,new,lamb) 138 | # print("4 appending nil to", code) 139 | app.append('nil') 140 | app.append('dum') 141 | return app 142 | else: 143 | code.append('ap') 144 | code.append(index(fn,names)) 145 | code.append('ld') 146 | app = genApp(args,names,code) 147 | # print("5 appending nil to", code) 148 | app.append('nil') 149 | return app 150 | else: 151 | code.append('ap') 152 | code = codegen(fn,names,code) 153 | app = genApp(args,names,code) 154 | # print("6 appending nil to", code) 155 | app.append('nil') 156 | return app 157 | 158 | 159 | def genBuiltin(args,names,code): 160 | if args == []: 161 | return code 162 | else: 163 | return genBuiltin(args[1:], names, codegen(args[0],names,code)) 164 | 165 | def genLambda(body,names,code): 166 | code.append(codegen(body,names,['rtn'])) 167 | code.append('ldf') 168 | # print("generated lambda:",code) 169 | return code 170 | 171 | def genIf(test,trueExpr,falseExpr,names,code): 172 | code.append(codegen(falseExpr,names,['join'])) 173 | code.append(codegen(trueExpr, names,['join'])) 174 | code.append('sel') 175 | return codegen(test,names,code) 176 | 177 | def genApp(args, names, code): 178 | if args == []: 179 | return code 180 | else: 181 | code.append('cons') 182 | return genApp(args[1:], names, 183 | codegen(args[0], names, code)) 184 | 185 | def main(): 186 | code = sys.stdin.read() 187 | print(codegen(parse(code), [], ['stop'])) 188 | 189 | if __name__ == '__main__': 190 | main() 191 | 192 | -------------------------------------------------------------------------------- /repl.py: -------------------------------------------------------------------------------- 1 | from codegen import * 2 | from secd import * 3 | import sys 4 | 5 | # read eval print loop 6 | 7 | machine = SECD() 8 | while True: 9 | line = input('SECDrepl> ') 10 | code = codegen(parse(line), [], ['stop']) 11 | machine.runProgram(code, []) 12 | -------------------------------------------------------------------------------- /secd.py: -------------------------------------------------------------------------------- 1 | import sys, ast 2 | 3 | class SECD: 4 | 5 | def __init__(self): 6 | self.s = [] 7 | self.e = [] 8 | self.c = [] 9 | self.d = [] 10 | self.running = True 11 | 12 | def runProgram(self,code,stack=[]): 13 | self.running = True 14 | self.loadProgram(code,stack) 15 | self.run() 16 | 17 | def loadProgram(self, code, stack=[]): 18 | self.s = stack 19 | self.e = [] 20 | self.c = code 21 | self.d = [] 22 | 23 | def readout(self): 24 | print('S: ', self.s) 25 | print('E: ', self.e) 26 | print('C: ', self.c) 27 | print('D: ', self.d) 28 | print() 29 | 30 | def run(self): 31 | self.readout() 32 | while self.running: 33 | self.execute() 34 | self.readout() 35 | 36 | def execute(self): 37 | cmd = self.c.pop().upper() 38 | op = {'ADD': self.perform_add, 39 | 'MUL': self.perform_mul, 40 | 'SUB': self.perform_sub, 41 | 'DIV': self.perform_div, 42 | 'CAR': self.perform_car, 43 | 'CDR': self.perform_cdr, 44 | 'NIL': self.perform_nil, 45 | 'ATOM': self.perform_atom, 46 | 'CONS': self.perform_cons, 47 | 'LDC': self.perform_ldc, 48 | 'LDF': self.perform_ldf, 49 | 'AP': self.perform_ap, 50 | 'LD': self.perform_ld, 51 | 'DUM': self.perform_dum, 52 | 'RAP': self.perform_rap, 53 | 'JOIN': self.perform_join, 54 | 'RTN': self.perform_rtn, 55 | 'SEL': self.perform_sel, 56 | 'ZERO': self.perform_zero, 57 | 'EQ': self.perform_eq, 58 | 'LEQ': self.perform_leq, 59 | 'STOP': self.perform_stop, 60 | }[cmd] 61 | 62 | op() 63 | 64 | def perform_stop(self): 65 | self.running = False 66 | 67 | def perform_add(self): 68 | a = self.s.pop() 69 | b = self.s.pop() 70 | self.s.append(a + b) 71 | 72 | def perform_sub(self): 73 | a = self.s.pop() 74 | b = self.s.pop() 75 | self.s.append(a - b) 76 | 77 | def perform_mul(self): 78 | a = self.s.pop() 79 | b = self.s.pop() 80 | self.s.append(a * b) 81 | 82 | def perform_div(self): 83 | a = self.s.pop() 84 | b = self.s.pop() 85 | self.s.append(a / b) 86 | 87 | def perform_nil(self): 88 | self.s.append([]) 89 | 90 | def perform_ldc(self): 91 | a = self.c.pop() 92 | self.s.append(a) 93 | 94 | def perform_ldf(self): 95 | fn = self.c.pop() 96 | env = self.e[:] 97 | self.s.append([env,fn]) 98 | 99 | def perform_ap(self): 100 | self.d.append(self.c) 101 | self.d.append(self.e) 102 | self.d.append(self.s) 103 | ce = self.s.pop() 104 | self.c = ce.pop() 105 | self.e = ce.pop() 106 | self.e.append(self.s.pop()) 107 | self.s = [] 108 | 109 | def perform_rtn(self): 110 | retval = self.s.pop() 111 | self.s = self.d.pop() 112 | self.s.append(retval) 113 | self.e = self.d.pop() 114 | self.c = self.d.pop() 115 | 116 | def perform_join(self): 117 | self.c = [] 118 | self.c.append(self.d.pop()) 119 | 120 | def perform_sel(self): 121 | x = self.s.pop() 122 | ct = self.c.pop() 123 | cf = self.c.pop() 124 | self.d.append(self.c) 125 | self.c = [] 126 | if x: 127 | self.c.append(ct) 128 | else: 129 | self.c.append(cf) 130 | 131 | def perform_ld(self): 132 | loc = self.c.pop() 133 | x,y = loc.pop(),loc.pop() 134 | val = self.e[-x][-y] # index from end 135 | self.s.append(val) 136 | 137 | def perform_dum(self): 138 | """ create a dummy env """ 139 | self.e.append([]) 140 | 141 | def perform_rap(self): 142 | closure = self.s.pop() 143 | fn = closure.pop() 144 | env = closure.pop() 145 | env.pop() 146 | v = self.s.pop() 147 | self.e.pop() 148 | self.d.append(self.c) 149 | self.d.append(self.e) 150 | self.d.append(self.s) 151 | self.e = env 152 | self.e.append(v) 153 | self.c = fn 154 | self.s = [] 155 | 156 | def perform_atom(self): 157 | self.s.append(self.s[-1] == []) 158 | 159 | def perform_cons(self): 160 | car = self.s.pop() 161 | cdr = self.s.pop() 162 | cdr.append(car) 163 | self.s.append(cdr) 164 | 165 | def perform_car(self): 166 | xs = self.s.pop() 167 | car = xs.pop() 168 | self.s.append(car) 169 | 170 | def perform_cdr(self): 171 | xs = self.s.pop() 172 | xs.pop() 173 | self.s.append(xs) 174 | 175 | def perform_zero(self): 176 | self.s.append(self.s[-1] == 0) 177 | 178 | def perform_eq(self): 179 | self.s.append(self.s[-1] == self.s[-2]) 180 | 181 | def perform_leq(self): 182 | self.s.append(self.s[-1] <= self.s[-2]) 183 | 184 | if __name__ == "__main__": 185 | code = ast.literal_eval(sys.stdin.read()) 186 | machine = SECD() 187 | machine.runProgram(code) 188 | -------------------------------------------------------------------------------- /test.py: -------------------------------------------------------------------------------- 1 | import secd 2 | import pytest 3 | 4 | @pytest.fixture(scope="session") 5 | def machine(): 6 | return secd.SECD() 7 | 8 | def test_perform_add(machine): 9 | machine.loadProgram([],[1,1]) 10 | machine.perform_add() 11 | assert machine.s.pop() == 2 12 | 13 | def test_perform_sub(machine): 14 | machine.loadProgram([],[8,10]) 15 | machine.perform_sub() 16 | assert machine.s.pop() == 2 17 | 18 | def test_perform_mul(machine): 19 | machine.loadProgram([],[10,8]) 20 | machine.perform_mul() 21 | assert machine.s.pop() == 80 22 | 23 | def test_perform_div(machine): 24 | machine.loadProgram([],[100,500]) 25 | machine.perform_div() 26 | assert machine.s.pop() == 5 27 | 28 | def test_perform_nil(machine): 29 | machine.perform_nil() 30 | assert machine.s.pop() == [] 31 | 32 | def test_perform_ldc(machine): 33 | machine.loadProgram([10],[1,2]) 34 | machine.perform_ldc() 35 | assert machine.s.pop() == 10 36 | 37 | def test_perform_ldf(machine): 38 | fn = [1,2,3] 39 | machine.loadProgram([fn],[0]) 40 | machine.e = [10,100] 41 | machine.perform_ldf() 42 | loaded = machine.s.pop() 43 | assert loaded.pop() == fn 44 | assert loaded.pop() == machine.e 45 | 46 | def test_perform_ap(machine): 47 | machine.s = [500, [4, 3], [[[99,999]],['rtn','add',[1,1],'ld',[2,1],'ld']]] 48 | machine.e = [[99,999]] 49 | machine.c = ['lol'] 50 | machine.d = [7] 51 | machine.perform_ap() 52 | assert machine.s == [] 53 | assert machine.e == [[99,999],[4,3]] 54 | assert machine.c == ['rtn','add',[1,1],'ld',[2,1],'ld'] 55 | assert machine.d == [7,['lol'],[[99,999]],[500]] 56 | 57 | def test_perform_rtn(machine): 58 | machine.s = [3,2,1,0] 59 | machine.c = [10000] 60 | machine.e = [20000] 61 | machine.d = [7,[6],[5],[4]] 62 | machine.perform_rtn() 63 | assert machine.s == [4,0] 64 | assert machine.e == [5] 65 | assert machine.c == [6] 66 | assert machine.d == [7] 67 | 68 | def test_perform_join(machine): 69 | machine.s = [0] 70 | machine.e = [1] 71 | machine.c = [5000] 72 | machine.d = [3,2] 73 | machine.perform_join() 74 | assert machine.s == [0] 75 | assert machine.e == [1] 76 | assert machine.c == [2] 77 | assert machine.d == [3] 78 | 79 | def test_perform_sel(machine): 80 | # case: True 81 | machine.s = [2,1,True] 82 | machine.e = [3] 83 | machine.c = [7,6,5,4] 84 | machine.d = [9,8] 85 | machine.perform_sel() 86 | assert machine.s == [2,1] 87 | assert machine.e == [3] 88 | assert machine.c == [4] 89 | assert machine.d == [9,8,[7,6]] 90 | 91 | # case: False 92 | machine.s = [2,1,False] 93 | machine.e = [3] 94 | machine.c = [7,6,5,4] 95 | machine.d = [9,8] 96 | machine.perform_sel() 97 | assert machine.s == [2,1] 98 | assert machine.e == [3] 99 | assert machine.c == [5] 100 | assert machine.d == [9,8,[7,6]] 101 | 102 | def test_perform_ld(machine): 103 | machine.s = [] 104 | machine.e = [[3,2,1],[[2,2],4],[8]] 105 | machine.c = [[2,3],[1,3],[2,2],[1,2],[1,1]] 106 | machine.d = [5000] 107 | machine.perform_ld() 108 | assert machine.s == [8] 109 | assert machine.e == [[3,2,1],[[2,2],4],[8]] 110 | assert machine.c == [[2,3],[1,3],[2,2],[1,2]] 111 | assert machine.d == [5000] 112 | machine.perform_ld() 113 | assert machine.s == [8,4] 114 | machine.perform_ld() 115 | assert machine.s == [8,4,[2,2]] 116 | machine.perform_ld() 117 | assert machine.s == [8,4,[2,2],1] 118 | machine.perform_ld() 119 | assert machine.s == [8,4,[2,2],1,2] 120 | 121 | def test_perform_dum(machine): 122 | machine.s = [3000] 123 | machine.e = [3,2,1] 124 | machine.c = [4000] 125 | machine.d = [5000] 126 | machine.perform_dum() 127 | assert machine.s == [3000] 128 | assert machine.e == [3,2,1,[]] 129 | assert machine.c == [4000] 130 | assert machine.d == [5000] 131 | 132 | def test_perform_rap(machine): 133 | machine.s = [4,3,2,[[1,0,[]],'f']] 134 | machine.e = [6,5,[]] 135 | machine.c = [8,7] 136 | machine.d = [10,9] 137 | machine.perform_rap() 138 | assert machine.s == [] 139 | assert machine.e == [1,0,2] 140 | assert machine.c == 'f' 141 | assert machine.d == [10,9,[8,7],[6,5],[4,3]] 142 | 143 | def test_perform_atom(machine): 144 | # true case 145 | machine.s = [3,2,1,[]] 146 | machine.e = [] 147 | machine.c = [] 148 | machine.d = [] 149 | machine.perform_atom() 150 | assert machine.s == [3,2,1,[],True] 151 | assert machine.e == [] 152 | assert machine.c == [] 153 | assert machine.d == [] 154 | # false case 155 | machine.s = [3,2,1] 156 | machine.e = [] 157 | machine.c = [] 158 | machine.d = [] 159 | machine.perform_atom() 160 | assert machine.s == [3,2,1,False] 161 | assert machine.e == [] 162 | assert machine.c == [] 163 | assert machine.d == [] 164 | 165 | def test_perform_cons(machine): 166 | machine.s = [3,2,1,[],0] 167 | machine.e = [] 168 | machine.c = [] 169 | machine.d = [] 170 | machine.perform_cons() 171 | assert machine.s == [3,2,1,[0]] 172 | assert machine.e == [] 173 | assert machine.c == [] 174 | assert machine.d == [] 175 | 176 | def test_perform_car(machine): 177 | machine.s = [100,[3,2,1]] 178 | machine.e = [] 179 | machine.c = [] 180 | machine.d = [] 181 | machine.perform_car() 182 | assert machine.s == [100,1] 183 | assert machine.e == [] 184 | assert machine.c == [] 185 | assert machine.d == [] 186 | 187 | def test_perform_cdr(machine): 188 | machine.s = [100,[3,2,1]] 189 | machine.e = [] 190 | machine.c = [] 191 | machine.d = [] 192 | machine.perform_cdr() 193 | assert machine.s == [100,[3,2]] 194 | assert machine.e == [] 195 | assert machine.c == [] 196 | assert machine.d == [] 197 | 198 | def test_perform_zero(machine): 199 | machine.s = [100,0] 200 | machine.e = [] 201 | machine.c = [] 202 | machine.d = [] 203 | machine.perform_zero() 204 | assert machine.s == [100,0,True] 205 | assert machine.e == [] 206 | assert machine.c == [] 207 | assert machine.d == [] 208 | machine.s = [100,99] 209 | machine.perform_zero() 210 | assert machine.s == [100,99,False] 211 | 212 | def test_perform_eq(machine): 213 | machine.s = [100,100] 214 | machine.e = [] 215 | machine.c = [] 216 | machine.d = [] 217 | machine.perform_eq() 218 | assert machine.s == [100,100,True] 219 | assert machine.e == [] 220 | assert machine.c == [] 221 | assert machine.d == [] 222 | machine.s = [100,99] 223 | machine.perform_eq() 224 | assert machine.s == [100,99,False] 225 | 226 | def test_perform_leq(machine): 227 | machine.s = [100,100] 228 | machine.e = [] 229 | machine.c = [] 230 | machine.d = [] 231 | machine.perform_leq() 232 | assert machine.s == [100,100,True] 233 | assert machine.e == [] 234 | assert machine.c == [] 235 | assert machine.d == [] 236 | machine.s = [100,101] 237 | machine.perform_leq() 238 | assert machine.s == [100,101,False] 239 | machine.s = [100,99] 240 | machine.perform_leq() 241 | assert machine.s == [100,99,True] 242 | 243 | def test_execute(machine): 244 | machine.loadProgram(['ADD'],[1,1]) 245 | machine.execute() 246 | assert machine.s == [2] 247 | 248 | machine.loadProgram([0,'ldc'],[2,1]) 249 | machine.execute() 250 | assert machine.s == [2,1,0] 251 | 252 | machine.loadProgram([0,'eq'],[2,1]) 253 | machine.execute() 254 | assert machine.s == [2,1,False] 255 | --------------------------------------------------------------------------------