def make_string(arguments): check_argument_number('make-string', arguments, 1, 2) string_length_atom = arguments[0] if not isinstance(string_length_atom, Integer): raise SchemeTypeError("String length must be an integer, " "got %d." % string_length_atom.__class__) string_length = string_length_atom.value if string_length < 0: raise InvalidArgument("String length must be non-negative, " "got %d." % string_length) if len(arguments) == 1: return String(' ' * string_length) else: repeated_character_atom = arguments[1] if not isinstance(repeated_character_atom, Character): raise SchemeTypeError("The second argument to make-string must be" " a character, got a %s." % repeated_character_atom.__class__) repeated_character = repeated_character_atom.value return String(repeated_character * string_length)
def is_char(arguments): check_argument_number('char?', arguments, 1, 1) if isinstance(arguments[0], Character): return Boolean(True) return Boolean(False)
def pair(arguments): check_argument_number('pair?', arguments, 1, 1) if isinstance(arguments[0], Cons): return Boolean(True) return Boolean(False)
def subtract(arguments): check_argument_number('-', arguments, 1) if len(arguments) == 1: # we just negate a single argument if isinstance(arguments[0], Integer): return Integer(-1 * arguments[0].value) elif isinstance(arguments[0], FloatingPoint): return FloatingPoint(-1 * arguments[0].value) else: raise SchemeTypeError("Subtraction is only defined for integers and " "floating point, you gave me %s." % arguments[0].__class__) total = copy(arguments[0]) for argument in arguments.tail: if not isinstance(argument, Number): raise SchemeTypeError("Subtraction is only defined for numbers, " "you gave me %s." % argument.__class__) # subtracting a float from an integer gives us a float if isinstance(total, Integer) and isinstance(argument, FloatingPoint): total = FloatingPoint(float(total.value)) total.value -= argument.value return total
def is_procedure(arguments): check_argument_number("procedure?", arguments, 1, 1) if callable(arguments[0]): return Boolean(True) return Boolean(False)
def is_string(arguments): check_argument_number('string?', arguments, 1, 1) if isinstance(arguments[0], String): return Boolean(True) return Boolean(False)
def is_procedure(arguments): check_argument_number('procedure?', arguments, 1, 1) if callable(arguments[0]): return Boolean(True) return Boolean(False)
def vector_length(arguments): check_argument_number('vector-length', arguments, 1, 1) # todo: check type vector = arguments[0] return Integer(len(vector))
def define(arguments, environment): check_argument_number('define', arguments, 2) if isinstance(arguments[0], Atom): return define_variable(arguments, environment) else: return define_function(arguments, environment)
def string_set(arguments): check_argument_number('string-set!', arguments, 3, 3) string_atom = arguments[0] if not isinstance(string_atom, String): raise SchemeTypeError("string-set! takes a string as its first argument, " "not a %s." % string_atom.__class__) char_index_atom = arguments[1] if not isinstance(char_index_atom, Integer): raise SchemeTypeError("string-set! takes an integer as its second argument, " "not a %s." % char_index_atom.__class__) replacement_char_atom = arguments[2] if not isinstance(replacement_char_atom, Character): raise SchemeTypeError("string-set! takes a character as its third argument, " "not a %s." % replacement_char_atom.__class__) string = string_atom.value char_index = char_index_atom.value if char_index >= len(string): # FIXME: this will say 0--1 if string is "" raise InvalidArgument("String index out of bounds: index must be in" " the range 0-%d, got %d." % (len(string) - 1, char_index)) characters = list(string) characters[char_index] = replacement_char_atom.value new_string = "".join(characters) string_atom.value = new_string return None
def car(arguments): # TODO: check type as well as arity check_argument_number('car', arguments, 1) list_given = arguments[0] return list_given[0]
def string_ref(arguments): check_argument_number('string-ref', arguments, 2, 2) string_atom = arguments[0] if not isinstance(string_atom, String): raise SchemeTypeError( "string-ref takes a string as its first argument, " "not a %s." % string_atom.__class__) char_index_atom = arguments[1] if not isinstance(char_index_atom, Integer): raise SchemeTypeError( "string-ref takes an integer as its second argument, " "not a %s." % char_index_atom.__class__) string = string_atom.value char_index = char_index_atom.value if char_index >= len(string): # FIXME: this will say 0--1 if string is "" raise InvalidArgument("String index out of bounds: index must be in" " the range 0-%d, got %d." % (len(string) - 1, char_index)) return Character(string[char_index])
def number(arguments): check_argument_number('number?', arguments, 1, 1) if isinstance(arguments[0], Number): return Boolean(True) return Boolean(False)
def is_vector(arguments): check_argument_number('make-vector', arguments, 1, 1) if isinstance(arguments[0], Vector): return Boolean(True) return Boolean(False)
def vector_ref(arguments): check_argument_number('vector-ref', arguments, 2, 2) vector = arguments[0] index = arguments[1].value return vector[index]
def named_function(_arguments, _environment): check_argument_number(function_name.value, _arguments, len(function_parameters), len(function_parameters)) local_environment = {} # evaluate arguments _arguments = deepcopy(_arguments) for i in range(len(_arguments)): (_arguments[i], _environment) = eval_s_expression(_arguments[i], _environment) # assign to parameters for (parameter_name, parameter_value) in zip(function_parameters, _arguments): local_environment[parameter_name.value] = parameter_value # create new environment, where local variables mask globals new_environment = dict(_environment, **local_environment) # evaluate the function block for s_exp in function_body: result, new_environment = eval_s_expression(s_exp, new_environment) # update any global variables that weren't masked for variable_name in _environment: if variable_name not in local_environment: _environment[variable_name] = new_environment[variable_name] return (result, _environment)
def display(arguments): check_argument_number('display', arguments, 1, 1) atom = arguments[0] # FIXME: should we check type? Is this funcction only for strings? print(atom.value, end='') return None
def greater_or_equal(arguments): check_argument_number('>=', arguments, 2) for i in range(len(arguments) - 1): if not arguments[i].value >= arguments[i+1].value: return Boolean(False) return Boolean(True)
def set_cdr(arguments): # TODO: check type as well as arity check_argument_number('set-cdr!', arguments, 2, 2) list_given = arguments[0] list_given.tail = arguments[1] return Nil()
def less_than(arguments): check_argument_number('<', arguments, 2) for i in range(len(arguments) - 1): if not arguments[i].value < arguments[i+1].value: return Boolean(False) return Boolean(True)
def exp(arguments): check_argument_number('exp', arguments, 1, 1) if arguments[0].__class__ not in [Integer, FloatingPoint]: raise SchemeTypeError("exp only takes integers or floats, " "got %s" % arguments[0].__class__) x1 = arguments[0].value return FloatingPoint(math.exp(x1))
def log(arguments): check_argument_number('log', arguments, 1, 1) if not isinstance(arguments[0], Number): raise SchemeTypeError("Log is only defined for numbers, " "you gave me %s." % arguments[0].__class__) x1 = arguments[0].value return FloatingPoint(math.log(x1))
def modulo(arguments): check_argument_number('modulo', arguments, 2, 2) if not isinstance(arguments[0], Integer) or not isinstance(arguments[1], Integer): raise SchemeTypeError("modulo is only defined for integers, " "got %s and %s." % (arguments[0].__class__, arguments[1].__class__)) return Integer(arguments[0].value % arguments[1].value)
def quasiquote(arguments, environment): """Returns the arguments unevaluated, except for any occurrences of unquote. """ def recursive_eval_unquote(s_expression, _environment): """Return a copy of s_expression, with all occurrences of unquoted s-expressions replaced by their evaluated values. Note that we can only have unquote-splicing in a sublist, since we can only return one value, e.g `,@(1 2 3). """ if isinstance(s_expression, Atom): return (s_expression, _environment) elif isinstance(s_expression, Nil): return (s_expression, _environment) elif s_expression[0] == Symbol("unquote"): check_argument_number('unquote', arguments, 1, 1) return eval_s_expression(s_expression[1], _environment) else: # return a list of s_expressions that have been # recursively checked for unquote list_elements = [] for element in s_expression: if isinstance(element, Cons) and \ element[0] == Symbol('unquote-splicing'): check_argument_number('unquote-splicing', element.tail, 1, 1) (result, _environment) = eval_s_expression(element[1], _environment) if not isinstance(result, Cons) and not isinstance( result, Nil): raise SchemeArityError( "unquote-splicing requires a list.") for item in result: list_elements.append(item) else: (result, _environment) = recursive_eval_unquote( element, _environment) list_elements.append(result) return (Cons.from_list(list_elements), _environment) check_argument_number('quasiquote', arguments, 1, 1) return recursive_eval_unquote(arguments[0], environment)
def string_length(arguments): check_argument_number('string-length', arguments, 1, 1) string_atom = arguments[0] if not isinstance(string_atom, String): raise SchemeTypeError("string-length takes a string as its argument, " "not a %s." % string_atom.__class__) string_length = len(string_atom.value) return Integer(string_length)
def vector_set(arguments): check_argument_number('vector-ref', arguments, 3, 3) vector = arguments[0] index = arguments[1].value new_value = arguments[2] vector[index] = new_value return Nil()
def named_variadic_function(_arguments, _environment): # a function that takes a variable number of arguments if dot_position == 0: explicit_parameters = Nil() else: explicit_parameters = deepcopy(function_parameters) # create a linked list holding all the parameters before the dot current_head = explicit_parameters # find the position in the list just before the dot for i in range(dot_position - 2): current_head = current_head.tail # then remove the rest of the list current_head.tail = Nil() improper_list_parameter = function_parameters[dot_position + 1] # check we have been given sufficient arguments for our explicit parameters check_argument_number(function_name.value, _arguments, len(explicit_parameters)) local_environment = {} # evaluate arguments _arguments = deepcopy(_arguments) for i in range(len(_arguments)): (_arguments[i], _environment) = eval_s_expression(_arguments[i], _environment) # assign parameters for (parameter, parameter_value) in zip(explicit_parameters, _arguments): local_environment[parameter] = parameter_value # put the remaining arguments in our improper parameter remaining_arguments = _arguments for i in range(len(explicit_parameters)): remaining_arguments = remaining_arguments.tail local_environment[improper_list_parameter.value] = remaining_arguments new_environment = dict(_environment, **local_environment) # evaluate our function_body in this environment for s_exp in function_body: result, new_environment = eval_s_expression(s_exp, new_environment) # update global variables that weren't masked by locals for variable_name in _environment: if variable_name not in local_environment: _environment[variable_name] = new_environment[variable_name] return (result, _environment)
def inexact(arguments): check_argument_number('inexact?', arguments, 1, 1) if isinstance(arguments[0], FloatingPoint): return Boolean(True) elif isinstance(arguments[0], Integer): return Boolean(False) else: raise SchemeTypeError("exact? only takes integers or floating point " "numbers as arguments, you gave me ""%s." % \ len(arguments))
def char_less_than(arguments): check_argument_number('char<?', arguments, 2, 2) if not isinstance(arguments[0], Character) or not isinstance(arguments[1], Character): raise SchemeTypeError("char<? takes only character arguments, got a " "%s and a %s." % (arguments[0].__class__, arguments[1].__class__)) if arguments[0].value < arguments[1].value: return Boolean(True) return Boolean(False)
def if_function(arguments, environment): check_argument_number('if', arguments, 2, 3) condition, environment = eval_s_expression(arguments[0], environment) # everything except an explicit false boolean is true if not condition == Boolean(False): then_expression = arguments[1] return eval_s_expression(then_expression, environment) else: if len(arguments) == 3: else_expression = arguments[2] return eval_s_expression(else_expression, environment)
def test_equivalence(arguments): check_argument_number('eqv?', arguments, 2, 2) if isinstance(arguments[0], Atom): # __eq__ is defined on Atom return Boolean(arguments[0] == arguments[1]) if isinstance(arguments[0], Cons): # __eq__ on Cons is deep equality return Boolean(arguments[0] is arguments[1]) else: # __eq__ is defined on Nil # todo: test vectors return Boolean(arguments[0] == arguments[1])
def char_less_than(arguments): check_argument_number('char<?', arguments, 2, 2) if not isinstance(arguments[0], Character) or not isinstance( arguments[1], Character): raise SchemeTypeError("char<? takes only character arguments, got a " "%s and a %s." % (arguments[0].__class__, arguments[1].__class__)) if arguments[0].value < arguments[1].value: return Boolean(True) return Boolean(False)
def remainder(arguments): check_argument_number('remainder', arguments, 2, 2) if not isinstance(arguments[0], Integer) or not isinstance(arguments[1], Integer): raise SchemeTypeError("remainder is only defined for integers, " "got %s and %s." % (arguments[0].__class__, arguments[1].__class__)) # as with quotient, we can't use Python's integer division here because it floors rather than truncates x1 = arguments[0].value x2 = arguments[1].value value = x1 - (math.trunc(x1 / x2) * x2) return Integer(value)
def divide(arguments): # TODO: support exact fractions # TODO: return integer if all arguments were integers and result is whole number check_argument_number('/', arguments, 1) if len(arguments) == 1: return FloatingPoint(1 / arguments[0].value) else: result = FloatingPoint(arguments[0].value) for argument in arguments.tail: result.value /= argument.value return result
def quotient(arguments): # integer division check_argument_number('quotient', arguments, 2, 2) if not isinstance(arguments[0], Integer) or not isinstance(arguments[1], Integer): raise SchemeTypeError("quotient is only defined for integers, " "got %s and %s." % (arguments[0].__class__, arguments[1].__class__)) # Python's integer division floors, whereas Scheme rounds towards zero x1 = arguments[0].value x2 = arguments[1].value result = math.trunc(x1 / x2) return Integer(result)
def string_ref(arguments): check_argument_number('string-ref', arguments, 2, 2) string_atom = arguments[0] if not isinstance(string_atom, String): raise SchemeTypeError("string-ref takes a string as its first argument, " "not a %s." % string_atom.__class__) char_index_atom = arguments[1] if not isinstance(char_index_atom, Integer): raise SchemeTypeError("string-ref takes an integer as its second argument, " "not a %s." % char_index_atom.__class__) string = string_atom.value char_index = char_index_atom.value if char_index >= len(string): # FIXME: this will say 0--1 if string is "" raise InvalidArgument("String index out of bounds: index must be in" " the range 0-%d, got %d." % (len(string) - 1, char_index)) return Character(string[char_index])
def cons(arguments): # TODO: check type as well as arity check_argument_number('cons', arguments, 2, 2) return Cons(arguments[0], arguments[1])