def object(arg, scope): obj = Object() while arg is not nil: assert type(arg) is Pair slot = arg.car if type(slot) is FormNode: func = eval_node(slot.car, scope) slot = FormNode(ValueNode('CACHE', func), slot.cdr) # this ensures we only eval the car of the form once if func is cons: assert type(slot.cdr) is Pair obj.set(slot.cdr, scope) elif func is get: assert type(slot.cdr) is Pair assert type(slot.cdr.cdr) is Pair key_node = slot.cdr.cdr.car obj.set(Pair(key_node, Pair(slot, nil)), scope) else: raise Exception("I don't know what to do with that yet") elif type(slot) is IdentifierNode: obj.set(Pair(slot, Pair(slot, nil)), scope) else: raise Exception("syntax error!") arg = arg.cdr return obj
def test_eval_builtin_id(self): self.assertEqual(2, isheval('(id 2)')) self.assertEqual(5, isheval('(id (add 2 3))')) self.assertEqual(Pair(1, nil), isheval('(id (list 1))')) self.assertEqual(Pair(1, nil), isheval('(id [1])')) self.assertEqual(Pair(1, 2), isheval('(id [1 | 2])')) self.assertEqual(1, isheval('(id 1)')) self.assertEqual(20, isheval('((id id) 20)'))
def test_eval_builtins(self): self.assertEqual(1, isheval('(car [1])')) self.assertEqual(1, isheval('(car (id [1]))')) self.assertEqual(nil, isheval('(cdr [1])')) self.assertEqual(Pair(1, 2), isheval('(call cons 1 2)')) self.assertEqual(Pair(1, 2), isheval('(apply cons (list 1 2))')) self.assertEqual(Pair(1, 2), isheval('(apply cons [1 2])')) self.assertEqual(1, isheval('(apply car [[1 2]])')) self.assertEqual(Pair(1, Pair(2, nil)), isheval('((curry list 1) 2)'))
def test_eval_functions_in_scope(self): my_id = isheval('(fn x x)') scope = Scope({'my_id': my_id}, root) self.assertEqual(my_id, isheval('my_id', scope)) self.assertEqual(my_id, isheval('(my_id | (my_id | my_id))', scope)) self.assertEqual(3, isheval('(my_id | 3)', scope)) self.assertEqual(Pair(my_id, nil), isheval('(my_id my_id)', scope)) self.assertEqual(my_id, isheval('(my_id | my_id)', scope)) self.assertEqual(Pair(20, nil), isheval('(my_id 20)', scope))
def test_dictionaries_can_still_have_attributes(self): self.assertEqual( Pair(10, Pair(20, 30)), isheval(''' (dict = (dictionary)) (set dict 5 10) (set dict foo 20) (set dict #foo 30) dict.5 : dict.foo : dict.#foo'''))
def dictionary(arg, scope): dct = Dictionary() while arg is not nil: assert type(arg) is Pair pair = eval_node(arg.car, scope) dct.set(Pair(ValueNode('CACHE', pair.car), Pair(ValueNode('CACHE', pair.cdr), nil)), scope) arg = arg.cdr return dct
def test_eval_square_brackets(self): self.assertEqual(Pair(1, nil), isheval('[1]')) self.assertEqual(Pair(1, Pair(2, Pair(3, nil))), isheval('[1 2 3]')) self.assertEqual(Pair(1, 2), isheval('[1 | 2]')) self.assertEqual(Pair(1, Pair(2, 3)), isheval('[1 2 | 3]')) self.assertEqual(Pair(10, nil), isheval('[(add 5 5)]')) self.assertEqual(Pair(10, 20), isheval('[(add 5 5) | (add 10 10)]'))
def test_cons_pattern_match_list(self): self._test_pattern('(pattern [a b | c])', '[1 2 | 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a b | c])', '[1 2]', { 'a': 1, 'b': 2, 'c': nil }) self._test_pattern_fails('(pattern [a b | c])', '[1]') self._test_pattern_fails('(pattern [a b | c])', 'nil') self._test_pattern('(pattern [a b c])', '[1 2 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern_fails('(pattern [a b c])', '[1 2 | 3]') self._test_pattern_fails('(pattern [a b c])', '[1 2]') self._test_pattern_fails('(pattern [a b c])', '[1]') self._test_pattern_fails('(pattern [a b c])', 'nil') self._test_pattern('(pattern []:[])', 'nil:nil', {}) self._test_pattern_fails('(pattern []:[])', 'nil') self._test_pattern('(pattern []:[]:[]:[])', 'nil:nil:nil:nil', {}) self._test_pattern_fails('(pattern []:[]:[]:[])', 'nil') self._test_pattern('(pattern []:a)', 'nil:1', {'a': 1}) self._test_pattern_fails('(pattern []:a)', 'nil') self._test_pattern('(pattern a:b)', '1:2', {'a': 1, 'b': 2}) self._test_pattern('(pattern a:b)', '[1 | 2]', {'a': 1, 'b': 2}) self._test_pattern('(pattern a:b)', '[1 2 3]', { 'a': 1, 'b': Pair(2, Pair(3, nil)) }) self._test_pattern_fails('(pattern a:b)', 'nil') self._test_pattern('(pattern a:b:c)', '1:2:3', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern a:b:c)', '[1 2 3]', { 'a': 1, 'b': 2, 'c': Pair(3, nil) }) self._test_pattern_fails('(pattern a:b:c)', '1:2') self._test_pattern_fails('(pattern a:b:c)', 'nil')
def eval_node(node, scope): if type(node) is Pair: return Pair(eval_node(node.car, scope), eval_node(node.cdr, scope)) # we're re-evaluating something that's already been evaluated. this might be a terrible idea. # currently used by the apply and curry builtins to pre-apply arguments before calling a built-in function # should just use ValueNodes in all cases if not isinstance(node, Node): return node if type(node) is FormNode: fn = eval_node(node.car, scope) if type(fn) is FunctionType: return fn(node.cdr, scope) else: return fn.call(node.cdr, scope) elif type(node) is IdentifierNode: return scope.get(node.identifier) elif type(node) is NumericLiteralNode: return int(node.value) elif type(node) is SymbolLiteralNode: return Symbol(node.value) elif type(node) is ValueNode: return node.value else: raise Exception("I don't know how to eval %s (%s)" % (str(node), type(node)))
def test_curry_only_evaluates_arguments_once(self): count = 0 def increment_count(args, scope): nonlocal count count += 1 return 5 scope = Scope({'inc': increment_count}, root) self.assertEqual(0, count) curried = isheval('(curry list (inc))', scope) self.assertEqual(1, count) scope.set('curried', curried) self.assertEqual(Pair(5, Pair(10, Pair(20, nil))), isheval('(curried 10 20)', scope)) self.assertEqual(1, count)
def test_promises(self): self.assertEqual( Pair(0, Pair(0, Pair(1, 1))), isheval(''' (x = 0) (side-effect = (fn - (x = (add x 1)) 20)) (a = x) (def y (delay (side-effect))) (b = x) (force y) (c = x) (force y) (d = x) a:b:c:d'''))
def test_eval_functions(self): self.assertEqual(20, isheval('((fn x 20))')) self.assertEqual(5, isheval('((fn x x) | 5)')) self.assertEqual(5, isheval('((fn x (car x)) | [5])')) self.assertEqual(5, isheval('((fn x (car x)) 5)')) self.assertEqual(Pair(5, nil), isheval('((fn x x) (add 2 3))')) self.assertEqual(30, isheval('((fn x (add 10 (car x))) 20)')) self.assertRaises(Exception, isheval, '((add 1 2))')
def test_eval_nested_functions(self): compose = isheval('(fn x (fn y ((car (cdr x)) ((car x) (car y)))))') self.assertEqual(Function, type(compose)) scope = Scope({'compose': compose}, root) self.assertEqual(compose, isheval('compose', scope)) self.assertEqual(10, isheval('((compose id id) 10)', scope)) self.assertEqual(Pair(10, nil), isheval('((compose id id) [10])', scope))
def test_multi_functions(self): self.assertEqual( Pair(11, 30), isheval(''' (def multi-test (mfn ([a] (add a 1)) ([a b] (add a b)) )) (multi-test 10):(multi-test 10 20)'''))
def test_pairs_force_cdrs(self): self.assertEqual( Pair(0, Pair(0, Pair(1, 1))), isheval(''' (x = 0) (side-effect = (fn - (x = (add x 1)) 20)) (a = x) (pair = 10:(delay (side-effect))) pair.cdr-slot (b = x) pair.cdr (c = x) (force pair.cdr-slot) (d = x) a:b:c:d'''))
def test_eval_list_special_form(self): self.assertEqual(Pair(7, Pair(8, Pair(9, nil))), isheval('(list 7 8 9)')) self.assertEqual(Pair(10, 20), isheval('(list 10 | 20)')) self.assertEqual(Pair(1, Pair(2, 3)), isheval('(list 1 2 | 3)')) self.assertEqual(Pair(10, 20), isheval('(list (add 5 5) | (add 10 10))')) self.assertRaises(Exception, isheval, '(list | 1)') self.assertRaises(Exception, isheval, '(list | [4 5 6])')
def __init__(self, arg, scope): self.functions = [] while arg is not nil: assert type(arg) is Pair assert type(arg.car) is FormNode func_arg = Pair(arg.car.car, arg.car.cdr) function = Function(func_arg, scope) self.functions.append(function) # print(function) arg = arg.cdr
def test_read_square_brackets_with_cdr_converts_to_list_special_form(self): self.assertEqual( FormNode(ValueNode('_list', specials.list_), Pair(IdentifierNode('a'), IdentifierNode('b'))), read_one('[a | b]'))
def cons(arg, scope): car = arg.car cdr = arg.cdr.car return Pair(eval_node(car, scope), eval_node(cdr, scope))
def cdr(arg, scope): assert type(arg) is Pair assert eval_node(arg.cdr, scope) is nil val = eval_node(arg.car, scope) return val.get(Pair(IdentifierNode('cdr'), nil), scope)
def test_read_misc(self): self.assertEqual( FormNode(IdentifierNode('id'), Pair(NumericLiteralNode('1'), nil)), read_one('(id 1)')) self.assertEqual( FormNode( IdentifierNode('id'), Pair(NumericLiteralNode('1'), Pair(NumericLiteralNode('2'), nil))), read_one('(id 1 2)')) self.assertEqual(Forms(IdentifierNode('id'), NumericLiteralNode('1')), read_one('(id 1)')) self.assertEqual( Forms( IdentifierNode('id'), FormNode( IdentifierNode('add'), Pair(NumericLiteralNode('1'), Pair(NumericLiteralNode('2'), nil)))), read_one('(id (add 1 2))')) self.assertEqual( Forms( IdentifierNode('id'), FormNode( ValueNode('_list', specials.list_), Pair(NumericLiteralNode('1'), Pair(NumericLiteralNode('2'), nil)))), read_one('(id [1 2])')) self.assertEqual( Forms( IdentifierNode('id'), FormNode( ValueNode('_list', specials.list_), Pair(NumericLiteralNode('1'), NumericLiteralNode('2')))), read_one('(id [1 | 2])')) self.assertEqual( Forms(Forms(IdentifierNode('id'), IdentifierNode('id')), NumericLiteralNode('1')), read_one('((id id) 1)')) self.assertEqual( Forms( IdentifierNode('print'), Forms(IdentifierNode('add'), NumericLiteralNode('5'), NumericLiteralNode('6'))), read_one('(print (add 5 6))')) self.assertEqual( Forms( IdentifierNode('print'), FormNode( ValueNode('_list', specials.list_), Pair(NumericLiteralNode('7'), NumericLiteralNode('8')))), read_one('(print [7 | 8])')) self.assertEqual( Forms( IdentifierNode('print'), Forms( ValueNode('_list', specials.list_), Forms(IdentifierNode('add'), NumericLiteralNode('10'), NumericLiteralNode('20'))), ), read_one('(print [(add 10 20)])'))
def test_identifier_pattern_match(self): self._test_pattern('(pattern a)', '10', {'a': 10}) self._test_pattern('(pattern a)', 'nil', {'a': nil}) self._test_pattern('(pattern a)', '[1 2 3]', {'a': Pair(1, Pair(2, Pair(3, nil)))})
def function_shorthand(arg, scope): if arg is nil: return Function(Pair(ValueNode('_default_pattern', default_arguments_pattern_singleton), nil), scope) assert type(arg) is Pair return Function(Pair(ValueNode('_default_pattern', default_arguments_pattern_singleton), Pair(FormNode(arg.car, arg.cdr), nil)), scope)
def list_(arg, scope): if arg is nil: return nil if type(arg) is not Pair: raise Exception('cannot have a cdr without a car') return Pair(eval_node(arg.car, scope), eval_node(arg.cdr, scope))
def test_defaulted_patterns(self): self._test_pattern('(pattern [a | b])', '1:2', {'a': 1, 'b': 2}) self._test_pattern('(pattern [a | b])', '[1]', {'a': 1, 'b': nil}) self._test_pattern('(pattern [a | b])', '1:2:3', { 'a': 1, 'b': Pair(2, 3) }) self._test_pattern_fails('(pattern [a | b])', 'nil') # This is a somewhat useless pattern -- the default can never be triggered, # since you can never have a cons cell without a cdr (although nil can be # thought of as a cons cell without a car). self._test_pattern('(pattern [a | b = 20])', '1:2', {'a': 1, 'b': 2}) self._test_pattern_fails('(pattern [a | b = 20])', '1') self._test_pattern_fails('(pattern [a | b = 20])', 'nil') self._test_pattern('(pattern [a = 10 | b])', '1:2', {'a': 1, 'b': 2}) # THIS IS COUNTER-INTUITIVE. # At least, it's not what I expected when I first wrote these tests. # To explain why it works, though, think of it as nothing:nil -- nothing cons nil is # just nil -- you didn't cons anything onto it, so it didn't change. And you can # match nothing:nil -- nothing goes to (pattern a = 10), which can accept nothing # and simply returns its default value. Then nil goes to b. And it works. self._test_pattern('(pattern [a = 10 | b])', 'nil', { 'a': 10, 'b': nil }) self._test_pattern_fails('(pattern [a = 10 | b])', '1') self._test_pattern('(pattern [a = 10 | b = 20])', '1:2', { 'a': 1, 'b': 2 }) # This is equivalent to nothing:nil, as explained above. self._test_pattern('(pattern [a = 10 | b = 20])', 'nil', { 'a': 10, 'b': nil }) self._test_pattern_fails('(pattern [a = 10 | b = 20])', '1') self._test_pattern('(pattern [a = 10 | b = 20]:c)', '[1 | 2]:3', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a = 10 | b = 20]:c)', 'nil', { 'a': 10, 'b': 20, 'c': nil }) self._test_pattern('(pattern [a b]:c)', '[1 2]:nil', { 'a': 1, 'b': 2, 'c': nil }) self._test_pattern_fails('(pattern [a = 10 b = 20]:c)', 'nil') # This is a little weird when you think of it as a list, but if you think of it as a ConsPattern # whose cdr_pattern is a ConsPattern then this does make sense. self._test_pattern('(pattern [a = 10 b = 20 | c = 30]:d)', 'nil', { 'a': 10, 'b': 20, 'c': 30, 'd': nil }) self._test_pattern('(pattern [a b]:c)', '[1 2]:nil', { 'a': 1, 'b': 2, 'c': nil }) self._test_pattern('(pattern [[a | b] | c])', '[1|2]:3', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern_fails('(pattern [[a | b] | c])', '[1|2]') self._test_pattern_fails('(pattern [[a | b] | c])', '[1]') self._test_pattern_fails('(pattern [[a | b] | c])', 'nil') self._test_pattern('(pattern [[a | b] c])', '[1:2 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern_fails('(pattern [[a | b] c])', '[1|2]') self._test_pattern_fails('(pattern [[a | b] c])', '[1 3]') self._test_pattern_fails('(pattern [[a | b] c])', '[1]') self._test_pattern_fails('(pattern [[a | b] c])', 'nil') self._test_pattern('(pattern a::even? = 10)', '7', {'a': 10}) self._test_pattern('(pattern a::even? = 10)', '8', {'a': 8}) self._test_pattern('(pattern [a b = 20])', '[1 2]', {'a': 1, 'b': 2}) self._test_pattern('(pattern [a b = 20])', '[1]', {'a': 1, 'b': 20}) self._test_pattern('(pattern [a b = 20])', '1:2:nil', {'a': 1, 'b': 2}) self._test_pattern_fails('(pattern [a b = 20])', '1:2:3') self._test_pattern_fails('(pattern [a b = 20])', 'nil') self._test_pattern('(pattern [a b c = 30])', '[1 2 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a b c = 30])', '[1 2]', { 'a': 1, 'b': 2, 'c': 30 }) self._test_pattern_fails('(pattern [a b c = 30])', '[1]') self._test_pattern_fails('(pattern [a b c = 30])', 'nil') self._test_pattern('(pattern [a b = 20 c = 30])', '[1 2 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a b = 20 c = 30])', '[1 2]', { 'a': 1, 'b': 2, 'c': 30 }) self._test_pattern('(pattern [a b = 20 c = 30])', '[1]', { 'a': 1, 'b': 20, 'c': 30 }) self._test_pattern_fails('(pattern [a b = 20 c = 30])', 'nil') self._test_pattern('(pattern [a = 10 b = 20 c = 30])', '[1 2 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a = 10 b = 20 c = 30])', '[1 2]', { 'a': 1, 'b': 2, 'c': 30 }) self._test_pattern('(pattern [a = 10 b = 20 c = 30])', '[1]', { 'a': 1, 'b': 20, 'c': 30 }) self._test_pattern('(pattern [a = 10 b = 20 c = 30])', 'nil', { 'a': 10, 'b': 20, 'c': 30 }) self._test_pattern_fails('(pattern [a = 10 b = 20 c = 30])', '1') self._test_pattern('(pattern [a b = 20 []:[]])', '[1 2 nil:nil]', { 'a': 1, 'b': 2 }) self._test_pattern_fails('(pattern [a b = 20 []:[]])', '[1]') self._test_pattern_fails('(pattern [a b = 20 []:[]])', '[1 2 nil]') self._test_pattern('(pattern [a b = 20 | []:[]])', '[1 2 | nil:nil]', { 'a': 1, 'b': 2 }) self._test_pattern('(pattern [a b = 20 | []:[]])', '[1 2 nil]', { 'a': 1, 'b': 2 }) self._test_pattern_fails('(pattern [a b = 20 | []:[]])', '[1]') self._test_pattern_fails('(pattern [a b = 20 | []:[]])', '[1 nil]') self._test_pattern('(pattern [a b = 20 c])', '[1 2 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a b = 20 c])', '[1 2 nil]', { 'a': 1, 'b': 2, 'c': nil }) self._test_pattern_fails('(pattern [a b = 20 c])', '[1 2]') self._test_pattern_fails('(pattern [a b = 20 c])', '[1]') self._test_pattern_fails('(pattern [a b = 20 c])', 'nil') self._test_pattern('(pattern [a b = 20 | c = 30])', '[1 2 | 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a b = 20 | c = 30])', '[1 2]', { 'a': 1, 'b': 2, 'c': nil }) self._test_pattern('(pattern [a b = 20 | c = 30])', '[1]', { 'a': 1, 'b': 20, 'c': nil }) # counter-intuitive! self._test_pattern_fails('(pattern [a b = 20 | c = 30])', 'nil') self._test_pattern('(pattern [a b = 20 | c])', '[1 2 | 3]', { 'a': 1, 'b': 2, 'c': 3 }) self._test_pattern('(pattern [a b = 20 | c])', '[1 2]', { 'a': 1, 'b': 2, 'c': nil }) self._test_pattern('(pattern [a b = 20 | c])', '[1]', { 'a': 1, 'b': 20, 'c': nil }) # counter-intuitive! self._test_pattern_fails('(pattern [a b = 20 | c])', 'nil') self._test_pattern('(pattern [a b = 20 3])', '[1 2 3]', { 'a': 1, 'b': 2 }) self._test_pattern_fails('(pattern [a b = 20 3])', '[1 2]') self._test_pattern_fails('(pattern [a b = 20 3])', '[1 3]') self._test_pattern_fails('(pattern [a b = 20 3])', '[1]') self._test_pattern_fails('(pattern [a b = 20 3])', 'nil') self._test_pattern('(pattern [a b = 20 | 3])', '[1 2 | 3]', { 'a': 1, 'b': 2 }) self._test_pattern_fails('(pattern [a b = 20 | 3])', '[1 2]') self._test_pattern_fails('(pattern [a b = 20 | 3])', '[1 3]') self._test_pattern_fails('(pattern [a b = 20 | 3])', '[1 | 2]') self._test_pattern_fails('(pattern [a b = 20 | 3])', '[1 | 3]') self._test_pattern_fails('(pattern [a b = 20 | 3])', '[1]') self._test_pattern_fails('(pattern [a b = 20 | 3])', 'nil') self._test_pattern('(pattern [a b = 20 []])', '[1 2 nil]', { 'a': 1, 'b': 2 }) self._test_pattern('(pattern [a b = 20 []])', '[1 nil nil]', { 'a': 1, 'b': nil }) self._test_pattern_fails('(pattern [a b = 20 []])', '[1 nil]') self._test_pattern_fails('(pattern [a b = 20 []])', '[1]') self._test_pattern_fails('(pattern [a b = 20 []])', 'nil') self._test_pattern('(pattern [a b = 20 | []])', '[1 2]', { 'a': 1, 'b': 2 }) self._test_pattern('(pattern [a b = 20 | []])', '[1]', { 'a': 1, 'b': 20 }) self._test_pattern_fails('(pattern [a b = 20 | []])', '[1 2 nil]')