Last active
September 11, 2024 22:53
-
-
Save jameslmartin/ffd6a5260cb1e0b63e67c1cd88420d87 to your computer and use it in GitHub Desktop.
Naive parser for a subset of Lisp
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import sys | |
from enum import Enum | |
from abc import ABC | |
class AST(ABC): | |
pass | |
class KnownSymbol(Enum): | |
LPAREN = "(" | |
RPAREN = ")" | |
IF = "if" | |
DEFINE = "define" | |
class Operator(Enum): | |
ADD = "+" | |
SUB = "-" | |
MULT = "*" | |
DIV = "/" | |
LESS = "<" | |
GREATER = ">" | |
LEQ = "<=" | |
GEQ = ">=" | |
EQ = "==" | |
NEQ = "!=" | |
class Terminal: | |
def __str__(self): | |
return str(self.spelling) | |
def __repr__(self): | |
return f"({self.__class__}, {self.spelling})" | |
class Number(Terminal): | |
spelling: (int, float) | |
def __init__(self, spelling: str): | |
try: | |
self.spelling = int(spelling) | |
except ValueError: | |
self.spelling = float(spelling) | |
class Symbol(Terminal): | |
spelling: str | |
def __init__(self, spelling: str): | |
match spelling: | |
case "(": | |
self.spelling = KnownSymbol.LPAREN | |
case ")": | |
self.spelling = KnownSymbol.RPAREN | |
case "+": | |
self.spelling = Operator.ADD | |
case "-": | |
self.spelling = Operator.SUB | |
case "*": | |
self.spelling = Operator.MULT | |
case "/": | |
self.spelling = Operation.DIV | |
case "if": | |
self.spelling = KnownSymbol.IF | |
case "define": | |
self.spelling = KnownSymbol.DEFINE | |
case _: | |
self.spelling = spelling | |
class Atom: | |
atom: (Symbol, Number) | |
def __init__(self, spelling: str): | |
try: | |
self.atom = Number(spelling) | |
except ValueError: | |
self.atom = Symbol(spelling) | |
def __str__(self): | |
return str(self.atom) | |
def __repr__(self): | |
return f"({self.atom.__class__}, {self.atom.spelling})" | |
Expression = list | Atom | |
# Expression: | |
# variable reference: Symbol | |
# constant literal: Number | |
# conditional: (if test: Expression consequence: Expression alternative: Expression) | |
# definition: (define Symbol Expression) | |
# procedure call: (Symbol Expression) encompasses all builtin operations, defined procedures | |
class Parser: | |
def parse(self, tokens: [str]) -> Expression: | |
if len(tokens) == 0: | |
raise SyntaxError("Unexpected EOF") | |
token = tokens.pop(0) | |
if token == KnownSymbol.LPAREN.value: # nested Expression | |
nested_expression = [] | |
while tokens[0] != KnownSymbol.RPAREN.value: | |
nested_expression.append(self.parse(tokens)) | |
if len(tokens) == 0: | |
raise SyntaxError("Unexpected EOF") | |
tokens.pop(0) # pop matching RPAREN | |
return nested_expression | |
elif token == KnownSymbol.RPAREN.value: | |
raise SyntaxError("Unexpected )") | |
else: | |
return Atom(token) | |
class Interpreter: | |
@classmethod | |
def eval(cls, x: Expression): | |
if isinstance(x, Atom): | |
return x.atom.spelling | |
if isinstance(x, list): | |
if x[0].atom.spelling in [o for o in Operator]: | |
(operator, lhs, rhs) = x | |
match operator.spelling: | |
case Operator.ADD: | |
return cls.eval(lhs) + cls.eval(rhs) | |
case _: | |
print("Not supported yet!") | |
if x[0].atom.spelling in [KnownSymbol.IF, KnownSymbol.DEFINE]: | |
# Special expressions | |
match x[0].spelling: | |
case KnownSymbol.IF: | |
print("Not supported yet!") | |
(_, test, consequence, alt) = x | |
else: | |
procedure = x[0].spelling | |
args = [cls.eval(arg) for arg in x[1:]] | |
match procedure: | |
# could generalize procedure calls with Env | |
# generalized procedure calls unpack args, naively leaving them as lists | |
case "list": | |
return args | |
case "first": | |
return list(*args)[0] | |
class Scanner: | |
@classmethod | |
def scan(cls, lisp: str): | |
tokenized_input = lisp.replace("(", " ( ").replace(")", " ) ").split() | |
return tokenized_input | |
def main(): | |
args = sys.argv | |
lisp = sys.argv[1] | |
scanner = Scanner() | |
parser = Parser() | |
parsed = parser.parse(scanner.scan(lisp)) | |
print(parsed) | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment