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 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 define_function(arguments, environment): function_name_with_parameters = arguments[0] function_name = function_name_with_parameters[0] if not isinstance(function_name, Symbol): raise SchemeTypeError("Function names must be symbols, not a %s." % function_name.__class__) # check that all our arguments are symbols: function_parameters = function_name_with_parameters.tail for parameter in function_parameters: if not isinstance(parameter, Symbol): raise SchemeTypeError( "Function arguments must be symbols, not a %s." % parameter.__class__) # check if this function can take a variable number of arguments is_variadic = False for parameter in function_parameters: if parameter.value == '.': if is_variadic: raise SchemeSyntaxError( "May not have . more than once in a parameter list.") else: is_variadic = True if is_variadic: return define_variadic_function(arguments, environment) else: return define_normal_function(arguments, environment)
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 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 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 make_lambda_function(arguments, environment): check_argument_number('lambda', arguments, 2) parameter_list = arguments[0] function_body = arguments.tail if isinstance(parameter_list, Atom): raise SchemeTypeError( "The first argument to `lambda` must be a list of variables.") for parameter in parameter_list: if not isinstance(parameter, Symbol): raise SchemeTypeError( "Parameters of lambda functions must be symbols, not %s." % parameter.__class__) def lambda_function(_arguments, _environment): check_argument_number('(anonymous function)', _arguments, len(parameter_list), len(parameter_list)) local_environment = {} for (parameter_name, parameter_expression) in zip(parameter_list, _arguments): local_environment[ parameter_name.value], _environment = eval_s_expression( parameter_expression, _environment) new_environment = dict(_environment, **local_environment) # now we have set up the correct scope, evaluate our 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) return (LambdaFunction(lambda_function), environment)
def equality(arguments): for argument in arguments: if not isinstance(argument, Number): raise SchemeTypeError("Numerical equality test is only defined for numbers, " "you gave me %s." % argument.__class__) if argument != arguments[0]: return Boolean(False) return Boolean(True)
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 eval_list(linked_list, environment): if not linked_list: raise SchemeSyntaxError("() is not syntactically valid.") # find the function/primitive we are calling function, environment = eval_s_expression(linked_list[0], environment) if isinstance(function, Atom): raise SchemeTypeError("You can only call functions, but " "you gave me a %s." % function.__class__) # call it (internally we require the function to decide whether or # not to evaluate the arguments) return function(linked_list.tail, environment)
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 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 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 define_variable(arguments, environment): if not isinstance(arguments[0], Symbol): raise SchemeTypeError( "Tried to assign to a %s, which isn't a symbol." % arguments[0].__class__) if arguments[0].value in environment: raise RedefinedVariable( "Cannot define %s, as it has already been defined." % arguments[0].value) variable_name = arguments[0].value variable_value_expression = arguments[1] result, environment = eval_s_expression(variable_value_expression, environment) environment[variable_name] = result return (None, environment)
def add(arguments): if not arguments: return Integer(0) if isinstance(arguments[0], Integer): total = Integer(0) elif isinstance(arguments[0], FloatingPoint): total = FloatingPoint(0.0) for argument in arguments: if not isinstance(argument, Number): raise SchemeTypeError("Addition is only defined for numbers, " "you gave me %s." % argument.__class__) # adding a float to 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 set_variable(arguments, environment): check_argument_number('set!', arguments, 2, 2) variable_name = arguments[0] if not isinstance(variable_name, Symbol): raise SchemeTypeError( "Tried to assign to a %s, which isn't a symbol." % variable_name.__class__) if variable_name.value not in environment: raise UndefinedVariable("Can't assign to undefined variable %s." % variable_name.value) variable_value_expression = arguments[1] result, environment = eval_s_expression(variable_value_expression, environment) environment[variable_name.value] = result return (None, environment)
def multiply(arguments): if not arguments: return Integer(1) if isinstance(arguments[0], Integer): product = Integer(1) elif isinstance(arguments[0], FloatingPoint): product = FloatingPoint(1.0) for argument in arguments: if not isinstance(argument, Number): raise SchemeTypeError("Multiplication is only defined for numbers, " "you gave me %s." % argument.__class__) if isinstance(product, Integer) and isinstance(argument, FloatingPoint): product = FloatingPoint(float(product.value)) product.value *= argument.value return product