From 2b02341336f142343bf6c8a58c622134ec45f232 Mon Sep 17 00:00:00 2001 From: Sven Heidemann Date: Wed, 27 Oct 2021 18:30:22 +0200 Subject: [PATCH] Added logic to handle lib & class declaration --- cpl-workspace.json | 6 +- src/cc_lang/cc-lang.json | 3 +- src/cc_lang_interpreter/application.py | 16 ++- .../cc-lang-interpreter.json | 2 +- src/cc_lang_interpreter/startup.py | 2 +- src/lexer/lexer.json | 3 +- src/lexer/model/token.py | 3 + src/parser/abc/ast.py | 34 ++++++ src/parser/abc/ast_abc.py | 7 -- src/parser/abc/parser_abc.py | 2 +- src/parser/model/__init__.py | 1 + src/parser/model/ast_types_enum.py | 12 ++ src/parser/model/class_ast.py | 18 +++ src/parser/model/library_ast.py | 18 +++ src/parser/model/parser_state_enum.py | 8 ++ src/parser/parser.json | 3 +- src/parser/service/parser_service.py | 109 ++++++++++++++++-- src/runtime/abc/runtime_service_abc.py | 17 +++ src/runtime/model/__init__.py | 1 + src/runtime/model/error.py | 16 +++ src/runtime/model/error_codes_enum.py | 21 ++++ src/runtime/runtime.json | 3 +- src/runtime/service/runtime_service.py | 20 ++++ 23 files changed, 292 insertions(+), 33 deletions(-) create mode 100644 src/parser/abc/ast.py delete mode 100644 src/parser/abc/ast_abc.py create mode 100644 src/parser/model/__init__.py create mode 100644 src/parser/model/ast_types_enum.py create mode 100644 src/parser/model/class_ast.py create mode 100644 src/parser/model/library_ast.py create mode 100644 src/parser/model/parser_state_enum.py create mode 100644 src/runtime/model/__init__.py create mode 100644 src/runtime/model/error.py create mode 100644 src/runtime/model/error_codes_enum.py diff --git a/cpl-workspace.json b/cpl-workspace.json index c3c5d8e..b3d3a98 100644 --- a/cpl-workspace.json +++ b/cpl-workspace.json @@ -1,12 +1,12 @@ { "WorkspaceSettings": { - "DefaultProject": "cc-lang", + "DefaultProject": "cc-lang-interpreter", "Projects": { + "cc-lang-interpreter": "src/cc_lang_interpreter/cc-lang-interpreter.json", "cc-lang": "src/cc_lang/cc-lang.json", "parser": "src/parser/parser.json", "lexer": "src/lexer/lexer.json", - "runtime": "src/runtime/runtime.json", - "cc-lang-interpreter": "src/cc_lang_interpreter/cc-lang-interpreter.json" + "runtime": "src/runtime/runtime.json" } } } \ No newline at end of file diff --git a/src/cc_lang/cc-lang.json b/src/cc_lang/cc-lang.json index 34a6b80..23ca813 100644 --- a/src/cc_lang/cc-lang.json +++ b/src/cc_lang/cc-lang.json @@ -16,7 +16,8 @@ "LicenseName": "", "LicenseDescription": "", "Dependencies": [ - "sh_cpl==2021.4.0.post2" + "sh_cpl-core==2021.10.0.post1", + "sh_cpl-query==2021.10.0.post1" ], "PythonVersion": ">=3.9.2", "PythonPath": { diff --git a/src/cc_lang_interpreter/application.py b/src/cc_lang_interpreter/application.py index 944190d..dd436a0 100644 --- a/src/cc_lang_interpreter/application.py +++ b/src/cc_lang_interpreter/application.py @@ -4,8 +4,11 @@ from cpl_core.application import ApplicationABC from cpl_core.configuration import ConfigurationABC from cpl_core.console import Console from cpl_core.dependency_injection import ServiceProviderABC +from cpl_query.extension.list import List from lexer.abc.lexer_abc import LexerABC +from lexer.model.token import Token +from parser.abc.ast import AST from parser.abc.parser_abc import ParserABC from runtime.abc.runtime_service_abc import RuntimeServiceABC @@ -22,15 +25,17 @@ class Application(ApplicationABC): self._path = config.get_configuration('p') def _interpret(self, line: str): - tokens = self._lexer.tokenize(line) - ast = self._parser.create_ast(tokens) - + tokens: List[Token] = self._lexer.tokenize(line) + ast: List[AST] = self._parser.create_ast(tokens) + line.replace("\n", "").replace("\t", "") - # Console.write_line(f'<{self._runtime.line_count}> LINE: {line}') + Console.write_line(f'<{self._runtime.line_count}> LINE: {line}') # header, values = ['Type', 'Value'], [] # tokens.for_each(lambda t: values.append([t.type, t.value])) # Console.table(header, values) + Console.write(ast, '\n') + def _console(self): i = 0 while True: @@ -62,8 +67,7 @@ class Application(ApplicationABC): self._runtime.line_count = i + 1 self._interpret(f[i]) - def configure(self): - pass + def configure(self): pass def main(self): if self._path is None: diff --git a/src/cc_lang_interpreter/cc-lang-interpreter.json b/src/cc_lang_interpreter/cc-lang-interpreter.json index 21a7165..6a74506 100644 --- a/src/cc_lang_interpreter/cc-lang-interpreter.json +++ b/src/cc_lang_interpreter/cc-lang-interpreter.json @@ -17,7 +17,7 @@ "LicenseDescription": "", "Dependencies": [ "sh_cpl-core==2021.10.0.post1", - "sh_cpl-query==2021.10.0" + "sh_cpl-query==2021.10.0.post1" ], "PythonVersion": ">=3.9.2", "PythonPath": { diff --git a/src/cc_lang_interpreter/startup.py b/src/cc_lang_interpreter/startup.py index 64f4ecd..8b60b04 100644 --- a/src/cc_lang_interpreter/startup.py +++ b/src/cc_lang_interpreter/startup.py @@ -23,8 +23,8 @@ class Startup(StartupABC): return config def configure_services(self, services: ServiceCollectionABC, env: ApplicationEnvironment) -> ServiceProviderABC: + services.add_singleton(RuntimeServiceABC, RuntimeService) services.add_singleton(LexerABC, LexerService) services.add_singleton(ParserABC, ParserService) - services.add_singleton(RuntimeServiceABC, RuntimeService) return services.build_service_provider() diff --git a/src/lexer/lexer.json b/src/lexer/lexer.json index ece0188..dc058d9 100644 --- a/src/lexer/lexer.json +++ b/src/lexer/lexer.json @@ -16,7 +16,8 @@ "LicenseName": "", "LicenseDescription": "", "Dependencies": [ - "sh_cpl==2021.4.0.post2" + "sh_cpl-core==2021.10.0.post1", + "sh_cpl-query==2021.10.0.post1" ], "PythonVersion": ">=3.9.2", "PythonPath": { diff --git a/src/lexer/model/token.py b/src/lexer/model/token.py index 34729c8..915ee51 100644 --- a/src/lexer/model/token.py +++ b/src/lexer/model/token.py @@ -19,5 +19,8 @@ class Token: def value(self, value: str): self._value = value + def __repr__(self) -> str: + return f'Token ' + def __str__(self) -> str: return f'Token: Type: {self._type}, Value: {self._value}' diff --git a/src/parser/abc/ast.py b/src/parser/abc/ast.py new file mode 100644 index 0000000..12102da --- /dev/null +++ b/src/parser/abc/ast.py @@ -0,0 +1,34 @@ +from typing import Optional, Union +from cpl_query.extension.list import List +from parser.model.ast_types_enum import ASTTypesEnum + + +class AST(): + + def __init__(self, type: ASTTypesEnum, value: Union[str, List['AST']], start: Optional[int] = None, end: Optional[int] = None): + self._type = type + self._value = value + self._start = start + self._end = end + + @property + def type(self) -> ASTTypesEnum: + return self._type + + @property + def value(self) -> Union[str, List['AST']]: + return self._value + + @property + def start(self) -> Optional[int]: + return self._start + + @property + def end(self) -> Optional[int]: + return self._end + + def __repr__(self) -> str: + return f'AST <{self._start},{self._end}>' + + def __str__(self) -> str: + return f'AST <{self._start},{self._end}>' diff --git a/src/parser/abc/ast_abc.py b/src/parser/abc/ast_abc.py deleted file mode 100644 index 4b66da6..0000000 --- a/src/parser/abc/ast_abc.py +++ /dev/null @@ -1,7 +0,0 @@ -from abc import ABC, abstractmethod - - -class AST(ABC): - - @abstractmethod - def __init__(self): pass diff --git a/src/parser/abc/parser_abc.py b/src/parser/abc/parser_abc.py index f0423e3..75b3b3c 100644 --- a/src/parser/abc/parser_abc.py +++ b/src/parser/abc/parser_abc.py @@ -2,7 +2,7 @@ from abc import ABC, abstractmethod from cpl_query.extension.list import List -from parser.abc.ast_abc import AST +from parser.abc.ast import AST from lexer.model.token import Token class ParserABC(ABC): diff --git a/src/parser/model/__init__.py b/src/parser/model/__init__.py new file mode 100644 index 0000000..425ab6c --- /dev/null +++ b/src/parser/model/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/src/parser/model/ast_types_enum.py b/src/parser/model/ast_types_enum.py new file mode 100644 index 0000000..ea56099 --- /dev/null +++ b/src/parser/model/ast_types_enum.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class ASTTypesEnum(Enum): + + Access = 'access' + Keyword = 'keyword' + Name = 'name' + + LibraryDeclaration = 'library_declaration' + ClassDeclaration = 'class_declaration' + diff --git a/src/parser/model/class_ast.py b/src/parser/model/class_ast.py new file mode 100644 index 0000000..dfd1116 --- /dev/null +++ b/src/parser/model/class_ast.py @@ -0,0 +1,18 @@ +from parser.abc.ast import AST + +from cpl_query.extension.list import List + + +class ClassAST(AST): + + def __init__(self): + AST.__init__(self) + self._body = List(AST) + + @property + def body(self) -> List['AST']: + return self._body + + @body.setter + def body(self, value: List['AST']): + self._body = value diff --git a/src/parser/model/library_ast.py b/src/parser/model/library_ast.py new file mode 100644 index 0000000..72049bc --- /dev/null +++ b/src/parser/model/library_ast.py @@ -0,0 +1,18 @@ +from parser.abc.ast import AST + +from cpl_query.extension.list import List + + +class LibraryAST(AST): + + def __init__(self): + AST.__init__(self) + self._body = List(AST) + + @property + def body(self) -> List['AST']: + return self._body + + @body.setter + def body(self, value: List['AST']): + self._body = value diff --git a/src/parser/model/parser_state_enum.py b/src/parser/model/parser_state_enum.py new file mode 100644 index 0000000..bf197c8 --- /dev/null +++ b/src/parser/model/parser_state_enum.py @@ -0,0 +1,8 @@ +from enum import Enum + + +class ParserStateEnum(Enum): + + Default = 0 + Library = 1 + Class = 2 diff --git a/src/parser/parser.json b/src/parser/parser.json index 16923bd..31bddf2 100644 --- a/src/parser/parser.json +++ b/src/parser/parser.json @@ -16,7 +16,8 @@ "LicenseName": "", "LicenseDescription": "", "Dependencies": [ - "sh_cpl==2021.4.0.post2" + "sh_cpl-core==2021.10.0.post1", + "sh_cpl-query==2021.10.0.post1" ], "PythonVersion": ">=3.9.2", "PythonPath": { diff --git a/src/parser/service/parser_service.py b/src/parser/service/parser_service.py index 811a4ab..449b495 100644 --- a/src/parser/service/parser_service.py +++ b/src/parser/service/parser_service.py @@ -1,19 +1,108 @@ +from parser.abc.ast import AST +from parser.abc.parser_abc import ParserABC +from parser.model.ast_types_enum import ASTTypesEnum + +from cc_lang.model.language_definition_classes import FormatCharacters, Keywords from cpl_core.console.console import Console from cpl_query.extension.list import List from lexer.model.token import Token -from parser.abc.ast_abc import AST -from parser.abc.parser_abc import ParserABC +from lexer.model.token_types import TokenTypes +from pynput.keyboard import Key +from parser.model.parser_state_enum import ParserStateEnum +from runtime.abc.runtime_service_abc import RuntimeServiceABC +from runtime.model.error import Error +from runtime.model.error_codes_enum import ErrorCodesEnum class ParserService(ParserABC): - def __init__(self): - pass + def __init__(self, runtime: RuntimeServiceABC): + self._runtime = runtime + + self._access_keywords = [ + Keywords.Public.value, + Keywords.Private.value, + Keywords.Static.value, + Keywords.This.value, + ] + + def _parse_library_or_class(self, tokens: List[Token], cls=False) -> AST: + """ Handles library declaration + + Args: + tokens (List[Token]): Tokens from lexer + + AST: + lib Main { + + public lib Main { + + public lib Main {} + + + Returns: + AST: Library or class AST + """ + end = None + ast = List(AST) + for i in range(0, tokens.count()): + token: Token = tokens[i] + + # if line contains } + if token.type == TokenTypes.Format_Character and token.value == FormatCharacters.Right_Brace.value: + end = self._runtime.line_count + elif i == tokens.count()-1 and token.type == TokenTypes.Format_Character and token.value == FormatCharacters.Left_Brace.value: + break + elif i == tokens.count()-1: + self._runtime.error( + Error(ErrorCodesEnum.Expected, FormatCharacters.Left_Brace.value)) + + if i == 0 and token.type == TokenTypes.Keyword and token.value in self._access_keywords: + ast.append(AST(ASTTypesEnum.Access, token.value, + self._runtime.line_count, self._runtime.line_count)) + + if i <= 1 and token.type == TokenTypes.Keyword and token.value == Keywords.Library.value: + ast.append(AST(ASTTypesEnum.Keyword, token.value, + self._runtime.line_count, self._runtime.line_count)) + + if i >= 1 and token.type == TokenTypes.Name: + ast.append(AST(ASTTypesEnum.Name, token.value, + self._runtime.line_count, self._runtime.line_count)) + + return AST(ASTTypesEnum.LibraryDeclaration if not cls else ASTTypesEnum.ClassDeclaration, ast, self._runtime.line_count, end) + + def _parse_variable(self, tokens: List[AST]) -> AST: + """ Parses variable declarations + + Args: + tokens (List[Token]): Tokens from lexer + + AST: + var test: number; + + var test: number = 0; + + var test: number = test; + + var test: number = TestClass(); + + private var test: number = 0; + + + Returns: + AST: Library or class AST + """ def create_ast(self, tokens: List[Token]) -> List[AST]: - for i in range(0, tokens.count()): - prev_token = tokens[i-1] if i-1 > 0 else None - token = tokens[i] - next_token = tokens[i+1] if i+1 < tokens.count() else None - - Console.write_line(token) + self._ast = List(AST) + + if tokens.where(lambda t: t.type == TokenTypes.Keyword and t.value == Keywords.Library.value).count() > 0: + self._ast.append(self._parse_library_or_class(tokens)) + + elif tokens.where(lambda t: t.type == TokenTypes.Keyword and t.value == Keywords.Class.value).count() > 0: + self._ast.append(self._parse_library_or_class(tokens, True)) + + elif tokens.where(lambda t: t.type == TokenTypes.Keyword and t.value == Keywords.Variable.value).count() > 0: + self._ast.append(self._parse_variable(tokens, True)) + + return self._ast diff --git a/src/runtime/abc/runtime_service_abc.py b/src/runtime/abc/runtime_service_abc.py index acd6550..f0d9130 100644 --- a/src/runtime/abc/runtime_service_abc.py +++ b/src/runtime/abc/runtime_service_abc.py @@ -1,5 +1,10 @@ from abc import ABC, abstractmethod +from cpl_core.console.console import Console +from cpl_core.console.foreground_color_enum import ForegroundColorEnum + +from runtime.model.error import Error + class RuntimeServiceABC(ABC): @@ -13,3 +18,15 @@ class RuntimeServiceABC(ABC): @line_count.setter @abstractmethod def line_count(self, line_count: int): pass + + @abstractmethod + def input(self, prefix: str) -> str: pass + + @abstractmethod + def output(self, text: str): pass + + @abstractmethod + def error(self, error: Error): pass + + @abstractmethod + def runtime_error(self, error: Error): pass diff --git a/src/runtime/model/__init__.py b/src/runtime/model/__init__.py new file mode 100644 index 0000000..425ab6c --- /dev/null +++ b/src/runtime/model/__init__.py @@ -0,0 +1 @@ +# imports diff --git a/src/runtime/model/error.py b/src/runtime/model/error.py new file mode 100644 index 0000000..232f13b --- /dev/null +++ b/src/runtime/model/error.py @@ -0,0 +1,16 @@ +from runtime.model.error_codes_enum import ErrorCodesEnum + + +class Error: + + def __init__(self, code: ErrorCodesEnum, msg: str): + self._code = code + self._msg = code.value.format(msg) + + @property + def code(self) -> ErrorCodesEnum: + return self._code + + @property + def message(self) -> str: + return self._msg \ No newline at end of file diff --git a/src/runtime/model/error_codes_enum.py b/src/runtime/model/error_codes_enum.py new file mode 100644 index 0000000..e821fe6 --- /dev/null +++ b/src/runtime/model/error_codes_enum.py @@ -0,0 +1,21 @@ +from enum import Enum + + +class ErrorCodesEnum(Enum): + + StartFailed = 'Start failed' + FileNotFound = 'File not found' + WrongFileType = 'Wrong file type' + + Unknown = 'Unknown {}' + Inaccessible = '{} inaccessible' + Unexpected = 'Unexpected {}' + Expected = 'Expected {}' + + LibInLib = 'Lib in lib' + LibInClass = 'Lib in class' + LibInFunc = 'Lib in func' + ClassInClass = 'Class in class' + ClassInFunc = 'Class in func' + FuncInLib = 'Func in lib' + FuncInFunc = 'Func in func' diff --git a/src/runtime/runtime.json b/src/runtime/runtime.json index 436f695..e8b27a2 100644 --- a/src/runtime/runtime.json +++ b/src/runtime/runtime.json @@ -16,7 +16,8 @@ "LicenseName": "", "LicenseDescription": "", "Dependencies": [ - "sh_cpl==2021.4.0.post2" + "sh_cpl-core==2021.10.0.post1", + "sh_cpl-query==2021.10.0.post1" ], "PythonVersion": ">=3.9.2", "PythonPath": { diff --git a/src/runtime/service/runtime_service.py b/src/runtime/service/runtime_service.py index 54d34a0..152d657 100644 --- a/src/runtime/service/runtime_service.py +++ b/src/runtime/service/runtime_service.py @@ -1,4 +1,6 @@ +from cpl_core.console import Console, ForegroundColorEnum from runtime.abc.runtime_service_abc import RuntimeServiceABC +from runtime.model.error import Error class RuntimeService(RuntimeServiceABC): @@ -13,3 +15,21 @@ class RuntimeService(RuntimeServiceABC): @line_count.setter def line_count(self, line_count: int): self._line_count = line_count + + def input(self, prefix: str) -> str: + return Console.read_line(prefix) + + def output(self, text: str) -> None: + Console.write_line(f'> {text}') + + def error(self, error: Error) -> None: + Console.set_foreground_color(ForegroundColorEnum.red) + Console.write_line(f'Error in line {self._line_count}\n{error.message}') + Console.color_reset() + exit() + + def runtime_error(self, error: Error) -> None: + Console.set_foreground_color(ForegroundColorEnum.red) + Console.write_line(f'{error.message}', 'red') + Console.color_reset() + exit()