--- /dev/null
+from __future__ import print_function
+
+import re
+import traceback
+
+class MiniObject(object):
+ def __init__(self, py_object, **meta):
+ ( "The following python types map to the following mini types:\n"
+ " bool -> boolean\n"
+ " str -> string\n"
+ " int -> integer\n"
+ " float -> float\n"
+ " tuple -> list (may contain different types)\n"
+ " list -> vector (may only contain one type)\n"
+ " dict -> map\n"
+ " MiniSymbol -> symbol\n"
+ " Pair -> pair"
+ "mini vectors and maps should be treated as though immutable"
+ "s-expressions should be parsed as tuples"
+ )
+ self.py_object = py_object
+ self.meta = meta
+
+ def __repr__(self):
+ if self.py_object == None:
+ return 'nil'
+
+ if isinstance(self.py_object,bool):
+ return 'true' if self.py_object else 'false'
+
+ return repr(self.py_object)
+
+ def __str__(self):
+ if isinstance(self.py_object,str):
+ return self.py_object
+
+ return repr(self)
+
+class Identifier(object):
+ def __init__(self,symbol,**kwargs):
+ assert isinstance(symbol,str)
+
+ self.symbol = symbol
+
+ self.start = kwargs.get('start')
+ self.end = kwargs.get('end')
+
+ def __repr__(self):
+ return '<identifier {}>'.format(self.symbol)
+
+def is_identifier(mini_object):
+ assert isinstance(mini_object, MiniObject)
+ if isinstance(mini_object.py_object, Identifier):
+ return TRUE
+ return FALSE
+
+SYMBOLS = {}
+
+class MiniSymbol(object):
+ def __init__(self,string):
+ self.string = string
+
+ def __eq__(self,other):
+ return self is other
+
+ def __repr__(self):
+ return '<symbol :{}>'.format(self.string)
+
+class MiniPair(object):
+ def __init__(self, car, cdr):
+ assert isinstance(car, MiniObject)
+ assert isinstance(cdr, MiniObject)
+
+ self.car = car
+ self.cdr = cdr
+
+ def __repr__(self):
+ return '<pair {}, {}>'.format(self.car, self.cdr)
+
+def evaluate_arguments(arguments_cons_list, environment):
+ if arguments_cons_list == NIL:
+ return NIL
+
+ return cons(
+ evaluate(car(arguments_cons_list), environment),
+ evaluate_arguments(cdr(arguments_cons_list), environment))
+
+class MiniEnvironment(MiniObject):
+ 'This acts like a dict in Python code and a cons-dict in mini code'
+ def __init__(self):
+ super(self.__class__, self).__init__(None)
+
+ def __getitem__(self,key):
+ assert isinstance(key,str)
+ key_symbol = create_symbol(key)
+
+ return cons_dict_get(self,key_symbol)
+
+ def __setitem__(self,key,value):
+ assert isinstance(key,str)
+ key_symbol = create_symbol(key)
+
+ assert isinstance(value, MiniObject)
+ self.py_object = cons_dict_set(self,key_symbol,value).py_object
+
+ def __contains__(self,key):
+ assert isinstance(key,str)
+ key_symbol = create_symbol(key)
+
+ return cons_dict_has_key(self,key_symbol) == TRUE
+
+ def get(self,key):
+ assert isinstance(key,str)
+
+ if key in self:
+ return self[key]
+
+ return None
+
+def dict_to_environment(dictionary):
+ result = MiniEnvironment()
+
+ for key,value in dictionary.iteritems():
+ result[key] = value
+
+ return result
+
+class MiniApplicative(object):
+ def __init__(self, operative):
+ assert callable(operative)
+ self.operative = operative
+
+ def __call__(self, pattern, environment):
+ assert isinstance(pattern, MiniObject)
+
+ return self.operative(pattern, environment)
+
+class MiniWrapper(object):
+ def __init__(self, operative):
+ assert isinstance(operative,MiniObject)
+ assert isinstance(operative.py_object, MiniApplicative) or isinstance(operative.py_object, MiniWrapper)
+
+ self.operative = operative
+
+ def __call__(self, pattern, environment):
+ assert isinstance(pattern, MiniObject)
+
+ return self.operative.py_object(evaluate_arguments(pattern, environment), environment)
+
+ def __repr__(self):
+ return "<wrapper {}>".format(repr(self.operative))
+
+def wrap(thing):
+ return MiniObject(MiniWrapper(thing))
+
+def unwrap(thing):
+ if isinstance(thing.py_object, MiniWrapper):
+ return thing.py_object.operative
+
+ raise Exception('UnwrapError')
+
+def create_symbol(string,**kwargs):
+ if string in SYMBOLS:
+ return SYMBOLS[string]
+
+ k = MiniObject(MiniSymbol(string), **kwargs)
+ SYMBOLS[string] = k
+ return k
+
+def create_cons_collection(py_collection):
+ result = NIL
+
+ for item in reversed(py_collection):
+ result = MiniObject(MiniPair(item, result))
+
+ return result
+
+def cons_collection_to_py_collection(cons_collection):
+ while cons_collection != NIL:
+ yield car(cons_collection)
+ cons_collection = cdr(cons_collection)
+
+token_regex = re.compile(r'''(?mx)
+ (\s*|\#.*?\n)*(?:
+ (?P<open_parenthese>\()|
+ (?P<close_parenthese>\))|
+ (?P<number>\-?\d+\.\d+|\-?\d+)|
+ (?P<string>"[^"]*")|
+ (?P<identifier>[_A-Za-z\?\-\+\*/=\>\<]+)|
+ (?P<symbol>\:[_A-Za-z\?\-\+\*/=\>\<]*)
+ )''')
+
+def parse_all(source):
+ def parse(matches, index_holder):
+ match = matches[index_holder[0]]
+ index_holder[0] += 1
+
+ if match.group('open_parenthese'):
+ r = []
+
+ while index_holder[0] < len(matches) and not matches[index_holder[0]].group('close_parenthese'):
+ r.append(parse(matches, index_holder))
+
+ if index_holder[0] == len(matches):
+ raise Exception('Unmatched parenthese (')
+
+ index_holder[0] += 1
+ return create_cons_collection(r)
+
+ if match.group('close_parenthese'):
+ raise Exception("Unmatched parenthese )")
+
+ if match.group('number'):
+ v = float(match.group('number'))
+ if v.is_integer(): v = int(v)
+
+ return MiniObject(
+ v,
+ start = match.start('number'),
+ end = match.end('number'))
+
+ if match.group('string'):
+ return MiniObject(
+ match.group('string')[1:-1],
+ start = match.start('string'),
+ end = match.end('string'))
+
+ if match.group('identifier'):
+ return MiniObject(Identifier(
+ match.group('identifier'),
+ start = match.start('identifier'),
+ end = match.end('identifier')))
+
+ if match.group('symbol'):
+ return create_symbol(
+ match.group('symbol')[1:],
+ start = match.start('symbol'),
+ end = match.end('symbol'))
+
+ assert False, "I'm not sure how this happened"
+
+ def parse_all_internal(matches, index_holder):
+ if index_holder[0] == len(matches):
+ return NIL
+
+ parsed_atom = parse(matches, index_holder)
+
+ return cons(parsed_atom, parse_all_internal(matches, index_holder))
+
+ matches = list(token_regex.finditer(source))
+ match_index_wrapped = [0]
+
+ return parse_all_internal(matches, match_index_wrapped)
+
+NIL = MiniObject(None)
+
+class Boolean(MiniObject):
+ def __init__(self, py_object, **kwargs):
+ super(Boolean,self).__init__(py_object, **kwargs)
+
+TRUE = Boolean(True)
+FALSE = Boolean(False)
+
+def is_number(arg):
+ if isinstance(arg, float):
+ return True
+
+ # isinstance(True, int) returns True
+ return isinstance(arg, int) and not isinstance(arg, bool)
+
+def py_to_mini(py_object):
+ assert callable(py_object)
+
+ def wrapped(pattern, environment):
+ result = py_object(*cons_collection_to_py_collection(pattern))
+
+ if is_number(result) or isinstance(result,MiniPair):
+ return MiniObject(result)
+
+ if isinstance(result,str):
+ return MiniObject(result)
+
+ return {
+ True : TRUE,
+ False : FALSE,
+ None : NIL,
+ }.get(result, result)
+
+ return MiniObject(MiniWrapper(MiniObject(MiniApplicative(wrapped))))
+
+def apply(applicative, pattern, environment):
+ assert isinstance(applicative, MiniObject)
+
+ return applicative.py_object(pattern, environment)
+
+def evaluate(expression, environment):
+ assert isinstance(expression, MiniObject)
+
+ if isinstance(expression.py_object, str) or is_number(expression.py_object):
+ return expression
+
+ if isinstance(expression.py_object, MiniSymbol):
+ return expression
+
+ if isinstance(expression.py_object, MiniPair):
+ applicative = evaluate(car(expression), environment)
+ arguments = cdr(expression)
+
+ assert isinstance(applicative, MiniObject)
+ assert isinstance(arguments, MiniObject)
+
+ if isinstance(applicative.py_object, MiniApplicative) or isinstance(applicative.py_object, MiniWrapper):
+ return apply(applicative, arguments, environment)
+
+ raise Exception("Expected applicative, got {}".format(applicative.py_object))
+
+ if isinstance(expression.py_object, Identifier):
+ parent_symbol = create_symbol('__parent__')
+
+ while environment != None:
+ if cons_dict_has_key(environment, create_symbol(expression.py_object.symbol)) == TRUE:
+ return cons_dict_get(environment, create_symbol(expression.py_object.symbol))
+
+ if cons_dict_has_key(environment, parent_symbol) == TRUE:
+ environment = cons_dict_get(environment, create_symbol('__parent__'))
+ else:
+ raise Exception('UndefinedIdentifierError: Undefined identifier {}'.format(expression.py_object.symbol))
+
+def length(string):
+ assert isinstance(string, MiniObject)
+
+ if isinstance(string.py_object, str):
+ return len(string.py_object)
+
+ raise Exception("TypeError")
+
+def concatenate(l,r):
+ # TODO Implement ropes: http://citeseer.ist.psu.edu/viewdoc/download?doi=10.1.1.14.9450&rep=rep1&type=pdf
+ # TODO Apply this to other collection types
+ if isinstance(l.py_object,str) and isinstance(r.py_object, str):
+ return MiniObject(l.py_object + r.py_object)
+
+ raise Exception('TypeError')
+
+def is_integer(arg):
+ return isinstance(arg, int) and not isinstance(arg, bool)
+
+def slice(string, start, end):
+ if not isinstance(string.py_object, str):
+ raise Exception('TypeError')
+
+ py_string = string.py_object
+
+ if is_integer(start.py_object):
+ py_start = start.py_object
+
+ elif start.py_object == None:
+ py_start = 0
+
+ else:
+ raise Exception('TypeError')
+
+ if is_integer(end.py_object):
+ py_end = end.py_object
+
+ elif end.py_object == None:
+ py_end = len(py_string)
+
+ else:
+ raise Exception('TypeError')
+
+ return MiniObject(py_string[py_start:py_end])
+
+def _assert(pattern, environment):
+ def assert_internal(*arguments):
+ if len(arguments) == 0:
+ raise Exception("ArgumentError: assert expects 1 or more arguments, received none")
+
+ if len(arguments) == 1:
+ description = 'assertion failed'
+ assertion = arguments
+
+ else:
+ description = arguments[0].py_object
+ assertion = arguments[1:]
+
+ if not isinstance(assertion[-1].py_object, bool):
+ raise Exception("TypeError: `assert` expected Boolean assertion but received {} {}".format(type(assertion[-1].py_object), assertion[-1]))
+
+ if assertion[-1] is TRUE:
+ return None
+
+ if assertion[-1] is FALSE:
+ raise Exception("AssertionError: {}".format(description))
+
+ assert False
+
+ # Execute in nested scope
+ return py_to_mini(assert_internal).py_object(pattern, nest(environment))
+
+def throws(pattern, environment):
+ if cons_collection_len(pattern) != 2:
+ raise Exception("throws? expects 2 argument, received {}".format(len(pattern)))
+
+ expression = car(pattern)
+ exception = evaluate(car(cdr(pattern)), environment)
+
+ if not isinstance(exception.py_object, str):
+ raise Exception('throws? expects a string as the second argument')
+
+ try:
+ evaluate(expression, environment)
+ return FALSE
+
+ except Exception as e:
+ if ':' in e.message:
+ exception_type, message = e.message.split(':',1)
+ else:
+ exception_type = e.message
+
+ if exception_type == exception.py_object:
+ return TRUE
+
+ raise
+
+def _not(argument):
+ if not isinstance(argument, Boolean):
+ raise Exception('TypeError: Expected Boolean but received {}'.format(type(argument)))
+
+ if argument == TRUE:
+ return FALSE
+
+ if argument == FALSE:
+ return TRUE
+
+ assert False
+
+def evaluate_expressions(expressions, environment):
+ result = NIL
+
+ while expressions != NIL:
+ result = evaluate(car(expressions), environment)
+ expressions = cdr(expressions)
+
+ return result
+
+def cons_collection_len(cons_collection):
+ result = 0
+
+ while cons_collection != NIL:
+ result += 1
+ cons_collection = cdr(cons_collection)
+
+ return result
+
+def define(pattern, environment):
+ if cons_collection_len(pattern) < 2:
+ raise Exception('DefineError: `define` expected two arguments, received {}'.format(cons_collection_len(pattern)))
+
+ head = car(pattern)
+ body = cdr(pattern)
+
+ if isinstance(head.py_object, Identifier):
+ identifier = head.py_object.symbol
+
+ if is_defined(head, environment) == TRUE:
+ raise Exception('AlreadyDefinedError: the identifier {} is already defined'.format(identifier))
+
+ environment[identifier] = evaluate_expressions(body, environment)
+
+ return NIL
+
+ elif isinstance(head.py_object, MiniPair):
+ raise Exception('NotImplementedError: Defining patterns is not yet implemented')
+
+ else:
+ raise Exception("DefineError")
+
+def defined_p(pattern, environment):
+ if cons_collection_len(pattern) != 1:
+ raise Exception("ArgumentError: `defined?` expects 1 argument, received {}".format(len(pattern)))
+
+ if not isinstance(car(pattern).py_object, Identifier):
+ raise Exception("TypeError: Expected Identifier but got {}".format(type(car(pattern).py_object)))
+
+ return is_defined(car(pattern), environment)
+
+def is_defined(identifier, environment):
+ assert isinstance(identifier, MiniObject)
+ assert isinstance(environment, MiniObject)
+
+ identifier_symbol = identifier_to_symbol(identifier)
+ parent_symbol = create_symbol('__parent__')
+
+ while True:
+ if cons_dict_has_key(environment, identifier_symbol) == TRUE:
+ return TRUE
+
+ elif cons_dict_has_key(environment, parent_symbol) == TRUE:
+ environment = cons_dict_get(environment, parent_symbol)
+
+ else:
+ return FALSE
+
+def _if(pattern, environment):
+ if not cons_collection_len(pattern) in [2,3]:
+ raise Exception("ArgumentError")
+
+ condition = car(pattern)
+ if_result_true = car(cdr(pattern))
+ if_result_false = car(cdr(cdr(pattern)))
+
+ result = evaluate(condition, environment)
+
+ if result is TRUE:
+ return evaluate(if_result_true, environment)
+ if result is FALSE:
+ return evaluate(if_result_false, environment)
+
+ raise Exception("TypeError: `if` expects boolean, received {}".format(type(result)))
+
+def nest(environment):
+ isinstance(environment,MiniEnvironment)
+
+ result = MiniEnvironment()
+ result['__parent__'] = environment
+ return result
+
+# This is vau from John N. Shutt's seminal paper
+# https://www.wpi.edu/Pubs/ETD/Available/etd-090110-124904/unrestricted/jshutt.pdf
+# While Greek letters are appropriate for an academic, theoretical context, they make for
+# poor variable names, so this is tentatively named `operative`
+def operative(pattern, defining_environment):
+ argument_list_identifier = None
+ argument_identifiers = None
+
+ calling_environment_identifier = car(cdr(pattern)).py_object.symbol
+
+ if isinstance(car(pattern).py_object, Identifier):
+ argument_list_identifier = car(pattern).py_object.symbol
+
+ if calling_environment_identifier == argument_list_identifier:
+ raise Exception("ArgumentError: Argument list identifier `{}` may not be the same as calling environment identifier".format(ai))
+
+ elif car(pattern).py_object == None or isinstance(car(pattern).py_object, MiniPair):
+ if not all([isinstance(arg.py_object, Identifier) for arg in cons_collection_to_py_collection(car(pattern))]):
+ raise Exception("ArgumentError: Unexpected {} {}".format(type(arg),arg))
+
+ argument_identifiers = [ai.py_object.symbol for ai in cons_collection_to_py_collection(car(pattern))]
+
+ existing = set()
+ for ai in argument_identifiers:
+ if ai in existing:
+ raise Exception("ArgumentError: Argument `{}` already defined".format(ai))
+
+ if calling_environment_identifier == ai:
+ raise Exception("ArgumentError: Argument `{}` may not be the same as calling environment identifier".format(ai))
+
+ existing.add(ai)
+
+ else:
+ raise Exception("ArgumentError: `operative` expected identifier or cons-list as first argument, received {}".format(type(car(pattern).py_object)))
+
+ if not isinstance(car(cdr(pattern)).py_object,Identifier):
+ raise Exception("ArgumentError: The second argument to `operative` should be an identifer")
+
+ def result(calling_pattern, calling_environment):
+ local_environment = nest(defining_environment)
+
+ assert (argument_list_identifier == None) != (argument_identifiers == None)
+ if argument_list_identifier != None:
+ local_environment[argument_list_identifier] = calling_pattern
+
+ if argument_identifiers != None:
+ if not cons_collection_len(calling_pattern) == len(argument_identifiers):
+ raise Exception("ArgumentError: operative expected {} arguments, received {}".format(len(argument_identifiers),len(calling_pattern)))
+
+ calling_pattern = list(cons_collection_to_py_collection(calling_pattern))
+
+ for i in range(len(argument_identifiers)):
+ local_environment[argument_identifiers[i]] = calling_pattern[i]
+
+ local_environment[calling_environment_identifier] = calling_environment
+
+ return evaluate_expressions(cdr(cdr(pattern)), local_environment)
+
+ return MiniObject(MiniApplicative(result))
+
+def read_file(filename):
+ assert isinstance(filename, MiniObject)
+
+ with open(filename.py_object, 'r') as f:
+ return f.read()
+
+def write_file(filename, string):
+ assert isinstance(filename, MiniObject)
+ assert isinstance(string, MiniObject)
+
+ with open(filename.py_object, 'w') as f:
+ f.write(string.py_object)
+
+def add(l,r):
+ if isinstance(l, MiniObject) and isinstance(r, MiniObject):
+ l = l.py_object
+ r = r.py_object
+
+ if is_number(l) and is_number(r):
+ return l + r
+
+ raise Excepion('TypeError')
+
+def subtract(l,r):
+ if isinstance(l, MiniObject) and isinstance(r, MiniObject):
+ l = l.py_object
+ r = r.py_object
+
+ if is_number(l) and is_number(r):
+ return l - r
+
+ raise Excepion('TypeError')
+
+def multiply(l,r):
+ if isinstance(l, MiniObject) and isinstance(r, MiniObject):
+ l = l.py_object
+ r = r.py_object
+
+ if is_number(l) and is_number(r):
+ return l * r
+
+ raise Excepion('TypeError')
+
+def divide(l,r):
+ if isinstance(l, MiniObject) and isinstance(r, MiniObject):
+ l = l.py_object
+ r = r.py_object
+
+ if is_number(l) and is_number(r):
+ if isinstance(l,int) and isinstance(r,int) and l % r != 0:
+ l = float(l)
+
+ return l / r
+
+ raise Excepion('TypeError')
+
+def idivide(l,r):
+ if isinstance(l, MiniObject) and isinstance(r, MiniObject):
+ l = l.py_object
+ r = r.py_object
+
+ if is_number(l) and is_number(r):
+ return l // r
+
+ raise Excepion('TypeError')
+
+def mod(l,r):
+ if isinstance(l, MiniObject) and isinstance(r, MiniObject):
+ l = l.py_object
+ r = r.py_object
+
+ if is_number(l) and is_number(r):
+ return l % r
+
+ raise Excepion('TypeError')
+
+def eq(l,r):
+ assert isinstance(l,MiniObject)
+ assert isinstance(r,MiniObject)
+
+ return l.py_object == r.py_object
+
+def lt(l,r):
+ assert isinstance(l,MiniObject)
+ assert isinstance(r,MiniObject)
+
+ if is_number(l.py_object) and is_number(r.py_object):
+ return l.py_object < r.py_object
+
+ if isinstance(l.py_object,str) and isinstance(r.py_object,str):
+ return l.py_object < r.py_object
+
+ if isinstance(l.py_object,MiniSymbol) and isinstance(r.py_object,MiniSymbol):
+ return l.py_object.string < r.py_object.string
+
+ raise TypeError('`<` expected number or string, received {} and {}'.format(l.py_object, r.py_object))
+
+def gt(l,r):
+ assert isinstance(l,MiniObject)
+ assert isinstance(r,MiniObject)
+
+ if is_number(l.py_object) and is_number(r.py_object):
+ return l.py_object > r.py_object
+
+ if isinstance(l.py_object,str) and isinstance(r.py_object,str):
+ return l.py_object > r.py_object
+
+ if isinstance(l.py_object,MiniSymbol) and isinstance(r.py_object,MiniSymbol):
+ return l.py_object.string > r.py_object.string
+
+ raise TypeError('`>` expected number or string, received {} and {}'.format(l.py_object, r.py_object))
+
+def le(l,r):
+ return lt(l,r) or eq(l,r)
+
+def ge(l,r):
+ return gt(l,r) or eq(l,r)
+
+def cons(l,r):
+ return MiniObject(MiniPair(l,r))
+
+def car(p):
+ return p.py_object.car
+
+def cdr(p):
+ return p.py_object.cdr
+
+def is_cons_list(mini_object):
+ assert isinstance(mini_object,MiniObject)
+
+ if eq(mini_object,NIL) or isinstance(mini_object.py_object,MiniPair):
+ return TRUE
+
+ return FALSE
+
+def cons_dict_set(dictionary,key,value):
+ assert isinstance(dictionary,MiniObject)
+ assert isinstance(key,MiniObject)
+ assert isinstance(value,MiniObject)
+
+ if eq(dictionary,NIL):
+ return cons(cons(key,value),cons(NIL,NIL))
+
+ current_node_key = car(car(dictionary))
+
+ if lt(key,current_node_key):
+ return cons(
+ car(dictionary),
+ cons(
+ cons_dict_set(car(cdr(dictionary)), key, value),
+ cdr(cdr(dictionary))))
+
+ if gt(key,current_node_key):
+ return cons(
+ car(dictionary),
+ cons(
+ car(cdr(dictionary)),
+ cons_dict_set(cdr(cdr(dictionary)), key, value)))
+
+ if eq(key,current_node_key):
+ return cons(cons(key,value), cdr(dictionary))
+
+ assert False
+
+def cons_dict_get(dictionary,key):
+ assert isinstance(dictionary, MiniObject)
+ assert isinstance(key, MiniObject)
+
+ if eq(dictionary,NIL):
+ raise Exception('KeyError: Dictionary does not contain key "{}"'.format(key))
+
+ current_node_key = car(car(dictionary))
+
+ if lt(key, current_node_key):
+ return cons_dict_get(car(cdr(dictionary)), key)
+
+ if gt(key, current_node_key):
+ return cons_dict_get(cdr(cdr(dictionary)), key)
+
+ if eq(key, current_node_key):
+ return cdr(car(dictionary))
+
+def cons_dict_has_key(dictionary,key):
+ assert isinstance(dictionary, MiniObject)
+ assert isinstance(key, MiniObject)
+
+ if eq(dictionary,NIL):
+ return FALSE
+
+ current_node_key = car(car(dictionary))
+
+ if lt(key, current_node_key):
+ return cons_dict_has_key(car(cdr(dictionary)), key)
+
+ if gt(key, current_node_key):
+ return cons_dict_has_key(cdr(cdr(dictionary)), key)
+
+ if eq(key, current_node_key):
+ return TRUE
+
+def identifier_to_symbol(identifier):
+ assert isinstance(identifier, MiniObject)
+
+ if not isinstance(identifier.py_object, Identifier):
+ raise Exception('`identifier->symbol` expected identifier, received {}'.format(type(identifier.py_object)))
+
+ return create_symbol(identifier.py_object.symbol)
+
+def read(string):
+ assert isinstance(string,MiniObject)
+
+ if not isinstance(string.py_object,str):
+ raise Exception("TypeError: `read` expected string, got {}".format(type(strin.py_object)))
+
+ result = parse_all(string.py_object)
+
+ assert cdr(result) == NIL
+
+ return car(result)
+
+builtins = {
+ # Builtin constants
+ 'true' : TRUE,
+ 'false' : FALSE,
+ 'nil' : NIL,
+
+ # Builtin comparison functions
+ '=' : py_to_mini(eq),
+ '<' : py_to_mini(lt),
+ '>' : py_to_mini(gt),
+ '<=' : py_to_mini(le),
+ '>=' : py_to_mini(ge),
+
+ # Builtin conversion functions
+ 'identifier->symbol' : py_to_mini(identifier_to_symbol),
+
+ # Builtin type test functions
+ 'cons-list?' : py_to_mini(is_cons_list),
+ 'identifier?' : py_to_mini(is_identifier),
+
+ # Builtin general functions
+ 'evaluate' : py_to_mini(evaluate),
+ 'evaluate-expressions' : py_to_mini(evaluate_expressions),
+ 'print' : py_to_mini(print),
+ 'prompt' : py_to_mini(raw_input),
+ 'read-file' : py_to_mini(read_file),
+ 'write-file' : py_to_mini(write_file),
+ 'read' : py_to_mini(read),
+ 'wrap' : py_to_mini(wrap),
+ 'unwrap' : py_to_mini(unwrap),
+
+ # Builtin number functions
+ '+' : py_to_mini(add),
+ '-' : py_to_mini(subtract),
+ '*' : py_to_mini(multiply),
+ '/' : py_to_mini(divide),
+ '//' : py_to_mini(idivide),
+ 'mod' : py_to_mini(mod),
+
+ # Builtin pair functions
+ 'cons' : py_to_mini(cons),
+ 'car' : py_to_mini(car),
+ 'cdr' : py_to_mini(cdr),
+
+ # Builtin cons dictionary functions
+ 'cons-dict-set' : py_to_mini(cons_dict_set),
+ 'cons-dict-get' : py_to_mini(cons_dict_get),
+
+ # Builtin string functions
+ 'concatenate' : py_to_mini(concatenate),
+ 'length' : py_to_mini(length),
+ 'slice' : py_to_mini(slice),
+
+ # Builtin boolean functions
+ 'not' : py_to_mini(_not),
+
+ # Builtin special forms
+ 'assert' : MiniObject(MiniApplicative(_assert)),
+ 'define' : MiniObject(MiniApplicative(define)),
+ 'defined?' : MiniObject(MiniApplicative(defined_p)),
+ 'if' : MiniObject(MiniApplicative(_if)),
+ 'operative' : MiniObject(MiniApplicative(operative)),
+ 'throws?' : MiniObject(MiniApplicative(throws)),
+}
+
+builtins = dict_to_environment(builtins)
+
+if __name__ == '__main__':
+ import os.path
+ import sys
+
+ arguments = sys.argv[1:]
+
+ predefineds = nest(builtins)
+ predefineds_filename = os.path.join(os.path.dirname(os.path.realpath(__file__)), 'predefineds.mini')
+
+ with open(predefineds_filename, 'r') as predefineds_file:
+ predefineds_source = predefineds_file.read()
+
+ try:
+ evaluate_expressions(parse_all(predefineds_source), predefineds)
+
+ except:
+ traceback.print_exc()
+
+ if len(arguments) == 0:
+ environment = nest(predefineds)
+
+ while True:
+ source = raw_input('>>> ')
+
+ try:
+ print(evaluate_expressions(parse_all(source), environment))
+
+ except:
+ traceback.print_exc()
+
+ else:
+ filename = arguments[0]
+ arguments = arguments[1:]
+
+ environment = nest(predefineds)
+ environment['__file__'] = MiniObject(os.path.join(os.path.realpath(filename)))
+ environment['__arguments__'] = create_cons_collection(map(MiniObject,arguments))
+
+ with open(filename,'r') as f:
+ source = f.read()
+
+ try:
+ print(evaluate_expressions(parse_all(source), environment))
+
+ except:
+ traceback.print_exc()
--- /dev/null
+# feature tests: variables
+(assert "Evaluating undefined variable throws exception"
+ (throws? undefined-identifier "UndefinedIdentifierError"))
+
+# pair tests
+(assert "`car` retrieves first argument to `cons`"
+ (car (cons true false)))
+(assert "`cdr` retrieves first argument to `cons`"
+ (cdr (cons false true)))
+
+# `=` tests
+(assert "`=` returns true for equal numbers" (= 0 0))
+(assert "`=` returns false for non-equal numbers" (not (= 0 1)))
+(assert "`=` returns true for equal strings" (= "Hello, world" "Hello, world"))
+(assert "`=` returns false for non-equal strings" (not (= "Hello, world" "Goodnight, moon")))
+(assert "`=` returns true for equal true values" (= true true))
+(assert "`=` returns true for equal false values" (= false false))
+(assert "`=` returns true for nils" (= nil nil))
+(assert "`=` returns true for equal symbols" (= :symbol :symbol))
+(assert "`=` returns false for non-equal symbols" (not (= :symbol :not-the-same)))
+(assert "`=` returns false for symbol-to-string comparison (string first)"
+ (not (= :symbol ":symbol")))
+(assert "`=` returns false for symbol-to-string comparison (symbol first)"
+ (not (= ":symbol" :symbol)))
+
+# `+` tests
+(assert "`+` adds" (= (+ 1 2) 3))
+
+# `-` tests
+(assert "`-` subtracts" (= (- 3 2) 1))
+
+# `*` tests
+(assert "`*` divides" (= (* 2 3) 6))
+
+# `/` tests
+(assert "`/` divides evenly" (= (/ 6 3) 2))
+(assert "`/` divides fractionally" (= (/ 7 2) 3.5))
+
+# `//` tests
+(assert "`//` divides evenly" (= (// 6 3) 2))
+(assert "`//` divides integrally" (= (// 7 2) 3))
+
+# `and` tests
+(assert "`and` returns false for both false" (not (and false false)))
+(assert "`and` returns false for left false and right true" (not (and false true)))
+(assert "`and` returns false for left true and right false" (not (and true false)))
+(assert "`and` returns true for both true" (and true true))
+(assert "`and` doesn't evalueate second argument if first is false" (not (and false (/ 1 0))))
+
+# `assert` tests
+(assert "`assert` executes without exception for true assertion" true)
+(assert "`assert` returns nil for true assertion" (= (assert true) nil))
+(assert "`assert` throws AssertionError for false assertion"
+ (throws? (assert false) "AssertionError"))
+(assert "`assert` throws TypeError for non-boolean assertion"
+ (throws? (assert 1) "TypeError"))
+(assert "`assert` can take multiple arguments" false true)
+(assert "`assert` executes assertion in a nested scope"
+ (assert "define identifier in nested scope"
+ (define identifier-in-nested-scope true)
+ true)
+ (not (defined? identifier-in-nested-scope)))
+
+# `merge-association-list-with-cons-dict` tests
+(assert "merge-association-list-with-cons-dict returns cons-dict"
+ (= :value
+ (cons-dict-get (merge-association-list-with-cons-dict (cons-list-zip (cons-list :key) (cons-list :value))
+ nil)
+ :key)))
+
+# `concatenate` tests
+(assert "`concatenate` concatenates strings"
+ (= (concatenate "Hello, " "world")
+ "Hello, world"))
+
+# `cond` tests
+(assert "`cond` returns nil for no conditions"
+ (= (cond) nil))
+(assert "`cond` returns nil for no true conditions"
+ (= (cond (false :not-returned)
+ (false :also-not-returned))
+ nil))
+(assert "`cond` returns true when pair evaluates to true"
+ (= (cond (false :not-returned)
+ (true :returned)
+ (false :also-not-returned))
+ :returned))
+(assert "`cond` does not execute bodies for false conditions"
+ (= (cond (false (/ 1 0))
+ (true :returned))
+ :returned))
+
+# `cons-dict` tests
+(assert "`cons-dict-get` returns association created by `cons-dict-set`"
+ (cons-dict-get (cons-dict-set nil :key true) :key))
+
+# `cons-list` tests
+(assert "`cons-list` first argument is first item"
+ (car (cons-list true)))
+(assert "`cons-list` terminated by null cdr"
+ (= (cdr (cons-list false)) nil))
+
+# `cons-list?` tests
+(assert "`cons-list?` returns true for nil"
+ (cons-list? nil))
+(assert "`cons-list?` returns true for cons-list"
+ (cons-list? (cons-list 1 2 3)))
+(assert "`cons-list?` returns false for non cons-list"
+ (not (cons-list? 1)))
+
+# `cons-list-map` tests
+(assert "`cons-list-map` returns empty list for empty list"
+ (= (cons-list-map identifier->symbol nil) nil))
+(assert "`cons-list-map` calls mapping function on items"
+ (define inc (wrap (operative (i) _ (+ i 1))))
+ (define mapped (cons-list-map inc (cons-list 1 2)))
+ (and (= 2 (car mapped))
+ (= 3 (car (cdr mapped)))))
+
+# `cons-list-zip` tests
+(assert "`cons-list-zip` returns nil for empty lists"
+ (= (cons-list-zip nil nil) nil))
+(assert "`cons-list-zip` returns association list"
+ (define a-list (cons-list-zip (cons-list :a) (cons-list :b)))
+ (and (= :a (car (car a-list)))
+ (= :b (cdr (car a-list)))))
+
+# `define` tests
+(assert "`define` adds identifier to environment"
+ (define previously-undefined-identifier true)
+ previously-undefined-identifier)
+(assert "`define` throws exception for already-defined variable"
+ (define already-defined-identifier :value)
+ (throws? (define already-defined-identifier :another-value) "AlreadyDefinedError"))
+
+# `defined?` tests
+(assert "`defined?` returns true for defined identifier"
+ (define identifier :value)
+ (defined? identifier))
+(assert "`defined?` returns false for undefined identifier"
+ (not (defined? undefined-identifier)))
+
+# `function` tests
+(assert "`function` creates function that returns body"
+ ((function _ true)))
+(assert "`function` closes around defining environment"
+ (define defining-environment-identifier true)
+ ((function _ defining-environment-identifier)))
+(assert "`function` with an identifier arg binding receives list"
+ (and (= ((function args (car args)) :arg) :arg)
+ (= ((function args (cdr args)) :arg) nil)))
+(assert "`function` with an s-expression arg binding receives arguments bound to names"
+ (and (= ((function (foo bar) foo) :baz :qux) :baz)
+ (= ((function (foo bar) bar) :baz :qux) :qux)))
+(assert "`function` can recurse"
+ (define factorial (function (n) (if (= n 1) 1 (* n (factorial (- n 1))))))
+ (and (= 6 (factorial 3))
+ (= 120 (factorial 5))))
+
+# `get-current-environment` tests
+(assert "`get-current-environment` contains local variables as symbols"
+ (define local-identifier true)
+ (cons-dict-get (get-current-environment) :local-identifier))
+(assert "`get-current-environment` contains parent scope under :__parent__"
+ (define parent-identifier true)
+ (assert (cons-dict-get (cons-dict-get (get-current-environment) :__parent__) :parent-identifier))
+ true)
+
+# `identifier->symbol` tests
+(assert "`identifier->symbol` returns a symbol when given an identifier"
+ (= (identifier->symbol (quote identifier)) :identifier))
+
+# `identifier?` tests
+(assert "`identifier?` returns true for identifier"
+ (identifier? (quote identifier)))
+(assert "`identifier?` returns false for non-identifier"
+ (not (identifier? 1)))
+
+# `if` tests
+(assert "`if` returns second argument for true condition"
+ (if true true false))
+(assert "`if` returns third argument for false condition"
+ (if false false true))
+(assert "`if` doesn't execute third argument for true condition"
+ (if true true undefined-identifier))
+(assert "`if` doesn't execute second argument for false condition"
+ (if false undefined-identifier true))
+
+# `length` tests
+(assert "`length` returns length of string"
+ (= 12 (length "Hello, world")))
+
+# `not` tests
+(assert "`not` returns false for true" (= (not true) false))
+(assert "`not` returns true for false" (= (not false) true))
+(assert "`not` throws TypeError for non-boolean argument"
+ (throws? (not 1) "TypeError"))
+
+# `operative` tests
+(assert "`operative` creates callable operative"
+ ((operative () env true)))
+(assert "`operative` receives the environment"
+ (define receives-environment (operative () env (evaluate (quote true-identifier) env)))
+ (define true-identifier true)
+ (receives-environment))
+(assert "`operative` receives arguments"
+ ((operative (arg) env (evaluate arg env)) true))
+(assert "`operative` executes in its own environment"
+ ((operative () env
+ (define should-not-be-defined false)))
+ (not (defined? should-not-be-defined)))
+(assert "`operative` doesn't evaluate its arguments"
+ ((operative (arg) env true) (/ 1 0)))
+(assert "`operative` with a symbol argument receives a cons-linked-list"
+ (and (= ((operative argument-list env (car argument-list)) 1) 1)
+ (= ((operative argument-list env (cdr argument-list)) 1) nil)))
+(assert "`operative` argument lists nest"
+ (and (= ((operative argument-list env (car (car argument-list))) (1)) 1)
+ (= ((operative argument-list env (cdr (car argument-list))) (1)) nil)))
+(assert "`operative` with an s-expression argument still receives lists"
+ (and (= ((operative (arg) env (car arg)) (1)) 1)
+ (= ((operative (arg) env (cdr arg)) (1)) nil)))
+(assert "`operative` executes body in a nested scope"
+ ((operative () env
+ (define defined-in-nested-scope :value)
+ (assert (defined? defined-in-nested-scope))))
+ (not (defined? defined-in-nested-scope)))
+(assert "`operative` with no args receives nil"
+ (= ((operative args-list _ args-list)) nil))
+
+# `or` tests
+(assert "`or` returns false for both false" (not (or false false)))
+(assert "`or` returns true for left false and right true" (or false true))
+(assert "`or` returns true for left true and right false" (or true false))
+(assert "`or` returns true for both true" (or true true))
+(assert "`or` doesn't evalueate second argument if first is true" (or true (/ 1 0)))
+
+# `read` tests
+(assert "`read` reads identifiers"
+ (= (identifier->symbol (read "identifier")) :identifier))
+
+# `slice` tests
+(assert "`slice` returns a slice of a string"
+ (= (slice "Hello, world" 1 11) "ello, worl"))
+(assert "`slice` uses start of string if start index is nil"
+ (= (slice "Hello, world" nil 11) "Hello, worl"))
+(assert "`slice` uses end of string if end index is nil"
+ (= (slice "Hello, world" 1 nil) "ello, world"))
+(assert "`slice` counts backward if start or end is negative"
+ (= (slice "Hello, world" -11 -1) "ello, worl"))
+
+# `throws?` tests
+(assert "`throws?` returns false when no exception is thrown"
+ (not (throws? (assert true) "AssertionError")))
+(assert "`throws?` returns true when the correct exception is thrown"
+ (throws? (assert false) "AssertionError"))
+(assert "`throws?` doesn't catch when the wrong exception is thrown"
+ (throws? (throws? (assert false) "TypeError") "AssertionError"))
+
+# `wrap` tests
+(assert "`wrap` evaluates arguments to wrapped operative"
+ ((wrap (operative (input) _ input)) (= 1 1)))