Skip to content

Commit

Permalink
Merge pull request #18 from SageTendo/arrays
Browse files Browse the repository at this point in the history
Arrays
  • Loading branch information
SageTendo authored Feb 21, 2024
2 parents af8815d + 1e4645e commit 77f66eb
Show file tree
Hide file tree
Showing 38 changed files with 449 additions and 111 deletions.
96 changes: 79 additions & 17 deletions src/Interpreter.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import sys

from src.core.AComponent import AComponent
from src.core.ASTNodes import PrintNode, BodyNode, ProgramNode, ArgsNode, ExprNode, SimpleExprNode, TermNode, \
FactorNode, OperatorNode, IdentifierNode, NumericLiteralNode, StringLiteralNode, InputNode, AssignmentNode, \
PostfixExprNode, CallNode, FuncDefNode, ReturnNode, BooleanNode, IfNode, WhileNode, ForNode, BreakNode, \
ContinueNode
from src.core.ASTNodes import (PrintNode, BodyNode, ProgramNode, ArgsNode, ExprNode, SimpleExprNode, TermNode,
FactorNode, OperatorNode, IdentifierNode, NumericLiteralNode, StringLiteralNode,
InputNode, AssignmentNode, PostfixExprNode, CallNode, FuncDefNode, ReturnNode,
BooleanNode, IfNode, WhileNode, ForNode, BreakNode, ContinueNode, ArrayNode)
from src.core.CacheMemory import cache_mem
from src.core.Environment import Environment
from src.core.RuntimeObject import RunTimeObject
from src.core.Symbol import ArraySymbol
from src.utils.Constants import WARNING
from src.utils.ErrorHandler import throw_unary_type_err, throw_invalid_operation_err, warning_msg, success_msg, emoji, \
InterpreterError, ErrorType
from src.utils.ErrorHandler import (throw_unary_type_err, throw_invalid_operation_err,
warning_msg, success_msg, emoji, InterpreterError, ErrorType)

MAX_VISIT_DEPTH = 5470
INTERNAL_RECURSION_LIMIT = 1010
Expand Down Expand Up @@ -240,22 +241,77 @@ def validate_range_node(range_node):
iterator_runtime_object = RunTimeObject(label="number", value=0, value_type="int")
self.current_env.insert_variable(node.identifier.value, iterator_runtime_object)

# incrementer to determine inclusive range and direction of iteration
# incrementer to determine direction of iteration
incrementer = 1 if range_start < range_end else -1
for i in range(range_start, range_end + incrementer, incrementer):
for i in range(range_start, range_end, incrementer):
iterator_runtime_object.value = i
if last_evaluated := self.__handle_conditional_execution(node.body):
return last_evaluated

def visit_array_def(self, node: 'ArrayNode'):
"""
Visits an ArrayNode and creates a new array in the symbol table
@param node: The ArrayNode to visit
"""
self.node_start_pos = node.start_pos
self.node_end_pos = node.end_pos

identifier = node.identifier
if node.size is not None:
array_size = self.__test_for_identifier(node.size.accept(self)).value
values = [RunTimeObject("null", value="null")] * int(array_size)

elif node.initial_values is not None:
array_size = len(node.initial_values)
values = [value.accept(self) for value in node.initial_values]

else:
array_size = -1
values = []

array_symbol = ArraySymbol(identifier, array_size, values)
self.current_env.insert_array(identifier, array_symbol)

def visit_array_access(self, node: 'ArrayNode'):
"""
Visits an ArrayNode and returns an element of the array in the symbol table
@param node: The ArrayNode to visit
"""
identifier = node.identifier
index = self.__test_for_identifier(node.index.accept(self)).value
array_symbol = self.current_env.lookup_array(identifier)

if index < 0 or index >= len(array_symbol.values):
raise InterpreterError(ErrorType.RUNTIME, "Array index out of bounds", node.start_pos, node.end_pos)

return array_symbol.values[index]

def visit_array_update(self, node: 'ArrayNode'):
"""
Visits an ArrayNode and updates the array in the symbol table
@param node: The ArrayNode to visit
"""
self.node_start_pos = node.start_pos
self.node_end_pos = node.end_pos

identifier = node.identifier
index = self.__test_for_identifier(node.index.accept(self)).value
value_runtime = node.value.accept(self)

array_symbol = self.current_env.lookup_array(identifier)
if int(index) < 0 or int(index) >= len(array_symbol.values):
raise InterpreterError(ErrorType.RUNTIME, "Array index out of bounds", node.start_pos, node.end_pos)

array_symbol.values[index] = RunTimeObject(value_runtime.label, value_runtime.value, value_runtime.type)

def visit_assignment(self, node: 'AssignmentNode'):
"""
Visits an assignment statement and handles the assignment operation of identifiers
@param node: The assignment node to visit
"""
lhs = node.left.accept(self)
rhs = node.right.accept(self)
self.current_env.insert_variable(lhs.value,
RunTimeObject(label=rhs.label, value=rhs.value, value_type=rhs.type))
self.current_env.insert_variable(lhs.value, RunTimeObject(rhs.label, rhs.value, rhs.type))

def visit_call(self, node: 'CallNode'):
"""
Expand All @@ -273,10 +329,10 @@ def visit_call(self, node: 'CallNode'):

function_args = node.args.accept(self)
if len(function_args) != len(function_symbol.params):
raise InterpreterError(ErrorType.RUNTIME, f"Invalid number of arguments provided...\n"
f"Expected {len(function_symbol.params)} "
f"but got {len(function_args)}",
node.args.start_pos, node.args.end_pos)
raise InterpreterError(ErrorType.RUNTIME,
f"Invalid number of arguments provided...\n"
f"Expected {len(function_symbol.params)} "
f"but got {len(function_args)}", node.args.start_pos, node.args.end_pos)

for i, param in enumerate(function_symbol.params): # assing arg values to local variables
local_env.insert_variable(param, function_args[i])
Expand Down Expand Up @@ -308,10 +364,16 @@ def visit_print(self, node: 'PrintNode'):
Visits a print statement node and prints the value(s) of evaluated argument(s) to the console
@param node: The print statement node to visit
"""
for arg in node.args.accept(self):
args = node.args.accept(self)
for i, arg in enumerate(args):
runtime_value = arg.value
print(runtime_value, end=' ')
print()
if i < len(args) - 1:
print(runtime_value, end=" ")
else:
print(runtime_value, end='')

if node.println: # print new line if println is used
print()

def visit_postfix_expr(self, node: 'PostfixExprNode'):
"""
Expand Down
13 changes: 10 additions & 3 deletions src/Lexer.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
'kawaii': TokenType.DEF, 'HAI': TokenType.TRUE, 'IIE': TokenType.FALSE,
'wa': TokenType.ASSIGN, "modoru": TokenType.RET, 'purasu': TokenType.PLUS,
'mainasu': TokenType.MINUS, 'purodakuto': TokenType.MULTIPLY, 'supuritto': TokenType.DIVIDE,
'ando': TokenType.AND, 'matawa': TokenType.OR, 'nai': TokenType.NOT, 'for': TokenType.FOR
'ando': TokenType.AND, 'matawa': TokenType.OR, 'nai': TokenType.NOT, 'for': TokenType.FOR,
"yomu_ln": TokenType.PRINTLN
}


Expand Down Expand Up @@ -119,6 +120,12 @@ def get_token(self):
elif self.char == '}':
token.type = TokenType.RBRACE
self.__next_char()
elif self.char == '[':
token.type = TokenType.LBRACKET
self.__next_char()
elif self.char == ']':
token.type = TokenType.RBRACKET
self.__next_char()
elif self.char == ':':
self.__next_char()

Expand Down Expand Up @@ -298,8 +305,8 @@ def _process_string(self, token):
processed_string += '\\'
else:
raise LexerError("Invalid escape character", self.line_number, self.col_number)

processed_string += self.char
else:
processed_string += self.char
self.__next_char()

self.__next_char()
Expand Down
114 changes: 102 additions & 12 deletions src/Parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

from src.Lexer import Lexer
from src.core.AComponent import AComponent
from src.core.ASTNodes import BodyNode, ReturnNode, ProgramNode, SimpleExprNode, AssignmentNode, \
IdentifierNode, \
PostfixExprNode, PrintNode, ArgsNode, NumericLiteralNode, StringLiteralNode, BooleanNode, FactorNode, ExprNode, \
CallNode, InputNode, BreakNode, ContinueNode, \
WhileNode, IfNode, ElifNode, FuncDefNode, TermNode, OperatorNode, ForNode
from src.core.ASTNodes import (BodyNode, ReturnNode, ProgramNode, SimpleExprNode, AssignmentNode,
IdentifierNode, PostfixExprNode, PrintNode, ArgsNode, NumericLiteralNode,
StringLiteralNode,
BooleanNode, FactorNode, ExprNode, CallNode, InputNode, BreakNode, ContinueNode,
WhileNode, IfNode, ElifNode, FuncDefNode, TermNode, OperatorNode, ForNode, ArrayNode)
from src.core.Types import TokenType
from src.utils.ErrorHandler import success_msg, warning_msg, throw_unexpected_token_err, ParserError, LexerError

Expand Down Expand Up @@ -196,16 +196,21 @@ def parse_statement(self):
statement_node = self.parse_return()

elif self.match(TokenType.ID):
# assignment statement
if self.peek_token().type == TokenType.ASSIGN:
statement_node = self.parse_assignment()

elif TokenType.postfix(self.peek_token()):
statement_node = self.parse_postfix()

# call (func calls) statement
elif self.peek_token().type == TokenType.LPAR:
statement_node = self.parse_func_call()

elif self.peek_token().type == TokenType.TO:
statement_node = self.parse_array_def()

elif self.peek_token().type == TokenType.LBRACKET:
statement_node = self.parse_array_assignment()

else:
self.__expect_and_consume(TokenType.ID)
throw_unexpected_token_err(
Expand All @@ -221,6 +226,8 @@ def parse_statement(self):
statement_node = self.parse_if()
elif self.match(TokenType.PRINT):
statement_node = self.parse_print()
elif self.match(TokenType.PRINTLN):
statement_node = self.parse_print(print_ln=True)
elif self.match(TokenType.INPUT):
statement_node = self.parse_input()

Expand Down Expand Up @@ -296,6 +303,78 @@ def parse_postfix(self):
postfix_node.end_pos = self.curr_tkn.pos
return postfix_node

def parse_index(self):
"""
Index: [ simple_expr ]
@return: SimpleExprNode
"""
self.debug("<Index>")

self.__expect_and_consume(TokenType.LBRACKET)
simple_expr = self.parse_simple_expr()
self.__expect_and_consume(TokenType.RBRACKET)

self.debug("</Index>")
return simple_expr

def parse_array_def(self):
"""
ArrayDef: ID => [ expr ] | { values* }
@return:
"""
self.debug("<ArrDef>")

identifier = self.curr_tkn.value
size = None
values = []
self.__expect_and_consume(TokenType.ID)
self.__expect_and_consume(TokenType.TO)

if self.match(TokenType.LBRACKET):
self.__expect_and_consume(TokenType.LBRACKET)
size = self.parse_simple_expr()
self.__expect_and_consume(TokenType.RBRACKET)

elif self.match(TokenType.LBRACE):
self.__expect_and_consume(TokenType.LBRACE)
while not self.match(TokenType.RBRACE):
values.append(self.parse_expr())
if self.match(TokenType.COMMA):
self.__expect_and_consume(TokenType.COMMA)
self.__expect_and_consume(TokenType.RBRACE)

self.debug("<ArrDef>")
return ArrayNode(label="array_def", identifier=identifier, size=size, initial_values=values)

def parse_array_access(self):
"""
ArrayAccess: ID index
@return:
"""
self.debug("<ArrayAccess>")

identifier = self.curr_tkn.value
self.__expect_and_consume(TokenType.ID)
index = self.parse_index()

self.debug("</ArrayAccess>")
return ArrayNode(label="array_access", identifier=identifier, index=index)

def parse_array_assignment(self):
"""
ArrayAssignment: ID index ASSIGN expr
"""
self.debug("<ArrayAssign>")

identifier = self.curr_tkn.value
self.__expect_and_consume(TokenType.ID)
index = self.parse_index()
self.__expect_and_consume(TokenType.ASSIGN)
expression = self.parse_expr()

self.debug("</ArrayAssign>")
return ArrayNode(label="array_update", identifier=identifier, index=index, value=expression)

def parse_assignment(self):
"""
Assignment: ID ASSIGN (expr | callable)
Expand Down Expand Up @@ -455,7 +534,7 @@ def parse_return(self):
return_node.end_pos = self.curr_tkn.pos
return return_node

def parse_print(self):
def parse_print(self, print_ln=False):
"""
PrintStatement: PRINT args
@return: PrintNode
Expand All @@ -464,9 +543,14 @@ def parse_print(self):

start_pos = self.curr_tkn.pos

self.__expect_and_consume(TokenType.PRINT)
# PrintlnStatement: PRINTLN args
if self.curr_tkn.type == TokenType.PRINTLN:
self.__expect_and_consume(TokenType.PRINTLN)
else:
self.__expect_and_consume(TokenType.PRINT)

args = self.parse_args()
print_node = PrintNode(args)
print_node = PrintNode(args, print_ln)

self.debug("</Print>")
print_node.start_pos = start_pos
Expand Down Expand Up @@ -685,11 +769,17 @@ def parse_factor(self):

if self.match(TokenType.ID):

# Check if it is a function call
if self.peek_token().type == TokenType.LPAR:
factor_node = self.parse_func_call()

elif self.peek_token().type == TokenType.LBRACKET:
start_pos = self.curr_tkn.pos
array_access_node = self.parse_array_access()
array_access_node.start_pos = start_pos
array_access_node.end_pos = self.curr_tkn.pos
factor_node = FactorNode(array_access_node)

else:
# Check if it is an identifier
factor_node = IdentifierNode(self.curr_tkn)
self.__expect_and_consume(TokenType.ID)

Expand Down
13 changes: 12 additions & 1 deletion src/core/ASTNodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,9 +133,10 @@ def __init__(self, msg=None):


class PrintNode(Node):
def __init__(self, args):
def __init__(self, args, print_ln=False):
super().__init__("print")
self.args = args
self.println = print_ln


class WhileNode(ConditionalNode):
Expand Down Expand Up @@ -222,6 +223,16 @@ def __init__(self, left, right=None):
self.right = right


class ArrayNode(Node):
def __init__(self, label, identifier, index=None, size=None, value=None, initial_values=None):
super().__init__(label)
self.identifier = identifier
self.index = index
self.size = size
self.value = value
self.initial_values = initial_values


class IdentifierNode(Node):
def __init__(self, token):
super().__init__("identifier")
Expand Down
Loading

0 comments on commit 77f66eb

Please sign in to comment.