def iter_macroexpand(env, source_obj, position=None): if env: compiler = env.get("__compiler__") or current() elif env is None: compiler = current() if compiler: env = compiler.env if env is None: raise CompilerException("macroexpand requires non-None env when" " no compiler is active") if position is None and is_pair(source_obj): position = source_obj.get_position() # expander = env_get_expander(env, source_obj) expander = compiler.find_expander(source_obj, env) while expander: expanded = expander() yield expanded # expander = env_get_expander(env, expanded) expander = compiler.find_expander(expanded, env) if is_pair(expanded): fill_position(expanded, position)
def error(self, message, source): """ Create a CompilerSyntaxError based on the message and source object. Will attempt to find position and line text based on the compiler's state. """ text = None pos = None if is_pair(source) and source is not nil: pos = source.get_position() if pos and exists(self.filename): with open(self.filename, "rt") as fin: for text, _lineno in zip(fin, range(0, pos[0])): # print(" ...", text) pass if not text: text = str(source) if pos: pos = (pos[0], 0) return CompilerSyntaxError(message, pos, text=text, filename=self.filename)
def test_nil(self): # singleton nil check Nil = type(nil) self.assertEqual(id(nil), id(Nil())) self.assertEqual(id(Nil()), id(Nil())) self.assertTrue(nil is Nil()) # behavior self.assertIsInstance(nil, pair) self.assertTrue(is_pair(nil)) self.assertTrue(is_nil(nil)) self.assertTrue(is_proper(nil)) self.assertFalse(nil) self.assertTrue(not nil) self.assertEqual(str(nil), "nil") self.assertEqual(repr(nil), "nil") self.assertEqual(nil, nil) self.assertNotEqual(nil, cons(1, nil)) self.assertNotEqual(cons(1, nil), nil) with self.assertRaises(TypeError): car(nil) with self.assertRaises(TypeError): cdr(nil) self.assertEqual(list(nil), list()) self.assertEqual(tuple(nil), tuple())
def compile(self, source_obj, tc, cont): """ Compile a supported source object into an expression. pair, symbol, keyword, and the pythonic constant types are valid source obj types. """ self.require_active() tc = self.tco_enabled and tc if is_pair(source_obj): dispatch = self.compile_pair elif is_symbol(source_obj) or is_lazygensym(source_obj): dispatch = self.compile_symbol elif is_keyword(source_obj): dispatch = self.compile_keyword elif isinstance(source_obj, CONST_TYPES): dispatch = self.compile_constant else: msg = "Unsupported source object %r" % source_obj raise CompilerException(msg) try: return dispatch(source_obj, tc, cont or self._compile_cont) except (CompilerException, SibilantSyntaxError): # these two should be propogated unchanged raise except Exception as ex: raise UncaughtCompilerException(ex, source_obj)
def test_recursive_cons(self): a = cons(1, 2, 3, recursive=True) self.assertTrue(a.is_proper()) self.assertTrue(a.is_recursive()) self.assertTrue(is_pair(a)) self.assertTrue(is_proper(a)) self.assertEqual(a.length(), 3) self.assertEqual(car(a), car(cdr(cdr(cdr(a))))) self.assertEqual(str(a), "(1 2 3 ...)") self.assertEqual(repr(a), "cons(1, 2, 3, recursive=True)") b = pair(0, a) c = pair(0, a) self.assertEqual(b, c) self.assertNotEqual(a, b) self.assertNotEqual(a, c) z = cons(1, cons(2, cons(3, nil))) setcdr(cdr(cdr(z)), z) self.assertEqual(a, z) self.assertEqual(z, a)
def compile(self, compiler, source_obj, tc, cont): expanded = self.expand() expanded = _symbol_None if expanded is None else expanded if is_pair(source_obj): called_by, source = source_obj res = cons(expanded, source) fill_position(res, source_obj.get_position()) expanded = res return tcf(cont, expanded, tc)
def compile_apply(self, source_obj: pair, tc, cont): """ Compile a runtime function apply expression. """ tc = tc and self.tco_enabled and not self.generator head, tail = source_obj if tc and self.self_ref and \ is_symbol(head) and (str(head) == self.name): return tcf(self.compile_tcr_apply, source_obj, tc, cont) pos = source_obj.get_position() if is_pair(head): # @trampoline def ccp(new_head, tc): # Continue Compiling Pair. This is how we finish # compiling a function invocation after first # compiling the head if new_head is None: # the original head pair compiled down to a None, # which means it pushed bytecode and left a value # on the stack. Complete the apply based on that. return tcf(self.complete_apply, tail, pos, tc, cont) else: # the original head pair was transformed, so now # we need to start over in a new compile_pair call # using a newly assembled expression. expr = pair(new_head, tail) expr.set_position(pos) return tcf(self.compile_pair, expr, tc, cont) # we need to compile the head first, to figure out if it # expands into a symbolic reference or something. We'll # use ccp as a temporary continuation. Note that the # evaluation of the head of the pair is never a tailcall # itself, even if it would be a tailcall to apply it as a # function afterwards. return tcf(self.compile_pair, head, False, ccp) elif tc: self.declare_tailcall() self.pseudop_get_global(_symbol_tailcall_full) return tcf(self.complete_apply, source_obj, pos, False, cont) else: self.add_expression(head) return tcf(self.complete_apply, tail, pos, tc, cont)
def test_improper_cons(self): z = cons(1, 2) self.assertEqual(z.length(), 2) self.assertFalse(z.is_proper()) self.assertEqual(car(z), 1) self.assertEqual(cdr(z), 2) self.assertEqual(str(z), "(1 . 2)") self.assertEqual(repr(z), "cons(1, 2)") self.assertTrue(is_pair(z)) self.assertFalse(is_proper(z)) self.assertNotEqual(z, None) self.assertNotEqual(z, nil) self.assertNotEqual(z, cons(1, nil)) self.assertNotEqual(z, cons(1, 3)) self.assertNotEqual(z, cons(1, 3, nil))
def compile_pair(self, source_obj: pair, tc, cont): """ Compile a pair expression. This will become either a literal nil, a macro expansion, a special invocation, or a runtime function application. """ if is_nil(source_obj): return tcf(self.compile_nil, source_obj, tc, cont) if not is_proper(source_obj): # print("** WUT", self, source_obj, tc, cont) msg = "cannot evaluate improper lists as expressions" raise self.error(msg, source_obj) self.pseudop_position_of(source_obj) head, tail = source_obj if is_symbol(head) or is_lazygensym(head): comp = self.find_compiled(head) if comp: # the head of the pair is a symbolic reference which # resolved to a compile-time object. Invoke that. return tcf(comp.compile, self, source_obj, tc, cont) else: return tcf(self.compile_apply, source_obj, tc, cont) elif is_pair(head): return tcf(self.compile_apply, source_obj, tc, cont) else: # TODO: should this be a compile-time error? If we have # something that isn't a symbolic reference or isn't a # pair, then WTF else would it be? Let's just let it break # at runtime, for now. return tcf(self.compile_apply, source_obj, tc, cont)
def compile(self, source_obj, tc, cont): """ Compile a supported source object into an expression. pair, symbol, keyword, and the pythonic constant types are valid source obj types. """ tc = self.tco_enabled and tc if is_pair(source_obj): dispatch = self.compile_pair elif is_symbol(source_obj) or is_lazygensym(source_obj): dispatch = self.compile_symbol elif is_keyword(source_obj): dispatch = self.compile_keyword elif isinstance(source_obj, CONST_TYPES): dispatch = self.compile_constant else: msg = "Unsupported source object %r" % source_obj raise self.error(msg, source_obj) return tcf(dispatch, source_obj, tc, cont or self._compile_cont)
def gather_formals(args, declared_at=None, filename=None): """ parses formals pair args into five values: (positional, keywords, defaults, stararg, starstararg) - positional is a list of symbols defining positional arguments - defaults is a list of keywords and expr pairs defining keyword arguments and their default value expression - kwonly is a list of keywords and expr pairs which are keyword-only arguments and theid default value expression - stararg is a symbol for variadic positional arguments - starstararg is a symbol for variadic keyword arguments """ undefined = object() err = partial(SibilantSyntaxError, location=declared_at, filename=filename) if is_symbol(args): return ((), (), (), args, None) elif isinstance(args, (list, tuple)): improper = False args = cons(*args, nil) elif is_proper(args): improper = False elif is_pair(args): improper = True else: raise err("formals must be symbol or pair, not %r" % args) positional = [] iargs = iter(args.unpack()) for arg in iargs: if is_keyword(arg): if improper: raise err("cannot mix improper formal with keywords") else: break elif is_symbol(arg) or is_lazygensym(arg): positional.append(arg) else: raise err("positional formals must be symbols, not %r" % arg) else: # handled all of args, done deal. if improper: return (positional[:-1], (), (), positional[-1], None) else: return (positional, (), (), None, None) defaults = [] kwonly = [] while arg not in (_keyword_star, _keyword_starstar): value = next(iargs, undefined) if value is undefined: raise err("missing value for keyword formal %s" % args) else: defaults.append((arg, value)) arg = next(iargs, undefined) if arg is undefined: break elif is_keyword(arg): continue else: raise err("keyword formals must be alternating keywords and" " values, not %r" % arg) star = None starstar = None if arg is undefined: return (positional, defaults, kwonly, None, None) if arg is _keyword_star: star = next(iargs, undefined) if star is undefined: raise err("* keyword requires symbol binding") elif star is nil: # nil means an ignored star arg, this is allowed. pass elif not (is_symbol(star) or is_lazygensym(star)): raise err("* keyword requires symbol binding, not %r" % star) arg = next(iargs, undefined) if arg is undefined: return (positional, defaults, kwonly, star, starstar) # while is_symbol(arg): # kwonly.append(arg) # arg = next(iargs, undefined) # # if arg is undefined: # return (positional, defaults, kwonly, star, starstar) if not is_keyword(arg): raise err("expected keyword in formals, got %r" % arg) # keyword formals after *: are considered keyword-only while arg not in (_keyword_star, _keyword_starstar): value = next(iargs, undefined) if value is undefined: raise err("missing value for keyword-only formal %s" % arg) else: kwonly.append((arg, value)) arg = next(iargs, undefined) if arg is undefined: break elif is_keyword(arg): continue else: raise err("keyword-only formals must be alternating keywords" " and values, not %r" % arg) if arg is _keyword_starstar: starstar = next(iargs, undefined) if starstar is undefined: raise err("** keyword requires symbol binding") elif not (is_symbol(starstar) or is_lazygensym(starstar)): raise err("** keyword requires symbol binding, not %r" % star) arg = next(iargs, undefined) if arg is not undefined: raise err("leftover formals %r" % arg) return (positional, defaults, kwonly, star, starstar)
def gather_parameters(args, declared_at=None, filename=None): """ parses parameter args into five values: (positional, keywords, values, stararg, starstararg) - positional is a list of expressions for positional arguments - keywords is a list of keywords defining keyword arguments - values is a list of expressions defining values for keywords - stararg is a symbol for variadic positional expression - starstararg is a symbol for variadic keyword expression """ undefined = object() def err(msg): return SibilantSyntaxError(msg, location=declared_at, filename=filename) if is_symbol(args) or is_lazygensym(args): return ((), (), (), args, None) elif isinstance(args, (list, tuple)): improper = False args = cons(*args, nil) if args else nil elif is_proper(args): improper = False elif is_pair(args): improper = True else: raise err("parameters must be symbol or pair, not %r" % args) positional = [] iargs = iter(args.unpack()) for arg in iargs: if is_keyword(arg): break else: positional.append(arg) else: # handled all of args, done deal. if improper: return (positional[:-1], (), (), positional[-1], None) else: return (positional, (), (), None, None) keywords = [] defaults = [] while arg not in (_keyword_star, _keyword_starstar): keywords.append(arg) value = next(iargs, undefined) if value is undefined: raise err("missing value for keyword parameter %s" % arg) else: defaults.append(value) arg = next(iargs, undefined) if arg is undefined: break elif is_keyword(arg): continue else: raise err("keyword parameters must be alternating keywords and" " values, not %r" % arg) star = None starstar = None if arg is undefined: return (positional, keywords, defaults, None, None) if arg is _keyword_star: star = next(iargs, undefined) if star is undefined: raise err("* keyword parameter needs value") arg = next(iargs, undefined) if arg is _keyword_starstar: starstar = next(iargs, undefined) if starstar is undefined: raise err("** keyword parameter needs value") arg = next(iargs, undefined) if arg is not undefined: raise err("leftover parameters %r" % arg) return (positional, keywords, defaults, star, starstar)
def gather_formals(args, declared_at=None, filename=None): """ parses formals pair args into five values: (positional, keywords, defaults, stararg, starstararg) - positional is a list of symbols defining positional arguments - defaults is a list of keywords and expr pairs defining keyword arguments and their default value expression - kwonly is a list of keywords and expr pairs which are keyword-only arguments and theid default value expression - stararg is a symbol for variadic positional arguments - starstararg is a symbol for variadic keyword arguments """ undefined = object() err = partial(SibilantSyntaxError, location=declared_at, filename=filename) if is_symbol(args): return ((), (), (), args, None) elif isinstance(args, (list, tuple)): improper = False args = cons(*args, nil) elif is_proper(args): improper = False elif is_pair(args): improper = True else: raise err("formals must be symbol or pair, not %r" % args) positional = [] iargs = iter(args.unpack()) for arg in iargs: if is_keyword(arg): if improper: raise err("cannot mix improper formal with keywords") else: break elif is_symbol(arg) or is_lazygensym(arg): positional.append(arg) else: raise err("positional formals must be symbols, nor %r" % arg) else: # handled all of args, done deal. if improper: return (positional[:-1], (), (), positional[-1], None) else: return (positional, (), (), None, None) defaults = [] kwonly = [] while arg not in (_keyword_star, _keyword_starstar): value = next(iargs, undefined) if value is undefined: raise err("missing value for keyword formal %s" % args) else: defaults.append((arg, value)) arg = next(iargs, undefined) if arg is undefined: break elif is_keyword(arg): continue else: raise err("keyword formals must be alternating keywords and" " values, not %r" % arg) star = None starstar = None if arg is undefined: return (positional, defaults, kwonly, None, None) if arg is _keyword_star: star = next(iargs, undefined) if star is undefined: raise err("* keyword requires symbol binding") elif star is nil: # nil means an ignored star arg, this is allowed. pass elif not (is_symbol(star) or is_lazygensym(star)): raise err("* keyword requires symbol binding, not %r" % star) arg = next(iargs, undefined) if arg is undefined: return (positional, defaults, kwonly, star, starstar) # while is_symbol(arg): # kwonly.append(arg) # arg = next(iargs, undefined) # # if arg is undefined: # return (positional, defaults, kwonly, star, starstar) if not is_keyword(arg): raise err("expected keyword in formals, got %r" % arg) # keyword formals after *: are considered keyword-only while arg not in (_keyword_star, _keyword_starstar): value = next(iargs, undefined) if value is undefined: raise err("missing value for keyword-only formal %s" % arg) else: kwonly.append((arg, value)) arg = next(iargs, undefined) if arg is undefined: break elif is_keyword(arg): continue else: raise err("keyword-only formals must be alternating keywords" " and values, not %r" % arg) if arg is _keyword_starstar: starstar = next(iargs, undefined) if starstar is undefined: raise err("** keyword requires symbol binding") elif not (is_symbol(starstar) or is_lazygensym(starstar)): raise err("** keyword requires symbol binding, not %r" % star) arg = next(iargs, undefined) if arg is not undefined: raise err("leftover formals %r" % arg) return (positional, defaults, kwonly, star, starstar)