commit ed97118df0a0701e0436cb24e0fefa27fceacb19 Author: edraft Date: Fri May 22 22:08:37 2020 +0200 added structure & interpreter & lexer & first ast stucture diff --git a/doc/definition.txt b/doc/definition.txt new file mode 100644 index 0000000..2f92369 --- /dev/null +++ b/doc/definition.txt @@ -0,0 +1,29 @@ +keywords: + builtin-functions: + output + input + range + length + + pass + if + elseif + else + + +global vars: + error: + code + msg + + sys: + os_name + +data types: + empty + any + string + number + bool + list + dict \ No newline at end of file diff --git a/doc/error_codes.txt b/doc/error_codes.txt new file mode 100644 index 0000000..c7f6c8d --- /dev/null +++ b/doc/error_codes.txt @@ -0,0 +1,13 @@ +Interpreter: + 1.0 Start failed + 1.1 File not found + +Runtime: + 2.0 Unknown keyword + 2.1 Unknown type + 2.2 Unknown variable + 2.3 Unknown function + 2.4 Unknown class + 2.5 Unknown library + 2.6 Access error: no export + 2.7 Expression error \ No newline at end of file diff --git a/doc/target/main.bl b/doc/target/main.bl new file mode 100644 index 0000000..c262583 --- /dev/null +++ b/doc/target/main.bl @@ -0,0 +1,16 @@ +use test1 from Tests; +use test2 as test3 from Tests; + +lib Main { + class Program { + func Main(args: list): void { + test_a = test1(); + test_a.dec_vars(); + test_a.is_error(); + if (!error) { + test_b = test3(); + test3.continue(); + } + } + } +} \ No newline at end of file diff --git a/doc/target/test.bl b/doc/target/test.bl new file mode 100644 index 0000000..618a4b4 --- /dev/null +++ b/doc/target/test.bl @@ -0,0 +1,33 @@ +lib Tests +{ + /* + declaration of some tests + */ + export class test1 + { + export test_string: string = 'Hello'; + export test_string_2: string = "Hello World"; + export test_num: num = 1; + export test_num_2: num = 1.0; + export test_num_3: num = this.test_num + this.test_num_2; + + export func dec_vars(): void + { + test_bool: bool = true; + test_bool_2: bool = false; + test_bool_3: bool = test_bool != test_bool_2; # true + } + + export is_error(): bool + { + if (error != empty) + { + output(error.code + ' ' + error.message); + } + else + { + output('continue'); + } + } + } +} \ No newline at end of file diff --git a/doc/target/test2.bl b/doc/target/test2.bl new file mode 100644 index 0000000..13997fe --- /dev/null +++ b/doc/target/test2.bl @@ -0,0 +1,12 @@ +lib Tests { + export class test2 { + string_a = string1(); + export func continue() { + input(string_a.string1 + ': '); + } + } + + class strings { + public string1 = "hello world"; + } +} \ No newline at end of file diff --git a/doc/test.bl b/doc/test.bl new file mode 100644 index 0000000..d69fca2 --- /dev/null +++ b/doc/test.bl @@ -0,0 +1,31 @@ +// hi1 +# hi2 +/* + hi3 +*/ + +lib Main { + class Program { + func Main() { + testBool: bool; + testEmpty: emptyType = empty; + output('Hello World'); + output(66); + output(3 + 3); + test: string = input('# '); + output(test); + output(false); + + if (testBool != empty) { + output(testEmpty); + } + test1234(range(0, 10)); + } + + public func test1234(param: list) { + for i in range(0, length(param)) { + output(i); + } + } + } +} \ No newline at end of file diff --git a/src/Interpreter/Interpreter.py b/src/Interpreter/Interpreter.py new file mode 100644 index 0000000..2c4a044 --- /dev/null +++ b/src/Interpreter/Interpreter.py @@ -0,0 +1,28 @@ +from Interpreter.Validator import Validator +from Interpreter.Lexer import Lexer +from Interpreter.Parser import Parser +from Interpreter.Repo import Repo +from Interpreter.Utils import Utils + + +class Interpreter: + + def __init__(self, repo: Repo, utils: Utils) -> None: + self.__repo = repo + self.__utils = utils + self.__lexer = Lexer(repo, utils) + self.__parser = Parser(repo, utils) + self.__validator = Validator(repo, utils) + + def interpret(self, line: str) -> bool: + toks = [] + if self.__repo.error is None: + toks = self.__lexer.tokenize(line) + + if self.__repo.error is None: + self.__parser.parse(toks) + + if self.__repo.error is None: + self.__validator.validate() + + return self.__repo.error is None diff --git a/src/Interpreter/Lexer.py b/src/Interpreter/Lexer.py new file mode 100644 index 0000000..afe7db2 --- /dev/null +++ b/src/Interpreter/Lexer.py @@ -0,0 +1,139 @@ +from Interpreter.Repo import Repo +from Interpreter.Utils import Utils +from Models.Token import Token + + +class Lexer: + + def __init__(self, repo: Repo, utils: Utils) -> None: + self.__repo = repo + self.__utils = utils + + self.__ml_comment = False + self.__toks = [] + + def __add_tok(self, value: str, type: str) -> None: + if value != '': + if type == 'word': + if value in self.__repo.keywords: + type = 'keyword' + + elif value in self.__repo.types: + type = 'type' + + elif value in self.__repo.bool_values: + type = 'bool' + + elif value == 'empty': + type = 'emptyType' + + else: + type = 'name' + + self.__toks.append(Token(type, value)) + + def tokenize(self, line: str) -> list: + self.__toks = [] + word = '' + ol_comment = False + is_string1 = False # 'hello' + is_string2 = False # "hello" + is_number = False + is_expr_char = False + + for i in range(0, len(line)): + c = line[i] + # ignore comments and spaces + if not ol_comment and not self.__ml_comment: + + # end of number + if not c.isdigit() and is_number: + self.__add_tok(word, 'number') + word = '' + is_number = False + + # end of expression char + if c not in self.__repo.expr_chars and is_expr_char: + self.__add_tok(word, 'expr_char') + word = '' + is_expr_char = False + + # comment filtering + if c == '#' and not is_string1 and not is_string2: + ol_comment = True + + elif c == '/' and line[+1] == '/': + ol_comment = True + + elif c == '/' and line[+1] == '*': + self.__ml_comment = True + i += 2 + + # begin of is_string1 + elif c == '\'' and not is_string1: + is_string1 = True + word = '' + + # end of is_string1 + elif c == '\'' and is_string1: + is_string1 = False + self.__add_tok(word, 'string') + word = '' + + # begin of is_string2 + elif c == '\"' and not is_string2: + is_string2 = True + word = '' + + # end of is_string2 + elif c == '\"' and is_string2: + is_string2 = False + self.__add_tok(word, 'string') + word = '' + + # format char + elif c in self.__repo.format_chars: + self.__add_tok(word, 'word') + self.__add_tok(c, 'format_char') + word = '' + + # begin of number + elif c.isdigit() and not is_number and word == '': + word += c + is_number = True + + # continue number + elif c.isdigit() and is_number: + word += c + + # begin expression char + elif c in self.__repo.expr_chars and not is_expr_char: + word += c + is_expr_char = True + + # continue expression char + elif c in self.__repo.expr_chars and is_expr_char: + word += c + + # bool expression char + elif c in self.__repo.expr_chars: + self.__add_tok(word, 'word') + self.__add_tok(c, 'bool_expr_char') + word = '' + + # end of word + elif c == ' ' and not is_string1 and not is_string2 or c == '\n': + self.__add_tok(word, 'word') + word = '' + + else: + word += c + + if c == '\n' and ol_comment: + ol_comment = False + + if c == '*' and line[i + 1] == '/': + self.__ml_comment = False + + # self.__repo.output_tokens(self.__toks) + return self.__toks diff --git a/src/Interpreter/Parser.py b/src/Interpreter/Parser.py new file mode 100644 index 0000000..8e10243 --- /dev/null +++ b/src/Interpreter/Parser.py @@ -0,0 +1,12 @@ +from Interpreter.Repo import Repo +from Interpreter.Utils import Utils + + +class Parser: + + def __init__(self, repo: Repo, utils: Utils) -> None: + self.__repo = repo + self.__utils = utils + + def parse(self, toks: list) -> None: + self.__repo.output_tokens(toks) diff --git a/src/Interpreter/Repo.py b/src/Interpreter/Repo.py new file mode 100644 index 0000000..2975356 --- /dev/null +++ b/src/Interpreter/Repo.py @@ -0,0 +1,54 @@ +class Repo: + + def __init__(self) -> None: + self.debug = True + + # interpreter + self.keywords = [ + # define keys + 'lib', + 'class', + 'func', + # builtin functions + 'output', + 'input', + 'length', + 'range', + # normal keywords + 'if', + 'elseif', + 'else', + 'pass', + 'in', + # loops + 'while', + 'for', + # access + 'public' + ] + self.types = [ + 'number', + 'string', + 'bool', + 'list', + 'dict', + 'emptyType', + 'void' + ] + self.expr_chars = ['+', '-', '*', '/', '='] + self.bool_expr_chars = ['<', '>', '!', '!=', '==', '>=', '<='] + self.bool_values = ['true', 'false'] + self.format_chars = ['{', '}', '(', ')', ';', ':', ','] + + # runtime + self.error = None + + def output_tokens(self, toks: list) -> None: + if self.debug and len(toks) > 0: + # outp_toks = [] + for tok in toks: + # outp_toks.append({tok.value: tok.type}) + print({tok.value: tok.type}) + + # print(outp_toks) + print('\n') diff --git a/src/Interpreter/Utils.py b/src/Interpreter/Utils.py new file mode 100644 index 0000000..18ec8bf --- /dev/null +++ b/src/Interpreter/Utils.py @@ -0,0 +1,19 @@ +from termcolor import colored + +from Interpreter.Repo import Repo + + +class Utils: + + def __init__(self, repo: Repo) -> None: + self.__repo = repo + + def input(self, prefix: str) -> str: + return input(prefix) + + def output(self, text: str) -> None: + print(f'-> {text}') + + def error(self) -> None: + if self.__repo is not None: + print(colored(f'{self.__repo.error.code}: {self.__repo.error.msg}', 'red')) diff --git a/src/Interpreter/Validator.py b/src/Interpreter/Validator.py new file mode 100644 index 0000000..7b2be4e --- /dev/null +++ b/src/Interpreter/Validator.py @@ -0,0 +1,12 @@ +from Interpreter.Repo import Repo +from Interpreter.Utils import Utils + + +class Validator: + + def __init__(self, repo: Repo, utils: Utils) -> None: + self.__repo = repo + self.__utils = utils + + def validate(self) -> None: + pass diff --git a/src/Interpreter/__init__.py b/src/Interpreter/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/Main.py b/src/Main.py new file mode 100644 index 0000000..c578749 --- /dev/null +++ b/src/Main.py @@ -0,0 +1,38 @@ +import os +import sys + +from Models.Error import Error +from ServiceInitializer import ServiceInitializer + + +class Main: + + def __init__(self) -> None: + self.__services = ServiceInitializer() + self.__utils = self.__services.utils + self.__repo = self.__services.repo + self.__interpreter = self.__services.interpreter + + def console(self) -> None: + print('sh-edraft.de basic language interpreter:') + cont = True + while cont: + cont = self.__interpreter.interpret(input('> ')) + + def files(self, file: str) -> None: + if os.path.isfile(file): + f = open(file, 'r', encoding='utf-8').readlines() + for line in f: + self.__interpreter.interpret(line) + else: + self.__repo.error = Error(1.1, 'File not found') + self.__utils.error() + + +if __name__ == '__main__': + main = Main() + print(sys.argv) + if len(sys.argv) == 2: + main.files(sys.argv[1]) + else: + main.console() diff --git a/src/Models/Class.py b/src/Models/Class.py new file mode 100644 index 0000000..95eb18f --- /dev/null +++ b/src/Models/Class.py @@ -0,0 +1,6 @@ +class Class: + + def __init__(self, name: str, ast: list, access: ''): + self.name = name + self.ast = ast + self.access = access diff --git a/src/Models/Error.py b/src/Models/Error.py new file mode 100644 index 0000000..594dc20 --- /dev/null +++ b/src/Models/Error.py @@ -0,0 +1,5 @@ +class Error: + + def __init__(self, code: float, msg: str): + self.code = code + self.msg = msg \ No newline at end of file diff --git a/src/Models/Func.py b/src/Models/Func.py new file mode 100644 index 0000000..38388d6 --- /dev/null +++ b/src/Models/Func.py @@ -0,0 +1,6 @@ +class Func: + + def __init__(self, name: str, ast: list, access='') -> None: + self.name = name + self.ast = ast + self.access = access diff --git a/src/Models/Lib.py b/src/Models/Lib.py new file mode 100644 index 0000000..776338a --- /dev/null +++ b/src/Models/Lib.py @@ -0,0 +1,5 @@ +class Lib: + + def __init__(self, name: str, ast: list): + self.name = name + self.ast = ast diff --git a/src/Models/Token.py b/src/Models/Token.py new file mode 100644 index 0000000..855acf5 --- /dev/null +++ b/src/Models/Token.py @@ -0,0 +1,5 @@ +class Token: + + def __init__(self, type: str, value: str) -> None: + self.type = type + self.value = value diff --git a/src/Models/Var.py b/src/Models/Var.py new file mode 100644 index 0000000..9eab3ee --- /dev/null +++ b/src/Models/Var.py @@ -0,0 +1,6 @@ +class Var: + + def __init__(self, name: str, value: str, type: str) -> None: + self.name = name + self.value = value + self.type = type diff --git a/src/Models/__init__.py b/src/Models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/src/ServiceInitializer.py b/src/ServiceInitializer.py new file mode 100644 index 0000000..9789e21 --- /dev/null +++ b/src/ServiceInitializer.py @@ -0,0 +1,12 @@ +from Interpreter.Validator import Validator +from Interpreter.Interpreter import Interpreter +from Interpreter.Utils import Utils +from Interpreter.Repo import Repo + + +class ServiceInitializer: + + def __init__(self) -> None: + self.repo = Repo() + self.utils = Utils(self.repo) + self.interpreter = Interpreter(self.repo, self.utils)