├── tests ├── fib.out ├── define_1.out ├── fac.out ├── helloworld.out ├── define_2.out ├── not.out ├── math.out ├── lists.out ├── string.out ├── define_1.in ├── not.in ├── math.in ├── define_2.in ├── fac.in ├── fib.in ├── lists.in ├── string.in └── helloworld.in ├── tests.py ├── parenthetic.py └── README.markdown /tests/fib.out: -------------------------------------------------------------------------------- 1 | 610.0 -------------------------------------------------------------------------------- /tests/define_1.out: -------------------------------------------------------------------------------- 1 | 3.0 2 | 3 | -------------------------------------------------------------------------------- /tests/fac.out: -------------------------------------------------------------------------------- 1 | 3628800.0 2 | 2.25 -------------------------------------------------------------------------------- /tests/helloworld.out: -------------------------------------------------------------------------------- 1 | hello world -------------------------------------------------------------------------------- /tests/define_2.out: -------------------------------------------------------------------------------- 1 | 3.0 2 | 4.0 3 | -------------------------------------------------------------------------------- /tests/not.out: -------------------------------------------------------------------------------- 1 | False 2 | True 3 | False 4 | True 5 | -------------------------------------------------------------------------------- /tests/math.out: -------------------------------------------------------------------------------- 1 | 0.0 2 | 5.0 3 | 6.0 4 | 1.0 5 | 2.0 6 | 72.0 7 | 2.0 8 | 0.28125 9 | -------------------------------------------------------------------------------- /tests/lists.out: -------------------------------------------------------------------------------- 1 | () 2 | (1.0, 3.0) 3 | (2.0, ()) 4 | (1.0, (2.0, (3.0, (4.0, ())))) 5 | 10.0 6 | -------------------------------------------------------------------------------- /tests/string.out: -------------------------------------------------------------------------------- 1 | 97.0 2 | a 3 | aa 4 | ('a', ('b', ('c', ('d', ())))) 5 | abcd 6 | abcdabcd 7 | -------------------------------------------------------------------------------- /tests/define_1.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - basic define functionality 3 | 4 | ( 5 | define 6 | (() ()()) 7 | 8 | x 9 | (() ()()()()) 10 | 11 | 3 12 | ((()) ()()()) 13 | ) 14 | 15 | (() ()()()()) -------------------------------------------------------------------------------- /tests/not.in: -------------------------------------------------------------------------------- 1 | tests: 2 | not 3 | equals 4 | 5 | ( not 6 | (() ()(()())) 7 | 8 | ( 9 | equal 10 | (() (())(())) 11 | 12 | ((()) ()()()) 13 | ((()) ()()()) 14 | ) 15 | ) 16 | 17 | ( not 18 | (() ()(()())) 19 | 20 | ( 21 | equal 22 | (() (())(())) 23 | 24 | ((()) ()()()) 25 | ((()) ()()()()) 26 | ) 27 | ) 28 | 29 | 30 | ( 31 | equal 32 | (() (())(())) 33 | 34 | ((()) ()) 35 | ((()) ()()) 36 | ) 37 | 38 | 39 | ( 40 | equal 41 | (() (())(())) 42 | 43 | ((())) 44 | ((())) 45 | ) -------------------------------------------------------------------------------- /tests/math.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - plus 3 | - minus 4 | - mult 5 | - div 6 | 7 | [+] 8 | ( (() (())) ) 9 | 10 | [+ 5] 11 | ( 12 | (() (())) 13 | ((()) ()()()()()) 14 | ) 15 | 16 | [+ 1 2 3] 17 | ( 18 | (() (())) 19 | ((()) ()) 20 | ((()) ()()) 21 | ((()) ()()()) 22 | ) 23 | 24 | [*] 25 | ( (() ()(())) ) 26 | 27 | [* 2] 28 | ( 29 | (() ()(())) 30 | ((()) ()()) 31 | ) 32 | 33 | [* 3 4 6] 34 | ( 35 | (() ()(())) 36 | ((()) ()()()) 37 | ((()) ()()()()) 38 | ((()) ()()()()()()) 39 | ) 40 | 41 | [/ 2] 42 | ( 43 | (() (())()) 44 | ((()) ()()) 45 | ) 46 | 47 | [/ 9 4 8] 48 | ( 49 | (() (())()) 50 | ((()) ()()()()()()()()()) 51 | ((()) ()()()()) 52 | ((()) ()()()()()()()()) 53 | ) -------------------------------------------------------------------------------- /tests/define_2.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - define within lambda 3 | - define hoisting 4 | - define correctly modifies the correct scope 5 | 6 | (() (())(())) 7 | (() (())(())(())) 8 | 9 | ( 10 | define 11 | (() ()()) 12 | 13 | (() (())(())) 14 | 15 | (( 16 | lambda 17 | (() ()) 18 | 19 | no arguments 20 | () 21 | 22 | define 23 | ( 24 | (() ()()) 25 | (() (())(())) 26 | ((()) ()()) 2 27 | ) 28 | 29 | ((()) ()()()) 30 | )) 31 | ) 32 | 33 | ( 34 | define 35 | (() ()()) 36 | 37 | (() (())(())(())) 38 | 39 | (( 40 | lambda 41 | (() ()) 42 | 43 | no arguments 44 | () 45 | 46 | ((()) ()()()()) 47 | )) 48 | ) 49 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | import os, sys, subprocess 2 | 3 | i = 0 4 | 5 | def fail(test,s): 6 | print "...failed!" 7 | print s 8 | print "Total tests passed: "+ str(i) +" / " + str(len(tests)) 9 | sys.exit() 10 | 11 | print "Running tests...\n" 12 | 13 | tests = map(lambda s: ''.join(s.split('.')[0:-1]),filter(lambda s: s.split('.')[-1] =='in', os.listdir('./tests'))) 14 | 15 | 16 | for test in tests: 17 | if not os.path.exists("./tests/" + test + ".out"): 18 | fail(test, "No .out file for test '" + test + "'.") 19 | 20 | with open("./tests/" + test + ".out") as f: 21 | expected = ''.join([c for c in str.strip(''.join(f.readlines())) if c not in ["\r"]]) 22 | actual = ''.join([c for c in str.strip(subprocess.check_output("cat ./tests/"+test+".in | python parenthetic.py",shell=True)) if c not in ["\r"]]) 23 | print str(i+1) + ". " + test, 24 | if expected != actual: 25 | fail(test,"Incorrect output. \nExpected output: \n" + expected + "\nActual output: \n"+actual) 26 | 27 | i += 1 28 | print "... good.\n(" + str(i) + "/" + str(len(tests)) + " tests passed)" 29 | 30 | print "\nCongratulations! All " + str(len(tests)) + " tests passed successfully." -------------------------------------------------------------------------------- /tests/fac.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - recursion with define 3 | - mult 4 | - shadowing built-ins 5 | 6 | ( 7 | define 8 | (() ()()) 9 | 10 | fac 11 | (() ()()(())) 12 | 13 | ( 14 | lambda 15 | (() ()) 16 | 17 | n 18 | ( (() (())()) ) 19 | 20 | ( if 21 | (() ()()()) 22 | 23 | ( equal 24 | (() (())(())) 25 | n 26 | (() (())()) 27 | 1 28 | ((()) ()) 29 | ) 30 | 31 | 1 32 | ((()) ()) 33 | 34 | ( 35 | * 36 | (() ()(())) 37 | 38 | n 39 | (()(())()) 40 | 41 | fac[n-1] 42 | ( 43 | (() ()()(())) 44 | ( 45 | minus 46 | (() (()())) 47 | 48 | (()(())()) 49 | 50 | ((()) ()) 51 | ) 52 | ) 53 | ) 54 | ) 55 | ) 56 | ) 57 | 58 | ( 59 | fac 60 | (() ()()(())) 61 | 62 | 10 63 | ((()) ()()()()()()()()()()) 64 | 65 | ) 66 | 67 | division [to test that div is unshadowed] 68 | ( 69 | (() (())()) 70 | 71 | ((()) ()()()()()()()()()) 72 | 73 | ((()) ()()()()) 74 | ) -------------------------------------------------------------------------------- /tests/fib.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - recursion without define 3 | - if/else 4 | - plus 5 | - minus 6 | - multiplication 7 | 8 | ( 9 | ( 10 | lambda 11 | (()()) 12 | params 13 | ( 14 | fib 15 | (() ((()))) 16 | ) 17 | call fib[6] 18 | ( 19 | fib 20 | (() ((()))) 21 | 22 | 15 23 | ( 24 | * 25 | (() ()(())) 26 | 27 | 5 28 | ((()) ()()()()()) 29 | 3 30 | ((()) ()()()) 31 | ) 32 | 33 | fib 34 | (() ((()))) 35 | ) 36 | ) 37 | fib function 38 | ( 39 | lambda 40 | (()()) 41 | 42 | params 43 | ( 44 | n 45 | (() ()()) 46 | 47 | fib 48 | (() ()()(())) 49 | ) 50 | 51 | if n <= 1 return n else fib[n - 1] + fib[n - 2] 52 | ( 53 | if 54 | (() ()()()) 55 | ( 56 | <= 57 | (() ()(())()) 58 | n 59 | (() ()()) 60 | 1 61 | ((()) ()) 62 | ) 63 | 64 | n 65 | (() ()()) 66 | 67 | fib[n-1] + fib[n-2] 68 | ( 69 | plus 70 | (() (())) 71 | 72 | fib[n - 1] 73 | ( 74 | fib 75 | (() ()()(())) 76 | 77 | n - 1 78 | ( 79 | minus 80 | (() (()())) 81 | n 82 | (() ()()) 83 | 1 84 | ((()) ()) 85 | ) 86 | ) 87 | 88 | fib[n - 2] 89 | ( 90 | fib 91 | (() ()()(())) 92 | 93 | n - 2 94 | ( 95 | minus 96 | (() (()())) 97 | n 98 | (() ()()) 99 | 2 100 | ((()) ()()) 101 | ) 102 | ) 103 | ) 104 | ) 105 | ) 106 | ) -------------------------------------------------------------------------------- /tests/lists.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - car 3 | - cdr 4 | - cons 5 | - empty, testing if a list equals empty 6 | - recursion on lists 7 | 8 | empty 9 | (() ((()))) 10 | 11 | [cons 1 3] 12 | ( 13 | cons 14 | (() ((()))()) 15 | 16 | 1 17 | ((()) ()) 18 | 19 | 2 20 | ((()) ()()()) 21 | ) 22 | 23 | cons 24 | ( 25 | cons 26 | (() ((()))()) 27 | 28 | ((()) ()()) 29 | 30 | (() ((()))) 31 | 32 | ) 33 | 34 | [define lst [1, 2, 3, 4]] 35 | 36 | ( 37 | define 38 | (() ()()) 39 | lst 40 | (() (())(())(())) 41 | 42 | ( 43 | cons 44 | (() ((()))()) 45 | 1 46 | ((()) ()) 47 | 48 | ( 49 | cons 50 | (() ((()))()) 51 | 2 52 | ((()) ()()) 53 | 54 | ( 55 | cons 56 | (() ((()))()) 57 | 3 58 | ((()) ()()()) 59 | 60 | ( 61 | cons 62 | (() ((()))()) 63 | 4 64 | ((()) ()()()()) 65 | empty 66 | (() ((()))) 67 | 68 | ) 69 | 70 | ) 71 | 72 | ) 73 | 74 | ) 75 | ) 76 | 77 | (() (())(())(())) 78 | 79 | reduce-sum 80 | ( 81 | define 82 | (() ()()) 83 | reduce-sum 84 | (() (())()(())()) 85 | 86 | ( 87 | lambda 88 | (() ()) 89 | 90 | lst 91 | ( (() (())()) ) 92 | 93 | if list is empty return 0 else first + reduce[rest] 94 | ( 95 | if 96 | (() ()()()) 97 | 98 | [equal lst empty] 99 | ( 100 | (() (())(())) 101 | 102 | (() (())()) 103 | 104 | (() ((()))) 105 | ) 106 | 107 | 0 108 | ((()) ) 109 | 110 | first + reduce[rest] 111 | ( 112 | plus 113 | (() (())) 114 | 115 | ( 116 | first 117 | (() ((()))(())) 118 | lst 119 | (() (())()) 120 | ) 121 | 122 | reduce-sum 123 | ( 124 | (() (())()(())()) 125 | ( 126 | rest 127 | (() ((()))()()) 128 | lst 129 | (() (())()) 130 | ) 131 | ) 132 | ) 133 | ) 134 | ) 135 | ) 136 | 137 | [reduce-sum lst] 138 | ( 139 | reduce-sum 140 | (() (())()(())()) 141 | lst 142 | (() (())(())(())) 143 | ) -------------------------------------------------------------------------------- /tests/string.in: -------------------------------------------------------------------------------- 1 | tests: 2 | - char 3 | - string 4 | - concatenation with builtin plus function 5 | 6 | ( define 7 | (() ()()) 8 | 97 9 | (() (()())) 10 | [+ 7 [* 9 10]] 11 | ( 12 | (() (())) 13 | ((()) ()()()()()()()) 14 | ( 15 | (() ()(())) 16 | ((()) ()()()()()()()()()) 17 | ((()) ()()()()()()()()()()) 18 | ) 19 | ) 20 | ) 21 | 22 | 97 23 | (() (()())) 24 | 25 | a 26 | ( 27 | (() (())(())()) 28 | (() (()())) 29 | ) 30 | 31 | aa 32 | ( 33 | (() (())) 34 | ( 35 | (() (())(())()) 36 | (() (()())) 37 | ) 38 | ( 39 | (() (())(())()) 40 | (() (()())) 41 | ) 42 | ) 43 | 44 | ( 45 | define 46 | (() ()()) 47 | lst 48 | (() (())(())(())) 49 | 50 | ( 51 | cons 52 | (() ((()))()) 53 | a 54 | ( 55 | (() (())(())()) 56 | ( 57 | (() (())) 58 | ((()) ) 59 | (() (()())) 60 | ) 61 | ) 62 | 63 | ( 64 | cons 65 | (() ((()))()) 66 | b 67 | ( 68 | (() (())(())()) 69 | ( 70 | (() (())) 71 | ((()) ()) 72 | (() (()())) 73 | ) 74 | ) 75 | 76 | ( 77 | cons 78 | (() ((()))()) 79 | c 80 | ( 81 | (() (())(())()) 82 | ( 83 | (() (())) 84 | ((()) ()()) 85 | (() (()())) 86 | ) 87 | ) 88 | 89 | ( 90 | cons 91 | (() ((()))()) 92 | d 93 | ( 94 | (() (())(())()) 95 | ( 96 | (() (())) 97 | ((()) ()()()) 98 | (() (()())) 99 | ) 100 | ) 101 | empty 102 | (() ((()))) 103 | 104 | ) 105 | 106 | ) 107 | 108 | ) 109 | 110 | ) 111 | ) 112 | 113 | lst 114 | (() (())(())(())) 115 | 116 | abcd 117 | ( 118 | string 119 | (() (())()(())) 120 | 121 | lst 122 | (() (())(())(())) 123 | ) 124 | 125 | abcdabcd 126 | ( 127 | (() (())) 128 | ( 129 | string 130 | (() (())()(())) 131 | 132 | lst 133 | (() (())(())(())) 134 | ) 135 | ( 136 | string 137 | (() (())()(())) 138 | 139 | lst 140 | (() (())(())(())) 141 | ) 142 | ) -------------------------------------------------------------------------------- /tests/helloworld.in: -------------------------------------------------------------------------------- 1 | define 97 2 | ( define 3 | (() ()()) 4 | 97 5 | (() (()())) 6 | [+ 7 [* 9 10]] 7 | ( 8 | (() (())) 9 | ((()) ()()()()()()()) 10 | ( 11 | (() ()(())) 12 | ((()) ()()()()()()()()()) 13 | ((()) ()()()()()()()()()()) 14 | ) 15 | ) 16 | ) 17 | 18 | define space 19 | ( 20 | define 21 | (() ()()) 22 | space 23 | (() (()()())) 24 | [char 32] 25 | ( 26 | (() (())(())()) 27 | ((()) ()()()()()()()()()() 28 | ()()()()()()()()()() 29 | ()()()()()()()()()() 30 | ()()) 31 | ) 32 | ) 33 | 34 | hello world 35 | 36 | ( 37 | plus 38 | (() (())) 39 | 40 | ( 41 | char 42 | (() (())(())()) 43 | ( 44 | plus 45 | (() (())) 46 | 97 47 | (() (()())) 48 | num 49 | ((()) ()()()()()()()) 50 | ) 51 | ) 52 | ( 53 | char 54 | (() (())(())()) 55 | ( 56 | plus 57 | (() (())) 58 | 97 59 | (() (()())) 60 | num 61 | ((()) ()()()()) 62 | ) 63 | ) 64 | ( 65 | char 66 | (() (())(())()) 67 | ( 68 | plus 69 | (() (())) 70 | 97 71 | (() (()())) 72 | num 73 | ((()) ()()()()()()()()()()()) 74 | ) 75 | ) 76 | ( 77 | char 78 | (() (())(())()) 79 | ( 80 | plus 81 | (() (())) 82 | 97 83 | (() (()())) 84 | num 85 | ((()) ()()()()()()()()()()()) 86 | ) 87 | ) 88 | ( 89 | char 90 | (() (())(())()) 91 | ( 92 | plus 93 | (() (())) 94 | 97 95 | (() (()())) 96 | num 97 | ((()) ()()()()()()()()()()()()()()) 98 | ) 99 | ) 100 | (() (()()())) 101 | ( 102 | char 103 | (() (())(())()) 104 | ( 105 | plus 106 | (() (())) 107 | 97 108 | (() (()())) 109 | num 110 | ((()) ()()()()()()()()()()()()()()()()()()()()()()) 111 | ) 112 | ) 113 | ( 114 | char 115 | (() (())(())()) 116 | ( 117 | plus 118 | (() (())) 119 | 97 120 | (() (()())) 121 | num 122 | ((()) ()()()()()()()()()()()()()()) 123 | ) 124 | ) 125 | ( 126 | char 127 | (() (())(())()) 128 | ( 129 | plus 130 | (() (())) 131 | 97 132 | (() (()())) 133 | num 134 | ((()) ()()()()()()()()()()()()()()()()()) 135 | ) 136 | ) 137 | ( 138 | char 139 | (() (())(())()) 140 | ( 141 | plus 142 | (() (())) 143 | 97 144 | (() (()())) 145 | num 146 | ((()) ()()()()()()()()()()()) 147 | ) 148 | ) 149 | ( 150 | char 151 | (() (())(())()) 152 | ( 153 | plus 154 | (() (())) 155 | 97 156 | (() (()())) 157 | num 158 | ((()) ()()()) 159 | ) 160 | ) 161 | ) 162 | -------------------------------------------------------------------------------- /parenthetic.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from itertools import izip 3 | import copy 4 | import operator 5 | import os 6 | import sys 7 | 8 | # map from paren strings to english names 9 | # for the predefined symbols (lambda, etc) 10 | to_english = defaultdict(lambda:None,\ 11 | {'()': 'lambda', 12 | '()()': 'define', 13 | '(())': 'plus', 14 | '(()())': 'minus', 15 | '()(())': 'mult', 16 | '(())()': 'div', 17 | '()()()': 'if', 18 | '(())(())': 'equal', 19 | '()(())()': 'LE', 20 | '()(()())': 'not', 21 | '((()))': 'empty', 22 | '((()))()': 'cons', 23 | '((()))(())': 'car', 24 | '((()))()()': 'cdr', 25 | '(())(())()': 'char', 26 | '(())()(())': 'string'}) 27 | 28 | # map from english to parenthetic 29 | to_scheme = defaultdict(lambda:None) 30 | for k,v in to_english.iteritems(): 31 | to_scheme[v] = k 32 | 33 | def Error(errorString = 'unmatched parens', debug_mode = False): 34 | """Handle errors. If in debug_mode, prints the error string. 35 | Otherwise raises an exception to trigger the ambiguous 36 | 'Parenthesis Mismatch' error. 37 | """ 38 | 39 | if debug_mode: 40 | print "Error: " + errorString 41 | sys.exit() 42 | else: 43 | raise Exception('paren mismatch') 44 | 45 | def bracketsMatch(chars): 46 | """Returns False if any parentheses in `chars` are not matched 47 | properly. Returns True otherwise. 48 | """ 49 | 50 | level = 0 51 | for p in chars: 52 | if p == '(': 53 | level += 1 54 | elif p == ')': 55 | level -= 1 56 | if level < 0: 57 | return False 58 | 59 | return level == 0 60 | 61 | def get_exprs(chars): 62 | """Returns a list of character sequences such that for each sequence, 63 | the first and last parenthesis match. 64 | 65 | For example, "(())()()" would be split into ["(())", "()", "()"] 66 | """ 67 | 68 | level = 0 69 | current = [] 70 | for p in chars: 71 | if p == '(' or p == ')': 72 | current.append(p) 73 | if p == '(': 74 | level += 1 75 | elif p == ')': 76 | level -= 1 77 | if level == 0: 78 | yield current 79 | current = [] 80 | 81 | 82 | 83 | ## built-in functions ## 84 | def builtin_accumulate(init, accumulate, environment, params): 85 | """Helper function that handles common logic for builtin functions. 86 | 87 | Given an initial value, and a two-parameter function, the environment, and 88 | a list of params to reduce, this function will reduce [init] + params using 89 | the accumulate function and finally returns the resulting value. 90 | """ 91 | result = init 92 | for param in params: 93 | value = interpret(param, environment) 94 | try: result = accumulate(result, value) 95 | except: Error(str(value) + ' is not the correct type') 96 | return result 97 | 98 | def builtin_plus(environment, params): 99 | if len(params) >= 1: 100 | return builtin_accumulate(interpret(params[0], environment), operator.add, environment, params[1:]) 101 | else: 102 | return 0.0 103 | 104 | def builtin_minus(environment, params): 105 | if len(params) == 0: 106 | Error('subtraction requires at least 1 param') 107 | return builtin_accumulate(interpret(params[0], environment), operator.sub, environment, params[1:]) 108 | 109 | def builtin_mult(environment, params): 110 | return builtin_accumulate(1.0, operator.mul, environment, params) 111 | 112 | def builtin_div(environment, params): 113 | if len(params) == 0: 114 | Error('division requires at least 1 param') 115 | return builtin_accumulate(interpret(params[0], environment), operator.div, environment, params[1:]) 116 | 117 | def builtin_LE(environment, params): 118 | return interpret(params[0], environment) <= interpret(params[1], environment) 119 | 120 | def builtin_lambda(environment, params): 121 | 122 | bodies = [body for body in params[1:]] 123 | params = params[0][1] 124 | if len(bodies) == 0: 125 | Error("a function had no body") 126 | for kind, name in params: 127 | if kind != 'symbol': 128 | Error('lambda must have only symbols as arguments') 129 | 130 | def ret(old_environment, arguments): 131 | #print bodies 132 | try: 133 | # create new environment based on args 134 | environment = copy.copy(old_environment) 135 | for param, arg in izip(params, arguments): 136 | environment[param[1]] = interpret(arg, old_environment) 137 | # evaluate the function bodies using the new environment 138 | return interpret_trees(bodies, environment, False) 139 | except: 140 | Error("Error evaluating a function") 141 | return ret 142 | 143 | def builtin_equal(environment, params): 144 | for param1, param2 in izip(params[:-1], params[1:]): 145 | if interpret(param1, environment) != interpret(param2, environment): 146 | return False 147 | return True 148 | 149 | def builtin_if(environment, params): 150 | if len(params) != 3: 151 | Error("'if' takes in exactly 3 params") 152 | 153 | if interpret(params[0], environment): 154 | return interpret(params[1], environment) 155 | return interpret(params[2], environment) 156 | 157 | def builtin_not(environment, params): 158 | return False if interpret(params[0], environment) else True 159 | 160 | def builtin_cons(environment, params): 161 | return (interpret(params[0], environment), interpret(params[1], environment)) 162 | 163 | def builtin_car(environment, params): 164 | result = interpret(params[0], environment) 165 | if not isinstance(result, tuple): 166 | Error("car must only be called on tuples") 167 | return result[0] 168 | 169 | def builtin_cdr(environment, params): 170 | result = interpret(params[0], environment) 171 | if not isinstance(result, tuple): 172 | Error("cdr must only be called on tuples") 173 | return result[1] 174 | 175 | def builtin_char(environment, params): 176 | result = interpret(params[0], environment) 177 | if result != int(result): 178 | Error("char must only be called on integers") 179 | return chr(int(result)) 180 | 181 | def builtin_string(environment, params): 182 | result = '' 183 | cur = interpret(params[0], environment) 184 | while cur != (): 185 | if not isinstance(cur, tuple) or not isinstance(cur[1], tuple): 186 | Error("string only works on lists of chars") 187 | result += cur[0] 188 | cur = cur[1] 189 | return result 190 | 191 | # define the default (top-level) scope 192 | default_environment = \ 193 | {to_scheme['plus']: builtin_plus, 194 | to_scheme['minus']: builtin_minus, 195 | to_scheme['mult']: builtin_mult, 196 | to_scheme['div']: builtin_div, 197 | to_scheme['lambda']: builtin_lambda, 198 | to_scheme['if']: builtin_if, 199 | to_scheme['equal']: builtin_equal, 200 | to_scheme['LE']: builtin_LE, 201 | to_scheme['not']: builtin_not, 202 | to_scheme['empty']: (), 203 | to_scheme['car']: builtin_car, 204 | to_scheme['cdr']: builtin_cdr, 205 | to_scheme['cons']: builtin_cons, 206 | to_scheme['char']: builtin_char, 207 | to_scheme['string']: builtin_string} 208 | 209 | # parse the tokens into an AST 210 | def parse(tokens): 211 | """Accepts a list of parentheses and returns a list of ASTs. 212 | 213 | Each AST is a pair (type, value). 214 | If type is 'symbol', value will be the paren sequence corresponding 215 | to the symbol. 216 | If type is 'int', value will be a float that is equal to an int. 217 | If type is expr, value will be a list of ASTs. 218 | """ 219 | 220 | # check for errors 221 | if not bracketsMatch(tokens): 222 | Error('paren mismatch') 223 | # to return - a list of exprs 224 | exprs = [] 225 | for expr in get_exprs(tokens): 226 | # check for errors 227 | if len(expr) < 2: 228 | Error('too few tokens in: ' + ''.join(tokens)) 229 | elif expr[0] != '(' or expr[-1] != ')': 230 | Error('expression found without () as wrapper') 231 | 232 | # pop off starting and ending ()s 233 | expr = expr[1:-1] 234 | 235 | # symbol 236 | if expr[:2] == ['(', ')'] and len(expr) > 2: 237 | exprs.append(('symbol', ''.join(expr[2:]))) 238 | # integer 239 | elif expr[:4] == ['(', '(', ')', ')'] and len(expr) >= 4: 240 | exprs.append(('num', expr[4:].count('('))) 241 | # expr 242 | else: 243 | exprs.append(('expr', parse(expr))) 244 | 245 | return exprs 246 | 247 | 248 | def interpret(tree, environment): 249 | """Interpret a single tree (may not be a define) and return the result""" 250 | kind, value = tree 251 | if kind == 'num': 252 | return float(value) 253 | elif kind == 'symbol': 254 | if value in environment: 255 | return environment[value] 256 | else: 257 | Error('Unresolved symbol - ' + value) 258 | elif kind == 'expr': 259 | function = interpret(value[0], environment) 260 | if not hasattr(function, '__call__'): 261 | Error('Symbol "'+value[0]+'" is not a function.') 262 | return function(environment, value[1:]) 263 | else: 264 | Error("Unknown tree kind") 265 | 266 | def interpret_trees(trees, environment, doprint = True): 267 | """Interpret a sequence of trees (may contain defines) 268 | and output the result. 269 | 270 | The trees passed in should be ASTs as returned by parse(). 271 | If doprint is true, the post-interpretation value of each tree is printed. 272 | """ 273 | 274 | environment = copy.copy(environment) 275 | 276 | # hoist define statements (note: trees.sort is stable) 277 | trees.sort(key = lambda x: 0 if x[0] == 'expr' and x[1][0][1] == to_scheme['define'] else 1) 278 | ret = None 279 | for tree in trees: 280 | if tree[0] == 'expr' and tree[1][0][0] == 'symbol' and tree[1][0][1] == to_scheme['define']: 281 | try: 282 | symbol = tree[1][1] 283 | if symbol[0] != 'symbol': 284 | Error('first argument to define must be a symbol') 285 | symbol = symbol[1] 286 | value = tree[1][2] 287 | environment[symbol] = interpret(value, environment) 288 | except: 289 | Error('error evaluating define statement') 290 | else: 291 | ret = interpret(tree, environment) 292 | if doprint: 293 | print ret 294 | return ret 295 | 296 | # read in the code ignoring all characters but '(' and ')' 297 | code = [] 298 | for line in sys.stdin.readlines(): 299 | code += [c for c in line if c in '()'] 300 | 301 | # parse and interpret the code. print 'Parenthesis Mismatch' 302 | # if an error occured. 303 | try: 304 | syntax_trees = parse(code) 305 | interpret_trees(syntax_trees, default_environment) 306 | except: 307 | print 'Parenthesis Mismatch' -------------------------------------------------------------------------------- /README.markdown: -------------------------------------------------------------------------------- 1 | #Parenthetic 2 | 3 | Parenthetic is a programming language that uses only ```(``` and ```)``` as code. All other characters are considered comments. 4 | 5 | ##Hello World 6 | 7 | The following Parenthetic program prints 'hello world': 8 | 9 | ((()()())(()(()()))((()(()))((())()()()()()()())((()()(()))((())()()()()()()()()()) 10 | ((())()()()()()()()()()()))))((()()())(()(()()()))((()(())(())())((())()()()()()()( 11 | )()()()()()()()()()()()()()()()()()()()()()()()()())))((()(()))((()(())(())())((()( 12 | ()))(()(()()))((())()()()()()()())))((()(())(())())((()(()))(()(()()))((())()()()() 13 | )))((()(())(())())((()(()))(()(()()))((())()()()()()()()()()()())))((()(())(())())( 14 | (()(()))(()(()()))((())()()()()()()()()()()())))((()(())(())())((()(()))(()(()()))( 15 | (())()()()()()()()()()()()()()())))(()(()()()))((()(())(())())((()(()))(()(()()))(( 16 | ())()()()()()()()()()()()()()()()()()()()()()())))((()(())(())())((()(()))(()(()()) 17 | )((())()()()()()()()()()()()()()())))((()(())(())())((()(()))(()(()()))((())()()()( 18 | )()()()()()()()()()()()()())))((()(())(())())((()(()))(()(()()))((())()()()()()()() 19 | ()()()())))((()(())(())())((()(()))(()(()()))((())()()())))) 20 | 21 | ##Installation 22 | 23 | 1. Clone the repo, which includes an interpreter written in Python 2.7: 24 | 25 | ```git clone git@github.com:cammckinnon/Parenthetic.git``` 26 | 27 | 2. Navigate to the Parenthetic directory and run the interpreter by typing: 28 | 29 | ```python parenthetic.py``` 30 | 31 | It accepts code as input from standard input. Input is read until EOF is found, after which the output is written to the console. 32 | 33 | 3. Or you can read input from a file like this: 34 | 35 | ```cat program.p | python parenthetic.py``` 36 | 37 | ##Syntax 38 | 39 | Parenthetic uses Lisp-style expressions, where parentheses enclose expressions: 40 | 41 | (foo arg1 arg2) 42 | 43 | Note that parenthetic programs with unmatched parentheses are invalid 44 | 45 | ###Integers 46 | 47 | A sequence of *n* parenthesis sets can be used to represent the integer *n*. For example the following sequence could represent the number 3: 48 | 49 | () () () 50 | 51 | In order to tell the interpreter that you want the sequence to represent an integer, you must pass it as an argument to the built-in ```(())``` macro. The macro acts like a function that accepts parenthesis sequences and returns integers. For example, the following program prints 3.0 to the console: 52 | 53 | ( 54 | integer macro 55 | (()) 56 | 3 sets of parentheses 57 | () () () 58 | ) 59 | 60 | *Output*: ```3.0``` 61 | 62 | Or equivalently: 63 | 64 | ((()) ()()()) 65 | 66 | *Output*: ```3.0``` 67 | 68 | Note that it doesn't matter how the parentheses in the sequence are nested within each other. For instance the following Parenthetic program prints 5.0 to the console: 69 | 70 | ((()) ((())) () () ) 71 | 72 | *Output*: ```5.0``` 73 | 74 | ###Symbols 75 | 76 | A symbol is a sequence of parentheses that corresponds to some data or a function. For example, the symbol for the built-in multiplication function is ```()(())```. Like with integers, there is a macro for interpreting parenthesis sequences as symbols. It is ```()```. 77 | 78 | For example, the following Parenthetic program prints 10 by using the multiplication function ```()(())``` to multiply 5 times 2: 79 | 80 | ( 81 | multiply [note the use of the [] macro] 82 | (() ()(())) 83 | 84 | 2 85 | ((()) ()()) 86 | 87 | 5 88 | ((()) ()()()()()) 89 | ) 90 | 91 | *Output*: ```10.0``` 92 | 93 | Equivalent Lisp code: 94 | 95 | (* 2 5) 96 | 97 | It is also possible to define your own symbols, using the built-in 'define' function, whose symbol is ```()()```. For example, the following code defines ```(())(())``` as 6, then adds multiplies by 2 to get 12. Remember that all characters other than ```(``` and ```)``` characters are comments (including ```[``` and ```]```).: 98 | 99 | define [[]][[]] as 6 100 | ( 101 | define 102 | (() ()()) 103 | 104 | [[]][[]] 105 | (() (())(())) 106 | 107 | 6 108 | ((()) ()()()()()()) 109 | ) 110 | 111 | [[]][[]] * 2 112 | ( 113 | multiply 114 | (() ()(())) 115 | 116 | [[]][[]] 117 | (() (())(())) 118 | 119 | 2 120 | ((()) ()()) 121 | ) 122 | 123 | *Output*: ```12.0``` 124 | 125 | Equivalent Lisp code: 126 | 127 | (define x 6) 128 | (* x 2) 129 | 130 | ##Standard Library 131 | 132 | Parenthetic has a built-in standard library that is available by default (no includes/library imports necessary): 133 | 134 | ###define 135 | Symbol: ```()()``` 136 | 137 | For details on **define** and its usage, see the Syntax->Symbols section above. 138 | 139 | ###multiply, divide, subtract, add 140 | These math operations can be performed on one or more numbers. Here are their symbols: 141 | 142 | - **subtract**: ```(()())``` 143 | - **multiply**: ```()(())``` 144 | - **divide**: ```(())()``` 145 | - **add**: ```(())```. Note: You can also use **add** for concatenating characters/strings together (see the **string** section below). 146 | 147 | Example: 148 | 149 | ( 150 | plus 151 | (() (())) 152 | 153 | 3 154 | ((()) ()()()) 155 | 156 | 6 157 | ((()) ()()()()()()) 158 | ) 159 | 160 | *Output*: ```9.0``` 161 | 162 | Equivalent Lisp code: 163 | 164 | (+ 3 6) 165 | 166 | 167 | ###lambda 168 | Symbol: ```()``` 169 | 170 | Facilitates anonymous functions. Here's an example where we use **define** and **lambda** to create a function that takes in a number and adds 1 to it: 171 | 172 | define a [][][] as a function that 173 | takes in a number n and returns n + 1 174 | ( 175 | define 176 | (() ()()) 177 | 178 | [][][] 179 | (() ()()()) 180 | 181 | ( 182 | lambda 183 | (() ()) 184 | 185 | ( 186 | n [[]][] 187 | (() (())()) 188 | ) 189 | 190 | n + 1 191 | ( 192 | plus 193 | (() (())) 194 | 195 | n [[]][] 196 | (() (())()) 197 | 198 | 1 199 | ((()) ()) 200 | ) 201 | ) 202 | ) 203 | 204 | 7 + 1 205 | ( 206 | plus 207 | (() ()()()) 208 | 7 209 | ((()) ()()()()()()()) 210 | ) 211 | 212 | 213 | *Output*: 8.0 214 | 215 | Equivalent Lisp code: 216 | 217 | (define f 218 | (lambda (n) 219 | (+ n 1))) 220 | 221 | (f 7) 222 | 223 | 224 | ###equal 225 | 226 | Symbol: ```(())(())``` 227 | 228 | Takes in two arguments. If they are equal, the True primitive is returned. Otherwise the False primitive is returned. 229 | 230 | Example: 231 | 232 | [equal 2 2] 233 | ( 234 | equal 235 | (() (())(())) 236 | 2 237 | ((()) ()()) 238 | 2 239 | ((()) (())) 240 | ) 241 | 242 | *Output*: ```True``` 243 | 244 | Equivalent Lisp code: 245 | 246 | (equal? 2 2) 247 | 248 | ###<= 249 | 250 | Symbol: ()(())() 251 | 252 | Takes in two numeric arguments *a* and *b*. If *a* <= *b*, the True primitive is returned. Otherwise the False primitive is returned. 253 | 254 | Example: 255 | 256 | [<= 3 4] 257 | ( 258 | <= 259 | (() ()(())()) 260 | 3 261 | ((()) ()()()) 262 | 4 263 | ((()) (())(())) 264 | ) 265 | 266 | *Output*: ```True``` 267 | 268 | Equivalent Lisp code: 269 | 270 | (<= 3 4) 271 | 272 | ###if 273 | 274 | Symbol: ```()()()``` 275 | 276 | Takes in three arguments: *condition*, *then*, and *else*. If *condition* is not false and not 0, the *then* argument is evaluated and returned. Otherwise, the *else* argument is evaluated and returned. 277 | 278 | Example: 279 | 280 | if 3 = 4, return 1, otherwise return 2 281 | ( 282 | if 283 | (() ()()()) 284 | 285 | [equal 3 4] 286 | ( 287 | equal 288 | (() (())(())) 289 | 3 290 | ((()) ()()()) 291 | 4 292 | ((()) ((()))()) 293 | ) 294 | 295 | 1 296 | ((()) ()) 297 | 2 298 | ((()) ()()) 299 | ) 300 | 301 | *Output*: ```2.0``` 302 | 303 | Equivalent Lisp code: 304 | 305 | (if (equal? 3 4) 1 2) 306 | 307 | ###not 308 | 309 | Symbol: ```()(()())``` 310 | 311 | If the argument is neither 0.0 nor False, True is returned. Otherwise, False is returned. 312 | 313 | Example: 314 | 315 | [not [equal 1 1]] 316 | ( 317 | not 318 | (() ()(()())) 319 | 320 | [equal 1 1] 321 | ( 322 | equal 323 | (() ()(())()) 324 | 1 325 | ((()) ()) 326 | 1 327 | ((()) ()) 328 | ) 329 | ) 330 | 331 | *Output*: ```False``` 332 | 333 | Equivalent Lisp code: 334 | 335 | (not (equal? 1 1)) 336 | 337 | ###cons 338 | 339 | Symbol: ```((()))()``` 340 | 341 | Takes in two arguments *a* and *b* and returns a pair ```(a, b)```. 342 | 343 | Example: 344 | 345 | ( 346 | cons 347 | (() ((()))()) 348 | 349 | 1 350 | ((()) ()) 351 | 352 | 2 353 | ((()) ()()) 354 | ) 355 | 356 | *Output*: ```(1.0, 2.0)``` 357 | 358 | Equivalent Lisp code: 359 | 360 | (cons 1 2) 361 | 362 | ###car 363 | 364 | Symbol: ```((()))(())``` 365 | 366 | Given a pair ```(a, b)```, returns `a`. 367 | 368 | Example: 369 | 370 | ( 371 | car 372 | (() ((()))(())) 373 | 374 | ( 375 | cons 376 | (() ((()))()) 377 | 378 | 1 379 | ((()) ()) 380 | 381 | 2 382 | ((()) ()()) 383 | ) 384 | ) 385 | 386 | *Output*: ```1.0``` 387 | 388 | Equivalent Lisp code: 389 | 390 | (car (cons 1 2)) 391 | 392 | ###cdr 393 | 394 | Symbol: ```((()))()()``` 395 | 396 | ( 397 | cdr 398 | (() ((()))()()) 399 | 400 | ( 401 | cons 402 | (() ((()))()) 403 | 404 | 1 405 | ((()) ()) 406 | 407 | 2 408 | ((()) ()()) 409 | ) 410 | ) 411 | 412 | *Output*: ```2.0``` 413 | 414 | Equivalent Lisp code: 415 | 416 | (cdr (cons 1 2)) 417 | 418 | ###empty 419 | 420 | Symbol: ```((()))``` 421 | 422 | **empty** exists to facilitate lists. We define a list as a pair such that applying **cdr** one or more times to the list will return **empty**. Note - empty is not a function; it can be accessed directly. When printed to the console, **empty** appears as ```()```. 423 | 424 | Example: 425 | 426 | (() ((()))) 427 | 428 | *Output*: `()` 429 | 430 | Equivalent Lisp code: 431 | 432 | (list) 433 | 434 | ###char 435 | 436 | Symbol: ```(())(())()``` 437 | 438 | Accepts one integer argument, and returns the corresponding ascii character. 439 | 440 | Example: 441 | 442 | ( 443 | char 444 | (() (())(())()) 445 | 446 | 33 [ascii value for '!'] 447 | ((()) ()()()()()()()()()() 448 | ()()()()()()()()()() 449 | ()()()()()()()()()() 450 | ()()()) 451 | ) 452 | 453 | *Output*: ```!``` 454 | 455 | ###string 456 | 457 | Symbol: ```(())()(())``` 458 | 459 | Accepts a list of characters, and returns a string. **string** is useful for displaying messages. 460 | 461 | Example: 462 | 463 | define 97 for easy access to a and b 464 | ( define 465 | (() ()()) 466 | 97 467 | (() (()())) 468 | [+ 7 [* 9 10]] 469 | ( 470 | (() (())) 471 | ((()) ()()()()()()()) 472 | ( 473 | (() ()(())) 474 | ((()) ()()()()()()()()()) 475 | ((()) ()()()()()()()()()()) 476 | ) 477 | ) 478 | ) 479 | 480 | [string ['a', ['b', []]]] 481 | ( 482 | string 483 | (() (())()(())) 484 | ( 485 | cons 486 | (() ((()))()) 487 | a 488 | ( 489 | (() (())(())()) 490 | ( 491 | (() (())) 492 | ((()) ) 493 | (() (()())) 494 | ) 495 | ) 496 | 497 | ( 498 | cons 499 | (() ((()))()) 500 | b 501 | ( 502 | (() (())(())()) 503 | ( 504 | (() (())) 505 | ((()) ()) 506 | (() (()())) 507 | ) 508 | ) 509 | empty 510 | (() ((()))) 511 | 512 | ) 513 | ) 514 | ) 515 | 516 | *Output*: ```ab``` 517 | 518 | Tip: You can also pass any combination of characters and strings into the **add** function (see above) to create strings. 519 | 520 | 521 | ##Error handling 522 | 523 | If your program has a runtime error or a compiletime error the interpreter will print "Parenthesis Mismatch" to standard output and then exit. 524 | 525 | ##Test Suite 526 | 527 | In the `./tests` folder there is a series of tests that check the interpreter for correctness. 528 | 529 | python tests.py 530 | 531 | You may find this useful if you wish to modify the interpreter's source code. 532 | 533 | ##Inspiration 534 | 535 | This language was inspired by a conversation with [Lucas](http://www.lucaswoj.com), who said that scheme looks like this: ())()()()))))(). Well, it does now! 536 | 537 | Also the esoteric language [Parenthesis Hell](http://esolangs.org/wiki/Parenthesis_Hell) was a great inspiration. --------------------------------------------------------------------------------