From 1daae9c30ac4ea7e259ba297df885cf3558e7a15 Mon Sep 17 00:00:00 2001 From: Mattia Date: Wed, 21 Oct 2020 18:07:15 +0200 Subject: [PATCH] initial commit --- JAPL/interpreter.py | 352 ++++++++++++++++++++++++++++++++++++++ JAPL/lexer.py | 212 +++++++++++++++++++++++ JAPL/meta/classtype.py | 7 + JAPL/meta/environment.py | 71 ++++++++ JAPL/meta/exceptions.py | 32 ++++ JAPL/meta/expression.py | 167 ++++++++++++++++++ JAPL/meta/functiontype.py | 8 + JAPL/meta/looptype.py | 7 + JAPL/meta/statement.py | 173 +++++++++++++++++++ JAPL/meta/tokenobject.py | 13 ++ JAPL/meta/tokentype.py | 55 ++++++ 11 files changed, 1097 insertions(+) create mode 100644 JAPL/interpreter.py create mode 100644 JAPL/lexer.py create mode 100644 JAPL/meta/classtype.py create mode 100644 JAPL/meta/environment.py create mode 100644 JAPL/meta/exceptions.py create mode 100644 JAPL/meta/expression.py create mode 100644 JAPL/meta/functiontype.py create mode 100644 JAPL/meta/looptype.py create mode 100644 JAPL/meta/statement.py create mode 100644 JAPL/meta/tokenobject.py create mode 100644 JAPL/meta/tokentype.py diff --git a/JAPL/interpreter.py b/JAPL/interpreter.py new file mode 100644 index 0000000..4c4a5ea --- /dev/null +++ b/JAPL/interpreter.py @@ -0,0 +1,352 @@ +import operator +from typing import List +from .types.callable import Callable +from .types.japlclass import JAPLClass +from .types.instance import JAPLInstance +from .meta.environment import Environment +from .meta.tokentype import TokenType +from .meta.exceptions import JAPLError, BreakException, ReturnException +from .types.native import Clock, Type, JAPLFunction, Truthy, Stringify, PrintFunction, IsInstance, IsSubclass, IsSuperclass +from .meta.expression import Expression, Variable, Literal, Logical, Binary, Unary, Grouping, Assignment, Call, Get, Set +from .meta.statement import Statement, StatementExpr, If, While, Del, Break, Return, Var, Block, Function, Class + + +class Interpreter(Expression.Visitor, Statement.Visitor): + """ + An interpreter for the JAPL + programming language + """ + + OPS = {TokenType.MINUS: operator.sub, TokenType.PLUS: operator.add, TokenType.SLASH: operator.truediv, + TokenType.STAR: operator.mul, TokenType.DEQ: operator.eq, TokenType.GT: operator.gt, + TokenType.GE: operator.ge, TokenType.LT: operator.lt, TokenType.LE: operator.le, TokenType.EQ: operator.eq, + TokenType.NE: operator.ne, TokenType.MOD: operator.mod, TokenType.POW: operator.pow} + + def __init__(self): + """Object constructor""" + + self.environment = Environment() + self.locals = {} + self.globals = self.environment + self.globals.define("clock", Clock()) + self.globals.define("type", Type()) + self.globals.define("truthy", Truthy()) + self.globals.define("stringify", Stringify()) + self.globals.define("print", PrintFunction()) + self.globals.define("isinstance", IsInstance()) + self.globals.define("issuperclass", IsSuperclass()) + self.globals.define("issubclass", IsSubclass()) + + def number_operand(self, op, operand): + """ + An helper method to check if the operand + to a unary operator is a number + """ + + if isinstance(operand, (int, float)): + return + raise JAPLError(op, + f"Unsupported unary operator '{op.lexeme}' for object of type '{type(operand).__name__}'") + + def compatible_operands(self, op, left, right): + """ + Helper method to check types when doing binary + operations + """ + + if op.kind == TokenType.SLASH and right == 0: + raise JAPLError(op, "Cannot divide by 0") + elif isinstance(left, (bool, type(None))) or isinstance(right, (bool, type(None))): + if op.kind not in (TokenType.DEQ, TokenType.NE): + raise JAPLError(op, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'") + return + elif isinstance(left, (int, float)) and isinstance(right, (int, float)): + return + elif op.kind in (TokenType.PLUS, TokenType.STAR, TokenType.DEQ, TokenType.NE): + if isinstance(left, str) and isinstance(right, str): + return + elif isinstance(left, str) and isinstance(right, int): + return + elif isinstance(left, int) and isinstance(right, str): + return + raise JAPLError(operator, f"Unsupported binary operator '{op.lexeme}' for objects of type '{type(left).__name__}' and '{type(right).__name__}'") + + def visit_literal(self, expr: Literal): + """ + Visits a Literal node in the Abstract Syntax Tree, + returning its value to the visitor + """ + + return expr.value + + def visit_logical(self, expr: Logical): + """Visits a logical node""" + + left = self.eval(expr.left) + if expr.operator.kind == TokenType.OR: + if bool(left): + return left + elif not bool(left): + return self.eval(expr.right) + return self.eval(expr.right) + + def eval(self, expr: Expression): + """ + Evaluates an expression by calling its accept() + method and passing self to it. This mechanism is known + as the 'Visitor Pattern': the expression object will + later call the interpreter's appropriate method to + evaluate itself + """ + + return expr.accept(self) + + def visit_grouping(self, grouping: Grouping): + """ + Visits a Grouping node in the Abstract Syntax Tree, + recursively evaluating its subexpressions + """ + + return self.eval(grouping.expr) + + def visit_unary(self, expr: Unary): + """ + Visits a Unary node in the Abstract Syntax Teee, + returning the negation of the given object, if + the operation is supported + """ + + right = self.eval(expr.right) + self.number_operand(expr.operator, right) + if expr.operator.kind == TokenType.NEG: + return not right + return -right + + def visit_binary(self, expr: Binary): + """ + Visits a Binary node in the Abstract Syntax Tree, + recursively evaulating both operands first and then + performing the operation specified by the operator + """ + + left = self.eval(expr.left) + right = self.eval(expr.right) + self.compatible_operands(expr.operator, left, right) + return self.OPS[expr.operator.kind](left, right) + + def visit_statement_expr(self, stmt: StatementExpr): + """ + Visits an expression statement and evaluates it + """ + + self.eval(stmt.expression) + + def visit_if(self, statement: If): + """ + Visits an If node and evaluates it + """ + + if self.eval(statement.condition): + self.exec(statement.then_branch) + elif statement.else_branch: + self.exec(statement.else_branch) + + def visit_class(self, stmt: Class): + """Visits a class declaration""" + + superclass = None + if stmt.superclass: + superclass = self.eval(stmt.superclass) + if not isinstance(superclass, JAPLClass): + raise JAPLError(stmt.superclass.name, "Superclass must be a class") + self.environment.define(stmt.name.lexeme, None) + if superclass: + environment = Environment(self.environment) + environment.define("super", superclass) + else: + environment = self.environment + methods = {} + for method in stmt.methods: + func = JAPLFunction(method, environment) + methods[method.name.lexeme] = func + klass = JAPLClass(stmt.name.lexeme, methods, superclass) + if superclass: + self.environment = environment.enclosing + self.environment.assign(stmt.name, klass) + + def visit_while(self, statement: While): + """ + Visits a while node and executes it + """ + + while self.eval(statement.condition): + try: + self.exec(statement.body) + except BreakException: + break + + def visit_var_stmt(self, stmt: Var): + """ + Visits a var statement + """ + + val = None + if stmt.init: + val = self.eval(stmt.init) + self.environment.define(stmt.name.lexeme, val) + + def lookup(self, name, expr: Expression): + """ + Performs name lookups in the closest scope + """ + + distance = self.locals.get(expr) + if distance is not None: + return self.environment.get_at(distance, name.lexeme) + else: + return self.globals.get(name) + + def visit_var_expr(self, expr: Variable): + """ + Visits a var expression + """ + + return self.lookup(expr.name, expr) + + def visit_del(self, stmt: Del): + """ + Visits a del expression + """ + + return self.environment.delete(stmt.name) + + def visit_assign(self, stmt: Assignment): + """ + Visits an assignment expression + """ + + right = self.eval(stmt.value) + distance = self.locals.get(stmt) + if distance is not None: + self.environment.assign_at(distance, stmt.name, right) + else: + self.globals.assign(stmt.name, right) + return right + + def visit_block(self, stmt: Block): + """ + Visits a new scope block + """ + + return self.execute_block(stmt.statements, Environment(self.environment)) + + def visit_break(self, stmt: Break): + """ + Visits a break statement + """ + + raise BreakException() + + def visit_call_expr(self, expr: Call): + """ + Visits a call expression + """ + + callee = self.eval(expr.callee) + if not isinstance(callee, Callable): + raise JAPLError(expr.paren, f"'{type(callee).__name__}' is not callable") + arguments = [] + for argument in expr.arguments: + arguments.append(self.eval(argument)) + function = callee + if function.arity != len(arguments): + raise JAPLError(expr.paren, f"Expecting {function.arity} arguments, got {len(arguments)}") + return function.call(self, arguments) + + def execute_block(self, statements: List[Statement], scope: Environment): + """ + Executes a block of statements + """ + + prev = self.environment + try: + self.environment = scope + for statement in statements: + self.exec(statement) + finally: + self.environment = prev + + def visit_return(self, statement: Return): + """ + Visits a return statement + """ + + value = None + if statement.value: + value = self.eval(statement.value) + raise ReturnException(value) + + def visit_function(self, statement: Function): + """ + Visits a function + """ + + function = JAPLFunction(statement, self.environment) + self.environment.define(statement.name.lexeme, function) + + def visit_get(self, expr: Get): + """Visits property get expressions and evaluates them""" + + obj = self.eval(expr.object) + if isinstance(obj, JAPLInstance): + return obj.get(expr.name) + raise JAPLError(expr.name, "Only instances have properties") + + def visit_set(self, expr: Set): + """Visits property set expressions and evaluates them""" + + obj = self.eval(expr.object) + if not isinstance(obj, JAPLInstance): + raise JAPLError(expr, "Only instances have fields") + value = self.eval(expr.value) + obj.set(expr.name, value) + + def visit_this(self, expr): + """Evaluates 'this' expressions""" + + return self.lookup(expr.keyword, expr) + + def visit_super(self, expr): + """Evaluates 'super' expressions""" + + distance = self.locals.get(expr) + superclass = self.environment.get_at(distance, "super") + instance = self.environment.get_at(distance - 1, "this") + meth = superclass.get_method(expr.method.lexeme) + if not meth: + raise JAPLError(expr.method, f"Undefined property '{expr.method.lexeme}'") + return meth.bind(instance) + + def exec(self, statement: Statement): + """ + Executes a statement + """ + + statement.accept(self) + + def interpret(self, statements: List[Statement]): + """ + Executes a JAPL program + """ + + for statement in statements: + self.exec(statement) + + def resolve(self, expr: Expression, depth: int): + """ + Stores the result of the name resolution: this + info will be used later to know exactly in which + environment to look up a given variable + """ + + self.locals[expr] = depth # How many environments to skip! diff --git a/JAPL/lexer.py b/JAPL/lexer.py new file mode 100644 index 0000000..ac24f1a --- /dev/null +++ b/JAPL/lexer.py @@ -0,0 +1,212 @@ +from .meta.tokenobject import Token +from .meta.tokentype import TokenType +from .meta.exceptions import ParseError +from typing import List + + +class Lexer(object): + """ + A simple tokenizer for the JAPL programming + language, scans a input source file and + produces a list of tokens. Some errors + are caught here as well. + """ + + TOKENS = {"(": TokenType.LP, ")": TokenType.RP, + "{": TokenType.LB, "}": TokenType.RB, + ".": TokenType.DOT, ",": TokenType.COMMA, + "-": TokenType.MINUS, "+": TokenType.PLUS, + ";": TokenType.SEMICOLON, "*": TokenType.STAR, + ">": TokenType.GT, "<": TokenType.LT, + "=": TokenType.EQ, "!": TokenType.NEG, + "/": TokenType.SLASH, "%": TokenType.MOD} + + RESERVED = {"or": TokenType.OR, "and": TokenType.AND, + "class": TokenType.CLASS, "fun": TokenType.FUN, + "if": TokenType.IF, "else": TokenType.ELSE, + "for": TokenType.FOR, "while": TokenType.WHILE, + "var": TokenType.VAR, "nil": TokenType.NIL, + "true": TokenType.TRUE, "false": TokenType.FALSE, + "return": TokenType.RETURN, + "this": TokenType.THIS, "super": TokenType.SUPER, + "del": TokenType.DEL, "break": TokenType.BREAK} + + def __init__(self, source: str): + """Object constructor""" + + self.source = source + self.tokens: List[Token] = [] + self.line: int = 1 # Points to the line being lexed + self.start: int = 0 # The position of the first character of the current lexeme + self.current: int = 0 # The position of the current character being lexed + + def step(self) -> str: + """ + 'Steps' one character in the source code and returns it + """ + + if self.done(): + return "" + self.current += 1 + return self.source[self.current - 1] + + def peek(self) -> str: + """ + Returns the current character without consuming it + or an empty string if all text has been consumed + """ + + if self.done(): + return "" + return self.source[self.current] + + def peek_next(self) -> str: + """ + Returns the next character after self.current + or an empty string if the input has been consumed + """ + + if self.current + 1 >= len(self.source): + return "" + return self.source[self.current + 1] + + def string(self, delimiter: str): + """Parses a string literal""" + + while self.peek() != delimiter and not self.done(): + if self.peek() == "\n": + self.line += 1 + self.step() + if self.done(): + raise ParseError(f"unterminated string literal at line {self.line}") + self.step() # Consume the '"' + value = self.source[self.start + 1:self.current - 1] # Get the actual string + self.tokens.append(self.create_token(TokenType.STR, value)) + + def number(self): + """Parses a number literal""" + + convert = int + while self.peek().isdigit(): + self.step() + if self.peek() == ".": + self.step() # Consume the "." + while self.peek().isdigit(): + self.step() + convert = float + self.tokens.append(self.create_token(TokenType.NUM, + convert(self.source[self.start:self.current]))) + + def identifier(self): + """Parses identifiers and reserved keywords""" + + while self.peek().isalnum() or self.is_identifier(self.peek()): + self.step() + kind = TokenType.ID + value = self.source[self.start:self.current] + if self.RESERVED.get(value, None): + kind = self.RESERVED[value] + self.tokens.append(self.create_token(kind)) + + def comment(self): + """Handles multi-line comments""" + + closed = False + while not self.done(): + end = self.peek() + self.peek_next() + if end == "/*": # Nested comments + self.step() + self.step() + self.comment() + elif end == "*/": + closed = True + self.step() # Consume the two ends + self.step() + break + self.step() + if self.done() and not closed: + raise ParseError(f"Unexpected EOF at line {self.line}") + + def match(self, char: str) -> bool: + """ + Returns True if the current character in self.source matches + the given character + """ + + if self.done(): + return False + elif self.source[self.current] != char: + return False + self.current += 1 + return True + + def done(self) -> bool: + """ + Helper method that's used by the lexer + to know if all source has been consumed + """ + + return self.current >= len(self.source) + + def create_token(self, kind: TokenType, literal: object = None) -> Token: + """ + Creates and returns a token object + """ + + return Token(kind, self.source[self.start:self.current], literal, self.line) + + def is_identifier(self, char: str): + """Returns if a character can be an identifier""" + + if char.isalpha() or char in ("_", ): # More coming soon + return True + + def scan_token(self): + """ + Scans for a single token and adds it to + self.tokens + """ + + char = self.step() + if char in (" ", "\t", "\r"): # Useless characters + return + elif char == "\n": # New line + self.line += 1 + elif char in ("'", '"'): # A string literal + self.string(char) + elif char.isdigit(): + self.number() + elif self.is_identifier(char): # Identifier or reserved keyword + self.identifier() + elif char in self.TOKENS: + if char == "/" and self.match("/"): + while self.peek() != "\n" and not self.done(): + self.step() # Who cares about comments? + elif char == "/" and self.match("*"): + self.comment() + elif char == "=" and self.match("="): + self.tokens.append(self.create_token(TokenType.DEQ)) + elif char == ">" and self.match("="): + self.tokens.append(self.create_token(TokenType.GE)) + elif char == "<" and self.match("="): + self.tokens.append(self.create_token(TokenType.LE)) + elif char == "!" and self.match("="): + self.tokens.append(self.create_token(TokenType.NE)) + elif char == "*" and self.match("*"): + self.tokens.append(self.create_token(TokenType.POW)) + else: + self.tokens.append(self.create_token(self.TOKENS[char])) + else: + raise ParseError(f"unexpected character '{char}' at line {self.line}") + + def lex(self) -> List[Token]: + """ + Performs lexical analysis on self.source + and returns a list of tokens + """ + + while not self.done(): + self.start = self.current + self.scan_token() + self.tokens.append(Token(TokenType.EOF, "", None, self.line)) + return self.tokens diff --git a/JAPL/meta/classtype.py b/JAPL/meta/classtype.py new file mode 100644 index 0000000..c257e05 --- /dev/null +++ b/JAPL/meta/classtype.py @@ -0,0 +1,7 @@ +from enum import Enum, auto + + +class ClassType(Enum): + + NONE = auto() + CLASS = auto() diff --git a/JAPL/meta/environment.py b/JAPL/meta/environment.py new file mode 100644 index 0000000..02f37e5 --- /dev/null +++ b/JAPL/meta/environment.py @@ -0,0 +1,71 @@ +from .exceptions import JAPLError +from .tokenobject import Token +from .expression import Variable + + +class Environment(object): + """ + A wrapper around a hashmap representing + a scope + """ + + def __init__(self, enclosing=None): + """Object constructor""" + + self.map = {} + self.enclosing = enclosing + + def define(self, name: str, attr: object): + """Defines a new variable in the scope""" + + self.map[name] = attr + + def get(self, name: Token): + """Gets a variable""" + + if name.lexeme in self.map: + return self.map[name.lexeme] + elif self.enclosing: + return self.enclosing.get(name) + raise JAPLError(name, f"Undefined name '{name.lexeme}'") + + def get_at(self, distance, name): + """Gets a variable in a specific scope""" + + return self.ancestor(distance).map.get(name) + + def ancestor(self, distance): + """Finds the scope specified by distance""" + + env = self + for _ in range(distance): + env = env.enclosing + return env + + def assign_at(self, distance, name, value): + """Same as get_at, but assigns instead of retrieving""" + + self.ancestor(distance).map[name.lexeme] = value + + def delete(self, var): + """Deletes a variable""" + + if var.name.lexeme in self.map: + del self.map[var.name.lexeme] + elif self.enclosing: + self.enclosing.delete(var) + else: + raise JAPLError(var.name, f"Undefined name '{var.name.lexeme}'") + + def assign(self, name: Token, value: object): + """Assigns a variable""" + + if name.lexeme in self.map: + if isinstance(value, Variable): + self.map[name.lexeme] = self.get(value.name) + else: + self.map[name.lexeme] = value + elif self.enclosing: + self.enclosing.assign(name, value) + else: + raise JAPLError(name, f"Undefined name '{name.lexeme}'") diff --git a/JAPL/meta/exceptions.py b/JAPL/meta/exceptions.py new file mode 100644 index 0000000..c67ecfc --- /dev/null +++ b/JAPL/meta/exceptions.py @@ -0,0 +1,32 @@ +from .tokentype import TokenType + + +class JAPLError(BaseException): + """JAPL's exceptions base class""" + + def __repr__(self): + return self.args[1] + + +class ParseError(JAPLError): + """An error occurred while parsing""" + + def __repr__(self): + if len(self.args) > 1: + message, token = self.args + if token.kind == TokenType.EOF: + return f"Unexpected error while parsing at line {token.line}, at end: {message}" + else: + return f"Unexpected error while parsing at line {token.line} at '{token.lexeme}': {message}" + return self.args[0] + + def __str__(self): + return self.__repr__() + + +class BreakException(JAPLError): + """Notifies a loop that it's time to break""" + + +class ReturnException(JAPLError): + """Notifies a function that it's time to return""" diff --git a/JAPL/meta/expression.py b/JAPL/meta/expression.py new file mode 100644 index 0000000..e97312c --- /dev/null +++ b/JAPL/meta/expression.py @@ -0,0 +1,167 @@ +from dataclasses import dataclass +from abc import ABC, abstractmethod +from .tokenobject import Token +from typing import List + + +class Expression(object): + """ + An object representing a JAPL expression. + This class is not meant to be instantiated directly, + inherit from it instead! + """ + + def accept(self, visitor): + raise NotImplementedError + + class Visitor(ABC): + """ + Visitor abstract base class to implement + the Visitor pattern + """ + + @abstractmethod + def visit_literal(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_binary(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_grouping(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_unary(self, visitor): + raise NotImplementedError + + @staticmethod + def visit_get(self, visitor): + raise NotImplementedError + + @staticmethod + def visit_set(self, visitor): + raise NotImplementedError + + +@dataclass +class Binary(Expression): + left: Expression + operator: Token + right: Expression + + def accept(self, visitor): + return visitor.visit_binary(self) + + +@dataclass +class Unary(Expression): + operator: Token + right: Expression + + def accept(self, visitor): + return visitor.visit_unary(self) + + +@dataclass +class Literal(Expression): + value: object + + def accept(self, visitor): + return visitor.visit_literal(self) + + +@dataclass +class Grouping(Expression): + expr: Expression + + def accept(self, visitor): + return visitor.visit_grouping(self) + + +@dataclass +class Variable(Expression): + name: Token + + def accept(self, visitor): + return visitor.visit_var_expr(self) + + def __hash__(self): + return super().__hash__() + + +@dataclass +class Assignment(Expression): + name: Token + value: Expression + + def accept(self, visitor): + return visitor.visit_assign(self) + + def __hash__(self): + return super().__hash__() + + +@dataclass +class Logical(Expression): + left: Expression + operator: Token + right: Expression + + def accept(self, visitor): + return visitor.visit_logical(self) + + +@dataclass +class Call(Expression): + callee: Expression + paren: Token + arguments: List[Expression] = () + + def accept(self, visitor): + return visitor.visit_call_expr(self) + + +@dataclass +class Get(Expression): + object: Expression + name: Token + + def accept(self, visitor): + return visitor.visit_get(self) + + +@dataclass +class Set(Expression): + object: Expression + name: Token + value: Expression + + def accept(self, visitor): + return visitor.visit_set(self) + + +@dataclass +class This(Expression): + keyword: Token + + def accept(self, visitor): + return visitor.visit_this(self) + + def __hash__(self): + return super().__hash__() + + +@dataclass +class Super(Expression): + keyword: Token + method: Token + + def accept(self, visitor): + return visitor.visit_super(self) + + def __hash__(self): + return super().__hash__() + + diff --git a/JAPL/meta/functiontype.py b/JAPL/meta/functiontype.py new file mode 100644 index 0000000..ed60a00 --- /dev/null +++ b/JAPL/meta/functiontype.py @@ -0,0 +1,8 @@ +from enum import Enum, auto + + +class FunctionType(Enum): + NONE = auto() + FUNCTION = auto() + METHOD = auto() + INIT = auto() diff --git a/JAPL/meta/looptype.py b/JAPL/meta/looptype.py new file mode 100644 index 0000000..a946143 --- /dev/null +++ b/JAPL/meta/looptype.py @@ -0,0 +1,7 @@ +from enum import Enum, auto + + +class LoopType(Enum): + + NONE = auto() + WHILE = auto() diff --git a/JAPL/meta/statement.py b/JAPL/meta/statement.py new file mode 100644 index 0000000..0a811c0 --- /dev/null +++ b/JAPL/meta/statement.py @@ -0,0 +1,173 @@ +from abc import ABC, abstractmethod +from dataclasses import dataclass +from .expression import Expression, Variable +from .tokenobject import Token +from typing import List, Any + + +class Statement(object): + """ + A Base Class representing JAPL statements + """ + + def accept(self, visitor): + raise NotImplementedError + + class Visitor(ABC): + """Wrapper to implement the Visitor Pattern""" + + + @abstractmethod + def visit_statement_expr(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_var_stmt(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_del(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_block(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_if(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_while(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_break(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_function(self, visitor): + raise NotImplementedError + + @abstractmethod + def visit_return(self, visitor): + raise NotImplementedError + + @staticmethod + def visit_class(self, visitor): + raise NotImplementedError + + +@dataclass +class StatementExpr(Statement): + """ + An expression statement + """ + + expression: Expression + + def accept(self, visitor): + visitor.visit_statement_expr(self) + + +@dataclass +class Var(Statement): + """ + A var statement + """ + + name: Token + init: Expression = None + + def accept(self, visitor): + visitor.visit_var_stmt(self) + + +@dataclass +class Del(Statement): + """ + A del statement + """ + + name: Any + + def accept(self, visitor): + visitor.visit_del(self) + + +@dataclass +class Block(Statement): + """A block statement""" + + statements: List[Statement] + + def accept(self, visitor): + visitor.visit_block(self) + + +@dataclass +class If(Statement): + """An if statement""" + + condition: Expression + then_branch: Statement + else_branch: Statement + + def accept(self, visitor): + visitor.visit_if(self) + + +@dataclass +class While(Statement): + """A while statement""" + + condition: Expression + body: Statement + + def accept(self, visitor): + visitor.visit_while(self) + + +@dataclass +class Break(Statement): + """A break statement""" + + token: Token + + def accept(self, visitor): + visitor.visit_break(self) + + +@dataclass +class Function(Statement): + """A function statement""" + + name: Token + params: List[Token] + body: List[Statement] + + def accept(self, visitor): + visitor.visit_function(self) + + +@dataclass +class Return(Statement, BaseException): + """A return statement""" + + keyword: Token + value: Expression + + def accept(self, visitor): + visitor.visit_return(self) + + +@dataclass +class Class(Statement): + """A class statement""" + + name: Token + methods: list + superclass: Variable + + def accept(self, visitor): + visitor.visit_class(self) diff --git a/JAPL/meta/tokenobject.py b/JAPL/meta/tokenobject.py new file mode 100644 index 0000000..998c5e2 --- /dev/null +++ b/JAPL/meta/tokenobject.py @@ -0,0 +1,13 @@ +from dataclasses import dataclass +from .tokentype import TokenType + +@dataclass +class Token(object): + """The representation of a JAPL token""" + + kind: TokenType + lexeme: str + literal: object + line: int + + diff --git a/JAPL/meta/tokentype.py b/JAPL/meta/tokentype.py new file mode 100644 index 0000000..bbc3178 --- /dev/null +++ b/JAPL/meta/tokentype.py @@ -0,0 +1,55 @@ +from enum import Enum, auto + +class TokenType(Enum): + """ + An enumeration for all JAPL types + """ + + LP = auto() + RP = auto() + LB = auto() + RB = auto() + COMMA = auto() + DOT = auto() + PLUS = auto() + MINUS = auto() + SLASH = auto() + SEMICOLON = auto() + STAR = auto() + + + NEG = auto() + NE = auto() + EQ = auto() + DEQ = auto() + GT = auto() + LT = auto() + GE = auto() + LE = auto() + MOD = auto() + POW = auto() + + ID = auto() + STR = auto() + NUM = auto() + + + AND = auto() + CLASS = auto() + ELSE = auto() + FOR = auto() + FUN = auto() + FALSE = auto() + IF = auto() + NIL = auto() + OR = auto() + RETURN = auto() + SUPER = auto() + THIS = auto() + TRUE = auto() + VAR = auto() + WHILE = auto() + DEL = auto() + BREAK = auto() + + EOF = auto()