def slurp(*args: t.MalType) -> t.MalType: if len(args) != 1 or not isinstance(args[0], t.MalString): raise MalError('Expected slurp arg to be string') try: with open(args[0].value, 'r') as f: return t.MalString(f.read()) except FileNotFoundError: raise MalError(f'File {args[0].value} does not exist') except OSError: raise MalError(f'Failed to read file {args[0].value}')
def wrapper(*args: t.MalType) -> t.MalType: if not all(isinstance(i, t.MalInt) for i in args): raise MalError('Only ints are supported as arguments') acc, *rest = map(o.attrgetter('value'), args) # type: Tuple[int, List[int]] for i in rest: acc = op(acc, i) return t.MalInt(acc)
def swap(*args: t.MalType) -> t.MalType: if (len(args) < 2 or not isinstance(args[0], t.MalAtom) or not isinstance(args[1], t.MalCallable)): raise MalError('Expected atom and function as arguments to swap!') atom, callable_, *rest = args assert isinstance(atom, t.MalAtom) and isinstance(callable_, t.MalCallable) if isinstance(callable_, t.MalTCOFunction): callable_ = callable_.fn assert isinstance(callable_, t.MalFunction), 'Unsupported callable' new_value = atom.inner = callable_.fn(atom.inner, *rest) return new_value
def EVAL(in_: t.MalType, env: Env) -> t.MalType: if not isinstance(in_, t.MalList): return eval_ast(in_, env) if not in_.items: return in_ f, *args = in_.items if isinstance(f, t.MalSymbol): if f.name == 'def!': if len(args) != 2: raise MalError('Expected 2 arguments to "def!"') dest, val = args if not isinstance(dest, t.MalSymbol): raise MalError('Expected symbol name to "def!"') res = env[dest.name] = EVAL(val, env) return res elif f.name == 'let*': if len(args) != 2: raise MalError('Expected 2 arguments to "let*"') new_env = env.new_child() bindings, body = args if (not isinstance(bindings, (t.MalVector, t.MalList)) or len(bindings.items) % 2 != 0): raise MalError('Expected let* bindings to be even length list') for i in range(0, len(bindings.items) // 2 + 1, 2): name, value = bindings.items[i:i + 2] if not isinstance(name, t.MalSymbol): raise MalError('Expected symbol name in let* binding') new_env[name.name] = EVAL(value, new_env) return EVAL(body, new_env) f, *args = cast(t.MalList, eval_ast(in_, env)).items if isinstance(f, t.MalFunction): return f.fn(*args) raise MalError(f'Value {pr_str(f)} is not callable')
def eval_ast(ast: t.MalType, env: Env): if isinstance(ast, t.MalSymbol): if ast.name in env: return env[ast.name] raise MalError(f'Unbound symbol {ast.name}') if isinstance(ast, (t.MalList, t.MalVector)): return type(ast)([EVAL(i, env) for i in ast.items]) if isinstance(ast, t.MalHashMap): items = [] for k, v in ast.items.items(): items.append(k) items.append(EVAL(v, env)) return t.MalHashMap(items) return ast
def eval_ast(ast: t.MalType, env: Env) -> t.MalType: if isinstance(ast, t.MalSymbol): try: return env[ast.name] except KeyError: raise MalError(f'{ast.name} not found') if isinstance(ast, (t.MalList, t.MalVector)): return type(ast)([EVAL(i, env) for i in ast.items]) if isinstance(ast, t.MalHashMap): items = [] for k, v in ast.items.items(): items.append(k) items.append(EVAL(v, env)) return t.MalHashMap(items) return ast
def list_p(*args: t.MalType) -> t.MalType: if len(args) != 1: raise MalError(f'Expected 1 argument to list?, got {len(args)}') return t.MalBool(isinstance(args[0], t.MalList))
def atom_p(*args: t.MalType) -> t.MalType: if len(args) != 1: raise MalError('Expected one argument to atom?') return t.MalBool(isinstance(args[0], t.MalAtom))
def EVAL(in_: t.MalType, env: Env) -> t.MalType: while True: if not isinstance(in_, t.MalList): return eval_ast(in_, env) if not in_.items: return in_ f, *args = in_.items if isinstance(f, t.MalSymbol): if f.name == 'def!': if len(args) != 2: raise MalError('Expected 2 arguments to "def!"') dest, val = args if not isinstance(dest, t.MalSymbol): raise MalError('Expected symbol name to "def!"') res = env[dest.name] = EVAL(val, env) return res if f.name == 'let*': if len(args) != 2: raise MalError('Expected 2 arguments to "let*"') new_env = env.new_child() bindings, body = args if (not isinstance(bindings, (t.MalVector, t.MalList)) or len(bindings.items) % 2 != 0): raise MalError( 'Expected let* bindings to be even length list') for i in range(0, len(bindings.items) // 2 + 1, 2): name, value = bindings.items[i:i + 2] if not isinstance(name, t.MalSymbol): raise MalError('Expected symbol name in let* binding') new_env[name.name] = EVAL(value, new_env) env = new_env in_ = body continue if f.name == 'do': if not args: raise MalError('Expected body in do expr') eval_ast(t.MalList(args[:-1]), env) in_ = args[-1] continue if f.name == 'if': if len(args) not in (2, 3): raise MalError('Expected 2 or 3 arguments to if') cond, then, *else_ = args if EVAL(cond, env).is_truthy(): in_ = then elif else_: in_ = else_[0] else: in_ = t.MalNil() continue if f.name == 'fn*': if len(args) != 2: raise MalError('Expected 2 arguments to fn*') params, body = args if not isinstance(params, (t.MalList, t.MalVector)): raise MalError('Parameters must be list or vector') if not all(isinstance(p, t.MalSymbol) for p in params.items): raise MalError('Parameters must be symbols') return t.MalTCOFunction( ast=body, params=params, env=env, fn=t.MalFunction( _make_closure( env, [cast(t.MalSymbol, p).name for p in params.items], body)), ) f, *args = cast(t.MalList, eval_ast(in_, env)).items if isinstance(f, t.MalFunction): return f.fn(*args) if isinstance(f, t.MalTCOFunction): in_ = f.ast binds = [ cast(t.MalSymbol, p).name for p in cast(t.MalSequence, f.params).items ] env = f.env.new_child(_make_fn_bindings(binds, list(args))) continue raise MalError(f'Value {pr_str(f)} is not callable')
def deref(*args: t.MalType) -> t.MalType: if len(args) != 1 or not isinstance(args[0], t.MalAtom): raise MalError('Expected deref argument to be atom') return args[0].inner
def count(*args: t.MalType) -> t.MalType: if len(args) != 1 or not isinstance(args[0], (t.MalSequence, t.MalNil)): raise MalError('Expected argument to count to be sequence') if isinstance(args[0], t.MalNil): return t.MalInt(0) return t.MalInt(len(args[0].items))
def read_string(*args: t.MalType) -> t.MalType: if len(args) != 1 or not isinstance(args[0], t.MalString): raise MalError('Expected read-string arg to be string') return read_str(args[0].value)
def empty_p(*args: t.MalType) -> t.MalType: if len(args) != 1 or not isinstance(args[0], t.MalSequence): raise MalError('Expected argument to empty? to be sequence') return t.MalBool(len(args[0].items) == 0)
def atom(*args: t.MalType) -> t.MalType: if len(args) != 1: raise MalError('Expected one argument to atom') return t.MalAtom(args[0])
def evil(*args: t.MalType) -> t.MalType: if len(args) != 1: raise MalError('Expected one argument to eval') return EVAL(args[0])
def _cmp_fn(*args: t.MalType) -> t.MalType: try: return t.MalBool(all(op(a, b) for a, b in _pairwise(args))) except TypeError: types = ', '.join([type(v).__name__ for v in args]) raise MalError(f'Cannot compare values of types {types}')
def reset(*args: t.MalType) -> t.MalType: if len(args) != 2 or not isinstance(args[0], t.MalAtom): raise MalError('Expected atom and value as arguments to reset!') args[0].inner = args[1] return args[1]