def _process_quasiquote_list_item(obj): # TODO: could we invoke (list) directly? if is_list(obj) and first(obj) == Symbol('unquote'): return List([Symbol('list', ns='kaa.core'), first(rest(obj))]) if is_list(obj) and first(obj) == Symbol('unquote-splice'): return first(rest(obj)) return List([Symbol('list', ns='kaa.core'), _process_quasiquote(obj)])
def _read_unquote(self, stream): if stream.peek_char() == '@': stream.pop_char() return List([Symbol('unquote-splice', meta={'source': stream.source_meta()}), self.read_next(stream)]) return List([Symbol('unquote', meta={'source': stream.source_meta()}), self.read_next(stream)])
def _read_symbol(self, s, meta): if any(special_form.name == s for special_form in SPECIAL_FORMS): return Symbol(s, None, meta) if '/' in s and s != '/': ns_name, sym_name = s.split('/', 1) else: ns_name, sym_name = None, s return self.ns.resolve(Symbol(sym_name, ns_name, meta))
def _process_quasiquote(obj): # `a -> 'a if not is_list(obj) or not obj: return List([Symbol('quote'), obj]) # `~a -> a if first(obj) == Symbol('unquote'): return obj # `(a ~b ~@c) -> (concat (list 'a) (list b) c) # TODO: could we invoke (concat) directly? return List([Symbol('concat', ns='kaa.core')] + [_process_quasiquote_list_item(o) for o in obj])
def import_ns(self, ns, symbol_names, alias=None): self.imported_namespaces[ns.name] = ns if symbol_names == '*' or symbol_names: importables = ns.exportables() imported_symbols = importables if symbol_names == '*' \ else tuple(Symbol(name, ns.name) for name in symbol_names) for sym in imported_symbols: self.import_symbol(sym) if alias: self.ns_aliases[alias] = ns.name
def import_module(self, mod, attrs=None, alias=None): self.imported_modules[mod.__name__] = mod if attrs == '*': raise NotImplementedError( '`import *` not yet implemented for modules') if attrs: for attr in attrs: self.import_symbol(Symbol(attr, mod.__name__)) if alias: self.ns_aliases[alias] = mod.__name__
def parse_def(form): check(len(form) == 3, '`def` requires 2 args', form) _, sym, val = form check(is_symbol(sym), '`def` name must be a symbol', form) # If namespace `a` imports symbol `foo` from namespace `b`, then provides # `(def foo …)`, the reader resolves the symbol to `b/foo`, but the # intention is to define `a/foo`. To get that behaviour, we discard the # symbol namespace here. # # FIXME: this leads to confusing behaviour if a namespaced symbol is passed # to the reader. We should throw an error we read a qualified symbol that # resolves to another namespace, not silently rewrite it. sym = Symbol(sym.name, None, sym.meta) return Def(sym, val)
def loop(self): self.ns[self.last_result_symbol] = None while True: try: exprs = self.read_exprs() if exprs and exprs[0] and is_list(exprs[0]) and \ exprs[0][0] == Symbol('debug', '__kaa__'): exprs = exprs[0][1:] pdb.set_trace() result = Evaluator(self.ns).evaluate_all(exprs) except KeyboardInterrupt: # Ctrl-C; user wants to abandon current input print() continue except EOFError: # Ctrl-D; user wants to quit print() break except Exception: # pylint: disable=broad-except traceback.print_exc() continue if result is not None: self.ns[self.last_result_symbol] = result print(serialize(result))
def _read_quote(self, stream): return List([Symbol('quote', meta={'source': stream.source_meta()}), self.read_next(stream)])
def import_symbol(self, sym): # TODO: handle name not found in module # TODO: warn if symbol collision with self.defs self.imported_symbol_refs[Symbol(sym.name, self.name)] = sym self.imported_symbols.add(sym)
def __setitem__(self, sym, value): assert isinstance(sym, Symbol), f'{sym} is not a Symbol' if sym.ns is None: sym = Symbol(sym.name, self.name, sym.meta) assert sym.ns == self.name, f'cannot define {sym} via namespace {self.name}' self.defs[sym] = value
def test_symbol_equality(): assert Symbol('foo') == Symbol('foo') assert Symbol('foo') != Symbol('bar') assert Symbol('foo') != Symbol('foo', 'ns') assert Symbol('foo', 'ns1') != Symbol('foo', 'ns2') assert Symbol('foo', 'ns') == Symbol('foo', 'ns')
def test_read_special_form(): assert read('def') == Symbol('def')
def test_read_list(): obj = read('(foo 42 bar)') assert isinstance(obj, List) assert obj[0] == Symbol('foo', 'testing') assert obj[1] == 42 assert obj[2] == Symbol('bar', 'testing')
def test_read_unqualified_symbol(): assert read('foo-bar') == Symbol('foo-bar', 'testing')
def test_read_qualified_symbol(): assert read('foo/bar') == Symbol('bar', 'foo')
def test_parse_lambda(): parsed = parse_lambda(read('(lambda (foo) bar)')) assert isinstance(parsed, Lambda) assert isinstance(parsed.params, Params) assert tuple(parsed.body) == ((Symbol('bar', 'testing'), ))
def __init__(self): self.ns = Namespace('repl') self.last_result_symbol = self.ns.resolve(Symbol('^'))
def test_resolve_qualified_with_alias(): ns = Namespace('testing', import_core=False) core = ns.load_ns('kaa.core') ns.import_ns(core, (), 'core') sym = Symbol('asdfasdf', 'core') assert ns.resolve(sym) == sym.in_ns('kaa.core')
def test_define(): ns = Namespace('testing', import_core=False) sym = Symbol('foo', 'testing') ns[sym] = 'bar' assert ns[sym] == 'bar'
def test_resolve_qualified_import_ref(): ns = Namespace('testing', import_core=True) sym = Symbol('defun', 'kaa.core') assert ns.resolve(sym) == sym
def test_resolve_unqualified_def(): ns = Namespace('testing', import_core=False) ns[Symbol('foo', 'testing')] = 'bar' sym = Symbol('foo') assert ns.resolve(sym) == sym.in_ns('testing')
def test_resolve_unqualified_unknown(): ns = Namespace('testing', import_core=False) sym = Symbol('foo') assert ns.resolve(sym) == sym.in_ns('testing')
def test_resolve_qualified_with_unknown(): ns = Namespace('testing', import_core=False) sym = Symbol('asfasdf', 'unknown') assert ns.resolve(sym) == sym
def _parse_except(form): check( is_list(form) and len(form) == 3 and form[0] == Symbol('except'), 'invalid except form', form) return form[1:3]
def test_lookup(): ns = Namespace('testing') ns[Symbol('x', 'testing')] = 42 assert ns[Symbol('x', 'testing')] == 42 assert ns[Symbol('defun', 'kaa.core')] is not None
return Try(expr, (_parse_except(except_) for except_ in excepts)) def _parse_except(form): check( is_list(form) and len(form) == 3 and form[0] == Symbol('except'), 'invalid except form', form) return form[1:3] def raise_invalid_top_level_except(form): check(False, '`except` must appear within `try`', form) SPECIAL_FORMS = { Symbol('def'): parse_def, Symbol('defmacro'): parse_defmacro, # Not invoked directly as a special form, but here so reader recognizes it # and doesn't attempt to resolve it as a normal symbol Symbol('except'): raise_invalid_top_level_except, Symbol('if'): parse_if, Symbol('import'): parse_import, Symbol('lambda'): parse_lambda, Symbol('raise'): parse_raise,