From 2616c34a33a0bb7f85a6a8241e6d8e33b22810dd Mon Sep 17 00:00:00 2001 From: sina-byn Date: Wed, 12 Jul 2023 11:23:29 +0330 Subject: [PATCH 1/6] .gitignore Added --- .gitignore | 1 + 1 file changed, 1 insertion(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ed8ebf5 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__ \ No newline at end of file From ea0bf52e7cffa40cbd1d3c8728c155c5ca0e1547 Mon Sep 17 00:00:00 2001 From: sina-byn Date: Wed, 12 Jul 2023 11:26:25 +0330 Subject: [PATCH 2/6] General Refactoring --- interpreter.py | 88 +++++++++++++++++++++++--------------------------- lexer.py | 54 +++++++++++++++---------------- parse.py | 71 +++++++++++++++++++--------------------- shell.py | 7 +++- tokens.py | 31 +++++++++++------- 5 files changed, 124 insertions(+), 127 deletions(-) diff --git a/interpreter.py b/interpreter.py index 6a4c264..3a3499f 100644 --- a/interpreter.py +++ b/interpreter.py @@ -1,5 +1,6 @@ from tokens import Integer, Float, Reserved + class Interpreter: def __init__(self, tree, base): self.tree = tree @@ -7,66 +8,68 @@ def __init__(self, tree, base): def read_INT(self, value): return int(value) - + def read_FLT(self, value): return float(value) - + def read_VAR(self, id): variable = self.data.read(id) variable_type = variable.type - return getattr(self, f"read_{variable_type}")(variable.value) + return getattr(self, f'read_{variable_type}')(variable.value) def compute_bin(self, left, op, right): - left_type = "VAR" if str(left.type).startswith("VAR") else str(left.type) - right_type = "VAR" if str(right.type).startswith("VAR") else str(right.type) - if op.value == "=": - left.type = f"VAR({right_type})" + left_type = 'VAR' if str(left.type).startswith( + 'VAR') else str(left.type) + right_type = 'VAR' if str(right.type).startswith( + 'VAR') else str(right.type) + if op.value == '=': + left.type = f'VAR({right_type})' self.data.write(left, right) return self.data.read_all() - left = getattr(self, f"read_{left_type}")(left.value) - right = getattr(self, f"read_{right_type}")(right.value) + left = getattr(self, f'read_{left_type}')(left.value) + right = getattr(self, f'read_{right_type}')(right.value) - if op.value == "+": + if op.value == '+': output = left + right - elif op.value == "-": + elif op.value == '-': output = left - right - elif op.value == "*": + elif op.value == '*': output = left * right - elif op.value == "/": + elif op.value == '/': output = left / right - elif op.value == ">": + elif op.value == '>': output = 1 if left > right else 0 - elif op.value == ">=": + elif op.value == '>=': output = 1 if left >= right else 0 - elif op.value == "<": + elif op.value == '<': output = 1 if left < right else 0 - elif op.value == "<=": + elif op.value == '<=': output = 1 if left <= right else 0 - elif op.value == "?=": + elif op.value == '?=': output = 1 if left == right else 0 - elif op.value == "and": + elif op.value == 'and': output = 1 if left and right else 0 - elif op.value == "or": + elif op.value == 'or': output = 1 if left or right else 0 - return Integer(output) if (left_type == "INT" and right_type == "INT") else Float(output) - + return Integer(output) if (left_type == 'INT' and right_type == 'INT') else Float(output) def compute_unary(self, operator, operand): - operand_type = "VAR" if str(operand.type).startswith("VAR") else str(operand.type) + operand_type = 'VAR' if str(operand.type).startswith( + 'VAR') else str(operand.type) - operand = getattr(self, f"read_{operand_type}")(operand.value) + operand = getattr(self, f'read_{operand_type}')(operand.value) - if operator.value == "+": + if operator.value == '+': output = +operand - elif operator.value == "-": + elif operator.value == '-': output = -operand - elif operator.value == "not": + elif operator.value == 'not': output = 1 if not operand else 0 - - return Integer(output) if (operand_type == "INT") else Float(output) + + return Integer(output) if (operand_type == 'INT') else Float(output) def interpret(self, tree=None): if tree is None: @@ -74,54 +77,43 @@ def interpret(self, tree=None): if isinstance(tree, list): if isinstance(tree[0], Reserved): - if tree[0].value == "if": + if tree[0].value == 'if': for idx, condition in enumerate(tree[1][0]): evaluation = self.interpret(condition) if evaluation.value == 1: return self.interpret(tree[1][1][idx]) - + if len(tree[1]) == 3: return self.interpret(tree[1][2]) - + else: return - elif tree[0].value == "while": + elif tree[0].value == 'while': condition = self.interpret(tree[1][0]) - + while condition.value == 1: - # Doing the action print(self.interpret(tree[1][1])) - - # Checking the condition condition = self.interpret(tree[1][0]) - + return - # Unary operation if isinstance(tree, list) and len(tree) == 2: expression = tree[1] if isinstance(expression, list): expression = self.interpret(expression) return self.compute_unary(tree[0], expression) - - # No operation + elif not isinstance(tree, list): return tree - - else: - # Post order traversal - # Evaluating left subtree + else: left_node = tree[0] if isinstance(left_node, list): left_node = self.interpret(left_node) - # Evaluating right subtree right_node = tree[2] if isinstance(right_node, list): right_node = self.interpret(right_node) - # Evaluating root node operator = tree[1] return self.compute_bin(left_node, operator, right_node) - diff --git a/lexer.py b/lexer.py index babc99e..06b263d 100644 --- a/lexer.py +++ b/lexer.py @@ -1,18 +1,17 @@ from tokens import Integer, Float, Operation, Declaration, Variable, Boolean, Comparison, Reserved +import string -# make varname = 50 class Lexer: - # while do - digits = "0123456789" - letters = "abcdefghijklmnopqrstuvwxyz" - operations = "+-/*()=" - stopwords = [" "] - declarations = ["make"] - boolean = ["and", "or", "not"] - comparisons = [">", "<", ">=", "<=", "?="] - specialCharacters = "><=?" - reserved = ["if", "elif", "else", "do", "while"] + digits = string.digits + letters = string.ascii_lowercase + operations = '+-/*()=' + stopwords = [' '] + declarations = ['make'] + boolean = ['and', 'or', 'not'] + comparisons = ['>', '<', '>=', '<=', '?='] + specialCharacters = '><=?' + reserved = ['if', 'elif', 'else', 'do', 'while'] def __init__(self, text): self.text = text @@ -20,16 +19,16 @@ def __init__(self, text): self.tokens = [] self.char = self.text[self.idx] self.token = None - + def tokenize(self): while self.idx < len(self.text): if self.char in Lexer.digits: self.token = self.extract_number() - + elif self.char in Lexer.operations: self.token = Operation(self.char) self.move() - + elif self.char in Lexer.stopwords: self.move() continue @@ -45,40 +44,39 @@ def tokenize(self): self.token = Reserved(word) else: self.token = Variable(word) - + elif self.char in Lexer.specialCharacters: - comparisonOperator = "" + comparisonOperator = '' while self.char in Lexer.specialCharacters and self.idx < len(self.text): comparisonOperator += self.char self.move() - + self.token = Comparison(comparisonOperator) - + self.tokens.append(self.token) - + return self.tokens def extract_number(self): - number = "" + number = '' isFloat = False - while (self.char in Lexer.digits or self.char == ".") and (self.idx < len(self.text)): - if self.char == ".": + while (self.char in Lexer.digits or self.char == '.') and (self.idx < len(self.text)): + if self.char == '.': isFloat = True number += self.char self.move() - + return Integer(number) if not isFloat else Float(number) - + def extract_word(self): - word = "" + word = '' while self.char in Lexer.letters and self.idx < len(self.text): word += self.char self.move() - + return word - + def move(self): self.idx += 1 if self.idx < len(self.text): self.char = self.text[self.idx] - diff --git a/parse.py b/parse.py index 5fb9f96..1e04f95 100644 --- a/parse.py +++ b/parse.py @@ -5,32 +5,32 @@ def __init__(self, tokens): self.token = self.tokens[self.idx] def factor(self): - if self.token.type == "INT" or self.token.type == "FLT": + if self.token.type == 'INT' or self.token.type == 'FLT': return self.token - elif self.token.value == "(": + elif self.token.value == '(': self.move() expression = self.boolean_expression() return expression - elif self.token.value == "not": + elif self.token.value == 'not': operator = self.token self.move() output = [operator, self.boolean_expression()] return output - - elif self.token.type.startswith("VAR"): + + elif self.token.type.startswith('VAR'): return self.token - elif self.token.value == "+" or self.token.value == "-": + elif self.token.value == '+' or self.token.value == '-': operator = self.token self.move() operand = self.boolean_expression() return [operator, operand] - + def term(self): left_node = self.factor() self.move() - - while self.token.value == "*" or self.token.value == "/": + + while self.token.value == '*' or self.token.value == '/': operator = self.token self.move() right_node = self.factor() @@ -40,18 +40,16 @@ def term(self): return left_node - # := and | or | not - def if_statement(self): self.move() condition = self.boolean_expression() - if self.token.value == "do": + if self.token.value == 'do': self.move() action = self.statement() - + return condition, action - elif self.tokens[self.idx-1].value == "do": + elif self.tokens[self.idx-1].value == 'do': action = self.statement() return condition, action @@ -63,12 +61,12 @@ def if_statements(self): conditions.append(if_statement[0]) actions.append(if_statement[1]) - while self.token.value == "elif": + while self.token.value == 'elif': if_statement = self.if_statement() conditions.append(if_statement[0]) actions.append(if_statement[1]) - if self.token.value == "else": + if self.token.value == 'else': self.move() self.move() else_action = self.statement() @@ -76,34 +74,34 @@ def if_statements(self): return [conditions, actions, else_action] return [conditions, actions] - + def while_statement(self): self.move() condition = self.boolean_expression() - - if self.token.value == "do": + + if self.token.value == 'do': self.move() action = self.statement() return [condition, action] - - elif self.tokens[self.idx-1].value == "do": + + elif self.tokens[self.idx-1].value == 'do': action = self.statement() return [condition, action] def comp_expression(self): left_node = self.expression() - while self.token.type == "COMP": + while self.token.type == 'COMP': operator = self.token self.move() right_node = self.expression() left_node = [left_node, operator, right_node] - + return left_node def boolean_expression(self): left_node = self.comp_expression() - while self.token.value == "and" or self.token.value == "or": + while self.token.value == 'and' or self.token.value == 'or': operator = self.token self.move() right_node = self.comp_expression() @@ -113,40 +111,38 @@ def boolean_expression(self): def expression(self): left_node = self.term() - while self.token.value == "+" or self.token.value == "-": + while self.token.value == '+' or self.token.value == '-': operator = self.token self.move() right_node = self.term() left_node = [left_node, operator, right_node] return left_node - + def variable(self): - if self.token.type.startswith("VAR"): + if self.token.type.startswith('VAR'): return self.token - + def statement(self): - if self.token.type == "DECL": - # Variable assignment + if self.token.type == 'DECL': self.move() left_node = self.variable() self.move() - if self.token.value == "=": + if self.token.value == '=': operation = self.token self.move() right_node = self.boolean_expression() return [left_node, operation, right_node] - elif self.token.type == "INT" or self.token.type == "FLT" or self.token.type == "OP" or self.token.value == "not": - # Arithmetic expression + elif self.token.type == 'INT' or self.token.type == 'FLT' or self.token.type == 'OP' or self.token.value == 'not': return self.boolean_expression() - - elif self.token.value == "if": + + elif self.token.value == 'if': return [self.token, self.if_statements()] - elif self.token.value == "while": + elif self.token.value == 'while': return [self.token, self.while_statement()] - + def parse(self): return self.statement() @@ -154,4 +150,3 @@ def move(self): self.idx += 1 if self.idx < len(self.tokens): self.token = self.tokens[self.idx] - diff --git a/shell.py b/shell.py index 1f48deb..9d6448d 100644 --- a/shell.py +++ b/shell.py @@ -6,7 +6,12 @@ base = Data() while True: - text = input("ShadowScript: ") + text = input('ShadowScript: ').strip() + + if text == '': + continue + elif text == 'exit': + break tokenizer = Lexer(text) tokens = tokenizer.tokenize() diff --git a/tokens.py b/tokens.py index 817c3ba..dc9d910 100644 --- a/tokens.py +++ b/tokens.py @@ -2,39 +2,46 @@ class Token: def __init__(self, type, value): self.type = type self.value = value - + def __repr__(self): return str(self.value) - + + class Integer(Token): def __init__(self, value): - super().__init__("INT", value) + super().__init__('INT', value) + class Float(Token): def __init__(self, value): - super().__init__("FLT", value) + super().__init__('FLT', value) + class Operation(Token): def __init__(self, value): - super().__init__("OP", value) + super().__init__('OP', value) + class Declaration(Token): def __init__(self, value): - super().__init__("DECL", value) + super().__init__('DECL', value) + class Variable(Token): def __init__(self, value): - super().__init__("VAR(?)", value) # Variable name, VAR, data type - # make a = 5 # VAR(?) + super().__init__('VAR(?)', value) + class Boolean(Token): def __init__(self, value): - super().__init__("BOOL", value) - + super().__init__('BOOL', value) + + class Comparison(Token): def __init__(self, value): - super().__init__("COMP", value) + super().__init__('COMP', value) + class Reserved(Token): def __init__(self, value): - super().__init__("RSV", value) \ No newline at end of file + super().__init__('RSV', value) From 8e3f016aa16ec343cf81182ab5b1d6b287f13bbe Mon Sep 17 00:00:00 2001 From: sina-byn Date: Wed, 12 Jul 2023 11:38:24 +0330 Subject: [PATCH 3/6] includes Method Added --- data.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/data.py b/data.py index 5ee2755..93efc0b 100644 --- a/data.py +++ b/data.py @@ -10,4 +10,10 @@ def read_all(self): def write(self, variable, expression): variable_name = variable.value - self.variables[variable_name] = expression \ No newline at end of file + self.variables[variable_name] = expression + + def includes(self, id): + if id in self.variables: + return True + + return False \ No newline at end of file From 7eee4b70f26b86c854ffb8e23d54ce0e9f047fd1 Mon Sep 17 00:00:00 2001 From: sina-byn Date: Wed, 12 Jul 2023 11:39:12 +0330 Subject: [PATCH 4/6] Variable Value Re-assignment Added --- parse.py | 13 ++++++++++++- shell.py | 2 +- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/parse.py b/parse.py index 1e04f95..51d2239 100644 --- a/parse.py +++ b/parse.py @@ -1,5 +1,6 @@ class Parser: - def __init__(self, tokens): + def __init__(self, tokens, base): + self.data = base self.tokens = tokens self.idx = 0 self.token = self.tokens[self.idx] @@ -134,6 +135,16 @@ def statement(self): right_node = self.boolean_expression() return [left_node, operation, right_node] + + elif self.token.type.startswith ('VAR'): + if self.data.includes(self.token.value): + left_node = self.variable() + self.move() + operation = self.token + self.move() + if operation.value == '=': + right_node = self.boolean_expression() + return [left_node, operation, right_node] elif self.token.type == 'INT' or self.token.type == 'FLT' or self.token.type == 'OP' or self.token.value == 'not': return self.boolean_expression() diff --git a/shell.py b/shell.py index 9d6448d..097e523 100644 --- a/shell.py +++ b/shell.py @@ -16,7 +16,7 @@ tokenizer = Lexer(text) tokens = tokenizer.tokenize() - parser = Parser(tokens) + parser = Parser(tokens, base) tree = parser.parse() interpreter = Interpreter(tree, base) From d3c67205fc93d6e531b2a222242aba47039269b2 Mon Sep 17 00:00:00 2001 From: sina-byn Date: Wed, 12 Jul 2023 11:48:46 +0330 Subject: [PATCH 5/6] File Executor Script Added --- execute.py | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 execute.py diff --git a/execute.py b/execute.py new file mode 100644 index 0000000..bdf20b4 --- /dev/null +++ b/execute.py @@ -0,0 +1,50 @@ +from lexer import Lexer +from parse import Parser +from interpreter import Interpreter +from data import Data +import argparse + + +class Executor: + def __init__(self): + self.data = Data() + + def execute(self, path): + if not path.endswith('.shs'): + print('Error: Invalid file extension. Supported Extensions:\n -.shs') + return + + try: + with open(path) as file: + lines = file.readlines() + for idx, line in enumerate(lines): + text = line.strip() + if text == '': + continue + elif text.strip() == 'exit': + break + + tokenizer = Lexer(text) + tokens = tokenizer.tokenize() + + parser = Parser(tokens, self.data) + tree = parser.parse() + + interpreter = Interpreter(tree, self.data) + result = interpreter.interpret() + + if result is not None: + print(f'{path}:{idx + 1}:', result) + + except: + print(f'Error: File {path} not found.') + + +if __name__ == "__main__": + description = "executor program for ShadowScript" + parser = argparse.ArgumentParser(description=description) + parser.add_argument("path", help="file path") + args = parser.parse_args() + + executor = Executor() + executor.execute(args.path) From 9f6f17963325e36fb2e92cc8d73748e2a1947bb8 Mon Sep 17 00:00:00 2001 From: sina-byn Date: Wed, 12 Jul 2023 12:32:03 +0330 Subject: [PATCH 6/6] Variable Re-declaration Error Added --- parse.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/parse.py b/parse.py index 51d2239..01c5184 100644 --- a/parse.py +++ b/parse.py @@ -128,15 +128,18 @@ def statement(self): if self.token.type == 'DECL': self.move() left_node = self.variable() - self.move() - if self.token.value == '=': - operation = self.token + if not self.data.includes(self.token.value): self.move() - right_node = self.boolean_expression() + if self.token.value == '=': + operation = self.token + self.move() + right_node = self.boolean_expression() - return [left_node, operation, right_node] - - elif self.token.type.startswith ('VAR'): + return [left_node, operation, right_node] + else: + return f'Variable {left_node} has already been declared' + + elif self.token.type.startswith('VAR'): if self.data.includes(self.token.value): left_node = self.variable() self.move()