Make your own programming language in Python
unknown
python
4 years ago
63 kB
10
Indexable
#######################################
# IMPORTS
#######################################
from strings_with_arrows import *
import string
import os
import math
#######################################
# CONSTANTS
#######################################
DIGITS = '0123456789'
LETTERS = string.ascii_letters
LETTERS_DIGITS = LETTERS + DIGITS
#######################################
# ERRORS
#######################################
class Error:
def __init__(self, pos_start, pos_end, error_name, details):
self.pos_start = pos_start
self.pos_end = pos_end
self.error_name = error_name
self.details = details
def as_string(self):
result = f'{self.error_name}: {self.details}\n'
result += f'File {self.pos_start.fn}, line {self.pos_start.ln + 1}'
result += '\n\n' + string_with_arrows(self.pos_start.ftxt, self.pos_start, self.pos_end)
return result
class IllegalCharError(Error):
def __init__(self, pos_start, pos_end, details):
super().__init__(pos_start, pos_end, 'Illegal Character', details)
class ExpectedCharError(Error):
def __init__(self, pos_start, pos_end, details):
super().__init__(pos_start, pos_end, 'Expected Character', details)
class InvalidSyntaxError(Error):
def __init__(self, pos_start, pos_end, details=''):
super().__init__(pos_start, pos_end, 'Invalid Syntax', details)
class RTError(Error):
def __init__(self, pos_start, pos_end, details, context):
super().__init__(pos_start, pos_end, 'Runtime Error', details)
self.context = context
def as_string(self):
result = self.generate_traceback()
result += f'{self.error_name}: {self.details}'
result += '\n\n' + string_with_arrows(self.pos_start.ftxt, self.pos_start, self.pos_end)
return result
def generate_traceback(self):
result = ''
pos = self.pos_start
ctx = self.context
while ctx:
result = f' File {pos.fn}, line {str(pos.ln + 1)}, in {ctx.display_name}\n' + result
pos = ctx.parent_entry_pos
ctx = ctx.parent
return 'Traceback (most recent call last):\n' + result
#######################################
# POSITION
#######################################
class Position:
def __init__(self, idx, ln, col, fn, ftxt):
self.idx = idx
self.ln = ln
self.col = col
self.fn = fn
self.ftxt = ftxt
def advance(self, current_char=None):
self.idx += 1
self.col += 1
if current_char == '\n':
self.ln += 1
self.col = 0
return self
def copy(self):
return Position(self.idx, self.ln, self.col, self.fn, self.ftxt)
#######################################
# TOKENS
#######################################
TT_INT = 'INT'
TT_FLOAT = 'FLOAT'
TT_STRING = 'STRING'
TT_IDENTIFIER = 'IDENTIFIER'
TT_KEYWORD = 'KEYWORD'
TT_PLUS = 'PLUS'
TT_MINUS = 'MINUS'
TT_MUL = 'MUL'
TT_DIV = 'DIV'
TT_POW = 'POW'
TT_EQ = 'EQ'
TT_LPAREN = 'LPAREN'
TT_RPAREN = 'RPAREN'
TT_LSQUARE = 'LSQUARE'
TT_RSQUARE = 'RSQUARE'
TT_EE = 'EE'
TT_NE = 'NE'
TT_LT = 'LT'
TT_GT = 'GT'
TT_LTE = 'LTE'
TT_GTE = 'GTE'
TT_COMMA = 'COMMA'
TT_ARROW = 'ARROW'
TT_NEWLINE = 'NEWLINE'
TT_EOF = 'EOF'
KEYWORDS = [
'VAR',
'AND',
'OR',
'NOT',
'IF',
'ELIF',
'ELSE',
'FOR',
'TO',
'STEP',
'WHILE',
'FUN',
'THEN',
'END',
'RETURN',
'CONTINUE',
'BREAK',
]
class Token:
def __init__(self, type_, value=None, pos_start=None, pos_end=None):
self.type = type_
self.value = value
if pos_start:
self.pos_start = pos_start.copy()
self.pos_end = pos_start.copy()
self.pos_end.advance()
if pos_end:
self.pos_end = pos_end.copy()
def matches(self, type_, value):
return self.type == type_ and self.value == value
def __repr__(self):
if self.value: return f'{self.type}:{self.value}'
return f'{self.type}'
#######################################
# LEXER
#######################################
class Lexer:
def __init__(self, fn, text):
self.fn = fn
self.text = text
self.pos = Position(-1, 0, -1, fn, text)
self.current_char = None
self.advance()
def advance(self):
self.pos.advance(self.current_char)
self.current_char = self.text[self.pos.idx] if self.pos.idx < len(self.text) else None
def make_tokens(self):
tokens = []
while self.current_char != None:
if self.current_char in ' \t':
self.advance()
elif self.current_char == '#':
self.skip_comment()
elif self.current_char in ';\n':
tokens.append(Token(TT_NEWLINE, pos_start=self.pos))
self.advance()
elif self.current_char in DIGITS:
tokens.append(self.make_number())
elif self.current_char in LETTERS:
tokens.append(self.make_identifier())
elif self.current_char == '"':
tokens.append(self.make_string())
elif self.current_char == '+':
tokens.append(Token(TT_PLUS, pos_start=self.pos))
self.advance()
elif self.current_char == '-':
tokens.append(self.make_minus_or_arrow())
elif self.current_char == '*':
tokens.append(Token(TT_MUL, pos_start=self.pos))
self.advance()
elif self.current_char == '/':
tokens.append(Token(TT_DIV, pos_start=self.pos))
self.advance()
elif self.current_char == '^':
tokens.append(Token(TT_POW, pos_start=self.pos))
self.advance()
elif self.current_char == '(':
tokens.append(Token(TT_LPAREN, pos_start=self.pos))
self.advance()
elif self.current_char == ')':
tokens.append(Token(TT_RPAREN, pos_start=self.pos))
self.advance()
elif self.current_char == '[':
tokens.append(Token(TT_LSQUARE, pos_start=self.pos))
self.advance()
elif self.current_char == ']':
tokens.append(Token(TT_RSQUARE, pos_start=self.pos))
self.advance()
elif self.current_char == '!':
token, error = self.make_not_equals()
if error: return [], error
tokens.append(token)
elif self.current_char == '=':
tokens.append(self.make_equals())
elif self.current_char == '<':
tokens.append(self.make_less_than())
elif self.current_char == '>':
tokens.append(self.make_greater_than())
elif self.current_char == ',':
tokens.append(Token(TT_COMMA, pos_start=self.pos))
self.advance()
else:
pos_start = self.pos.copy()
char = self.current_char
self.advance()
return [], IllegalCharError(pos_start, self.pos, "'" + char + "'")
tokens.append(Token(TT_EOF, pos_start=self.pos))
return tokens, None
def make_number(self):
num_str = ''
dot_count = 0
pos_start = self.pos.copy()
while self.current_char != None and self.current_char in DIGITS + '.':
if self.current_char == '.':
if dot_count == 1: break
dot_count += 1
num_str += self.current_char
self.advance()
if dot_count == 0:
return Token(TT_INT, int(num_str), pos_start, self.pos)
else:
return Token(TT_FLOAT, float(num_str), pos_start, self.pos)
def make_string(self):
string = ''
pos_start = self.pos.copy()
escape_character = False
self.advance()
escape_characters = {
'n': '\n',
't': '\t'
}
while self.current_char != None and (self.current_char != '"' or escape_character):
if escape_character:
string += escape_characters.get(self.current_char, self.current_char)
else:
if self.current_char == '\\':
escape_character = True
else:
string += self.current_char
self.advance()
escape_character = False
self.advance()
return Token(TT_STRING, string, pos_start, self.pos)
def make_identifier(self):
id_str = ''
pos_start = self.pos.copy()
while self.current_char != None and self.current_char in LETTERS_DIGITS + '_':
id_str += self.current_char
self.advance()
tok_type = TT_KEYWORD if id_str in KEYWORDS else TT_IDENTIFIER
return Token(tok_type, id_str, pos_start, self.pos)
def make_minus_or_arrow(self):
tok_type = TT_MINUS
pos_start = self.pos.copy()
self.advance()
if self.current_char == '>':
self.advance()
tok_type = TT_ARROW
return Token(tok_type, pos_start=pos_start, pos_end=self.pos)
def make_not_equals(self):
pos_start = self.pos.copy()
self.advance()
if self.current_char == '=':
self.advance()
return Token(TT_NE, pos_start=pos_start, pos_end=self.pos), None
self.advance()
return None, ExpectedCharError(pos_start, self.pos, "'=' (after '!')")
def make_equals(self):
tok_type = TT_EQ
pos_start = self.pos.copy()
self.advance()
if self.current_char == '=':
self.advance()
tok_type = TT_EE
return Token(tok_type, pos_start=pos_start, pos_end=self.pos)
def make_less_than(self):
tok_type = TT_LT
pos_start = self.pos.copy()
self.advance()
if self.current_char == '=':
self.advance()
tok_type = TT_LTE
return Token(tok_type, pos_start=pos_start, pos_end=self.pos)
def make_greater_than(self):
tok_type = TT_GT
pos_start = self.pos.copy()
self.advance()
if self.current_char == '=':
self.advance()
tok_type = TT_GTE
return Token(tok_type, pos_start=pos_start, pos_end=self.pos)
def skip_comment(self):
self.advance()
while self.current_char != '\n':
self.advance()
self.advance()
#######################################
# NODES
#######################################
class NumberNode:
def __init__(self, tok):
self.tok = tok
self.pos_start = self.tok.pos_start
self.pos_end = self.tok.pos_end
def __repr__(self):
return f'{self.tok}'
class StringNode:
def __init__(self, tok):
self.tok = tok
self.pos_start = self.tok.pos_start
self.pos_end = self.tok.pos_end
def __repr__(self):
return f'{self.tok}'
class ListNode:
def __init__(self, element_nodes, pos_start, pos_end):
self.element_nodes = element_nodes
self.pos_start = pos_start
self.pos_end = pos_end
class VarAccessNode:
def __init__(self, var_name_tok):
self.var_name_tok = var_name_tok
self.pos_start = self.var_name_tok.pos_start
self.pos_end = self.var_name_tok.pos_end
class VarAssignNode:
def __init__(self, var_name_tok, value_node):
self.var_name_tok = var_name_tok
self.value_node = value_node
self.pos_start = self.var_name_tok.pos_start
self.pos_end = self.value_node.pos_end
class BinOpNode:
def __init__(self, left_node, op_tok, right_node):
self.left_node = left_node
self.op_tok = op_tok
self.right_node = right_node
self.pos_start = self.left_node.pos_start
self.pos_end = self.right_node.pos_end
def __repr__(self):
return f'({self.left_node}, {self.op_tok}, {self.right_node})'
class UnaryOpNode:
def __init__(self, op_tok, node):
self.op_tok = op_tok
self.node = node
self.pos_start = self.op_tok.pos_start
self.pos_end = node.pos_end
def __repr__(self):
return f'({self.op_tok}, {self.node})'
class IfNode:
def __init__(self, cases, else_case):
self.cases = cases
self.else_case = else_case
self.pos_start = self.cases[0][0].pos_start
self.pos_end = (self.else_case or self.cases[len(self.cases) - 1])[0].pos_end
class ForNode:
def __init__(self, var_name_tok, start_value_node, end_value_node, step_value_node, body_node, should_return_null):
self.var_name_tok = var_name_tok
self.start_value_node = start_value_node
self.end_value_node = end_value_node
self.step_value_node = step_value_node
self.body_node = body_node
self.should_return_null = should_return_null
self.pos_start = self.var_name_tok.pos_start
self.pos_end = self.body_node.pos_end
class WhileNode:
def __init__(self, condition_node, body_node, should_return_null):
self.condition_node = condition_node
self.body_node = body_node
self.should_return_null = should_return_null
self.pos_start = self.condition_node.pos_start
self.pos_end = self.body_node.pos_end
class FuncDefNode:
def __init__(self, var_name_tok, arg_name_toks, body_node, should_auto_return):
self.var_name_tok = var_name_tok
self.arg_name_toks = arg_name_toks
self.body_node = body_node
self.should_auto_return = should_auto_return
if self.var_name_tok:
self.pos_start = self.var_name_tok.pos_start
elif len(self.arg_name_toks) > 0:
self.pos_start = self.arg_name_toks[0].pos_start
else:
self.pos_start = self.body_node.pos_start
self.pos_end = self.body_node.pos_end
class CallNode:
def __init__(self, node_to_call, arg_nodes):
self.node_to_call = node_to_call
self.arg_nodes = arg_nodes
self.pos_start = self.node_to_call.pos_start
if len(self.arg_nodes) > 0:
self.pos_end = self.arg_nodes[len(self.arg_nodes) - 1].pos_end
else:
self.pos_end = self.node_to_call.pos_end
class ReturnNode:
def __init__(self, node_to_return, pos_start, pos_end):
self.node_to_return = node_to_return
self.pos_start = pos_start
self.pos_end = pos_end
class ContinueNode:
def __init__(self, pos_start, pos_end):
self.pos_start = pos_start
self.pos_end = pos_end
class BreakNode:
def __init__(self, pos_start, pos_end):
self.pos_start = pos_start
self.pos_end = pos_end
#######################################
# PARSE RESULT
#######################################
class ParseResult:
def __init__(self):
self.error = None
self.node = None
self.last_registered_advance_count = 0
self.advance_count = 0
self.to_reverse_count = 0
def register_advancement(self):
self.last_registered_advance_count = 1
self.advance_count += 1
def register(self, res):
self.last_registered_advance_count = res.advance_count
self.advance_count += res.advance_count
if res.error: self.error = res.error
return res.node
def try_register(self, res):
if res.error:
self.to_reverse_count = res.advance_count
return None
return self.register(res)
def success(self, node):
self.node = node
return self
def failure(self, error):
if not self.error or self.last_registered_advance_count == 0:
self.error = error
return self
#######################################
# PARSER
#######################################
class Parser:
def __init__(self, tokens):
self.tokens = tokens
self.tok_idx = -1
self.advance()
def advance(self):
self.tok_idx += 1
self.update_current_tok()
return self.current_tok
def reverse(self, amount=1):
self.tok_idx -= amount
self.update_current_tok()
return self.current_tok
def update_current_tok(self):
if self.tok_idx >= 0 and self.tok_idx < len(self.tokens):
self.current_tok = self.tokens[self.tok_idx]
def parse(self):
res = self.statements()
if not res.error and self.current_tok.type != TT_EOF:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Token cannot appear after previous tokens"
))
return res
###################################
def statements(self):
res = ParseResult()
statements = []
pos_start = self.current_tok.pos_start.copy()
while self.current_tok.type == TT_NEWLINE:
res.register_advancement()
self.advance()
statement = res.register(self.statement())
if res.error: return res
statements.append(statement)
more_statements = True
while True:
newline_count = 0
while self.current_tok.type == TT_NEWLINE:
res.register_advancement()
self.advance()
newline_count += 1
if newline_count == 0:
more_statements = False
if not more_statements: break
statement = res.try_register(self.statement())
if not statement:
self.reverse(res.to_reverse_count)
more_statements = False
continue
statements.append(statement)
return res.success(ListNode(
statements,
pos_start,
self.current_tok.pos_end.copy()
))
def statement(self):
res = ParseResult()
pos_start = self.current_tok.pos_start.copy()
if self.current_tok.matches(TT_KEYWORD, 'RETURN'):
res.register_advancement()
self.advance()
expr = res.try_register(self.expr())
if not expr:
self.reverse(res.to_reverse_count)
return res.success(ReturnNode(expr, pos_start, self.current_tok.pos_start.copy()))
if self.current_tok.matches(TT_KEYWORD, 'CONTINUE'):
res.register_advancement()
self.advance()
return res.success(ContinueNode(pos_start, self.current_tok.pos_start.copy()))
if self.current_tok.matches(TT_KEYWORD, 'BREAK'):
res.register_advancement()
self.advance()
return res.success(BreakNode(pos_start, self.current_tok.pos_start.copy()))
expr = res.register(self.expr())
if res.error:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected 'RETURN', 'CONTINUE', 'BREAK', 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'"
))
return res.success(expr)
def expr(self):
res = ParseResult()
if self.current_tok.matches(TT_KEYWORD, 'VAR'):
res.register_advancement()
self.advance()
if self.current_tok.type != TT_IDENTIFIER:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected identifier"
))
var_name = self.current_tok
res.register_advancement()
self.advance()
if self.current_tok.type != TT_EQ:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected '='"
))
res.register_advancement()
self.advance()
expr = res.register(self.expr())
if res.error: return res
return res.success(VarAssignNode(var_name, expr))
node = res.register(self.bin_op(self.comp_expr, ((TT_KEYWORD, 'AND'), (TT_KEYWORD, 'OR'))))
if res.error:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'"
))
return res.success(node)
def comp_expr(self):
res = ParseResult()
if self.current_tok.matches(TT_KEYWORD, 'NOT'):
op_tok = self.current_tok
res.register_advancement()
self.advance()
node = res.register(self.comp_expr())
if res.error: return res
return res.success(UnaryOpNode(op_tok, node))
node = res.register(self.bin_op(self.arith_expr, (TT_EE, TT_NE, TT_LT, TT_GT, TT_LTE, TT_GTE)))
if res.error:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected int, float, identifier, '+', '-', '(', '[', 'IF', 'FOR', 'WHILE', 'FUN' or 'NOT'"
))
return res.success(node)
def arith_expr(self):
return self.bin_op(self.term, (TT_PLUS, TT_MINUS))
def term(self):
return self.bin_op(self.factor, (TT_MUL, TT_DIV))
def factor(self):
res = ParseResult()
tok = self.current_tok
if tok.type in (TT_PLUS, TT_MINUS):
res.register_advancement()
self.advance()
factor = res.register(self.factor())
if res.error: return res
return res.success(UnaryOpNode(tok, factor))
return self.power()
def power(self):
return self.bin_op(self.call, (TT_POW, ), self.factor)
def call(self):
res = ParseResult()
atom = res.register(self.atom())
if res.error: return res
if self.current_tok.type == TT_LPAREN:
res.register_advancement()
self.advance()
arg_nodes = []
if self.current_tok.type == TT_RPAREN:
res.register_advancement()
self.advance()
else:
arg_nodes.append(res.register(self.expr()))
if res.error:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected ')', 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'"
))
while self.current_tok.type == TT_COMMA:
res.register_advancement()
self.advance()
arg_nodes.append(res.register(self.expr()))
if res.error: return res
if self.current_tok.type != TT_RPAREN:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected ',' or ')'"
))
res.register_advancement()
self.advance()
return res.success(CallNode(atom, arg_nodes))
return res.success(atom)
def atom(self):
res = ParseResult()
tok = self.current_tok
if tok.type in (TT_INT, TT_FLOAT):
res.register_advancement()
self.advance()
return res.success(NumberNode(tok))
elif tok.type == TT_STRING:
res.register_advancement()
self.advance()
return res.success(StringNode(tok))
elif tok.type == TT_IDENTIFIER:
res.register_advancement()
self.advance()
return res.success(VarAccessNode(tok))
elif tok.type == TT_LPAREN:
res.register_advancement()
self.advance()
expr = res.register(self.expr())
if res.error: return res
if self.current_tok.type == TT_RPAREN:
res.register_advancement()
self.advance()
return res.success(expr)
else:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected ')'"
))
elif tok.type == TT_LSQUARE:
list_expr = res.register(self.list_expr())
if res.error: return res
return res.success(list_expr)
elif tok.matches(TT_KEYWORD, 'IF'):
if_expr = res.register(self.if_expr())
if res.error: return res
return res.success(if_expr)
elif tok.matches(TT_KEYWORD, 'FOR'):
for_expr = res.register(self.for_expr())
if res.error: return res
return res.success(for_expr)
elif tok.matches(TT_KEYWORD, 'WHILE'):
while_expr = res.register(self.while_expr())
if res.error: return res
return res.success(while_expr)
elif tok.matches(TT_KEYWORD, 'FUN'):
func_def = res.register(self.func_def())
if res.error: return res
return res.success(func_def)
return res.failure(InvalidSyntaxError(
tok.pos_start, tok.pos_end,
"Expected int, float, identifier, '+', '-', '(', '[', IF', 'FOR', 'WHILE', 'FUN'"
))
def list_expr(self):
res = ParseResult()
element_nodes = []
pos_start = self.current_tok.pos_start.copy()
if self.current_tok.type != TT_LSQUARE:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected '['"
))
res.register_advancement()
self.advance()
if self.current_tok.type == TT_RSQUARE:
res.register_advancement()
self.advance()
else:
element_nodes.append(res.register(self.expr()))
if res.error:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected ']', 'VAR', 'IF', 'FOR', 'WHILE', 'FUN', int, float, identifier, '+', '-', '(', '[' or 'NOT'"
))
while self.current_tok.type == TT_COMMA:
res.register_advancement()
self.advance()
element_nodes.append(res.register(self.expr()))
if res.error: return res
if self.current_tok.type != TT_RSQUARE:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected ',' or ']'"
))
res.register_advancement()
self.advance()
return res.success(ListNode(
element_nodes,
pos_start,
self.current_tok.pos_end.copy()
))
def if_expr(self):
res = ParseResult()
all_cases = res.register(self.if_expr_cases('IF'))
if res.error: return res
cases, else_case = all_cases
return res.success(IfNode(cases, else_case))
def if_expr_b(self):
return self.if_expr_cases('ELIF')
def if_expr_c(self):
res = ParseResult()
else_case = None
if self.current_tok.matches(TT_KEYWORD, 'ELSE'):
res.register_advancement()
self.advance()
if self.current_tok.type == TT_NEWLINE:
res.register_advancement()
self.advance()
statements = res.register(self.statements())
if res.error: return res
else_case = (statements, True)
if self.current_tok.matches(TT_KEYWORD, 'END'):
res.register_advancement()
self.advance()
else:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
"Expected 'END'"
))
else:
expr = res.register(self.statement())
if res.error: return res
else_case = (expr, False)
return res.success(else_case)
def if_expr_b_or_c(self):
res = ParseResult()
cases, else_case = [], None
if self.current_tok.matches(TT_KEYWORD, 'ELIF'):
all_cases = res.register(self.if_expr_b())
if res.error: return res
cases, else_case = all_cases
else:
else_case = res.register(self.if_expr_c())
if res.error: return res
return res.success((cases, else_case))
def if_expr_cases(self, case_keyword):
res = ParseResult()
cases = []
else_case = None
if not self.current_tok.matches(TT_KEYWORD, case_keyword):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected '{case_keyword}'"
))
res.register_advancement()
self.advance()
condition = res.register(self.expr())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'THEN'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'THEN'"
))
res.register_advancement()
self.advance()
if self.current_tok.type == TT_NEWLINE:
res.register_advancement()
self.advance()
statements = res.register(self.statements())
if res.error: return res
cases.append((condition, statements, True))
if self.current_tok.matches(TT_KEYWORD, 'END'):
res.register_advancement()
self.advance()
else:
all_cases = res.register(self.if_expr_b_or_c())
if res.error: return res
new_cases, else_case = all_cases
cases.extend(new_cases)
else:
expr = res.register(self.statement())
if res.error: return res
cases.append((condition, expr, False))
all_cases = res.register(self.if_expr_b_or_c())
if res.error: return res
new_cases, else_case = all_cases
cases.extend(new_cases)
return res.success((cases, else_case))
def for_expr(self):
res = ParseResult()
if not self.current_tok.matches(TT_KEYWORD, 'FOR'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'FOR'"
))
res.register_advancement()
self.advance()
if self.current_tok.type != TT_IDENTIFIER:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected identifier"
))
var_name = self.current_tok
res.register_advancement()
self.advance()
if self.current_tok.type != TT_EQ:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected '='"
))
res.register_advancement()
self.advance()
start_value = res.register(self.expr())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'TO'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'TO'"
))
res.register_advancement()
self.advance()
end_value = res.register(self.expr())
if res.error: return res
if self.current_tok.matches(TT_KEYWORD, 'STEP'):
res.register_advancement()
self.advance()
step_value = res.register(self.expr())
if res.error: return res
else:
step_value = None
if not self.current_tok.matches(TT_KEYWORD, 'THEN'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'THEN'"
))
res.register_advancement()
self.advance()
if self.current_tok.type == TT_NEWLINE:
res.register_advancement()
self.advance()
body = res.register(self.statements())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'END'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'END'"
))
res.register_advancement()
self.advance()
return res.success(ForNode(var_name, start_value, end_value, step_value, body, True))
body = res.register(self.statement())
if res.error: return res
return res.success(ForNode(var_name, start_value, end_value, step_value, body, False))
def while_expr(self):
res = ParseResult()
if not self.current_tok.matches(TT_KEYWORD, 'WHILE'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'WHILE'"
))
res.register_advancement()
self.advance()
condition = res.register(self.expr())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'THEN'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'THEN'"
))
res.register_advancement()
self.advance()
if self.current_tok.type == TT_NEWLINE:
res.register_advancement()
self.advance()
body = res.register(self.statements())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'END'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'END'"
))
res.register_advancement()
self.advance()
return res.success(WhileNode(condition, body, True))
body = res.register(self.statement())
if res.error: return res
return res.success(WhileNode(condition, body, False))
def func_def(self):
res = ParseResult()
if not self.current_tok.matches(TT_KEYWORD, 'FUN'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'FUN'"
))
res.register_advancement()
self.advance()
if self.current_tok.type == TT_IDENTIFIER:
var_name_tok = self.current_tok
res.register_advancement()
self.advance()
if self.current_tok.type != TT_LPAREN:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected '('"
))
else:
var_name_tok = None
if self.current_tok.type != TT_LPAREN:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected identifier or '('"
))
res.register_advancement()
self.advance()
arg_name_toks = []
if self.current_tok.type == TT_IDENTIFIER:
arg_name_toks.append(self.current_tok)
res.register_advancement()
self.advance()
while self.current_tok.type == TT_COMMA:
res.register_advancement()
self.advance()
if self.current_tok.type != TT_IDENTIFIER:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected identifier"
))
arg_name_toks.append(self.current_tok)
res.register_advancement()
self.advance()
if self.current_tok.type != TT_RPAREN:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected ',' or ')'"
))
else:
if self.current_tok.type != TT_RPAREN:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected identifier or ')'"
))
res.register_advancement()
self.advance()
if self.current_tok.type == TT_ARROW:
res.register_advancement()
self.advance()
body = res.register(self.expr())
if res.error: return res
return res.success(FuncDefNode(
var_name_tok,
arg_name_toks,
body,
True
))
if self.current_tok.type != TT_NEWLINE:
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected '->' or NEWLINE"
))
res.register_advancement()
self.advance()
body = res.register(self.statements())
if res.error: return res
if not self.current_tok.matches(TT_KEYWORD, 'END'):
return res.failure(InvalidSyntaxError(
self.current_tok.pos_start, self.current_tok.pos_end,
f"Expected 'END'"
))
res.register_advancement()
self.advance()
return res.success(FuncDefNode(
var_name_tok,
arg_name_toks,
body,
False
))
###################################
def bin_op(self, func_a, ops, func_b=None):
if func_b == None:
func_b = func_a
res = ParseResult()
left = res.register(func_a())
if res.error: return res
while self.current_tok.type in ops or (self.current_tok.type, self.current_tok.value) in ops:
op_tok = self.current_tok
res.register_advancement()
self.advance()
right = res.register(func_b())
if res.error: return res
left = BinOpNode(left, op_tok, right)
return res.success(left)
#######################################
# RUNTIME RESULT
#######################################
class RTResult:
def __init__(self):
self.reset()
def reset(self):
self.value = None
self.error = None
self.func_return_value = None
self.loop_should_continue = False
self.loop_should_break = False
def register(self, res):
self.error = res.error
self.func_return_value = res.func_return_value
self.loop_should_continue = res.loop_should_continue
self.loop_should_break = res.loop_should_break
return res.value
def success(self, value):
self.reset()
self.value = value
return self
def success_return(self, value):
self.reset()
self.func_return_value = value
return self
def success_continue(self):
self.reset()
self.loop_should_continue = True
return self
def success_break(self):
self.reset()
self.loop_should_break = True
return self
def failure(self, error):
self.reset()
self.error = error
return self
def should_return(self):
# Note: this will allow you to continue and break outside the current function
return (
self.error or
self.func_return_value or
self.loop_should_continue or
self.loop_should_break
)
#######################################
# VALUES
#######################################
class Value:
def __init__(self):
self.set_pos()
self.set_context()
def set_pos(self, pos_start=None, pos_end=None):
self.pos_start = pos_start
self.pos_end = pos_end
return self
def set_context(self, context=None):
self.context = context
return self
def added_to(self, other):
return None, self.illegal_operation(other)
def subbed_by(self, other):
return None, self.illegal_operation(other)
def multed_by(self, other):
return None, self.illegal_operation(other)
def dived_by(self, other):
return None, self.illegal_operation(other)
def powed_by(self, other):
return None, self.illegal_operation(other)
def get_comparison_eq(self, other):
return None, self.illegal_operation(other)
def get_comparison_ne(self, other):
return None, self.illegal_operation(other)
def get_comparison_lt(self, other):
return None, self.illegal_operation(other)
def get_comparison_gt(self, other):
return None, self.illegal_operation(other)
def get_comparison_lte(self, other):
return None, self.illegal_operation(other)
def get_comparison_gte(self, other):
return None, self.illegal_operation(other)
def anded_by(self, other):
return None, self.illegal_operation(other)
def ored_by(self, other):
return None, self.illegal_operation(other)
def notted(self, other):
return None, self.illegal_operation(other)
def execute(self, args):
return RTResult().failure(self.illegal_operation())
def copy(self):
raise Exception('No copy method defined')
def is_true(self):
return False
def illegal_operation(self, other=None):
if not other: other = self
return RTError(
self.pos_start, other.pos_end,
'Illegal operation',
self.context
)
class Number(Value):
def __init__(self, value):
super().__init__()
self.value = value
def added_to(self, other):
if isinstance(other, Number):
return Number(self.value + other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def subbed_by(self, other):
if isinstance(other, Number):
return Number(self.value - other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def multed_by(self, other):
if isinstance(other, Number):
return Number(self.value * other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def dived_by(self, other):
if isinstance(other, Number):
if other.value == 0:
return None, RTError(
other.pos_start, other.pos_end,
'Division by zero',
self.context
)
return Number(self.value / other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def powed_by(self, other):
if isinstance(other, Number):
return Number(self.value ** other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def get_comparison_eq(self, other):
if isinstance(other, Number):
return Number(int(self.value == other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def get_comparison_ne(self, other):
if isinstance(other, Number):
return Number(int(self.value != other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def get_comparison_lt(self, other):
if isinstance(other, Number):
return Number(int(self.value < other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def get_comparison_gt(self, other):
if isinstance(other, Number):
return Number(int(self.value > other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def get_comparison_lte(self, other):
if isinstance(other, Number):
return Number(int(self.value <= other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def get_comparison_gte(self, other):
if isinstance(other, Number):
return Number(int(self.value >= other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def anded_by(self, other):
if isinstance(other, Number):
return Number(int(self.value and other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def ored_by(self, other):
if isinstance(other, Number):
return Number(int(self.value or other.value)).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def notted(self):
return Number(1 if self.value == 0 else 0).set_context(self.context), None
def copy(self):
copy = Number(self.value)
copy.set_pos(self.pos_start, self.pos_end)
copy.set_context(self.context)
return copy
def is_true(self):
return self.value != 0
def __str__(self):
return str(self.value)
def __repr__(self):
return str(self.value)
Number.null = Number(0)
Number.false = Number(0)
Number.true = Number(1)
Number.math_PI = Number(math.pi)
class String(Value):
def __init__(self, value):
super().__init__()
self.value = value
def added_to(self, other):
if isinstance(other, String):
return String(self.value + other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def multed_by(self, other):
if isinstance(other, Number):
return String(self.value * other.value).set_context(self.context), None
else:
return None, Value.illegal_operation(self, other)
def is_true(self):
return len(self.value) > 0
def copy(self):
copy = String(self.value)
copy.set_pos(self.pos_start, self.pos_end)
copy.set_context(self.context)
return copy
def __str__(self):
return self.value
def __repr__(self):
return f'"{self.value}"'
class List(Value):
def __init__(self, elements):
super().__init__()
self.elements = elements
def added_to(self, other):
new_list = self.copy()
new_list.elements.append(other)
return new_list, None
def subbed_by(self, other):
if isinstance(other, Number):
new_list = self.copy()
try:
new_list.elements.pop(other.value)
return new_list, None
except:
return None, RTError(
other.pos_start, other.pos_end,
'Element at this index could not be removed from list because index is out of bounds',
self.context
)
else:
return None, Value.illegal_operation(self, other)
def multed_by(self, other):
if isinstance(other, List):
new_list = self.copy()
new_list.elements.extend(other.elements)
return new_list, None
else:
return None, Value.illegal_operation(self, other)
def dived_by(self, other):
if isinstance(other, Number):
try:
return self.elements[other.value], None
except:
return None, RTError(
other.pos_start, other.pos_end,
'Element at this index could not be retrieved from list because index is out of bounds',
self.context
)
else:
return None, Value.illegal_operation(self, other)
def copy(self):
copy = List(self.elements)
copy.set_pos(self.pos_start, self.pos_end)
copy.set_context(self.context)
return copy
def __str__(self):
return ", ".join([str(x) for x in self.elements])
def __repr__(self):
return f'[{", ".join([repr(x) for x in self.elements])}]'
class BaseFunction(Value):
def __init__(self, name):
super().__init__()
self.name = name or "<anonymous>"
def generate_new_context(self):
new_context = Context(self.name, self.context, self.pos_start)
new_context.symbol_table = SymbolTable(new_context.parent.symbol_table)
return new_context
def check_args(self, arg_names, args):
res = RTResult()
if len(args) > len(arg_names):
return res.failure(RTError(
self.pos_start, self.pos_end,
f"{len(args) - len(arg_names)} too many args passed into {self}",
self.context
))
if len(args) < len(arg_names):
return res.failure(RTError(
self.pos_start, self.pos_end,
f"{len(arg_names) - len(args)} too few args passed into {self}",
self.context
))
return res.success(None)
def populate_args(self, arg_names, args, exec_ctx):
for i in range(len(args)):
arg_name = arg_names[i]
arg_value = args[i]
arg_value.set_context(exec_ctx)
exec_ctx.symbol_table.set(arg_name, arg_value)
def check_and_populate_args(self, arg_names, args, exec_ctx):
res = RTResult()
res.register(self.check_args(arg_names, args))
if res.should_return(): return res
self.populate_args(arg_names, args, exec_ctx)
return res.success(None)
class Function(BaseFunction):
def __init__(self, name, body_node, arg_names, should_auto_return):
super().__init__(name)
self.body_node = body_node
self.arg_names = arg_names
self.should_auto_return = should_auto_return
def execute(self, args):
res = RTResult()
interpreter = Interpreter()
exec_ctx = self.generate_new_context()
res.register(self.check_and_populate_args(self.arg_names, args, exec_ctx))
if res.should_return(): return res
value = res.register(interpreter.visit(self.body_node, exec_ctx))
if res.should_return() and res.func_return_value == None: return res
ret_value = (value if self.should_auto_return else None) or res.func_return_value or Number.null
return res.success(ret_value)
def copy(self):
copy = Function(self.name, self.body_node, self.arg_names, self.should_auto_return)
copy.set_context(self.context)
copy.set_pos(self.pos_start, self.pos_end)
return copy
def __repr__(self):
return f"<function {self.name}>"
class BuiltInFunction(BaseFunction):
def __init__(self, name):
super().__init__(name)
def execute(self, args):
res = RTResult()
exec_ctx = self.generate_new_context()
method_name = f'execute_{self.name}'
method = getattr(self, method_name, self.no_visit_method)
res.register(self.check_and_populate_args(method.arg_names, args, exec_ctx))
if res.should_return(): return res
return_value = res.register(method(exec_ctx))
if res.should_return(): return res
return res.success(return_value)
def no_visit_method(self, node, context):
raise Exception(f'No execute_{self.name} method defined')
def copy(self):
copy = BuiltInFunction(self.name)
copy.set_context(self.context)
copy.set_pos(self.pos_start, self.pos_end)
return copy
def __repr__(self):
return f"<built-in function {self.name}>"
#####################################
def execute_print(self, exec_ctx):
print(str(exec_ctx.symbol_table.get('value')))
return RTResult().success(Number.null)
execute_print.arg_names = ['value']
def execute_print_ret(self, exec_ctx):
return RTResult().success(String(str(exec_ctx.symbol_table.get('value'))))
execute_print_ret.arg_names = ['value']
def execute_input(self, exec_ctx):
text = input()
return RTResult().success(String(text))
execute_input.arg_names = []
def execute_input_int(self, exec_ctx):
while True:
text = input()
try:
number = int(text)
break
except ValueError:
print(f"'{text}' must be an integer. Try again!")
return RTResult().success(Number(number))
execute_input_int.arg_names = []
def execute_clear(self, exec_ctx):
os.system('cls' if os.name == 'nt' else 'cls')
return RTResult().success(Number.null)
execute_clear.arg_names = []
def execute_is_number(self, exec_ctx):
is_number = isinstance(exec_ctx.symbol_table.get("value"), Number)
return RTResult().success(Number.true if is_number else Number.false)
execute_is_number.arg_names = ["value"]
def execute_is_string(self, exec_ctx):
is_number = isinstance(exec_ctx.symbol_table.get("value"), String)
return RTResult().success(Number.true if is_number else Number.false)
execute_is_string.arg_names = ["value"]
def execute_is_list(self, exec_ctx):
is_number = isinstance(exec_ctx.symbol_table.get("value"), List)
return RTResult().success(Number.true if is_number else Number.false)
execute_is_list.arg_names = ["value"]
def execute_is_function(self, exec_ctx):
is_number = isinstance(exec_ctx.symbol_table.get("value"), BaseFunction)
return RTResult().success(Number.true if is_number else Number.false)
execute_is_function.arg_names = ["value"]
def execute_append(self, exec_ctx):
list_ = exec_ctx.symbol_table.get("list")
value = exec_ctx.symbol_table.get("value")
if not isinstance(list_, List):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"First argument must be list",
exec_ctx
))
list_.elements.append(value)
return RTResult().success(Number.null)
execute_append.arg_names = ["list", "value"]
def execute_pop(self, exec_ctx):
list_ = exec_ctx.symbol_table.get("list")
index = exec_ctx.symbol_table.get("index")
if not isinstance(list_, List):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"First argument must be list",
exec_ctx
))
if not isinstance(index, Number):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"Second argument must be number",
exec_ctx
))
try:
element = list_.elements.pop(index.value)
except:
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
'Element at this index could not be removed from list because index is out of bounds',
exec_ctx
))
return RTResult().success(element)
execute_pop.arg_names = ["list", "index"]
def execute_extend(self, exec_ctx):
listA = exec_ctx.symbol_table.get("listA")
listB = exec_ctx.symbol_table.get("listB")
if not isinstance(listA, List):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"First argument must be list",
exec_ctx
))
if not isinstance(listB, List):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"Second argument must be list",
exec_ctx
))
listA.elements.extend(listB.elements)
return RTResult().success(Number.null)
execute_extend.arg_names = ["listA", "listB"]
def execute_len(self, exec_ctx):
list_ = exec_ctx.symbol_table.get("list")
if not isinstance(list_, List):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"Argument must be list",
exec_ctx
))
return RTResult().success(Number(len(list_.elements)))
execute_len.arg_names = ["list"]
def execute_run(self, exec_ctx):
fn = exec_ctx.symbol_table.get("fn")
if not isinstance(fn, String):
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
"Second argument must be string",
exec_ctx
))
fn = fn.value
try:
with open(fn, "r") as f:
script = f.read()
except Exception as e:
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
f"Failed to load script \"{fn}\"\n" + str(e),
exec_ctx
))
_, error = run(fn, script)
if error:
return RTResult().failure(RTError(
self.pos_start, self.pos_end,
f"Failed to finish executing script \"{fn}\"\n" +
error.as_string(),
exec_ctx
))
return RTResult().success(Number.null)
execute_run.arg_names = ["fn"]
BuiltInFunction.print = BuiltInFunction("print")
BuiltInFunction.print_ret = BuiltInFunction("print_ret")
BuiltInFunction.input = BuiltInFunction("input")
BuiltInFunction.input_int = BuiltInFunction("input_int")
BuiltInFunction.clear = BuiltInFunction("clear")
BuiltInFunction.is_number = BuiltInFunction("is_number")
BuiltInFunction.is_string = BuiltInFunction("is_string")
BuiltInFunction.is_list = BuiltInFunction("is_list")
BuiltInFunction.is_function = BuiltInFunction("is_function")
BuiltInFunction.append = BuiltInFunction("append")
BuiltInFunction.pop = BuiltInFunction("pop")
BuiltInFunction.extend = BuiltInFunction("extend")
BuiltInFunction.len = BuiltInFunction("len")
BuiltInFunction.run = BuiltInFunction("run")
#######################################
# CONTEXT
#######################################
class Context:
def __init__(self, display_name, parent=None, parent_entry_pos=None):
self.display_name = display_name
self.parent = parent
self.parent_entry_pos = parent_entry_pos
self.symbol_table = None
#######################################
# SYMBOL TABLE
#######################################
class SymbolTable:
def __init__(self, parent=None):
self.symbols = {}
self.parent = parent
def get(self, name):
value = self.symbols.get(name, None)
if value == None and self.parent:
return self.parent.get(name)
return value
def set(self, name, value):
self.symbols[name] = value
def remove(self, name):
del self.symbols[name]
#######################################
# INTERPRETER
#######################################
class Interpreter:
def visit(self, node, context):
method_name = f'visit_{type(node).__name__}'
method = getattr(self, method_name, self.no_visit_method)
return method(node, context)
def no_visit_method(self, node, context):
raise Exception(f'No visit_{type(node).__name__} method defined')
###################################
def visit_NumberNode(self, node, context):
return RTResult().success(
Number(node.tok.value).set_context(context).set_pos(node.pos_start, node.pos_end)
)
def visit_StringNode(self, node, context):
return RTResult().success(
String(node.tok.value).set_context(context).set_pos(node.pos_start, node.pos_end)
)
def visit_ListNode(self, node, context):
res = RTResult()
elements = []
for element_node in node.element_nodes:
elements.append(res.register(self.visit(element_node, context)))
if res.should_return(): return res
return res.success(
List(elements).set_context(context).set_pos(node.pos_start, node.pos_end)
)
def visit_VarAccessNode(self, node, context):
res = RTResult()
var_name = node.var_name_tok.value
value = context.symbol_table.get(var_name)
if not value:
return res.failure(RTError(
node.pos_start, node.pos_end,
f"'{var_name}' is not defined",
context
))
value = value.copy().set_pos(node.pos_start, node.pos_end).set_context(context)
return res.success(value)
def visit_VarAssignNode(self, node, context):
res = RTResult()
var_name = node.var_name_tok.value
value = res.register(self.visit(node.value_node, context))
if res.should_return(): return res
context.symbol_table.set(var_name, value)
return res.success(value)
def visit_BinOpNode(self, node, context):
res = RTResult()
left = res.register(self.visit(node.left_node, context))
if res.should_return(): return res
right = res.register(self.visit(node.right_node, context))
if res.should_return(): return res
if node.op_tok.type == TT_PLUS:
result, error = left.added_to(right)
elif node.op_tok.type == TT_MINUS:
result, error = left.subbed_by(right)
elif node.op_tok.type == TT_MUL:
result, error = left.multed_by(right)
elif node.op_tok.type == TT_DIV:
result, error = left.dived_by(right)
elif node.op_tok.type == TT_POW:
result, error = left.powed_by(right)
elif node.op_tok.type == TT_EE:
result, error = left.get_comparison_eq(right)
elif node.op_tok.type == TT_NE:
result, error = left.get_comparison_ne(right)
elif node.op_tok.type == TT_LT:
result, error = left.get_comparison_lt(right)
elif node.op_tok.type == TT_GT:
result, error = left.get_comparison_gt(right)
elif node.op_tok.type == TT_LTE:
result, error = left.get_comparison_lte(right)
elif node.op_tok.type == TT_GTE:
result, error = left.get_comparison_gte(right)
elif node.op_tok.matches(TT_KEYWORD, 'AND'):
result, error = left.anded_by(right)
elif node.op_tok.matches(TT_KEYWORD, 'OR'):
result, error = left.ored_by(right)
if error:
return res.failure(error)
else:
return res.success(result.set_pos(node.pos_start, node.pos_end))
def visit_UnaryOpNode(self, node, context):
res = RTResult()
number = res.register(self.visit(node.node, context))
if res.should_return(): return res
error = None
if node.op_tok.type == TT_MINUS:
number, error = number.multed_by(Number(-1))
elif node.op_tok.matches(TT_KEYWORD, 'NOT'):
number, error = number.notted()
if error:
return res.failure(error)
else:
return res.success(number.set_pos(node.pos_start, node.pos_end))
def visit_IfNode(self, node, context):
res = RTResult()
for condition, expr, should_return_null in node.cases:
condition_value = res.register(self.visit(condition, context))
if res.should_return(): return res
if condition_value.is_true():
expr_value = res.register(self.visit(expr, context))
if res.should_return(): return res
return res.success(Number.null if should_return_null else expr_value)
if node.else_case:
expr, should_return_null = node.else_case
expr_value = res.register(self.visit(expr, context))
if res.should_return(): return res
return res.success(Number.null if should_return_null else expr_value)
return res.success(Number.null)
def visit_ForNode(self, node, context):
res = RTResult()
elements = []
start_value = res.register(self.visit(node.start_value_node, context))
if res.should_return(): return res
end_value = res.register(self.visit(node.end_value_node, context))
if res.should_return(): return res
if node.step_value_node:
step_value = res.register(self.visit(node.step_value_node, context))
if res.should_return(): return res
else:
step_value = Number(1)
i = start_value.value
if step_value.value >= 0:
condition = lambda: i < end_value.value
else:
condition = lambda: i > end_value.value
while condition():
context.symbol_table.set(node.var_name_tok.value, Number(i))
i += step_value.value
value = res.register(self.visit(node.body_node, context))
if res.should_return() and res.loop_should_continue == False and res.loop_should_break == False: return res
if res.loop_should_continue:
continue
if res.loop_should_break:
break
elements.append(value)
return res.success(
Number.null if node.should_return_null else
List(elements).set_context(context).set_pos(node.pos_start, node.pos_end)
)
def visit_WhileNode(self, node, context):
res = RTResult()
elements = []
while True:
condition = res.register(self.visit(node.condition_node, context))
if res.should_return(): return res
if not condition.is_true():
break
value = res.register(self.visit(node.body_node, context))
if res.should_return() and res.loop_should_continue == False and res.loop_should_break == False: return res
if res.loop_should_continue:
continue
if res.loop_should_break:
break
elements.append(value)
return res.success(
Number.null if node.should_return_null else
List(elements).set_context(context).set_pos(node.pos_start, node.pos_end)
)
def visit_FuncDefNode(self, node, context):
res = RTResult()
func_name = node.var_name_tok.value if node.var_name_tok else None
body_node = node.body_node
arg_names = [arg_name.value for arg_name in node.arg_name_toks]
func_value = Function(func_name, body_node, arg_names, node.should_auto_return).set_context(context).set_pos(node.pos_start, node.pos_end)
if node.var_name_tok:
context.symbol_table.set(func_name, func_value)
return res.success(func_value)
def visit_CallNode(self, node, context):
res = RTResult()
args = []
value_to_call = res.register(self.visit(node.node_to_call, context))
if res.should_return(): return res
value_to_call = value_to_call.copy().set_pos(node.pos_start, node.pos_end)
for arg_node in node.arg_nodes:
args.append(res.register(self.visit(arg_node, context)))
if res.should_return(): return res
return_value = res.register(value_to_call.execute(args))
if res.should_return(): return res
return_value = return_value.copy().set_pos(node.pos_start, node.pos_end).set_context(context)
return res.success(return_value)
def visit_ReturnNode(self, node, context):
res = RTResult()
if node.node_to_return:
value = res.register(self.visit(node.node_to_return, context))
if res.should_return(): return res
else:
value = Number.null
return res.success_return(value)
def visit_ContinueNode(self, node, context):
return RTResult().success_continue()
def visit_BreakNode(self, node, context):
return RTResult().success_break()
#######################################
# RUN
#######################################
global_symbol_table = SymbolTable()
global_symbol_table.set("NULL", Number.null)
global_symbol_table.set("FALSE", Number.false)
global_symbol_table.set("TRUE", Number.true)
global_symbol_table.set("MATH_PI", Number.math_PI)
global_symbol_table.set("PRINT", BuiltInFunction.print)
global_symbol_table.set("PRINT_RET", BuiltInFunction.print_ret)
global_symbol_table.set("INPUT", BuiltInFunction.input)
global_symbol_table.set("INPUT_INT", BuiltInFunction.input_int)
global_symbol_table.set("CLEAR", BuiltInFunction.clear)
global_symbol_table.set("CLS", BuiltInFunction.clear)
global_symbol_table.set("IS_NUM", BuiltInFunction.is_number)
global_symbol_table.set("IS_STR", BuiltInFunction.is_string)
global_symbol_table.set("IS_LIST", BuiltInFunction.is_list)
global_symbol_table.set("IS_FUN", BuiltInFunction.is_function)
global_symbol_table.set("APPEND", BuiltInFunction.append)
global_symbol_table.set("POP", BuiltInFunction.pop)
global_symbol_table.set("EXTEND", BuiltInFunction.extend)
global_symbol_table.set("LEN", BuiltInFunction.len)
global_symbol_table.set("RUN", BuiltInFunction.run)
def run(fn, text):
# Generate tokens
lexer = Lexer(fn, text)
tokens, error = lexer.make_tokens()
if error: return None, error
# Generate AST
parser = Parser(tokens)
ast = parser.parse()
if ast.error: return None, ast.error
# Run program
interpreter = Interpreter()
context = Context('<program>')
context.symbol_table = global_symbol_table
result = interpreter.visit(ast.node, context)
return result.value, result.error
Editor is loading...