initial commit

This commit is contained in:
Mattia 2020-10-21 18:07:15 +02:00 committed by GitHub
parent 0b7d0aae84
commit 1daae9c30a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 1097 additions and 0 deletions

352
JAPL/interpreter.py Normal file
View File

@ -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!

212
JAPL/lexer.py Normal file
View File

@ -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

7
JAPL/meta/classtype.py Normal file
View File

@ -0,0 +1,7 @@
from enum import Enum, auto
class ClassType(Enum):
NONE = auto()
CLASS = auto()

71
JAPL/meta/environment.py Normal file
View File

@ -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}'")

32
JAPL/meta/exceptions.py Normal file
View File

@ -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"""

167
JAPL/meta/expression.py Normal file
View File

@ -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__()

View File

@ -0,0 +1,8 @@
from enum import Enum, auto
class FunctionType(Enum):
NONE = auto()
FUNCTION = auto()
METHOD = auto()
INIT = auto()

7
JAPL/meta/looptype.py Normal file
View File

@ -0,0 +1,7 @@
from enum import Enum, auto
class LoopType(Enum):
NONE = auto()
WHILE = auto()

173
JAPL/meta/statement.py Normal file
View File

@ -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)

13
JAPL/meta/tokenobject.py Normal file
View File

@ -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

55
JAPL/meta/tokentype.py Normal file
View File

@ -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()