storyscript/lexer.py
import numpy as np
# I tried to convince DeepSource that all of the objects existed
from .langData import Exceptions, LambdaExpr, BASE_KEYWORDS, PRIMITIVE_TYPE, Types, mismatch_type, LISTDECLARE_KEYW, PythonFunctionObject, invalid_value, Array, not_enough_args_for_import_statement
from typing import NoReturn
from .langParser import Parser
from .SymbolTable import SymbolTable
from storyscript_mathparse import values
from . import executor
from traceback import print_exc
all_variable_name = None
all_function_name = None
class Lexer:
def __init__(
self,
symbol_table: SymbolTable,
parser: Parser = None,
build_cache: bool = False,
) -> NoReturn:
self.symbol_table: SymbolTable = symbol_table
self.parser: Parser = parser
self.build_cache: bool = build_cache
if parser is None:
self.parser: Parser = Parser(symbol_table)
def variable_setting(self, tc: list, original_text) -> tuple:
if tc[1] == "=": # Set operator
value = " ".join(tc[2:])
if value.startswith("new Dynamic"):
value = value[13:-1]
res, error = self.analyse_command(value.split())
if error:
return res, error
value = ""
for i in tc[2:]:
value += i + " "
value = value[:-1]
valtype = self.parser.parse_type_from_value(res)
if valtype == Exceptions.InvalidSyntax:
return invalid_value, Exceptions.InvalidValue
vartype = self.symbol_table.get_variable_type(tc[0])
# Check if Value Type matches Variable type
if valtype != vartype:
return f"{mismatch_type} Expected {vartype.value}, Found {valtype.value}.", Exceptions.InvalidValue
if vartype == Types.Action:
self.symbol_table.set_function(tc[0], res)
self.symbol_table.set_variable(tc[0], res, vartype)
return None, None
if tc[1] == "+=": # Add & Set operator
operator = "+"
elif tc[1] == "-=": # Subtract & Set operator
operator = "-"
elif tc[1] == "*=": # Multiply & Set operator
operator = "*"
elif tc[1] == "/=": # Divide & Set operator
operator = "/"
elif tc[1] == "%=": # Modulo Operaion & Set operator
operator = "%"
else:
res, error = self.parser.parse_expression(original_text)
return res, error
vartype = self.symbol_table.get_variable_type(tc[0])
keepFloat = False
if vartype == Types.Float:
keepFloat = True
res, error = self.analyse_command(tc[2:])
if error:
return res, error
res, error = self.parser.parse_expression(
f"{tc[0]} {operator} {str(res)}", keepFloat
)
value = ""
try:
if tc[2] in all_variable_name:
tc[2] = (self.symbol_table.GetVariable(tc[2]))[1]
if tc[4] in all_variable_name:
tc[4] = (self.symbol_table.GetVariable(tc[4]))[1]
except IndexError:
pass
value = " ".join(tc[2:])
valtype = self.parser.parse_type_from_value(res)
if valtype == Exceptions.InvalidSyntax:
return invalid_value, Exceptions.InvalidValue
# Check if Value Type matches Variable type
if valtype != vartype:
return mismatch_type, Exceptions.InvalidValue
if vartype == Types.Action:
if operator == "+":
return "InvalidOperatorException: You cannot use += with lambda expression, maybe you are looking for Event?", Exceptions.InvalidOperatorException
return f"InvalidOperatorException: You cannot use {operator}= with lambda expression.", Exceptions.InvalidOperatorException
self.symbol_table.set_variable(tc[0], res, vartype)
return None, None
def if_else_statement(self, tc: list) -> tuple:
run_code, error = self.parser.parse_conditions(
self.parser.parse_condition_list(tc[1:]), self.analyse_command
)
if error:
return run_code, error
is_in_code_block = False
is_in_else_block = False
have_passed_then_keyword = False
ifstatement = {"if": [], "else": []}
commands = []
command = []
endkeywordcount = 0 # All "end" keyword in the expression
endkeywordpassed = 0 # All "end" keyword passed
elsekeywordcount = 0 # All "else" keyword in the expression
elsekeywordpassed = 0 # All "else" keyword passed
for i in tc[2:]:
if i == "end":
endkeywordcount += 1
elif i == "else":
elsekeywordcount += 1
for i in tc:
if not have_passed_then_keyword and i == "then":
is_in_code_block = True
have_passed_then_keyword = True
continue
if is_in_code_block:
if i == "&&":
commands.append(command)
command = []
continue
if i == "end":
endkeywordpassed += 1
if endkeywordcount == endkeywordpassed:
commands.append(command)
command = []
if is_in_else_block:
ifstatement["else"] = commands
else:
ifstatement["if"] = commands
is_in_else_block = False
is_in_code_block = False
continue
if i == "else":
elsekeywordpassed += 1
if (
elsekeywordcount == elsekeywordpassed
and endkeywordpassed + 1 == endkeywordcount
):
commands.append(command)
command = []
ifstatement["if"] = commands
commands = []
is_in_else_block = True
continue
command.append(i)
# Run the code if the condition is true
if run_code:
for i in ifstatement["if"]:
res, error = self.analyse_command(i)
if error:
return res, error
if res is not None:
if isinstance(res, str) and res.startswith("EXITREQUEST"):
return res, error
print(res)
else:
# Iterate through commands
for i in ifstatement["else"]:
res, error = self.analyse_command(i)
if error:
return res, error
if res is not None:
print(res)
return None, None
def loopfor_statement(self, tc: list) -> tuple:
try:
commands = [] # list of commands
command = []
endkeywordcount = 0 # All "end" keyword in the expression
endkeywordpassed = 0 # All "end" keyword passed
for i in tc[2:]:
if i == "end":
endkeywordcount += 1
for i in tc[2:]:
if i == "&&":
commands.append(command)
command = []
continue
if i == "end":
endkeywordpassed += 1
if endkeywordcount == endkeywordpassed:
commands.append(command)
command = []
break
command.append(i)
vartable, functable = self.symbol_table.copyvalue()
scoped_variable_table = SymbolTable()
scoped_variable_table.importdata(vartable, functable)
commandlexer = Lexer(scoped_variable_table)
index = 0
if tc[1].endswith(":"):
tc[1] = tc[1][:-1]
times, error = self.analyse_command([tc[1]])
if error:
return times, error
times = int(times)
while index < times:
scoped_variable_table = SymbolTable()
scoped_variable_table.importdata(vartable, functable)
commandlexer.symbol_table = scoped_variable_table
for i in commands:
res, error = commandlexer.analyse_command(i)
if error:
return res, error
if res is not None:
print(res)
index += 1
return None, None
except ValueError:
return (
"InvalidValue: Count must be an Integer. (Whole number)",
Exceptions.InvalidValue,
)
def switch_case_statement(self, tc: list) -> tuple:
cases = {}
case = []
command = []
is_in_case_block = False
is_after_case_keyword = False
current_case_key = None
for i in tc[2:]:
if i == "case":
is_after_case_keyword = True
continue
if is_after_case_keyword:
outkey = i
if outkey.endswith(":"):
outkey = outkey[:-1]
current_case_key = outkey
is_after_case_keyword = False
is_in_case_block = True
continue
if is_in_case_block:
if i == "&&":
case.append(command)
command = []
continue
if i == "break":
case.append(command)
cases[current_case_key] = case
command = []
case = []
is_in_case_block = False
continue
command.append(i)
if i == "end":
break
if tc[1] in all_variable_name:
tc[1] = self.symbol_table.GetVariable(tc[1])[1]
scopedVariableTable = SymbolTable()
vartable, functable = self.symbol_table.copyvalue()
scopedVariableTable.importdata(vartable, functable)
commandLexer = Lexer(scopedVariableTable)
if cases.get(tc[1]):
for i in cases[tc[1]]:
res, error = commandLexer.analyse_command(i)
if error:
return res, error
if res is not None:
print(res)
elif cases.get("default"):
for i in cases["default"]:
res, error = commandLexer.analyse_command(i)
if error:
return res, error
if res is not None:
print(res)
return None, None
def ternary_operator(self, tc: list) -> tuple:
condition_end_pos = 1
truecase = []
falsecase = []
# Positions: "condition" [0], "truecase" [1], "falsecase" [2]
current_position = 0
loop_index = 0
current_command = []
for i in tc[1:]:
loop_index += 1
if i == ":":
if current_position == 0:
condition_end_pos = loopIndex
current_command = []
elif current_position == 1:
truecase.append(currentCommand)
current_command = []
elif current_position == 2:
falsecase.append(currentCommand)
current_command = []
current_position += 1
continue
if i == "&&":
if current_position == 0:
return (
'InvalidSyntax: "&&" cannot be used in Conditions.',
Exceptions.InvalidSyntax,
)
if current_position == 1:
truecase.append(current_command)
currentCommand = []
elif current_position == 2:
falsecase.append(current_command)
currentCommand = []
continue
current_command.append(i)
run_code, error = self.parser.parse_conditions(
self.parser.parse_condition_list(tc[1:condition_end_pos] + ["then"]),
self.analyse_command,
)
if error:
return run_code, error
if run_code:
for i in truecase:
res, error = self.analyse_command(i)
if error:
return res, error
if res is not None:
print(res)
else:
for i in falsecase:
res, error = self.analyse_command(i)
if error:
return res, error
if res is not None:
print(res)
return None, None
def parse_lambda_expression(self, tc):
# Lambda expression
# Syntax: lambda return (arguments) => function body && another expression
# Example: lambda void () => print("Hello!")
return_type = self.parser.parse_type_string(tc[1])
joined_expr = " ".join(tc[2:])
in_arguments_list = False
in_function_body = False
is_found_equal_sign = False
function_body = ""
arguments = ""
for i in joined_expr:
if in_function_body:
function_body += i
elif i == "=":
is_found_equal_sign = True
elif i == ">" and is_found_equal_sign:
is_found_equal_sign = False
in_function_body = True
elif i == "(":
in_arguments_list = True
elif i == ")":
in_arguments_list = False
elif in_arguments_list:
if is_found_equal_sign:
arguments += "="
arguments += i
is_found_equal_sign = False
existing_arguments: set = set()
def parse_function_argument(arg):
arg = arg.split()
arg[0] = self.parser.parse_type_string(arg[0])
if arg[1] in existing_arguments:
# Raise an error if an argument is defined multiple times
raise ValueError(f"Arguments named \"{arg[1]}\" is defined multiple times (in lambda expression). Please consider renaming the argument.")
if arg[1] in all_variable_name or arg[1] in all_function_name:
raise ValueError(f"an Argument named \"{arg[1]}\" is already defined either as a function or a variable. Please consider renaming the argument.")
existing_arguments.add(arg[1])
return arg
try:
args = arguments.split(",")
if not args[0]:
args = []
else:
arguments = list(map(parse_function_argument, args))
except ValueError as e:
# Handle the error if an argument is defined multiple times
return f"AlreadyDefined: {e}", Exceptions.AlreadyDefined
return LambdaExpr(arguments, return_type, function_body), None
def imports_map_methods(self, module):
print("[DEBUG] Importing a python file and manually mapping...")
for i in dir(module):
# Skip private functions
if i.startswith("_"):
continue
attr = getattr(module, i)
if isinstance(attr, type):
print("[INFO] Class found. Skipping...")
continue
# Check If the method is not a function
if not isinstance(attr, (type(executor.remove_string_postfix_prefix), type(self.imports_map_methods))):
continue
# Assemble the Python function object from the available information.
self.symbol_table.set_function(i, PythonFunctionObject(attr.__annotations__.get("return"), attr.__name__, attr.__code__.co_varnames, attr))
def handle_imports(self, tc: list) -> tuple:
if len(tc) < 2:
return not_enough_args_for_import_statement, Exceptions.NotDefinedException
if tc[1] == "all":
if len(tc) < 3:
return not_enough_args_for_import_statement, Exceptions.NotDefinedException
filepath, error = self.analyse_command(f"print({' '.join(tc[2:])})".split(), original_text=f"print({' '.join(tc[2:])})")
if error:
return filepath, error
# Dependencies importing
# if filepath in MODULES:
# modules_info = __import__(MODULES[filepath])
with open(filepath) as f:
for i in f.readlines():
self.analyse_command(i)
return None, None
filepath, error = self.analyse_command(f"print({' '.join(tc[1:])})".split())
if error:
return filepath, error
if filepath.startswith("python:"):
# If the user meant to import python files
filepath = filepath.removeprefix("python:")
if filepath.endswith(".py"):
filepath = filepath[:-3]
module = __import__(filepath)
if hasattr(module, "STORYSCRIPT_METHOD_MAPPING") and module.STORYSCRIPT_METHOD_MAPPING:
if not hasattr(module, "METHODS"):
return f"NotDefinedException: File {filepath} is trying to map methods, but no method mapping dictionary is found.", Exceptions.NotDefinedException
for i in module.METHODS:
method = module.METHODS[i]
if isinstance(method, dict):
try:
method = PythonFunctionObject(method["return_type"], method["name"], method["arguments"], method["action"])
self.symbol_table.set_function(i, method)
continue
except KeyError:
return (
f"InvalidValue: The method \"{i}\" dictionary is missing a key.",
Exceptions.InvalidValue,
)
return f"InvalidTypeException: Unknown method mapping type. (Error occurred while scanning method \"{i}\")", Exceptions.InvalidTypeException
else:
self.imports_map_methods(module)
return None, None
def handle_new_keyword(self, tc: list, original_text: str) -> tuple:
class_name = original_text.split("(")[0].strip().removeprefix("new").strip()
if class_name == "Dynamic":
return original_text, None
# new type[shape]
def finalize_shape(shape):
res, error = self.analyse_command(shape.split())
if error:
raise ValueError(res)
return int(res)
# Check if the declaration was `new int[5][5]` or `new int [5][5]`
arrtype = None
arr_size = []
if tc[1].endswith("]"):
arrtype = tc[1].split("[")[0]
arr_shape = tc[1][len(arrtype):][1:-1]
if arr_shape.find("][") != -1:
arr_shape = arr_shape.split("][")
else:
arr_shape = [arr_shape]
if len(arrShape) <= 0:
arr_size = []
else:
try:
arr_size = list(map(finalize_shape, arr_shape))
except ValueError as ve:
return str(ve), Exceptions.GeneralException
else:
# new int [5][5]
arrtype = tc[1]
arr_shape = " ".join(tc[2:])[1:-1]
if arr_shape.find("][") != -1:
arr_shape = arr_shape.split("][")
else:
arr_shape = [arr_shape]
if not arr_shape:
return "NotDefinedException: Array shape cannot be empty!", Exceptions.NotDefinedException
arr_size = list(map(finalize_shape, arr_shape))
init_val = b"0"
args = {"ndmin": len(arr_size)}
if arrtype == "int":
init_val = 0
args["dtype"] = "i"
elif arrtype == "float":
init_val = 0
args["dtype"] = "f"
elif arrtype == "string":
init_val = ""
args["dtype"] = "S"
elif arrtype == "bool":
init_val = False
return Array(
arrtype, arr_size,
np.array(
[init_val] * arr_size[-1],
**args
),
), None
def handle_variable_declaration(self, tc: list) -> tuple:
if tc[0] == "void":
return "InvalidTypeException: void is not eligible as a type for variable.", Exceptions.InvalidTypeException
try:
definedType = self.parser.parse_type_string(tc[0])
if tc[1] in all_variable_name:
return (
f"AlreadyDefined: a Variable {tc[1]} is already defined",
Exceptions.AlreadyDefined,
)
# Checking for variable naming violation
if not self.parser.check_naming_violation(tc[1]):
return (
"InvalidValue: a Variable name cannot start with digits or keywords.",
Exceptions.InvalidValue,
)
# var(0) a(1) =(2) 3(3)
value = " ".join(tc[3:])
is_dynamic = False
if value.startswith("new Dynamic"):
is_dynamic = True
value = value[13:-1]
res, error = self.analyse_command(value.split())
if error:
return res, error
if definedType == Types.Float:
if isinstance(res, values.Number):
res = float(res.value)
else:
res = float(res)
vartype = self.parser.parse_type_from_value(res)
if vartype == Types.Integer and definedType == Types.Float:
vartype = Types.Float
# Checks If existing variable type matches the New value type
if tc[0] != "var" and definedType != vartype and not is_dynamic and res not in {"null", None}:
return (
f"{mismatch_type} Expected {definedType.value}, found {vartype.value}.",
Exceptions.InvalidValue,
)
if vartype == Exceptions.InvalidSyntax:
return "InvalidSyntax: Invalid value", Exceptions.InvalidSyntax
if vartype == Types.Action:
self.symbol_table.set_function(tc[1], res)
if res is None:
res = "null"
self.symbol_table.set_variable(tc[1], res, definedType)
return None, None
except IndexError:
# var(0) a(1)
if tc[0] == "var":
return (
"InvalidSyntax: Initial value needed for var keyword",
Exceptions.InvalidSyntax,
)
vartype = self.parser.parse_type_string(tc[0])
if vartype == Exceptions.InvalidSyntax:
return "InvalidSyntax: Invalid type", Exceptions.InvalidSyntax
self.symbol_table.set_variable(tc[1], "null", vartype)
return None, None
def handle_array_declaration(self, tc: list, all_variable_name: list) -> tuple:
# Checking for variable naming violation
if not self.parser.check_naming_violation(tc[1]):
return (
"InvalidValue: a Variable name cannot start with digits or keywords.",
Exceptions.InvalidValue,
)
if tc[1] in all_variable_name:
return (
f"AlreadyDefined: a Variable {tc[1]} is already defined",
Exceptions.AlreadyDefined,
)
# int[] arr = new int [5][5]
res, error = self.analyse_command(tc[3:])
if error:
return res, error
self.symbol_table.set_variable(tc[1], res, Types.Array)
return None, None
def handle_base_keywords(self, tc: list, original_text: str) -> tuple:
if tc[0] in PRIMITIVE_TYPE:
return self.handle_variable_declaration(tc)
if tc[0] in LISTDECLARE_KEYW:
return self.handle_array_declaration(tc, all_variable_name)
if tc[0] == "if":
return self.if_else_statement(tc)
if tc[0] == "throw":
# Go to the Throw keyword function
return self.parser.throw_keyword(tc, self.analyse_command)
if tc[0] == "del":
if tc[1] in all_variable_name:
self.symbol_table.DeleteVariable(tc[1])
return None, None
if tc[1] in all_function_name:
self.symbol_table.DeleteFunction(tc[1])
return None, None
return (
"InvalidValue: The Input is not a variable.",
Exceptions.InvalidValue,
)
if tc[0] == "loopfor":
return self.loopfor_statement(tc)
if tc[0] == "switch":
return self.switch_case_statement(tc)
if tc[0] == "?":
return self.ternary_operator(tc)
if tc[0] == "import":
return self.handle_imports(tc)
if tc[0] == "lambda":
return self.parse_lambda_expression(tc)
if tc[0] == "new":
return self.handle_new_keyword(tc, original_text)
if tc[0] == "null":
return "null", None
return (
"NotImplementedException: This feature is not implemented",
Exceptions.NotImplementedException,
)
def handle_array_setting_variable_methods(self, functioncall: list, function_name: str) -> tuple:
# Get the arguments list by splitting and trim the string
arguments = list(map(lambda msg: msg.strip(), self.parser.split_arguments(self.parser.parse_argument(functioncall[1:], "."))))
index = []
value = None
try:
for i in arguments:
if i.startswith("value") and i.find("=") != -1:
if value:
return "AlreadyDefined: value arguments is already defined. you cannot define it again.", Exceptions.AlreadyDefined
value, error = self.analyse_command(i.split("=")[1:], original_text=" ".join(i.split("=")[1:]))
if error:
return value, error
else:
index_num, error = self.analyse_command([i], original_text=i)
if error:
return index_num, error
index.append(int(index_num))
except ValueError as ve:
return f"InvalidTypeException: {ve}", Exceptions.InvalidTypeException
if not value:
return "NotDefinedException: value arguments is required but not defined.", Exceptions.NotDefinedException
old_data = self.symbol_table.GetVariable(functioncall[0])[1]
new_data = old_data.data
arrdups = []
try:
if len(index) > 1:
# Multi-dimensional array accessing
for i in index:
if arrdups == []:
arrdups.append(new_data[i])
else:
content = arrdups[-1][i]
if not isinstance(content, np.ndarray):
break
arrdups.append(content)
if function_name == "AddOnIndex":
arrdups[-1][index[-1]] += value
else:
arrdups[-1][index[-1]] = value
# merge all array duplications into one array duplication
for v, i in zip(enumerate(arrdups), index):
if v[0] + 1 >= len(arrdups) - 1:
break
v[1][i] = arrdups[v[0] + 1]
arrdups[v[0]] = v[1]
else:
if function_name == "AddOnIndex":
new_data[index] += value
else:
new_data[index] = value
arrdups.append(new_data)
except IndexError as ie:
return f"InvalidIndexException: {ie}", Exceptions.InvalidIndexException
self.symbol_table.set_variable(functioncall[0], Array(old_data.dtype, old_data.shape, arrdups[0]), Types.Array)
return None, None
def handle_array_variable_methods(self, functioncall: list, function_name: str) -> tuple:
if function_name == "Get":
argument, error = self.analyse_command([self.parser.parse_argument(functioncall[1:], ".")])
if error:
return argument, error
if not isinstance(argument, (values.Number, int)):
return f"InvalidTypeException: Expected argument #1 to be Number, Found {type(argument).__name__}", Exceptions.InvalidTypeException
try:
data = self.symbol_table.GetVariable(functioncall[0])[1].data[int(argument)]
if isinstance(data, np.intc):
data = int(data)
elif isinstance(data, np.str_):
data = str(data)
elif isinstance(data, np.float64):
data = float(data)
return data, None
except IndexError as ie:
return f"InvalidIndexException: {ie}", Exceptions.InvalidIndexException
if function_name in {"Set", "AddOnIndex"}:
return self.handle_array_setting_variable_methods(functioncall, function_name)
if function_name == "Length":
return len(self.symbol_table.GetVariable(functioncall[0])[1].data), None
def handle_variable_methods(self, functioncall: list, function_name: str) -> tuple:
vartype = self.symbol_table.get_variable_type(functioncall[0])
if vartype == Types.Array:
return self.handle_array_variable_methods(functioncall, function_name)
if vartype == Types.Integer and function_name == "ToString":
value = self.symbol_table.GetVariable(functioncall[0].strip())[1]
if value == "null":
return "InvalidTypeException: Cannot convert \"null\" (with type \"void\") to string!", Exceptions.InvalidTypeException
return f"\"{value}\"", None
def primitive_type_functions(self, functioncall: list, function_name: str) -> tuple:
error = None
def argument_resolver(msg):
arg = msg.strip()
res, err = self.analyse_command([arg], original_text=arg)
# If there was an error, stop the argument parsing
if err:
nonlocal error
error = (res, err)
raise ValueError
return res
arguments = None
try:
arguments = list(map(argument_resolver, self.parser.split_arguments(self.parser.parse_argument(functioncall[1:], "."))))
except ValueError:
return error
if functioncall[0] == "int":
if function_name == "FromString":
argument = arguments[0]
if isinstance(argument, values.Number):
return "InvalidTypeException: Expected argument #1 to be String, Found number.", Exceptions.InvalidTypeException
if isinstance(argument, str) and argument.startswith('"') \
and argument.endswith('"'):
argument = argument[1:-1]
try:
return int(argument), None
except ValueError as e:
return f"InvalidValue: {e}", Exceptions.InvalidValue
if function_name == "FromFloat":
argument = arguments[0]
result = argument
if not isinstance(argument, (values.Number, int, float)):
return f"InvalidTypeException: Expected argument #1 to be Number, Found {type(argument).__name__}", Exceptions.InvalidTypeException
if isinstance(argument, values.Number):
result = int(argument.value)
else:
result = int(argument)
return result, None
# Check If a float is a full number.
if function_name == "IsFloatFullNumber":
argument = arguments[0]
if executor.check_is_float_full_number(argument):
return "true", None
return "false", None
if functioncall[0] == "string":
if function_name in {"FromInt", "FromFloat"}:
if not isinstance(arguments[0], values.Number):
try:
int(arguments[0])
except ValueError:
return f"InvalidTypeException: Expected argument #1 to be Number, Found {type(argument).__name__}", Exceptions.InvalidTypeException
return f"\"{arguments[0]}\"", None
if function_name == "Substring":
return arguments[0][int(arguments[1]):int(arguments[2])], None
if function_name == "Trim":
msg = executor.safe_list_get(arguments, 0)
if msg is None:
return "NotDefinedException: Undefined argument #1 \"msg\" in string.Trim method call.", Exceptions.NotDefinedException
msg = executor.remove_string_postfix_prefix(msg, '"')
if executor.safe_list_get(arguments, 1) is not None:
# string.Trim(" hewwo ", " ")
return f"\"{msg.strip(arguments[1])}\"", None
return f"\"{msg.strip()}\"", None
def handle_function(self, functioncall: list, original_text: str) -> tuple:
# Parse the function name. (Space safe)
function_name = functioncall[1].split("(")[0]
if functioncall[0].strip() in PRIMITIVE_TYPE:
return self.primitive_type_functions(functioncall, function_name)
if functioncall[0].strip() in all_variable_name:
return self.handle_variable_methods(functioncall, function_name)
res, error = self.parser.parse_expression(original_text)
return res, error
def analyse_command(self, tc: list, original_text: str = None) -> tuple:
if len(tc) == 0 or tc[0] == "//":
return None, None
if not original_text:
original_text = " ".join(tc[0:])
global all_variable_name
global all_function_name
all_variable_name = self.symbol_table.get_all_variable_name()
all_function_name = self.symbol_table.get_all_function_name()
functioncall: list = original_text.split(".")
function_name = original_text.split("(")[0].strip()
if tc[0] in all_variable_name:
try:
return self.variable_setting(tc, original_text)
except IndexError:
var = self.symbol_table.GetVariable(tc[0])[1]
if isinstance(var, str) and var.startswith("new Dynamic ("):
var = var.removeprefix("new Dynamic (")
if var.endswith(")"):
var = var[:-1]
return var, None
elif function_name == "typeof":
value = self.parser.parse_argument(original_text)
res, error = self.analyse_command(value.split(), original_text=value)
if error:
return res, error
res = self.parser.parse_type_from_value(res)
return f'"{res.value}"', None
elif function_name == "print":
value = self.parser.parse_argument(original_text)
res, error = self.analyse_command(value.split())
if error:
return res, error
res = str(res)
if res.startswith("new Dynamic ("):
res = executor.remove_string_postfix(res.removeprefix("new Dynamic").strip().removeprefix("("), ")")
res = executor.remove_string_postfix_prefix(res, '"')
print(res)
return None, None
elif function_name == "input":
value = self.parser.parse_argument(original_text)
if value.startswith("new Dynamic ("):
value = value.removeprefix("new Dynamic (")[:-1]
value, error = self.analyse_command(value.split(), original_text=value)
if error:
return value, error
if isinstance(value, str):
if value.startswith('"'):
value = value[1:]
if value.endswith('"'):
value = value[:-1]
if value is None:
value = ""
res = input(str(value)) # Recieve the Input from the User
return f'"{res}"', None # Return the Recieved Input
elif function_name == "exit":
value, error = self.analyse_command(self.parser.parse_argument(original_text))
if error:
return value, error
if isinstance(value, str):
value = executor.remove_string_postfix_prefix(value, '"')
if value is None:
value = 0
return f"EXITREQUEST {value}", None
elif tc[0] in BASE_KEYWORDS:
return self.handle_base_keywords(tc, original_text)
elif function_name in all_function_name:
custom_symbol_table = self.symbol_table
function_object = self.symbol_table.get_function(function_name)
# Parse arguments
arguments = self.parser.split_arguments(self.parser.parse_argument(original_text))
argpos = 0
if isinstance(function_object, LambdaExpr):
for value, name in zip(arguments, function_object.arguments):
res, error = self.analyse_command(value, original_text=value)
if error:
return res, error
valtype = self.parser.parse_type_from_value(res)
if isinstance(name[0], str):
name[0] = self.parser.parse_type_string(name[0])
if valtype != name[0]:
return f"InvalidTypeException: Invalid type, Expected {name[0].value} for argument #{argpos}, found {valtype.value}.", Exceptions.InvalidTypeException
custom_symbol_table.set_variable(name[1], res, name[0])
argpos += 1
else:
args = []
for value in arguments:
res, error = self.analyse_command(value, original_text=value)
if error:
return res, error
if isinstance(res, values.Number):
res = res.value
valtype = self.parser.parse_type_from_value(res)
args.append(res)
try:
res = function_object.function_body(*args)
return res, None
except Exception as e: # skipcq PYL-W0703
print_exc()
return f"(Python Exception): {e}", "(Python Exception)"
flex = Lexer(custom_symbol_table, self.parser)
res, error = flex.analyse_command(function_object.function_body.split(), original_text=function_object.function_body)
for name in function_object.arguments:
custom_symbol_table.DeleteVariable(name[1])
if error:
return res, error
valtype = self.parser.parse_type_from_value(res)
if valtype != function_object.return_type:
return f"InvalidTypeException: Return value mismatched. Expected {function_object.return_type.value}, found {valtype.value}.", Exceptions.InvalidTypeException
return res, error
elif len(functioncall) > 1:
return self.handle_function(functioncall, original_text)
else:
res, error = self.parser.parse_expression(original_text)
return res, error