def exec_(self, instructions: t.Iterable[Instruction]): stack = [] for instruction in instructions: operator = instruction.operator location = instruction.location args = instruction.args if operator == Operator.push: value, = args if not isinstance(value, Symbol): stack.append(value) continue content = value.content try: bound_value = self.env[content] except KeyError: raise LispError(f'undefined symbol "{content}"', location) stack.append(bound_value) elif operator == Operator.call: proc = stack.pop() if not callable(proc): raise LispError( f'head of procedure call expression is not a procedure', location) arg_count, = args assert arg_count <= len(stack), "The virtual machine "\ "encountered a stack underflow." args = [stack.pop() for _ in range(arg_count)] args.reverse() stack.append(proc(*args)) elif operator == Operator.def_: symbol, = args assert stack, "The virtual machine encountered a stack "\ "underflow." value = stack.pop() self.env[symbol.content] = value else: assert False, "The virtual machine encountered an invalid "\ "operator." return stack
def eval_real_literal(location: Location, c: str, *, sign, value, base, denominator): if c is None: return sign * value try: digit = basedigit(c, base) except ValueError: pass else: return partial( eval_real_literal, sign=sign, value=value + Fraction(digit, denominator), base=base, denominator=denominator * base, ) if c == '_': return partial( eval_real_literal, sign=sign, value=value, base=base, denominator=denominator, ) raise LispError( f'Invalid character in base {base} real literal', location, )
def scan_char_code_with_base(self, location: Location, c: str, *, fragment: LexemeFragment, delimiter: str, base: int, code: int)\ -> t.Iterator[ScannerYield]: yield from () try: digit = basedigit(c, base) except ValueError: pass else: return partial( self.scan_char_code_with_base, fragment=fragment, delimiter=delimiter, base=base, code=base * code + digit, ) if c == ')': fragment.content.append(chr(code)) return partial(self.scan_string, fragment=fragment, delimiter=delimiter, ) else: raise LispError('Invalid character in character code', location)
def scan_char_code(self, location: Location, c: str, *, fragment: LexemeFragment, delimiter: str, code: int)\ -> t.Iterator[ScannerYield]: yield from () if c.isdigit(): return partial(self.scan_char_code, fragment=fragment, delimiter=delimiter, code=10 * code + (ord(c) - ord('0')), ) elif c == '#': return partial(self.scan_char_code_with_base, fragment=fragment, delimiter=delimiter, base=code, code=0, ) elif c == ')': fragment.content.append(chr(code)) return partial(self.scan_string, fragment=fragment, delimiter=delimiter, ) else: raise LispError('Invalid character code', location)
def end(self) -> Expr: fragment = self.expr_stack.pop() if self.expr_stack: raise LispError( 'Unmatched opening parentheses', self.expr_stack[-1].location, ) return ComplexExpr(fragment.location, tuple(fragment.subexprs))
def compile_definition(location, tail): try: name_expr, value_expr = tail except ValueError: raise LispError( f'Invalid definition; got {len(tail)} arguments but' ' definitions must have exactly 2 arguments', location) from None if isinstance(name_expr, Token): symbol = name_expr.content if isinstance(symbol, Symbol): expr_stack.append( Instruction(Operator.def_, location, [name_expr.content])) expr_stack.append(value_expr) return raise LispError('Invalid name in definition; it must be a symbol', name_expr.location)
def compile_expr(expr: Expr) -> t.Iterator[Instruction]: expr_stack = [expr] def compile_definition(location, tail): try: name_expr, value_expr = tail except ValueError: raise LispError( f'Invalid definition; got {len(tail)} arguments but' ' definitions must have exactly 2 arguments', location) from None if isinstance(name_expr, Token): symbol = name_expr.content if isinstance(symbol, Symbol): expr_stack.append( Instruction(Operator.def_, location, [name_expr.content])) expr_stack.append(value_expr) return raise LispError('Invalid name in definition; it must be a symbol', name_expr.location) while expr_stack: expr = expr_stack.pop() location = expr.location if isinstance(expr, Instruction): yield expr elif isinstance(expr, Token): yield Instruction(Operator.push, location, [expr.content]) elif not expr.subexprs: raise LispError('empty procedure call expression', location) else: head = expr.subexprs[0] tail = expr.subexprs[1:] if isinstance(head, Token): value = head.content if isinstance(value, Symbol): if value.content == 'def': compile_definition(location, tail) continue expr_stack.append(Instruction(Operator.call, location, [len(tail)])) expr_stack.append(head) expr_stack.extend(reversed(tail))
def parse(self, tokens: t.Iterable[Token]) -> None: for token in tokens: content = token.content if isinstance(content, ParserDirective): if content.content == '(': self.expr_stack.append(ExprFragment(token.location, [])) elif content.content == ')': if len(self.expr_stack) <= 1: raise LispError('Unmatched closing parenthesis', token.location) fragment = self.expr_stack.pop() expr = ComplexExpr(fragment.location, tuple(fragment.subexprs)) self.expr_stack[-1].subexprs.append(expr) else: assert False, "The parser received an invalid directive." else: self.expr_stack[-1].subexprs.append(token)
def scan_escape_sequence(self, location: Location, c: str, *, fragment: LexemeFragment, delimiter: str) -> t.Iterator[ScannerYield]: yield from () if c == '(': return partial(self.scan_char_code, fragment=fragment, delimiter=delimiter, code=0, ) try: escaped_c = self.SIMPLE_ESCAPE_SEQUENCES[c] except KeyError: raise LispError('Invalid escape sequence', location) fragment.content.append(escaped_c) return partial(self.scan_string, fragment=fragment, delimiter=delimiter, )
def eval_integer_literal_with_base(location: Location, c: str, *, sign: int, value: int, base: int): if c is None: return sign * value try: digit = basedigit(c, base) except ValueError: pass else: return partial( eval_integer_literal_with_base, sign=sign, value=base * value + digit, base=base, ) if c == '_': return partial( eval_integer_literal_with_base, sign=sign, value=value, base=base, ) if c == '.': return partial( eval_real_literal, sign=sign, value=Fraction(value), base=base, denominator=base, ) raise LispError( f'Invalid character in base {base} integer literal', location, )
def eval_integer_literal(location: Location, c: str, *, sign: int, value: int): if c is None: return sign * value if c.isdigit(): return partial( eval_integer_literal, sign=sign, value=10 * value + (ord(c) - ord('0')), ) if c == '_': return partial(eval_integer_literal, sign=sign, value=value) if c == '#': return partial( eval_integer_literal_with_base, sign=sign, value=0, base=value, ) if c == '.': return partial( eval_real_literal, sign=sign, value=Fraction(value), base=10, denominator=10, ) raise LispError( 'Invalid character in base 10 integer literal', location, )