├── fastpy_build ├── src │ ├── .gitignore │ ├── cmake-build-debug │ │ ├── CMakeFiles │ │ │ ├── progress.marks │ │ │ ├── src.dir │ │ │ │ ├── objects1.rsp │ │ │ │ ├── progress.make │ │ │ │ ├── linklibs.rsp │ │ │ │ ├── depend.make │ │ │ │ ├── flags.make │ │ │ │ ├── cmake_clean.cmake │ │ │ │ ├── depend.internal │ │ │ │ ├── link.txt │ │ │ │ ├── CXX.includecache │ │ │ │ ├── DependInfo.cmake │ │ │ │ └── build.make │ │ │ ├── cmake.check_cache │ │ │ ├── 3.20.2 │ │ │ │ ├── CompilerIdC │ │ │ │ │ └── a.exe │ │ │ │ ├── CompilerIdCXX │ │ │ │ │ └── a.exe │ │ │ │ ├── CMakeDetermineCompilerABI_C.bin │ │ │ │ ├── CMakeDetermineCompilerABI_CXX.bin │ │ │ │ ├── CMakeRCCompiler.cmake │ │ │ │ ├── CMakeSystem.cmake │ │ │ │ ├── CMakeCCompiler.cmake │ │ │ │ └── CMakeCXXCompiler.cmake │ │ │ ├── clion-environment.txt │ │ │ ├── TargetDirectories.txt │ │ │ ├── clion-log.txt │ │ │ ├── CMakeDirectoryInformation.cmake │ │ │ ├── Makefile.cmake │ │ │ └── Makefile2 │ │ ├── cmake_install.cmake │ │ ├── Makefile │ │ └── src.cbp │ ├── CMakeLists.txt │ ├── main.cpp │ ├── game.hpp │ ├── print.hpp │ └── include │ │ └── builtin.hpp └── bin │ └── main.exe ├── examples ├── while.fpy ├── hello_world.fpy ├── my_module.fpy ├── sum.fpy ├── turn_off_logging.fpy ├── builtin_logging.fpy ├── condition.fpy ├── variables.fpy ├── import.fpy ├── first_run_from_ide.fpy ├── far_future.fpy └── first_tic_tac_toe.fpy ├── requirements.txt ├── docs ├── imgs │ ├── FirstRunFromIDE.gif │ └── highlighting │ │ ├── img.png │ │ ├── step_1.png │ │ ├── step_2.png │ │ ├── step_3.png │ │ └── step_4.png ├── Highlighting.md ├── TODO.md └── Customizing.md ├── config ├── logging.json ├── builtin.json ├── analyzer.json ├── lexer.json ├── argparse.json ├── operators.json ├── transpiler.json ├── code_highlighting.xml └── parser.json ├── fastpy ├── exceptions │ ├── __init__.py │ └── errors.py ├── filesystem │ ├── __init__.py │ └── filesystem.py ├── log │ ├── __init__.py │ ├── config.py │ ├── formatter.py │ └── logger.py ├── config │ ├── __init__.py │ ├── config.py │ └── json_config.py ├── dev_kit │ ├── __init__.py │ ├── config.py │ └── transpiler.py ├── module │ ├── __init__.py │ └── module.py ├── semantic_analyzer │ ├── __init__.py │ ├── config.py │ ├── scope.py │ ├── analyzers.py │ └── node_analyzers.py ├── singleton.py ├── transpiler │ ├── __init__.py │ ├── config.py │ ├── code.py │ ├── transpilers.py │ └── node_transpilers.py ├── __init__.py ├── lexer │ ├── __init__.py │ ├── special_symbols.py │ ├── token_types.py │ ├── config.py │ ├── tokens.py │ ├── lexers.py │ └── detectors.py ├── parser │ ├── __init__.py │ ├── structure.py │ ├── config.py │ ├── ast.py │ ├── validators.py │ ├── nodes.py │ ├── parsers.py │ └── node_parsers.py └── import_tools.py ├── cpp_code ├── base.cpp ├── base_main.cpp ├── minimal.cpp ├── minimal_main.cpp └── libs │ └── builtin.hpp ├── config.py ├── main.py ├── LICENSE ├── .gitignore ├── main.fpy └── README.md /fastpy_build/src/.gitignore: -------------------------------------------------------------------------------- 1 | cmake-build-debug -------------------------------------------------------------------------------- /examples/while.fpy: -------------------------------------------------------------------------------- 1 | i = 20 2 | 3 | while i > 0: 4 | i = i - 1 -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/progress.marks: -------------------------------------------------------------------------------- 1 | 2 2 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/requirements.txt -------------------------------------------------------------------------------- /examples/hello_world.fpy: -------------------------------------------------------------------------------- 1 | # Just a comment :D 2 | 3 | log('Hello, World!') # print hello world 4 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/objects1.rsp: -------------------------------------------------------------------------------- 1 | CMakeFiles/src.dir/main.cpp.obj 2 | -------------------------------------------------------------------------------- /fastpy_build/bin/main.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/fastpy_build/bin/main.exe -------------------------------------------------------------------------------- /docs/imgs/FirstRunFromIDE.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/docs/imgs/FirstRunFromIDE.gif -------------------------------------------------------------------------------- /examples/my_module.fpy: -------------------------------------------------------------------------------- 1 | fun hello(): 2 | log('Hello!') 3 | 4 | 5 | fun world() -> null: 6 | log('World') -------------------------------------------------------------------------------- /config/logging.json: -------------------------------------------------------------------------------- 1 | { 2 | "format": "%(levelname)s: %(message)s", 3 | "app_name": "FastPy", 4 | "level": 10 5 | } -------------------------------------------------------------------------------- /docs/imgs/highlighting/img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/docs/imgs/highlighting/img.png -------------------------------------------------------------------------------- /examples/sum.fpy: -------------------------------------------------------------------------------- 1 | a: int = input('a >>') 2 | b: int = input('b >>') 3 | 4 | log('a + b =', a + b) 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /docs/imgs/highlighting/step_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/docs/imgs/highlighting/step_1.png -------------------------------------------------------------------------------- /docs/imgs/highlighting/step_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/docs/imgs/highlighting/step_2.png -------------------------------------------------------------------------------- /docs/imgs/highlighting/step_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/docs/imgs/highlighting/step_3.png -------------------------------------------------------------------------------- /docs/imgs/highlighting/step_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/docs/imgs/highlighting/step_4.png -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/progress.make: -------------------------------------------------------------------------------- 1 | CMAKE_PROGRESS_1 = 1 2 | CMAKE_PROGRESS_2 = 2 3 | 4 | -------------------------------------------------------------------------------- /examples/turn_off_logging.fpy: -------------------------------------------------------------------------------- 1 | $ENABLE_LOGGING = false 2 | 3 | log("Hello, World!") # nothing 4 | log_error("ERROR!") # nothing -------------------------------------------------------------------------------- /fastpy/exceptions/__init__.py: -------------------------------------------------------------------------------- 1 | """This module contains all possible internal exceptions of the FastPy tools.""" 2 | 3 | from .errors import * 4 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/cmake.check_cache: -------------------------------------------------------------------------------- 1 | # This file is generated by cmake for dependency checking of the CMakeCache.txt file 2 | -------------------------------------------------------------------------------- /examples/builtin_logging.fpy: -------------------------------------------------------------------------------- 1 | log("Hello!") # white output 2 | log_error('Hello!') # red output 3 | log_warning('Hello!') # yellow output 4 | log_info('Hello!') # blue output -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/linklibs.rsp: -------------------------------------------------------------------------------- 1 | -lkernel32 -luser32 -lgdi32 -lwinspool -lshell32 -lole32 -loleaut32 -luuid -lcomdlg32 -ladvapi32 2 | -------------------------------------------------------------------------------- /fastpy_build/src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.20) 2 | project(src) 3 | 4 | set(CMAKE_CXX_STANDARD 20) 5 | 6 | add_executable(src main.cpp print.hpp include/builtin.hpp) 7 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CompilerIdC/a.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CompilerIdC/a.exe -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CompilerIdCXX/a.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CompilerIdCXX/a.exe -------------------------------------------------------------------------------- /cpp_code/base.cpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | {% block includes %} 4 | 5 | {% endblock %} 6 | 7 | 8 | {{ additional_includes }} 9 | 10 | 11 | 12 | {{ external_code }} 13 | 14 | 15 | {{ internal_code }} 16 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | import fastpy 2 | import os 3 | 4 | # argparse config 5 | ARGPARSE_CONFIG = fastpy.config.JsonConfig( 6 | filepath=os.path.join(fastpy.config.CONFIG_FOLDER, 'argparse.json'), 7 | authoload=True 8 | ) 9 | -------------------------------------------------------------------------------- /cpp_code/base_main.cpp: -------------------------------------------------------------------------------- 1 | {% block includes %} 2 | 3 | {% endblock %} 4 | 5 | 6 | {{ additional_includes }} 7 | 8 | 9 | {{ external_code }} 10 | 11 | 12 | 13 | int main(){ 14 | {{ internal_code }} 15 | } -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeDetermineCompilerABI_C.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeDetermineCompilerABI_C.bin -------------------------------------------------------------------------------- /cpp_code/minimal.cpp: -------------------------------------------------------------------------------- 1 | {% extends 'base.cpp' %} 2 | 3 | {% block includes %} 4 | #include 5 | #include 6 | #include"include/termcolor.hpp" 7 | #include"include/builtin.hpp" 8 | {% endblock %} 9 | 10 | -------------------------------------------------------------------------------- /fastpy/filesystem/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module simplifies working with the file system by extending the base os module. 3 | """ 4 | 5 | from .filesystem import * 6 | 7 | __all__ = [ 8 | 'FileSystem' 9 | ] 10 | -------------------------------------------------------------------------------- /fastpy/log/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is responsible for pretty internal information printing. 3 | """ 4 | 5 | from .logger import * 6 | from .config import * 7 | 8 | __all__ = [ 9 | 'Logger', 10 | ] 11 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeDetermineCompilerABI_CXX.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CrazyProger1/FastPy/HEAD/fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeDetermineCompilerABI_CXX.bin -------------------------------------------------------------------------------- /fastpy/config/__init__.py: -------------------------------------------------------------------------------- 1 | """This module provides tools to help you load configs.""" 2 | 3 | from .json_config import * 4 | 5 | __all__ = [ 6 | 'JsonConfig', 7 | 'CONFIG_FOLDER', 8 | 'BaseConfig' 9 | ] 10 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/clion-environment.txt: -------------------------------------------------------------------------------- 1 | ToolSet: w64 6.0 (local)@D:\Programming\C++\Compilers\mingw64 2 | Options: 3 | 4 | Options:-DCMAKE_CXX_COMPILER=D:/Programming/C++/Compilers/mingw64/bin/g++.exe -------------------------------------------------------------------------------- /cpp_code/minimal_main.cpp: -------------------------------------------------------------------------------- 1 | {% extends 'base_main.cpp' %} 2 | 3 | {% block includes %} 4 | #include 5 | #include 6 | #include"include/termcolor.hpp" 7 | #include"include/builtin.hpp" 8 | {% endblock %} 9 | 10 | -------------------------------------------------------------------------------- /fastpy/dev_kit/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides an API for FastPy language development tools, such as transpilation API. 3 | """ 4 | 5 | from .transpiler import TranspileAPI 6 | 7 | __all__ = [ 8 | 'TranspileAPI' 9 | ] 10 | -------------------------------------------------------------------------------- /fastpy/module/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module provides a convenient class for working with sources. 3 | Used in every step of FastPy transpilation. 4 | """ 5 | 6 | from .module import * 7 | 8 | 9 | __all__ = [ 10 | 'Module' 11 | ] -------------------------------------------------------------------------------- /examples/condition.fpy: -------------------------------------------------------------------------------- 1 | error = true # auto type detection 2 | error: bool = true # same as above, but with the type specified 3 | 4 | if error == true: 5 | log_error('Error!') 6 | 7 | # same as above 8 | # if error: 9 | # log_error('Error!') 10 | -------------------------------------------------------------------------------- /fastpy_build/src/main.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | #include 4 | #include"include/termcolor.hpp" 5 | #include"include/builtin.hpp" 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | int main(){ 17 | auto abc; 18 | 19 | 20 | } -------------------------------------------------------------------------------- /examples/variables.fpy: -------------------------------------------------------------------------------- 1 | a: str = "Hello!" # string var 2 | b: bool = true # bool var 3 | c: int = 10 # int var 4 | 5 | d = "Hello!" # auto type definition 6 | e = true 7 | f = 10 8 | 9 | g: str # value by default is '' 10 | h: bool # false 11 | i: int # 0 12 | -------------------------------------------------------------------------------- /fastpy/semantic_analyzer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is responsible for analyzing an Abstract Syntax Tree obtained using the parser. 3 | """ 4 | 5 | from .analyzers import * 6 | 7 | __all__ = [ 8 | 'BaseAnalyzer', 9 | 'create_analyzer' 10 | ] 11 | -------------------------------------------------------------------------------- /fastpy/singleton.py: -------------------------------------------------------------------------------- 1 | 2 | def singleton(cls): 3 | instances = {} 4 | 5 | def wrapper(*args, **kwargs): 6 | if cls not in instances: 7 | instances.update({cls: cls(*args, **kwargs)}) 8 | return instances[cls] 9 | 10 | return wrapper 11 | -------------------------------------------------------------------------------- /fastpy_build/src/game.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include 5 | #include 6 | #include"include/termcolor.hpp" 7 | #include"include/builtin.hpp" 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | void start (){ 16 | log("startgame"); 17 | 18 | }; 19 | 20 | 21 | -------------------------------------------------------------------------------- /fastpy_build/src/print.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | 4 | #include 5 | #include 6 | #include"include/termcolor.hpp" 7 | #include"include/builtin.hpp" 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | void print (str txt){ 16 | log(txt); 17 | 18 | }; 19 | 20 | 21 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/depend.make: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | CMakeFiles/src.dir/main.cpp.obj: \ 5 | ../include/builtin.hpp \ 6 | ../include/termcolor.hpp \ 7 | ../main.cpp 8 | -------------------------------------------------------------------------------- /fastpy/transpiler/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is responsible for converting the Abstract Syntax Tree into C++ source code. 3 | """ 4 | 5 | from .transpilers import * 6 | from .config import * 7 | 8 | __all__ = [ 9 | 'create_transpiler', 10 | 'BaseTranspiler', 11 | 'Transpiler', 12 | ] 13 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/flags.make: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | # compile CXX with D:/Programming/C++/Compilers/mingw64/bin/g++.exe 5 | CXX_DEFINES = 6 | 7 | CXX_INCLUDES = 8 | 9 | CXX_FLAGS = -g -std=gnu++1z 10 | 11 | -------------------------------------------------------------------------------- /examples/import.fpy: -------------------------------------------------------------------------------- 1 | import('print.fpy') # import built in module 2 | import('my_module.fpy') # import custom module - my_module.fpy 3 | import_parts('my_module.fpy', 'world') # import world from my.fpy 4 | 5 | 6 | print.print('Hello, World!') # will print: 'Hello, World!' 7 | 8 | my_module.hello() # will print: 'Hello!' 9 | world() # will print: 'World' -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeRCCompiler.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_RC_COMPILER "D:/Programming/C++/Compilers/mingw64/bin/windres.exe") 2 | set(CMAKE_RC_COMPILER_ARG1 "") 3 | set(CMAKE_RC_COMPILER_LOADED 1) 4 | set(CMAKE_RC_SOURCE_FILE_EXTENSIONS rc;RC) 5 | set(CMAKE_RC_OUTPUT_EXTENSION .obj) 6 | set(CMAKE_RC_COMPILER_ENV_VAR "RC") 7 | -------------------------------------------------------------------------------- /fastpy/dev_kit/config.py: -------------------------------------------------------------------------------- 1 | from ..config import JsonConfig, CONFIG_FOLDER 2 | from ..filesystem import FileSystem as Fs 3 | 4 | builtin_config = JsonConfig( 5 | filepath=Fs.join(CONFIG_FOLDER, 'builtin.json'), 6 | authoload=True 7 | ) 8 | 9 | BUILTIN_TYPES = builtin_config['builtin_types'] 10 | BUILTIN_FUNCTIONS = builtin_config['builtin_functions'] 11 | -------------------------------------------------------------------------------- /fastpy/log/config.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from ..config import JsonConfig, CONFIG_FOLDER 4 | import os 5 | 6 | logging_config = JsonConfig(os.path.join(CONFIG_FOLDER, 'logging.json'), authoload=True) 7 | 8 | # logger config 9 | APP_NAME = logging_config['app_name'] 10 | LEVEL = logging_config['level'] 11 | 12 | # formatter config 13 | FORMAT = logging_config['format'] 14 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/TargetDirectories.txt: -------------------------------------------------------------------------------- 1 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir 2 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug/CMakeFiles/edit_cache.dir 3 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug/CMakeFiles/rebuild_cache.dir 4 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/cmake_clean.cmake: -------------------------------------------------------------------------------- 1 | file(REMOVE_RECURSE 2 | "CMakeFiles/src.dir/main.cpp.obj" 3 | "libsrc.dll.a" 4 | "src.exe" 5 | "src.exe.manifest" 6 | "src.pdb" 7 | ) 8 | 9 | # Per-language clean rules from dependency scanning. 10 | foreach(lang CXX) 11 | include(CMakeFiles/src.dir/cmake_clean_${lang}.cmake OPTIONAL) 12 | endforeach() 13 | -------------------------------------------------------------------------------- /config/builtin.json: -------------------------------------------------------------------------------- 1 | { 2 | "builtin_functions": { 3 | "import": "import", 4 | "log": "log", 5 | "log_error": "log_error", 6 | "log_warning": "log_waring", 7 | "log_info": "log_info", 8 | "input": "input" 9 | }, 10 | "builtin_types": { 11 | "string": "str", 12 | "integer": "int", 13 | "boolean": "bool", 14 | "list": "list", 15 | "vector": "vector" 16 | } 17 | } -------------------------------------------------------------------------------- /fastpy/__init__.py: -------------------------------------------------------------------------------- 1 | """This module provides a set of tools for development on FastPy lang.""" 2 | 3 | import fastpy.config 4 | import fastpy.exceptions 5 | import fastpy.log 6 | import fastpy.lexer 7 | import fastpy.transpiler 8 | import fastpy.semantic_analyzer 9 | from fastpy.dev_kit import * 10 | 11 | __all__ = [ 12 | 'config', 13 | 'log', 14 | 'exceptions', 15 | 'TranspileAPI', 16 | ] 17 | -------------------------------------------------------------------------------- /fastpy/exceptions/errors.py: -------------------------------------------------------------------------------- 1 | class BaseError(Exception): 2 | pass 3 | 4 | 5 | class ConfigNotLoadedError(BaseError): 6 | pass 7 | 8 | 9 | class LexingError(BaseError): 10 | pass 11 | 12 | 13 | class ParsingError(BaseError): 14 | pass 15 | 16 | 17 | class AnalyzingError(BaseError): 18 | pass 19 | 20 | 21 | class TranspilingError(BaseError): 22 | pass 23 | 24 | 25 | class CompilationError(BaseError): 26 | pass 27 | -------------------------------------------------------------------------------- /examples/first_run_from_ide.fpy: -------------------------------------------------------------------------------- 1 | import('print.fpy') 2 | 3 | world_text: str = "World!" 4 | condition = true 5 | 6 | fun print_hello(): 7 | hello_text: str = 'Hello, ' 8 | log_info(hello_text) 9 | 10 | fun print_text(txt: str, endl: bool = true): 11 | log_info(txt, endl) 12 | 13 | 14 | if condition: 15 | print_hello() 16 | print_text(world_text) 17 | 18 | print('First FastPy run from IDE!!!!!!') # imported print function from print lib -------------------------------------------------------------------------------- /fastpy/lexer/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is responsible for splitting the source code into tokens. 3 | """ 4 | 5 | from .lexers import * 6 | from .tokens import * 7 | from .config import * 8 | from .detectors import * 9 | 10 | __all__ = [ 11 | 'Lexer', 12 | 'Token', 13 | 'BaseLexer', 14 | 'BaseToken', 15 | 'create_lexer', 16 | 'code_from_tokens', 17 | 'create_token', 18 | 'BaseDetector', 19 | 'TokenTypes' 20 | ] 21 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/depend.internal: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | CMakeFiles/src.dir/main.cpp.obj 5 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/builtin.hpp 6 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/termcolor.hpp 7 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/main.cpp 8 | -------------------------------------------------------------------------------- /fastpy/parser/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This module is responsible for building an Abstract Syntax Tree from the tokens obtained using the lexer. 3 | """ 4 | 5 | from .parsers import * 6 | from .ast import * 7 | from .config import * 8 | from .node_parsers import * 9 | from .nodes import * 10 | 11 | __all__ = [ 12 | 'BaseAST', 13 | 'AST', 14 | 'BaseParser', 15 | 'Parser', 16 | 'create_parser', 17 | 'create_ast', 18 | 'BaseNode', 19 | 'nodes' 20 | 21 | ] 22 | -------------------------------------------------------------------------------- /fastpy/lexer/special_symbols.py: -------------------------------------------------------------------------------- 1 | from .token_types import * 2 | 3 | SPECIAL_SYMBOLS = { 4 | '(': TokenTypes.start_parenthesis, 5 | ')': TokenTypes.end_parenthesis, 6 | '[': TokenTypes.start_square, 7 | ']': TokenTypes.end_square, 8 | '{': TokenTypes.start_braces, 9 | '}': TokenTypes.end_braces, 10 | '<': TokenTypes.start_chevrons, 11 | '>': TokenTypes.end_chevrons, 12 | ' ': TokenTypes.gap, 13 | '\t': TokenTypes.tab, 14 | ',': TokenTypes.comma 15 | } 16 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/clion-log.txt: -------------------------------------------------------------------------------- 1 | "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" -DCMAKE_BUILD_TYPE=Debug -DCMAKE_CXX_COMPILER=D:/Programming/C++/Compilers/mingw64/bin/g++.exe -DCMAKE_DEPENDS_USE_COMPILER=FALSE -G "CodeBlocks - MinGW Makefiles" D:\Programming\Python\Projects\FastPy-public\fastpy_build\src 2 | -- Configuring done 3 | -- Generating done 4 | -- Build files have been written to: D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug 5 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeSystem.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_HOST_SYSTEM "Windows-10.0.19043") 2 | set(CMAKE_HOST_SYSTEM_NAME "Windows") 3 | set(CMAKE_HOST_SYSTEM_VERSION "10.0.19043") 4 | set(CMAKE_HOST_SYSTEM_PROCESSOR "AMD64") 5 | 6 | 7 | 8 | set(CMAKE_SYSTEM "Windows-10.0.19043") 9 | set(CMAKE_SYSTEM_NAME "Windows") 10 | set(CMAKE_SYSTEM_VERSION "10.0.19043") 11 | set(CMAKE_SYSTEM_PROCESSOR "AMD64") 12 | 13 | set(CMAKE_CROSSCOMPILING "FALSE") 14 | 15 | set(CMAKE_SYSTEM_LOADED 1) 16 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/link.txt: -------------------------------------------------------------------------------- 1 | "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" -E rm -f CMakeFiles\src.dir/objects.a 2 | D:\Programming\C++\Compilers\mingw64\bin\ar.exe cr CMakeFiles\src.dir/objects.a @CMakeFiles\src.dir\objects1.rsp 3 | D:\Programming\C++\Compilers\mingw64\bin\g++.exe -g -Wl,--whole-archive CMakeFiles\src.dir/objects.a -Wl,--no-whole-archive -o src.exe -Wl,--out-implib,libsrc.dll.a -Wl,--major-image-version,0,--minor-image-version,0 @CMakeFiles\src.dir\linklibs.rsp 4 | -------------------------------------------------------------------------------- /fastpy/import_tools.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | imported = {} 4 | 5 | 6 | def import_class(path: str): 7 | """ 8 | Imports Python classes 9 | 10 | :param path: Python classpath (separated by ".") 11 | :return: Python class 12 | """ 13 | 14 | if path in imported.keys(): 15 | return imported.get(path) 16 | 17 | components = path.split('.') 18 | mod = __import__(components[0]) 19 | for comp in components[1:]: 20 | mod = getattr(mod, comp) 21 | imported.update({path: mod}) 22 | return mod 23 | -------------------------------------------------------------------------------- /fastpy/parser/structure.py: -------------------------------------------------------------------------------- 1 | from .nodes import * 2 | 3 | 4 | class Structure: 5 | """ 6 | Contains information about currently detected structure, such as if condition or function 7 | """ 8 | 9 | def __init__(self, node, level: int = 0): 10 | self._level = level 11 | self._node: NodeWithBody = node 12 | 13 | def push_node(self, node: BaseNode): 14 | self._node.push_node_to_body(node=node) 15 | 16 | def within_struct(self, level: int): 17 | return level == self._level 18 | 19 | def __repr__(self): 20 | return str(self._node) 21 | -------------------------------------------------------------------------------- /docs/Highlighting.md: -------------------------------------------------------------------------------- 1 | # FastPy code highlighting 2 | 3 | ## Notepad++ 4 | 5 | ![](imgs/highlighting/img.png) 6 | 7 | If you want such highlighting, do follow steps: 8 | 9 | 1) Open .fpy source file in Notepad++ 10 | 11 | ![](imgs/highlighting/step_1.png) 12 | 13 | 2) Go to tab **Language > User Defined Language > Define your language...** 14 | ![](imgs/highlighting/step_2.png) 15 | 16 | 3) Click **Import...** and select that [file](../config/code_highlighting.xml) 17 | ![](imgs/highlighting/step_3.png) 18 | 19 | 4)Then select FastPy in **Language** dropdown menu 20 | ![](imgs/highlighting/step_4.png) -------------------------------------------------------------------------------- /fastpy/config/config.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | CONFIG_FOLDER = 'config' # Folder with all configs 4 | 5 | 6 | class BaseConfig(ABC): 7 | """Config interface""" 8 | 9 | @abstractmethod 10 | def load(self) -> None: ... 11 | 12 | @abstractmethod 13 | def save(self) -> None: ... 14 | 15 | @abstractmethod 16 | def get(self, key: str | int, default: any) -> any: ... 17 | 18 | @abstractmethod 19 | def __getitem__(self, item: str | int): ... 20 | 21 | @abstractmethod 22 | def __iter__(self): ... 23 | 24 | @abstractmethod 25 | def __repr__(self): ... 26 | -------------------------------------------------------------------------------- /fastpy/semantic_analyzer/config.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from ..config import JsonConfig, CONFIG_FOLDER 4 | from ..filesystem import FileSystem as Fs 5 | 6 | analyzer_config = JsonConfig( 7 | filepath=os.path.join(CONFIG_FOLDER, 'analyzer.json'), 8 | authoload=True 9 | ) 10 | 11 | ANALYZER_CLASS_PATH: str = analyzer_config['semantic_analyzer_class'] # analyzer class path to import 12 | NODE_ANALYZING: dict = analyzer_config['node_analyzing'] # node analyzing data 13 | 14 | builtin_config = JsonConfig( 15 | filepath=Fs.join(CONFIG_FOLDER, 'builtin.json'), 16 | authoload=True 17 | ) 18 | 19 | BUILTIN_TYPES = builtin_config['builtin_types'] 20 | BUILTIN_FUNCTIONS = builtin_config['builtin_functions'] 21 | -------------------------------------------------------------------------------- /config/analyzer.json: -------------------------------------------------------------------------------- 1 | { 2 | "semantic_analyzer_class": "fastpy.semantic_analyzer.analyzers.Analyzer", 3 | "node_analyzing": { 4 | "fastpy.parser.nodes.AssignNode": "fastpy.semantic_analyzer.node_analyzers.AssignNodeAnalyzer", 5 | "fastpy.parser.nodes.FuncNode": "fastpy.semantic_analyzer.node_analyzers.FuncNodeAnalyzer", 6 | "fastpy.parser.nodes.IfNode": "fastpy.semantic_analyzer.node_analyzers.IfNodeAnalyzer", 7 | "fastpy.parser.nodes.CallNode": "fastpy.semantic_analyzer.node_analyzers.CallNodeAnalyzer", 8 | "fastpy.parser.nodes.WhileNode": "fastpy.semantic_analyzer.node_analyzers.WhileNodeAnalyzer", 9 | "fastpy.parser.nodes.ElseNode": "fastpy.semantic_analyzer.node_analyzers.ElseNodeAnalyzer" 10 | } 11 | } -------------------------------------------------------------------------------- /fastpy/lexer/token_types.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TokenTypes(Enum): 5 | operator = 1 6 | identifier = 2 7 | literal = 3 8 | comma = 4 9 | start_parenthesis = 5 10 | end_parenthesis = 6 11 | start_braces = 7 12 | end_braces = 8 13 | start_square = 9 14 | end_square = 10 15 | gap = 11 16 | tab = 12 17 | number = 13 18 | endline = 14 19 | start_chevrons = 15 20 | end_chevrons = 16 21 | 22 | def __eq__(self, other): 23 | if isinstance(other, int): 24 | return self.value == other 25 | elif isinstance(other, str): 26 | return self.name == other 27 | 28 | return super(TokenTypes, self).__eq__(other) 29 | -------------------------------------------------------------------------------- /fastpy/module/module.py: -------------------------------------------------------------------------------- 1 | from ..filesystem import FileSystem as Fs 2 | 3 | 4 | class Module: 5 | def __init__(self, 6 | filepath: str, 7 | name: str = None, 8 | authoload: bool = True 9 | ): 10 | 11 | self.filepath = filepath 12 | self.filename = Fs.get_filename_without_ext(self.filepath) 13 | self.name = name or self.filename 14 | self.source_code = '' 15 | self.tokens = [] 16 | 17 | if authoload: 18 | self.load_source() 19 | 20 | def load_source(self): 21 | if Fs.exists(self.filepath): 22 | with open(self.filepath, 'r') as sf: 23 | self.source_code = sf.read() 24 | 25 | def __repr__(self): 26 | return f'Module({self.name})<{self.filepath}>' 27 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/CMakeDirectoryInformation.cmake: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | # Relative path conversion top directories. 5 | set(CMAKE_RELATIVE_PATH_TOP_SOURCE "D:/Programming/Python/Projects/FastPy-public/fastpy_build/src") 6 | set(CMAKE_RELATIVE_PATH_TOP_BINARY "D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug") 7 | 8 | # Force unix paths in dependencies. 9 | set(CMAKE_FORCE_UNIX_PATHS 1) 10 | 11 | 12 | # The C and CXX include file regular expressions for this directory. 13 | set(CMAKE_C_INCLUDE_REGEX_SCAN "^.*$") 14 | set(CMAKE_C_INCLUDE_REGEX_COMPLAIN "^$") 15 | set(CMAKE_CXX_INCLUDE_REGEX_SCAN ${CMAKE_C_INCLUDE_REGEX_SCAN}) 16 | set(CMAKE_CXX_INCLUDE_REGEX_COMPLAIN ${CMAKE_C_INCLUDE_REGEX_COMPLAIN}) 17 | -------------------------------------------------------------------------------- /config/lexer.json: -------------------------------------------------------------------------------- 1 | { 2 | "lexer_class": "fastpy.lexer.lexers.Lexer", 3 | "token_class": "fastpy.lexer.tokens.Token", 4 | "comment_start": "#", 5 | "token_detection": { 6 | "operator": { 7 | "detector": "fastpy.lexer.detectors.OperatorDetector", 8 | "regexes": [ 9 | "" 10 | ] 11 | }, 12 | "literal": { 13 | "detector": "fastpy.lexer.detectors.LiteralDetector", 14 | "regexes": [ 15 | "'[\\S\\s ]+'", 16 | "\"[\\S\\s ]+\"" 17 | ] 18 | }, 19 | "number": { 20 | "detector": "fastpy.lexer.detectors.UniversalDetector", 21 | "regexes": [ 22 | "[\\d]+" 23 | ] 24 | }, 25 | "identifier": { 26 | "detector": "fastpy.lexer.detectors.UniversalDetector", 27 | "regexes": [ 28 | "[a-zA-Z_][a-zA-Z0-9_]*" 29 | ] 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import fastpy 2 | import argparse 3 | from config import * 4 | 5 | 6 | def make_action(args: argparse.Namespace): 7 | """Performs actions such as transpiling or compiling depending on the input""" 8 | if args.translate: 9 | fastpy.TranspileAPI(**vars(args)).transpile() 10 | 11 | 12 | def setup_argparse() -> argparse.ArgumentParser: 13 | """Configures the console argument parser""" 14 | argparser = argparse.ArgumentParser(**ARGPARSE_CONFIG['parser']) 15 | 16 | for argument_config in ARGPARSE_CONFIG['arguments']: 17 | argparser.add_argument(*argument_config['args'], **argument_config['kwargs']) 18 | 19 | return argparser 20 | 21 | 22 | def main(): 23 | argparser = setup_argparse() 24 | parsed_args = argparser.parse_args() 25 | make_action(parsed_args) 26 | 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /docs/TODO.md: -------------------------------------------------------------------------------- 1 | # FastPy TODO list 2 | 3 | ## Main tasks 4 | 5 | - [x] Variables 6 | - [x] scopes 7 | - [x] creation 8 | - [ ] Functions 9 | - [x] return 10 | - [x] body 11 | - [x] arguments 12 | - [ ] decorators 13 | - [ ] Modules 14 | - [x] importing 15 | - [ ] namespaces 16 | - [x] Conditions 17 | - [x] if 18 | - [x] elif 19 | - [x] else 20 | - [ ] Loops 21 | - [ ] for 22 | - [x] while 23 | - [ ] Match-Case 24 | - [ ] Builtin 25 | - [ ] types 26 | - [x] str 27 | - [x] bool 28 | - [x] int 29 | - [ ] list 30 | - [ ] dict / map 31 | - [ ] functions 32 | - [x] logging 33 | - [x] input 34 | - [x] Binary operations 35 | - [x] number 36 | - [x] variable 37 | - [x] string 38 | - [x] Logic operations 39 | - [x] number 40 | - [x] variable 41 | - [x] string 42 | - [ ] Templates -------------------------------------------------------------------------------- /fastpy/log/formatter.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import colorama 3 | from .config import * 4 | 5 | colorama.init() 6 | 7 | 8 | class CustomFormatter(logging.Formatter): 9 | green = colorama.Fore.GREEN 10 | white = colorama.Fore.WHITE 11 | blue = colorama.Fore.BLUE 12 | yellow = colorama.Fore.YELLOW 13 | red = colorama.Fore.RED 14 | bold_red = colorama.Fore.RED + colorama.Style.BRIGHT 15 | reset = colorama.Style.RESET_ALL 16 | 17 | FORMATS = { 18 | logging.DEBUG: blue + FORMAT + reset, 19 | logging.INFO: green + FORMAT + reset, 20 | logging.WARNING: yellow + FORMAT + reset, 21 | logging.ERROR: red + FORMAT + reset, 22 | logging.CRITICAL: bold_red + FORMAT + reset 23 | } 24 | 25 | def format(self, record): 26 | log_fmt = self.FORMATS.get(record.levelno) 27 | formatter = logging.Formatter(log_fmt) 28 | return formatter.format(record) 29 | -------------------------------------------------------------------------------- /fastpy/parser/config.py: -------------------------------------------------------------------------------- 1 | from ..config import JsonConfig, CONFIG_FOLDER 2 | import os 3 | 4 | parser_config = JsonConfig( 5 | filepath=os.path.join(CONFIG_FOLDER, 'parser.json'), 6 | authoload=True 7 | ) 8 | 9 | AST_CLASS_PATH: str = parser_config[ 10 | 'ast_class' 11 | ] # Abstract Syntax Tree classpath to import, by default - fastpy.parser.ast.AST 12 | 13 | PARSER_CLASS_PATH: str = parser_config[ 14 | 'parser_class' 15 | ] # main parser classpath to import, by default - fastpy.parser.parsers.Parser 16 | 17 | NODE_PARSING: dict = parser_config.get('node_parsing', {}) # node parsing data 18 | 19 | operators_config = JsonConfig( 20 | filepath=os.path.join(CONFIG_FOLDER, 'operators.json'), 21 | authoload=True 22 | ) 23 | 24 | BIN_OP_NAMES: list = operators_config['binary_operator_names'] # names of binary operators 25 | LOGIC_OP_NAMES: list = operators_config['logic_operator_names'] # names of logic operators 26 | -------------------------------------------------------------------------------- /config/argparse.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser": { 3 | "description": "FastPy language translator." 4 | }, 5 | "arguments": [ 6 | { 7 | "args": [ 8 | "-s", 9 | "--source" 10 | ], 11 | "kwargs": { 12 | "help": ".fpy main source file", 13 | "required": true 14 | } 15 | }, 16 | { 17 | "args": [ 18 | "-t", 19 | "--translate" 20 | ], 21 | "kwargs": { 22 | "help": "translate source files to C++", 23 | "action": "store_true" 24 | } 25 | }, 26 | { 27 | "args": [ 28 | "-o", 29 | "--output" 30 | ], 31 | "kwargs": { 32 | "help": "output folder" 33 | } 34 | }, 35 | { 36 | "args": [ 37 | "-c", 38 | "--compile" 39 | ], 40 | "kwargs": { 41 | "help": "compile out cpp sources", 42 | "action": "store_true" 43 | } 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /fastpy/lexer/config.py: -------------------------------------------------------------------------------- 1 | from ..config import JsonConfig, CONFIG_FOLDER 2 | from fastpy.import_tools import import_class 3 | import os 4 | import pydoc 5 | 6 | lexer_config = JsonConfig( 7 | filepath=os.path.join(CONFIG_FOLDER, 'lexer.json'), 8 | authoload=True 9 | ) 10 | 11 | COMMENT_START_SYMBOL = lexer_config['comment_start'] 12 | TOKEN_CLASS_PATH = lexer_config['token_class'] # Token classpath to import, by default - fastpy.lexer.tokens.Token 13 | LEXER_CLASS_PATH = lexer_config['lexer_class'] # Lexer classpath to import, by default - fastpy.lexer.tokens.Token 14 | TOKEN_DETECTION = lexer_config.get('token_detection', {}) # token detection data 15 | 16 | operators_config = JsonConfig( 17 | filepath=os.path.join(CONFIG_FOLDER, 'operators.json'), 18 | authoload=True 19 | ) 20 | 21 | OPERATORS = operators_config['operators'] # dict of operators and it names 22 | SIMILAR_OPERATORS = operators_config['similar_operators'] # dict of operators and lists of similar operators 23 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/CXX.includecache: -------------------------------------------------------------------------------- 1 | #IncludeRegexLine: ^[ ]*[#%][ ]*(include|import)[ ]*[<"]([^">]+)([">]) 2 | 3 | #IncludeRegexScan: ^.*$ 4 | 5 | #IncludeRegexComplain: ^$ 6 | 7 | #IncludeRegexTransform: 8 | 9 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/builtin.hpp 10 | iostream 11 | - 12 | string 13 | - 14 | termcolor.hpp 15 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/termcolor.hpp 16 | 17 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/termcolor.hpp 18 | iostream 19 | - 20 | unistd.h 21 | - 22 | io.h 23 | - 24 | windows.h 25 | - 26 | 27 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/main.cpp 28 | iostream 29 | - 30 | string 31 | - 32 | include/termcolor.hpp 33 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/termcolor.hpp 34 | include/builtin.hpp 35 | D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/include/builtin.hpp 36 | 37 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/DependInfo.cmake: -------------------------------------------------------------------------------- 1 | 2 | # Consider dependencies only in project. 3 | set(CMAKE_DEPENDS_IN_PROJECT_ONLY OFF) 4 | 5 | # The set of languages for which implicit dependencies are needed: 6 | set(CMAKE_DEPENDS_LANGUAGES 7 | "CXX" 8 | ) 9 | # The set of files for implicit dependencies of each language: 10 | set(CMAKE_DEPENDS_CHECK_CXX 11 | "D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/main.cpp" "D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/main.cpp.obj" 12 | ) 13 | set(CMAKE_CXX_COMPILER_ID "GNU") 14 | 15 | # The include file search paths: 16 | set(CMAKE_CXX_TARGET_INCLUDE_PATH 17 | ) 18 | 19 | # The set of dependency files which are needed: 20 | set(CMAKE_DEPENDS_DEPENDENCY_FILES 21 | ) 22 | 23 | # Targets to which this target links. 24 | set(CMAKE_TARGET_LINKED_INFO_FILES 25 | ) 26 | 27 | # Fortran module output directory. 28 | set(CMAKE_Fortran_TARGET_MODULE_DIR "") 29 | -------------------------------------------------------------------------------- /fastpy/semantic_analyzer/scope.py: -------------------------------------------------------------------------------- 1 | from ..parser.nodes import * 2 | from .config import * 3 | 4 | 5 | class Scope: 6 | def __init__(self, node: BaseNode | None = None): 7 | self._node = node 8 | self._scope: dict[str, NamedNode] = {} 9 | 10 | def push(self, node: NamedNode): 11 | self._scope.update({node.identifier.text: node}) 12 | 13 | def push_several(self, nodes: list[NamedNode] | tuple[NamedNode]): 14 | self._scope.update({ 15 | node.identifier.text: node for node in nodes 16 | }) 17 | 18 | def get_global(self): 19 | for identifier, node in self._scope.items(): 20 | if isinstance(node, FuncNode): 21 | yield node 22 | 23 | def is_scope_node(self, node: BaseNode): 24 | return self._node == node 25 | 26 | def already_defined(self, name: str): 27 | if name in BUILTIN_FUNCTIONS.values() or name in BUILTIN_TYPES.values(): 28 | return True 29 | 30 | return self._scope.get(name) is not None 31 | 32 | def __repr__(self): 33 | return str(tuple(self._scope.keys())) 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 CrazyProger1 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /examples/far_future.fpy: -------------------------------------------------------------------------------- 1 | 2 | interface Car: 3 | public: 4 | fun __init__(name: str, weight: int): ... 5 | 6 | 7 | @property 8 | fun brand() -> str: ... 9 | 10 | 11 | @property 12 | fun name() -> str: ... 13 | 14 | 15 | @property 16 | fun weight() -> int: ... 17 | 18 | 19 | class Tesla(Car): # Car implementation 20 | private: 21 | __name: str 22 | __weight: int 23 | public: 24 | fun __init__(name: str, weight: int): 25 | __name = name 26 | __weight = weight 27 | 28 | 29 | @property 30 | fun brand() -> str: 31 | return 'Tesla' 32 | 33 | 34 | @property 35 | fun name() -> str: 36 | return __name 37 | 38 | 39 | @property 40 | fun weight() -> int: 41 | return __weight 42 | 43 | 44 | 45 | fun car_factory(car_brand: str, name: str, weight: int) -> Car: 46 | match car_brand: 47 | case "Tesla": 48 | return Tesla(name, weight) 49 | default: 50 | return null 51 | 52 | 53 | some_car: Car = car_factory("Tesla", "Model X", 2301) 54 | log(some_car.name) # Model X 55 | log(some_car.weight) # 2301 -------------------------------------------------------------------------------- /fastpy/transpiler/config.py: -------------------------------------------------------------------------------- 1 | from ..config import JsonConfig, CONFIG_FOLDER 2 | from ..filesystem import FileSystem as Fs 3 | 4 | transpiler_config = JsonConfig( 5 | filepath=Fs.join(CONFIG_FOLDER, 'transpiler.json'), 6 | authoload=True 7 | ) 8 | 9 | TRANSPILER_CLASS_PATH = transpiler_config[ 10 | 'transpiler_class' 11 | ] # Transpailer classpath to import, by default - fastpy.transpiler.transpilers.Transpiler 12 | 13 | CPP_TEMPLATES_DIR = transpiler_config['cpp_templates_dir'] # folder of C++ templates 14 | CPP_MAIN_TEMPLATE_PATH = transpiler_config['cpp_main_template'] # main C++ template filename 15 | CPP_TEMPLATE_PATH = transpiler_config['cpp_template'] # module C++ template filename 16 | 17 | NODE_TRANSPILING = transpiler_config['node_transpiling'] # node transpiling data 18 | 19 | builtin_config = JsonConfig( 20 | filepath=Fs.join(CONFIG_FOLDER, 'builtin.json'), 21 | authoload=True 22 | ) 23 | 24 | BUILTIN_TYPES: dict = builtin_config['builtin_types'] 25 | BUILTIN_FUNCTIONS: dict = builtin_config['builtin_functions'] 26 | 27 | operators_config = JsonConfig( 28 | filepath=Fs.join(CONFIG_FOLDER, 'operators.json'), 29 | authoload=True 30 | ) 31 | 32 | OPERATORS_EQUIVALENTS = operators_config['fastpy_cpp_equivalents'] # C++ equivalents of FastPy operators 33 | -------------------------------------------------------------------------------- /config/operators.json: -------------------------------------------------------------------------------- 1 | { 2 | "operators": { 3 | "+": "addition", 4 | "-": "subtraction", 5 | "*": "multiplication", 6 | "/": "division", 7 | "^": "exponentiation", 8 | "%": "modulus", 9 | "if": "if", 10 | "elif": "elif", 11 | "else": "else", 12 | "=": "assign", 13 | ">": "greater", 14 | "<": "less", 15 | "==": "equal", 16 | "!=": "not_equal", 17 | "and": "and", 18 | "or": "or", 19 | "not": "not", 20 | "fun": "function", 21 | "return": "return", 22 | "while": "while", 23 | "for": "for", 24 | "break": "break", 25 | "continue": "continue", 26 | "@": "decorator", 27 | "$": "directive", 28 | ":": "body_start", 29 | "->": "return_type", 30 | ".": "access" 31 | }, 32 | "similar_operators": { 33 | "-": [ 34 | "->" 35 | ], 36 | "=": [ 37 | "==" 38 | ] 39 | }, 40 | "binary_operator_names": [ 41 | "addition", 42 | "subtraction", 43 | "multiplication", 44 | "division", 45 | "exponentiation", 46 | "modulus", 47 | "greater", 48 | "less", 49 | "equal", 50 | "not_equal" 51 | ], 52 | "logic_operator_names": [ 53 | "and", 54 | "or" 55 | ], 56 | "fastpy_cpp_equivalents": { 57 | "and": "&&", 58 | "or": "||" 59 | } 60 | } -------------------------------------------------------------------------------- /cpp_code/libs/builtin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // requirements 4 | 5 | #include 6 | #include 7 | #include"termcolor.hpp" 8 | 9 | 10 | 11 | // builtin types 12 | 13 | typedef std::string str; 14 | 15 | 16 | 17 | // builtin functions 18 | 19 | template 20 | void log(const T &t, bool endl = false) { 21 | std::cout << t; 22 | if (endl) { 23 | std::cout << std::endl; 24 | } 25 | std::cout << termcolor::reset; 26 | } 27 | 28 | 29 | template 30 | void log_error(const T &t, bool endl = false) { 31 | std::cout << termcolor::on_red << termcolor::grey; 32 | log(t, endl); 33 | } 34 | 35 | template 36 | void log_info(const T &t, bool endl = false) { 37 | std::cout << termcolor::on_blue << termcolor::grey; 38 | log(t, endl); 39 | 40 | } 41 | 42 | template 43 | void log_warning(const T &t, bool endl = false) { 44 | std::cout << termcolor::on_yellow << termcolor::grey; 45 | log(t, endl); 46 | } 47 | 48 | template 49 | T input(const str &t) { 50 | if (!t.empty()) { 51 | log(t); 52 | } 53 | 54 | T inp; 55 | if constexpr (std::is_same_v, str>) 56 | getline(std::cin, inp); 57 | else 58 | std::cin>>inp; 59 | 60 | return inp; 61 | } 62 | -------------------------------------------------------------------------------- /fastpy_build/src/include/builtin.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // requirements 4 | 5 | #include 6 | #include 7 | #include"termcolor.hpp" 8 | 9 | 10 | 11 | // builtin types 12 | 13 | typedef std::string str; 14 | 15 | 16 | 17 | // builtin functions 18 | 19 | template 20 | void log(const T &t, bool endl = false) { 21 | std::cout << t; 22 | if (endl) { 23 | std::cout << std::endl; 24 | } 25 | std::cout << termcolor::reset; 26 | } 27 | 28 | 29 | template 30 | void log_error(const T &t, bool endl = false) { 31 | std::cout << termcolor::on_red << termcolor::grey; 32 | log(t, endl); 33 | } 34 | 35 | template 36 | void log_info(const T &t, bool endl = false) { 37 | std::cout << termcolor::on_blue << termcolor::grey; 38 | log(t, endl); 39 | 40 | } 41 | 42 | template 43 | void log_warning(const T &t, bool endl = false) { 44 | std::cout << termcolor::on_yellow << termcolor::grey; 45 | log(t, endl); 46 | } 47 | 48 | template 49 | T input(const str &t) { 50 | if (!t.empty()) { 51 | log(t); 52 | } 53 | 54 | T inp; 55 | if constexpr (std::is_same_v, str>) 56 | getline(std::cin, inp); 57 | else 58 | std::cin>>inp; 59 | 60 | return inp; 61 | } 62 | -------------------------------------------------------------------------------- /config/transpiler.json: -------------------------------------------------------------------------------- 1 | { 2 | "transpiler_class": "fastpy.transpiler.transpilers.Transpiler", 3 | "cpp_main_template": "minimal_main.cpp", 4 | "cpp_templates_dir": "cpp_code", 5 | "cpp_template": "minimal.cpp", 6 | "node_transpiling": { 7 | "fastpy.parser.nodes.AssignNode": "fastpy.transpiler.node_transpilers.AssignNodeTranspiler", 8 | "fastpy.parser.nodes.ValueNode": "fastpy.transpiler.node_transpilers.ValueNodeTranspiler", 9 | "fastpy.parser.nodes.VariableNode": "fastpy.transpiler.node_transpilers.VariableNodeTranspiler", 10 | "fastpy.parser.nodes.BinOpNode": "fastpy.transpiler.node_transpilers.OperationsNodeTranspiler", 11 | "fastpy.parser.nodes.FuncNode": "fastpy.transpiler.node_transpilers.FuncNodeTranspiler", 12 | "fastpy.parser.nodes.CallNode": "fastpy.transpiler.node_transpilers.CallNodeTranspiler", 13 | "fastpy.parser.nodes.IfNode": "fastpy.transpiler.node_transpilers.IfNodeTranspiler", 14 | "fastpy.parser.nodes.LogicOpNode": "fastpy.transpiler.node_transpilers.OperationsNodeTranspiler", 15 | "fastpy.parser.nodes.ElseNode": "fastpy.transpiler.node_transpilers.ElseNodeTranspiler", 16 | "fastpy.parser.nodes.WhileNode": "fastpy.transpiler.node_transpilers.WhileNodeTranspiler", 17 | "fastpy.parser.nodes.ReturnNode": "fastpy.transpiler.node_transpilers.ReturnNodeTranspiler" 18 | } 19 | } -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/cmake_install.cmake: -------------------------------------------------------------------------------- 1 | # Install script for directory: D:/Programming/Python/Projects/FastPy-public/fastpy_build/src 2 | 3 | # Set the install prefix 4 | if(NOT DEFINED CMAKE_INSTALL_PREFIX) 5 | set(CMAKE_INSTALL_PREFIX "C:/Program Files (x86)/src") 6 | endif() 7 | string(REGEX REPLACE "/$" "" CMAKE_INSTALL_PREFIX "${CMAKE_INSTALL_PREFIX}") 8 | 9 | # Set the install configuration name. 10 | if(NOT DEFINED CMAKE_INSTALL_CONFIG_NAME) 11 | if(BUILD_TYPE) 12 | string(REGEX REPLACE "^[^A-Za-z0-9_]+" "" 13 | CMAKE_INSTALL_CONFIG_NAME "${BUILD_TYPE}") 14 | else() 15 | set(CMAKE_INSTALL_CONFIG_NAME "Debug") 16 | endif() 17 | message(STATUS "Install configuration: \"${CMAKE_INSTALL_CONFIG_NAME}\"") 18 | endif() 19 | 20 | # Set the component getting installed. 21 | if(NOT CMAKE_INSTALL_COMPONENT) 22 | if(COMPONENT) 23 | message(STATUS "Install component: \"${COMPONENT}\"") 24 | set(CMAKE_INSTALL_COMPONENT "${COMPONENT}") 25 | else() 26 | set(CMAKE_INSTALL_COMPONENT) 27 | endif() 28 | endif() 29 | 30 | # Is this installation the result of a crosscompile? 31 | if(NOT DEFINED CMAKE_CROSSCOMPILING) 32 | set(CMAKE_CROSSCOMPILING "FALSE") 33 | endif() 34 | 35 | # Set default install directory permissions. 36 | if(NOT DEFINED CMAKE_OBJDUMP) 37 | set(CMAKE_OBJDUMP "D:/Programming/C++/Compilers/mingw64/bin/objdump.exe") 38 | endif() 39 | 40 | if(CMAKE_INSTALL_COMPONENT) 41 | set(CMAKE_INSTALL_MANIFEST "install_manifest_${CMAKE_INSTALL_COMPONENT}.txt") 42 | else() 43 | set(CMAKE_INSTALL_MANIFEST "install_manifest.txt") 44 | endif() 45 | 46 | string(REPLACE ";" "\n" CMAKE_INSTALL_MANIFEST_CONTENT 47 | "${CMAKE_INSTALL_MANIFEST_FILES}") 48 | file(WRITE "D:/Programming/Python/Projects/FastPy-public/fastpy_build/src/cmake-build-debug/${CMAKE_INSTALL_MANIFEST}" 49 | "${CMAKE_INSTALL_MANIFEST_CONTENT}") 50 | -------------------------------------------------------------------------------- /fastpy/filesystem/filesystem.py: -------------------------------------------------------------------------------- 1 | import os 2 | import shutil 3 | 4 | 5 | class FileSystem: 6 | @staticmethod 7 | def read_file(filepath: str) -> str: 8 | with open(filepath, 'r') as f: 9 | return f.read() 10 | 11 | @staticmethod 12 | def write_file(filepath: str, content: str): 13 | with open(filepath, 'w') as f: 14 | f.write(content) 15 | 16 | @staticmethod 17 | def remove_quotes(path: str): 18 | for symbol in ['"', "'"]: 19 | path = path.replace(symbol, '') 20 | return path 21 | 22 | @staticmethod 23 | def normalize_path(path: str) -> str: 24 | path = FileSystem.remove_quotes(path) 25 | if os.path.exists(path): 26 | return os.path.abspath(path) 27 | else: 28 | raise FileNotFoundError(f'The path "{path}" does not exist.') 29 | 30 | @staticmethod 31 | def replace_ext(path: str, new_ext: str) -> str: 32 | path, current_ext = os.path.splitext(path) 33 | return path + new_ext 34 | 35 | @staticmethod 36 | def copy_files(src_folder: str, dest_folder: str): 37 | for file in os.listdir(src_folder): 38 | shutil.copyfile(os.path.join(src_folder, file), os.path.join(dest_folder, file)) 39 | 40 | @staticmethod 41 | def get_filename_without_ext(path: str) -> str: 42 | return os.path.splitext(os.path.basename(path))[0] 43 | 44 | @staticmethod 45 | def get_filename(path: str): 46 | return os.path.basename(path) 47 | 48 | @staticmethod 49 | def exists(path: str) -> bool: 50 | return os.path.exists(path) 51 | 52 | @staticmethod 53 | def join(*parts) -> str: 54 | return os.path.join(*parts) 55 | 56 | @staticmethod 57 | def makedirs(path: str): 58 | try: 59 | os.makedirs(path) 60 | except FileExistsError: 61 | pass 62 | 63 | @staticmethod 64 | def execute(command: str) -> int: 65 | return os.system('call ' + command) 66 | -------------------------------------------------------------------------------- /fastpy/config/json_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import json 3 | from .config import * 4 | from ..exceptions import ConfigNotLoadedError 5 | 6 | 7 | class JsonConfig(BaseConfig): 8 | """Json config loader""" 9 | 10 | def __init__(self, filepath: str, authoload: bool = True): 11 | self._filepath = None 12 | self._config = None 13 | self._config_type: type[list | dict] | None = None 14 | 15 | self.set_filepath(filepath) 16 | 17 | if authoload: 18 | self.load() 19 | 20 | @property 21 | def type(self): 22 | return self._config_type 23 | 24 | def set_filepath(self, filepath: str) -> None: 25 | if not os.path.exists(filepath): 26 | raise FileNotFoundError(f'File "{filepath}" not found') 27 | 28 | self._filepath = filepath 29 | 30 | def load(self): 31 | with open(self._filepath, 'r') as cf: 32 | try: 33 | self._config = json.load(cf) 34 | except json.decoder.JSONDecodeError as e: 35 | raise json.decoder.JSONDecodeError(f'File "{self._filepath}" has wrong format', e.doc, e.pos) 36 | self._config_type = type(self._config) 37 | 38 | def save(self): 39 | with open(self._filepath, 'w') as cf: 40 | if self._config: 41 | json.dump(self._config, cf) 42 | 43 | def get(self, key: str | int, default: any = None) -> any: 44 | try: 45 | return self.__getitem__(key) 46 | except (KeyError, IndexError): 47 | return default 48 | 49 | def __getitem__(self, item): 50 | if self._config: 51 | return self._config[item] 52 | else: 53 | raise ConfigNotLoadedError('Config not loaded yet') 54 | 55 | def __iter__(self): 56 | if issubclass(self._config_type, dict): 57 | return iter(self._config.items()) 58 | 59 | elif issubclass(self._config_type, list): 60 | return iter(self._config) 61 | 62 | def __repr__(self): 63 | if self._config: 64 | return json.dumps(self._config, indent=4, sort_keys=True) 65 | 66 | return 'Not loaded' 67 | -------------------------------------------------------------------------------- /fastpy/transpiler/code.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | 3 | 4 | class BaseCode(ABC): 5 | """ 6 | Code interface. 7 | This class is used by transpiler for separation convenience of internal and external code. 8 | Internal code - code in main function, external - outside of main func 9 | """ 10 | 11 | @abstractmethod 12 | def push_internal(self, code: str, **kwargs): ... 13 | 14 | @abstractmethod 15 | def push_external(self, code: str, **kwargs): ... 16 | 17 | @property 18 | @abstractmethod 19 | def internal(self) -> str: ... 20 | 21 | @property 22 | @abstractmethod 23 | def external(self) -> str: ... 24 | 25 | 26 | class Code(BaseCode): 27 | def __init__(self): 28 | self._internal = '' 29 | self._external = '' 30 | 31 | def push_internal(self, code: str, **kwargs): 32 | auto_semicolon = kwargs.get('auto_semicolon', True) 33 | endl = kwargs.get('endl', True) 34 | 35 | if not code: 36 | return 37 | 38 | if auto_semicolon and not code.endswith(';'): 39 | if code.endswith('\n'): 40 | for char in reversed(code): 41 | if char == ';': 42 | break 43 | 44 | elif char not in ('\n', ' '): 45 | code += ';' 46 | break 47 | 48 | else: 49 | code += ';' 50 | 51 | if endl: 52 | code += '\n' 53 | 54 | self._internal += code 55 | 56 | def push_external(self, code: str, **kwargs): 57 | auto_semicolon = kwargs.get('auto_semicolon', True) 58 | endl = kwargs.get('endl', False) 59 | 60 | if not code: 61 | return 62 | 63 | code = code.strip() 64 | 65 | if auto_semicolon and not code.endswith(';'): 66 | code += ';' 67 | 68 | if endl: 69 | code += '\n' 70 | self._external += code 71 | 72 | def __repr__(self): 73 | return self._internal 74 | 75 | @property 76 | def internal(self) -> str: 77 | return self._internal 78 | 79 | @property 80 | def external(self) -> str: 81 | return self._external 82 | -------------------------------------------------------------------------------- /fastpy/lexer/tokens.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from .token_types import TokenTypes 3 | from fastpy.import_tools import import_class 4 | from .config import * 5 | 6 | 7 | class BaseToken(ABC): 8 | """Token interface""" 9 | 10 | @property 11 | @abstractmethod 12 | def type(self) -> TokenTypes: ... 13 | 14 | @property 15 | @abstractmethod 16 | def text(self) -> str: ... 17 | 18 | @property 19 | @abstractmethod 20 | def line(self) -> int: ... 21 | 22 | @property 23 | @abstractmethod 24 | def name(self) -> str: ... 25 | 26 | 27 | class Token(BaseToken): 28 | """Basic token implementation of FastPy""" 29 | 30 | def __init__(self, 31 | token_type: TokenTypes, 32 | text: str, 33 | line: int, 34 | name: str = None): 35 | """ 36 | 37 | :param token_type: 38 | :param text: code part from which this token is extracted 39 | :param line: line number of code part 40 | :param name: operator name, used only for operators customization, for non-op tokens is None 41 | """ 42 | 43 | self._type = token_type 44 | self._text = text 45 | self._line = line 46 | self._name = name 47 | 48 | @property 49 | def type(self) -> TokenTypes: 50 | return self._type 51 | 52 | @property 53 | def text(self) -> str: 54 | return self._text 55 | 56 | @property 57 | def line(self) -> int: 58 | return self._line 59 | 60 | @property 61 | def name(self) -> str | None: 62 | return self._name 63 | 64 | def __repr__(self): 65 | return f'{self._text}({self._type.name})' 66 | 67 | 68 | def code_from_tokens(tokens: list[BaseToken] | tuple[BaseToken]): 69 | code = '' 70 | for token in tokens: 71 | code += token.text + ' ' 72 | return code 73 | 74 | 75 | _token_class: BaseToken | None = None 76 | 77 | 78 | def create_token( 79 | token_type: TokenTypes, 80 | text: str, 81 | line: int, 82 | name: str = None, 83 | **kwargs) -> BaseToken: 84 | 85 | """Token factory""" 86 | 87 | global _token_class 88 | 89 | if not _token_class: 90 | _token_class = import_class(TOKEN_CLASS_PATH) 91 | 92 | return _token_class(token_type, text, line, name, **kwargs) 93 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # PyCharm 132 | .idea -------------------------------------------------------------------------------- /fastpy/log/logger.py: -------------------------------------------------------------------------------- 1 | from .formatter import * 2 | from datetime import datetime 3 | 4 | 5 | class Logger: 6 | logger = logging.getLogger(APP_NAME) 7 | logger.setLevel(LEVEL) 8 | 9 | stream_handler = logging.StreamHandler() 10 | stream_handler.setLevel(LEVEL) 11 | stream_handler.setFormatter(CustomFormatter()) 12 | 13 | logger.addHandler(stream_handler) 14 | 15 | @staticmethod 16 | def log_debug(*msg: any, sep: str = ' '): 17 | Logger.logger.debug(sep.join(map(str, msg))) 18 | 19 | @staticmethod 20 | def log_info(*msg: any, sep: str = ' '): 21 | Logger.logger.info(sep.join(map(str, msg))) 22 | 23 | @staticmethod 24 | def log_waring(*msg: any, sep: str = ' '): 25 | Logger.logger.info(sep.join(map(str, msg))) 26 | 27 | @staticmethod 28 | def log_error(*msg: any, sep: str = ' '): 29 | Logger.logger.error(sep.join(map(str, msg))) 30 | 31 | @staticmethod 32 | def log_critical(*msg: any, sep: str = ' '): 33 | Logger.logger.critical(sep.join(map(str, msg))) 34 | 35 | @staticmethod 36 | def print_raw(raw: any, raw_name: any): 37 | if Logger.logger.level == logging.DEBUG: 38 | print(colorama.Fore.WHITE, end='') 39 | print(raw_name) 40 | print('+--------------------------------------------------------+') 41 | print(raw) 42 | print('+--------------------------------------------------------+') 43 | print(colorama.Style.RESET_ALL, end='') 44 | 45 | @staticmethod 46 | def info(*msg: any, sep: str = ' ', pattern: str = None, ending_message: str = None): 47 | def deco(func): 48 | def wrapper(*args, **kwargs): 49 | if pattern: 50 | Logger.log_info(pattern.format(**kwargs), sep=sep) 51 | else: 52 | Logger.log_info(*msg, sep=sep) 53 | 54 | start = datetime.now() 55 | out = func(*args, **kwargs) 56 | end = datetime.now() 57 | lead_time = end - start 58 | 59 | if ending_message: 60 | if '{time}' in ending_message: 61 | Logger.log_info(ending_message.format(time=lead_time)) 62 | else: 63 | Logger.log_info(ending_message) 64 | 65 | return out 66 | 67 | return wrapper 68 | 69 | return deco 70 | 71 | @staticmethod 72 | def catch_errors(): 73 | def deco(func): 74 | def wrapper(*args, **kwargs): 75 | try: 76 | out = func(*args, **kwargs) 77 | except Exception as e: 78 | Logger.log_critical(e) 79 | os.system('pause') 80 | exit(-1) 81 | 82 | return out 83 | 84 | return wrapper 85 | 86 | return deco 87 | -------------------------------------------------------------------------------- /examples/first_tic_tac_toe.fpy: -------------------------------------------------------------------------------- 1 | a: str = ' ' 2 | b: str = ' ' 3 | c: str = ' ' 4 | d: str = ' ' 5 | e: str = ' ' 6 | f: str = ' ' 7 | g: str = ' ' 8 | h: str = ' ' 9 | i: str = ' ' 10 | 11 | who_move: str = 'X' 12 | continue_game: bool = true 13 | 14 | 15 | fun next_player(who_move: str) -> str: 16 | if who_move == 'X': 17 | return 'O' 18 | else: 19 | return 'X' 20 | 21 | 22 | 23 | 24 | fun draw_vert_border(endl: bool = false): 25 | log('|', endl) 26 | 27 | fun draw_horz_border(): 28 | log('+-+-+-+', true) 29 | 30 | fun draw_row(a: str, b: str, c: str): 31 | draw_vert_border() 32 | log(a) 33 | draw_vert_border() 34 | log(b) 35 | draw_vert_border() 36 | log(c) 37 | draw_vert_border(true) 38 | 39 | fun draw(a: str, b: str, c: str, d: str, e: str, f: str, g: str, h: str, i: str): 40 | draw_horz_border() 41 | draw_row(a, b, c) 42 | draw_horz_border() 43 | draw_row(d, e, f) 44 | draw_horz_border() 45 | draw_row(g, h, i) 46 | draw_horz_border() 47 | 48 | 49 | 50 | fun ask_cell(who_move: str) -> int: 51 | log(who_move) 52 | cell: int = input(' choose cell >>') 53 | return cell 54 | 55 | fun check_three(a: str, b: str, c: str) -> bool: 56 | return a == b and b == c and c != ' ' 57 | 58 | fun check_game_over(a: str, b: str, c: str, d: str, e: str, f: str, g: str, h: str, i: str) -> bool: 59 | r1 = check_three(a, b, c) 60 | r2 = check_three(a, d, g) 61 | r3 = check_three(a, e, i) 62 | r4 = check_three(b, e, h) 63 | r5 = check_three(d, e, f) 64 | r6 = check_three(g, e, c) 65 | r7 = check_three(c, f, i) 66 | r8 = check_three(g, h , i) 67 | return r1 or r2 or r3 or r4 or r5 or r6 or r7 or r8 68 | 69 | fun check_draw(a: str, b: str, c: str, d: str, e: str, f: str, g: str, h: str, i: str) -> bool: 70 | return a != ' ' and b != ' ' and c != ' ' and d != ' ' and e != ' ' and f != ' ' and g != ' ' and h != ' ' and i != ' ' 71 | 72 | while continue_game: 73 | draw(a, b, c, d, e, f, g, h, i) 74 | cell = ask_cell(who_move) 75 | 76 | if cell == 1: 77 | a = who_move 78 | elif cell == 2: 79 | b = who_move 80 | elif cell == 3: 81 | c = who_move 82 | elif cell == 4: 83 | d = who_move 84 | elif cell == 5: 85 | e = who_move 86 | elif cell == 6: 87 | f = who_move 88 | elif cell == 7: 89 | g = who_move 90 | elif cell == 8: 91 | h = who_move 92 | elif cell == 9: 93 | i = who_move 94 | else: 95 | log_error('Enter number in range: 1 - 9!', true) 96 | who_move = next_player(who_move) 97 | 98 | game_over = check_game_over(a, b, c, d, e, f, g, h, i) 99 | drw = check_draw(a, b, c, d, e, f, g, h, i) 100 | 101 | if drw: 102 | draw(a, b, c, d, e, f, g, h, i) 103 | log_info('Draw!') 104 | continue_game = false 105 | elif game_over: 106 | draw(a, b, c, d, e, f, g, h, i) 107 | log_info('Game Over! ') 108 | log_info('Won: ') 109 | log_info(who_move) 110 | continue_game = false 111 | 112 | 113 | 114 | who_move = next_player(who_move) 115 | 116 | 117 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeCCompiler.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_C_COMPILER "D:/Programming/C++/Compilers/mingw64/bin/gcc.exe") 2 | set(CMAKE_C_COMPILER_ARG1 "") 3 | set(CMAKE_C_COMPILER_ID "GNU") 4 | set(CMAKE_C_COMPILER_VERSION "7.3.0") 5 | set(CMAKE_C_COMPILER_VERSION_INTERNAL "") 6 | set(CMAKE_C_COMPILER_WRAPPER "") 7 | set(CMAKE_C_STANDARD_COMPUTED_DEFAULT "11") 8 | set(CMAKE_C_COMPILE_FEATURES "c_std_90;c_function_prototypes;c_std_99;c_restrict;c_variadic_macros;c_std_11;c_static_assert") 9 | set(CMAKE_C90_COMPILE_FEATURES "c_std_90;c_function_prototypes") 10 | set(CMAKE_C99_COMPILE_FEATURES "c_std_99;c_restrict;c_variadic_macros") 11 | set(CMAKE_C11_COMPILE_FEATURES "c_std_11;c_static_assert") 12 | 13 | set(CMAKE_C_PLATFORM_ID "MinGW") 14 | set(CMAKE_C_SIMULATE_ID "") 15 | set(CMAKE_C_COMPILER_FRONTEND_VARIANT "") 16 | set(CMAKE_C_SIMULATE_VERSION "") 17 | 18 | 19 | 20 | 21 | set(CMAKE_AR "D:/Programming/C++/Compilers/mingw64/bin/ar.exe") 22 | set(CMAKE_C_COMPILER_AR "D:/Programming/C++/Compilers/mingw64/bin/gcc-ar.exe") 23 | set(CMAKE_RANLIB "D:/Programming/C++/Compilers/mingw64/bin/ranlib.exe") 24 | set(CMAKE_C_COMPILER_RANLIB "D:/Programming/C++/Compilers/mingw64/bin/gcc-ranlib.exe") 25 | set(CMAKE_LINKER "D:/Programming/C++/Compilers/mingw64/bin/ld.exe") 26 | set(CMAKE_MT "") 27 | set(CMAKE_COMPILER_IS_GNUCC 1) 28 | set(CMAKE_C_COMPILER_LOADED 1) 29 | set(CMAKE_C_COMPILER_WORKS TRUE) 30 | set(CMAKE_C_ABI_COMPILED TRUE) 31 | set(CMAKE_COMPILER_IS_MINGW 1) 32 | set(CMAKE_COMPILER_IS_CYGWIN ) 33 | if(CMAKE_COMPILER_IS_CYGWIN) 34 | set(CYGWIN 1) 35 | set(UNIX 1) 36 | endif() 37 | 38 | set(CMAKE_C_COMPILER_ENV_VAR "CC") 39 | 40 | if(CMAKE_COMPILER_IS_MINGW) 41 | set(MINGW 1) 42 | endif() 43 | set(CMAKE_C_COMPILER_ID_RUN 1) 44 | set(CMAKE_C_SOURCE_FILE_EXTENSIONS c;m) 45 | set(CMAKE_C_IGNORE_EXTENSIONS h;H;o;O;obj;OBJ;def;DEF;rc;RC) 46 | set(CMAKE_C_LINKER_PREFERENCE 10) 47 | 48 | # Save compiler ABI information. 49 | set(CMAKE_C_SIZEOF_DATA_PTR "8") 50 | set(CMAKE_C_COMPILER_ABI "") 51 | set(CMAKE_C_BYTE_ORDER "LITTLE_ENDIAN") 52 | set(CMAKE_C_LIBRARY_ARCHITECTURE "") 53 | 54 | if(CMAKE_C_SIZEOF_DATA_PTR) 55 | set(CMAKE_SIZEOF_VOID_P "${CMAKE_C_SIZEOF_DATA_PTR}") 56 | endif() 57 | 58 | if(CMAKE_C_COMPILER_ABI) 59 | set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_C_COMPILER_ABI}") 60 | endif() 61 | 62 | if(CMAKE_C_LIBRARY_ARCHITECTURE) 63 | set(CMAKE_LIBRARY_ARCHITECTURE "") 64 | endif() 65 | 66 | set(CMAKE_C_CL_SHOWINCLUDES_PREFIX "") 67 | if(CMAKE_C_CL_SHOWINCLUDES_PREFIX) 68 | set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_C_CL_SHOWINCLUDES_PREFIX}") 69 | endif() 70 | 71 | 72 | 73 | 74 | 75 | set(CMAKE_C_IMPLICIT_INCLUDE_DIRECTORIES "D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include;D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include-fixed;D:/Programming/C++/Compilers/mingw64/x86_64-w64-mingw32/include") 76 | set(CMAKE_C_IMPLICIT_LINK_LIBRARIES "mingw32;gcc;moldname;mingwex;pthread;advapi32;shell32;user32;kernel32;iconv;mingw32;gcc;moldname;mingwex") 77 | set(CMAKE_C_IMPLICIT_LINK_DIRECTORIES "D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0;D:/Programming/C++/Compilers/mingw64/lib/gcc;D:/Programming/C++/Compilers/mingw64/x86_64-w64-mingw32/lib;D:/Programming/C++/Compilers/mingw64/lib") 78 | set(CMAKE_C_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") 79 | -------------------------------------------------------------------------------- /docs/Customizing.md: -------------------------------------------------------------------------------- 1 | # FastPy customizing 2 | 3 | ### Customize operators 4 | 5 | If you don't like how the operators look, you can change the [ops config file](../config/operators.json). 6 | 7 | ### Customize stages 8 | 9 | For stage customization you need to inherit ```BaseClass``` of that stage and change the eponymous config file. 10 | 11 | #### Lexing 12 | 13 | *Config file:* [lexer.json](../config/lexer.json) 14 | 15 | *Base interface classes:* 16 | 17 | - Base stage class: ```BaseLexer``` 18 | - Token base class: ```BaseToken``` 19 | - Detector base class: ```BaseDetector``` 20 | 21 | *Examples:* 22 | 23 | ```python 24 | from fastpy.lexer import BaseLexer, BaseToken, BaseDetector 25 | from fastpy.module import Module 26 | 27 | 28 | class MyLexer(BaseLexer): 29 | """Your implementation of BaseLexer interface""" 30 | 31 | def __init__(self, module: Module): 32 | """ 33 | 34 | :param module: the module parameter contains information about the currently processed file 35 | """ 36 | 37 | def lex(self) -> list[BaseToken]: 38 | """ 39 | Splits the code into a list of tokens 40 | 41 | :return: list of tokens 42 | """ 43 | ``` 44 | 45 | #### Parsing 46 | 47 | *Config file:* [parser.json](../config/parser.json) 48 | 49 | *Base interface classes:* 50 | 51 | - Base stage class: ```BaseParser``` 52 | - Abstract Syntax Tree base class: ```BaseAST``` 53 | - Node parser class: ```BaseNodeParser``` 54 | 55 | *Examples:* 56 | 57 | ```python 58 | from fastpy.parser import BaseParser, BaseNode, BaseAST 59 | from fastpy.lexer import BaseToken 60 | from fastpy.module import Module 61 | 62 | 63 | class MyParser(BaseParser): 64 | """Your implementation of BaseParser interface""" 65 | 66 | def __init__(self, 67 | module: Module, 68 | tokens: list[BaseToken]): 69 | """ 70 | 71 | :param module: the module parameter contains information about the currently processed file 72 | :param tokens: list of tokens - output of previous stage 73 | """ 74 | 75 | def parse(self) -> BaseAST: 76 | """ 77 | Parses tokens and returns an Abstract Syntax Tree 78 | 79 | :return: Abstract Syntax Tree 80 | """ 81 | ``` 82 | 83 | #### Transpiling 84 | 85 | *Config file:* [transpiler.json](../config/transpiler.json) 86 | 87 | *Base interface classes:* 88 | 89 | - Base stage class: ```BaseTranspiler``` 90 | - Base node transpiler class: ```BaseNodeTranspiler``` 91 | 92 | *Examples:* 93 | 94 | ```python 95 | from fastpy.transpiler import BaseTranspiler 96 | from fastpy.module import Module 97 | from fastpy.parser import BaseAST 98 | 99 | 100 | class MyTranspiler(BaseTranspiler): 101 | """Your implementation transpailer interface""" 102 | 103 | def __init__(self, module: Module, ast: BaseAST): 104 | """ 105 | 106 | :param module: module: the module parameter contains information about the currently processed file 107 | :param ast: Abstract Syntax Tree - output of previous stage 108 | """ 109 | 110 | def transpile(self) -> str: 111 | """ 112 | Transpile an Abstract Syntax Tree to source C++ code 113 | 114 | :return: C++ code 115 | """ 116 | ``` -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/Makefile.cmake: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | # The generator used is: 5 | set(CMAKE_DEPENDS_GENERATOR "MinGW Makefiles") 6 | 7 | # The top level Makefile was generated from the following files: 8 | set(CMAKE_MAKEFILE_DEPENDS 9 | "CMakeCache.txt" 10 | "../CMakeLists.txt" 11 | "CMakeFiles/3.20.2/CMakeCCompiler.cmake" 12 | "CMakeFiles/3.20.2/CMakeCXXCompiler.cmake" 13 | "CMakeFiles/3.20.2/CMakeRCCompiler.cmake" 14 | "CMakeFiles/3.20.2/CMakeSystem.cmake" 15 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeCInformation.cmake" 16 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeCXXInformation.cmake" 17 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeCommonLanguageInclude.cmake" 18 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeExtraGeneratorDetermineCompilerMacrosAndIncludeDirs.cmake" 19 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeFindCodeBlocks.cmake" 20 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeGenericSystem.cmake" 21 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeInitializeConfigs.cmake" 22 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeLanguageInformation.cmake" 23 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeRCInformation.cmake" 24 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeSystemSpecificInformation.cmake" 25 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/CMakeSystemSpecificInitialize.cmake" 26 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Compiler/CMakeCommonCompilerMacros.cmake" 27 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Compiler/GNU-C.cmake" 28 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Compiler/GNU-CXX.cmake" 29 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Compiler/GNU.cmake" 30 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows-GNU-C-ABI.cmake" 31 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows-GNU-C.cmake" 32 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows-GNU-CXX-ABI.cmake" 33 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows-GNU-CXX.cmake" 34 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows-GNU.cmake" 35 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows-windres.cmake" 36 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/Windows.cmake" 37 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/Platform/WindowsPaths.cmake" 38 | "D:/Programs/CLion 2021.2.2/bin/cmake/win/share/cmake-3.20/Modules/ProcessorCount.cmake" 39 | ) 40 | 41 | # The corresponding makefile is: 42 | set(CMAKE_MAKEFILE_OUTPUTS 43 | "Makefile" 44 | "CMakeFiles/cmake.check_cache" 45 | ) 46 | 47 | # Byproducts of CMake generate step: 48 | set(CMAKE_MAKEFILE_PRODUCTS 49 | "CMakeFiles/CMakeDirectoryInformation.cmake" 50 | ) 51 | 52 | # Dependency information for all targets: 53 | set(CMAKE_DEPEND_INFO_FILES 54 | "CMakeFiles/src.dir/DependInfo.cmake" 55 | ) 56 | -------------------------------------------------------------------------------- /main.fpy: -------------------------------------------------------------------------------- 1 | #a: str = ' ' 2 | #b: str = ' ' 3 | #c: str = ' ' 4 | #d: str = ' ' 5 | #e: str = ' ' 6 | #f: str = ' ' 7 | #g: str = ' ' 8 | #h: str = ' ' 9 | #i: str = ' ' 10 | # 11 | #who_move: str = 'X' 12 | #continue_game: bool = true 13 | # 14 | # 15 | #fun next_player(who_move: str) -> str: 16 | # if who_move == 'X': 17 | # return 'O' 18 | # else: 19 | # return 'X' 20 | # 21 | # 22 | # 23 | # 24 | #fun draw_vert_border(endl: bool = false): 25 | # log('|', endl) 26 | # 27 | #fun draw_horz_border(): 28 | # log('+-+-+-+', true) 29 | # 30 | #fun draw_row(a: str, b: str, c: str): 31 | # draw_vert_border() 32 | # log(a) 33 | # draw_vert_border() 34 | # log(b) 35 | # draw_vert_border() 36 | # log(c) 37 | # draw_vert_border(true) 38 | # 39 | #fun draw(a: str, b: str, c: str, d: str, e: str, f: str, g: str, h: str, i: str): 40 | # draw_horz_border() 41 | # draw_row(a, b, c) 42 | # draw_horz_border() 43 | # draw_row(d, e, f) 44 | # draw_horz_border() 45 | # draw_row(g, h, i) 46 | # draw_horz_border() 47 | # 48 | # 49 | # 50 | #fun ask_cell(who_move: str) -> int: 51 | # log(who_move) 52 | # cell: int = input(' choose cell >>') 53 | # return cell 54 | # 55 | #fun check_three(a: str, b: str, c: str) -> bool: 56 | # return a == b and b == c and c != ' ' 57 | # 58 | #fun check_game_over(a: str, b: str, c: str, d: str, e: str, f: str, g: str, h: str, i: str) -> bool: 59 | # r1 = check_three(a, b, c) 60 | # r2 = check_three(a, d, g) 61 | # r3 = check_three(a, e, i) 62 | # r4 = check_three(b, e, h) 63 | # r5 = check_three(d, e, f) 64 | # r6 = check_three(g, e, c) 65 | # r7 = check_three(c, f, i) 66 | # r8 = check_three(g, h , i) 67 | # return r1 or r2 or r3 or r4 or r5 or r6 or r7 or r8 68 | # 69 | #fun check_draw(a: str, b: str, c: str, d: str, e: str, f: str, g: str, h: str, i: str) -> bool: 70 | # return a != ' ' and b != ' ' and c != ' ' and d != ' ' and e != ' ' and f != ' ' and g != ' ' and h != ' ' and i != ' ' 71 | # 72 | #while continue_game: 73 | # draw(a, b, c, d, e, f, g, h, i) 74 | # cell = ask_cell(who_move) 75 | # 76 | # if cell == 1: 77 | # a = who_move 78 | # elif cell == 2: 79 | # b = who_move 80 | # elif cell == 3: 81 | # c = who_move 82 | # elif cell == 4: 83 | # d = who_move 84 | # elif cell == 5: 85 | # e = who_move 86 | # elif cell == 6: 87 | # f = who_move 88 | # elif cell == 7: 89 | # g = who_move 90 | # elif cell == 8: 91 | # h = who_move 92 | # elif cell == 9: 93 | # i = who_move 94 | # else: 95 | # log_error('Enter number in range: 1 - 9!', true) 96 | # who_move = next_player(who_move) 97 | # 98 | # game_over = check_game_over(a, b, c, d, e, f, g, h, i) 99 | # drw = check_draw(a, b, c, d, e, f, g, h, i) 100 | # 101 | # if drw: 102 | # draw(a, b, c, d, e, f, g, h, i) 103 | # log_info('Draw!') 104 | # continue_game = false 105 | # elif game_over: 106 | # draw(a, b, c, d, e, f, g, h, i) 107 | # log_info('Game Over! ') 108 | # log_info('Won: ') 109 | # log_info(who_move) 110 | # continue_game = false 111 | # 112 | # 113 | # 114 | # who_move = next_player(who_move) 115 | # 116 | # 117 | 118 | abc: list = [1, 2, 3, 4, 5, 6, 7, 8] 119 | 120 | 121 | 122 | #fun templated_func(arg: auto) -> T: 123 | # arg_copy: T = arg 124 | # return arg_copy -------------------------------------------------------------------------------- /fastpy/parser/ast.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..import_tools import import_class 3 | from .config import * 4 | from .nodes import * 5 | from typing import Iterable 6 | from ..module import Module 7 | 8 | 9 | class BaseAST(ABC): 10 | """Abstract Syntax Tree interface""" 11 | 12 | @abstractmethod 13 | def add_module(self, module: Module) -> None: 14 | """ 15 | 16 | :param module: module for which a branch should be created 17 | """ 18 | 19 | @abstractmethod 20 | def push_node(self, module: Module, node: BaseNode) -> None: 21 | """ 22 | 23 | :param module: module to which the node should be added to the branch 24 | :param node: node to be added 25 | """ 26 | 27 | @abstractmethod 28 | def pop_node(self, module: Module, index: int) -> BaseNode: 29 | """ 30 | 31 | :param module: module that branch contains node to be removed 32 | :param index: index of node to be removed 33 | :return: node that was removed 34 | """ 35 | 36 | @abstractmethod 37 | def remove_node(self, module: Module, node: BaseNode) -> None: 38 | """ 39 | 40 | :param module: module that branch contains node to be removed 41 | :param node: node to be removed 42 | """ 43 | 44 | @abstractmethod 45 | def nodes(self, module_name: str) -> Iterable[BaseNode]: 46 | """ 47 | 48 | :param module_name: name of the module branch 49 | :return: iterator of all nodes in module branch 50 | """ 51 | 52 | 53 | class AST(BaseAST): 54 | """Basic AST implementation of FastPy""" 55 | 56 | def __init__(self): 57 | self._tree = { 58 | '__main__': [] 59 | } 60 | 61 | def add_module(self, module: Module) -> None: 62 | self._tree.update({module.name: []}) 63 | 64 | def push_node(self, module: Module, node: BaseNode): 65 | if self._tree.get(module.name) is None: 66 | self._tree.update({module.name: [node]}) 67 | return 68 | 69 | self._tree.get(module.name).append(node) 70 | 71 | def _check_module_existence(self, module_name: str): 72 | module_nodes_list = self._tree.get(module_name) 73 | if not module_nodes_list: 74 | raise ValueError(f'Module with that name "{module_name}" is not exists') 75 | 76 | def pop_node(self, module: Module, index: int) -> BaseNode: 77 | self._check_module_existence(module.name) 78 | return self._tree.get(module.name).pop(index) 79 | 80 | def remove_node(self, module: Module, node: BaseNode) -> None: 81 | self._check_module_existence(module.name) 82 | self._tree.get(module.name).remove(node) 83 | 84 | def __repr__(self): 85 | out = '' 86 | 87 | for key, value in self._tree.items(): 88 | out += '\n' + key + ':\n ' 89 | out += '\n '.join(map(str, value)) 90 | 91 | return out 92 | 93 | def nodes(self, module_name: str) -> Iterable[BaseNode]: 94 | module_nodes = self._tree.get(module_name) 95 | if not module_nodes: 96 | return () 97 | 98 | for node in module_nodes: 99 | yield node 100 | 101 | 102 | _ast_instance = None 103 | 104 | 105 | def create_ast() -> BaseAST: 106 | """AST factory""" 107 | global _ast_instance 108 | 109 | if not _ast_instance: 110 | _ast_instance = import_class(AST_CLASS_PATH)() 111 | 112 | return _ast_instance 113 | -------------------------------------------------------------------------------- /fastpy/semantic_analyzer/analyzers.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..module import Module 3 | from ..parser import BaseParser, BaseAST 4 | from ..parser.nodes import * 5 | from ..exceptions import * 6 | from .config import * 7 | from ..import_tools import import_class 8 | from ..log import Logger 9 | from .node_analyzers import * 10 | from .scope import * 11 | 12 | 13 | class BaseAnalyzer(ABC): 14 | """Parser interface""" 15 | 16 | @abstractmethod 17 | def __init__(self, 18 | module: Module, 19 | ast: BaseAST): 20 | """ 21 | 22 | :param module: module: the module parameter contains information about the currently processed file 23 | :param ast: Abstract Syntax Tree - output of previous parsing stage 24 | """ 25 | 26 | @abstractmethod 27 | def analyze(self) -> None: 28 | """Parses tokens and returns an Abstract Syntax Tree""" 29 | 30 | 31 | class Analyzer(BaseAnalyzer): 32 | """Basic Analyzer implementation of FastPy""" 33 | 34 | def __init__(self, 35 | module: Module, 36 | ast: BaseAST): 37 | self._current_module = module 38 | self._ast = ast 39 | self._analyzers = {} 40 | self._current_scope: Scope | None = Scope() 41 | self._scopes = [self._current_scope] 42 | self._load_node_analyzers() 43 | 44 | def _load_node_analyzers(self): 45 | for node_class_path, analyzer_class_path in NODE_ANALYZING.items(): 46 | self._analyzers.update({ 47 | import_class(node_class_path): import_class(analyzer_class_path)() 48 | }) 49 | 50 | def _detect_scope_start(self, node: BaseNode): 51 | if isinstance(node, FuncNode): 52 | global_vars = self._current_scope.get_global() 53 | self._current_scope = Scope(node) 54 | 55 | self._current_scope.push_several(global_vars) 56 | self._scopes.append(self._current_scope) 57 | 58 | def _detect_scope_end(self, node: BaseNode): 59 | if self._current_scope.is_scope_node(node): 60 | self._scopes.pop(-1) 61 | self._current_scope = self._scopes[-1] 62 | 63 | def _analyze_node(self, node: BaseNode, **kwargs): 64 | Logger.log_info(f'Analyzing: {node.line}: {node}') 65 | 66 | self._detect_scope_start(node) 67 | 68 | analyzer: BaseNodeAnalyzer = self._analyzers.get(node.__class__) 69 | 70 | try: 71 | if analyzer: 72 | analyzer.analyze( 73 | node=node, 74 | module=self._current_module, 75 | ast=self._ast, 76 | analyze_node_clb=self._analyze_node, 77 | scope=self._current_scope 78 | ) 79 | except AnalyzingError as e: 80 | Logger.log_critical(f'{self._current_module.filepath}: {node.line}: {e}') 81 | os.system('pause') 82 | exit(-1) 83 | 84 | self._detect_scope_end(node) 85 | 86 | if isinstance(node, NamedNode): 87 | self._current_scope.push(node) 88 | 89 | @Logger.info('Start analyzing...', ending_message='Analysis completed in {time}') 90 | def analyze(self) -> None: 91 | for node in self._ast.nodes(self._current_module.name): 92 | self._analyze_node(node) 93 | 94 | 95 | def create_analyzer(module: Module, ast: BaseAST) -> BaseAnalyzer: 96 | """Analyzer factory""" 97 | return import_class(ANALYZER_CLASS_PATH)( 98 | module=module, 99 | ast=ast 100 | ) 101 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/Makefile2: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | # Default target executed when no arguments are given to make. 5 | default_target: all 6 | .PHONY : default_target 7 | 8 | #============================================================================= 9 | # Special targets provided by cmake. 10 | 11 | # Disable implicit rules so canonical targets will work. 12 | .SUFFIXES: 13 | 14 | # Disable VCS-based implicit rules. 15 | % : %,v 16 | 17 | # Disable VCS-based implicit rules. 18 | % : RCS/% 19 | 20 | # Disable VCS-based implicit rules. 21 | % : RCS/%,v 22 | 23 | # Disable VCS-based implicit rules. 24 | % : SCCS/s.% 25 | 26 | # Disable VCS-based implicit rules. 27 | % : s.% 28 | 29 | .SUFFIXES: .hpux_make_needs_suffix_list 30 | 31 | # Command-line flag to silence nested $(MAKE). 32 | $(VERBOSE)MAKESILENT = -s 33 | 34 | #Suppress display of executed commands. 35 | $(VERBOSE).SILENT: 36 | 37 | # A target that is always out of date. 38 | cmake_force: 39 | .PHONY : cmake_force 40 | 41 | #============================================================================= 42 | # Set environment variables for the build. 43 | 44 | SHELL = cmd.exe 45 | 46 | # The CMake executable. 47 | CMAKE_COMMAND = "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" 48 | 49 | # The command to remove a file. 50 | RM = "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" -E rm -f 51 | 52 | # Escaping for special characters. 53 | EQUALS = = 54 | 55 | # The top-level source directory on which CMake was run. 56 | CMAKE_SOURCE_DIR = D:\Programming\Python\Projects\FastPy-public\fastpy_build\src 57 | 58 | # The top-level build directory on which CMake was run. 59 | CMAKE_BINARY_DIR = D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug 60 | 61 | #============================================================================= 62 | # Directory level rules for the build root directory 63 | 64 | # The main recursive "all" target. 65 | all: CMakeFiles/src.dir/all 66 | .PHONY : all 67 | 68 | # The main recursive "preinstall" target. 69 | preinstall: 70 | .PHONY : preinstall 71 | 72 | # The main recursive "clean" target. 73 | clean: CMakeFiles/src.dir/clean 74 | .PHONY : clean 75 | 76 | #============================================================================= 77 | # Target rules for target CMakeFiles/src.dir 78 | 79 | # All Build rule for target. 80 | CMakeFiles/src.dir/all: 81 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/depend 82 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/build 83 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --progress-dir=D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles --progress-num=1,2 "Built target src" 84 | .PHONY : CMakeFiles/src.dir/all 85 | 86 | # Build rule for subdir invocation for target. 87 | CMakeFiles/src.dir/rule: cmake_check_build_system 88 | $(CMAKE_COMMAND) -E cmake_progress_start D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles 2 89 | $(MAKE) $(MAKESILENT) -f CMakeFiles\Makefile2 CMakeFiles/src.dir/all 90 | $(CMAKE_COMMAND) -E cmake_progress_start D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles 0 91 | .PHONY : CMakeFiles/src.dir/rule 92 | 93 | # Convenience name for target. 94 | src: CMakeFiles/src.dir/rule 95 | .PHONY : src 96 | 97 | # clean rule for target. 98 | CMakeFiles/src.dir/clean: 99 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/clean 100 | .PHONY : CMakeFiles/src.dir/clean 101 | 102 | #============================================================================= 103 | # Special targets to cleanup operation of make. 104 | 105 | # Special rule to run CMake to check the build system integrity. 106 | # No rule that depends on this can have commands that come from listfiles 107 | # because they might be regenerated. 108 | cmake_check_build_system: 109 | $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles\Makefile.cmake 0 110 | .PHONY : cmake_check_build_system 111 | 112 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # FastPy 2 | 3 | FastPy is a statically typed programming language with pythonic syntax. 4 | 5 | ## About 6 | 7 | FastPy is a new general purpose, multi-paradigm, high performance, statically typed programming language which have 8 | pytonic intuitive syntax. 9 | 10 | ## Examples 11 | 12 | - ["Hello, World"](examples/hello_world.fpy) program: 13 | 14 | ```python 15 | # Just a comment :D 16 | 17 | log('Hello, World!') # print hello world 18 | ``` 19 | 20 | - ["Far Future"](examples/far_future.fpy) program: 21 | 22 | ```python 23 | interface Car: 24 | public: 25 | fun __init__(name: str, weight: int): ... 26 | 27 | 28 | @property 29 | fun brand() -> str: ... 30 | 31 | 32 | @property 33 | fun name() -> str: ... 34 | 35 | 36 | @property 37 | fun weight() -> int: ... 38 | 39 | 40 | class Tesla(Car): # Car implementation 41 | private: 42 | __name: str 43 | __weight: int 44 | public: 45 | fun __init__(name: str, weight: int): 46 | __name = name 47 | __weight = weight 48 | 49 | 50 | @property 51 | fun brand() -> str: 52 | return 'Tesla' 53 | 54 | 55 | @property 56 | fun name() -> str: 57 | return __name 58 | 59 | 60 | @property 61 | fun weight() -> int: 62 | return __weight 63 | 64 | 65 | 66 | fun car_factory(car_brand: str, name: str, weight: int) -> Car: 67 | match car_brand: 68 | case "Tesla": 69 | return Tesla(name, weight) 70 | default: 71 | return null 72 | 73 | 74 | some_car: Car = car_factory("Tesla", "Model X", 2301) 75 | log(some_car.name) # Model X 76 | log(some_car.weight) # 2301 77 | ``` 78 | 79 | *\*For more samples, see [examples directory](examples).* 80 | 81 | ## Features 82 | 83 | - [x] Transpailable to C++. Therefore, compiled. Therefore, fast 84 | - [x] Easy intuitive syntax, similar to Python 85 | - [x] Statically typed 86 | - [x] Easy expandable 87 | - [x] Flexible, you can customize it for yourself or completely rewrite each component 88 | - [x] Built-in logging system 89 | 90 | ## Customizing 91 | 92 | That language is built in such a way that allows to support customizing at every stage of transpiling or interpreting. For more 93 | info, see [customizing doc file](docs/Customizing.md). 94 | 95 | ## Highlighting 96 | 97 | To enable syntax highlighting, see [highlighting doc file](docs/Highlighting.md). 98 | 99 | ## Requirements 100 | 101 | To transpile and compile your program, you need to install the requirements. 102 | 103 | ### Windows 104 | 105 | 1) First, you need to install **[Python 3.10](https://www.python.org/downloads/release/python-3105/)** 106 | 2) Then install some libs: 107 | 108 | ```shell 109 | pip install -r requirements.txt 110 | ``` 111 | 112 | 3) Next, you need to install **[MinGW](https://sourceforge.net/projects/mingw/)** (optional, only used for compilation) 113 | 114 | ## Transpiling 115 | 116 | *Usage:* 117 | 118 | ```shell 119 | python main.py [-h] -s SOURCE [-t] [-o OUTPUT] [-c] 120 | ``` 121 | 122 | *Examples:* 123 | 124 | - Transpile file **main.fpy** to C++ and save to **some_folder/src**: 125 | 126 | ```shell 127 | python main.py -s main.fpy -t -o some_folder 128 | ``` 129 | 130 | - Transpile file **main.fpy** to C++ and save to **some_folder/src** and compile: 131 | 132 | ```shell 133 | python main.py -s main.fpy -t -c -o some_folder 134 | ``` 135 | 136 | *First run from IDE (script: [file](examples/first_run_from_ide.fpy)):* 137 | 138 | ![First run from IDE](docs/imgs/FirstRunFromIDE.gif) 139 | 140 | ## TODO 141 | 142 | The entire list of completed and scheduled tasks is available in the [TODO file](docs/TODO.md). 143 | 144 | ## Contributors 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | ## Status 155 | 156 | Project frozen until studying end :( 157 | 158 | ## Licence 159 | 160 | FastPy uses the MIT license. See the bundled [LICENSE](LICENSE) file for details. 161 | -------------------------------------------------------------------------------- /fastpy/semantic_analyzer/node_analyzers.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..singleton import singleton 3 | from ..parser.nodes import * 4 | from ..parser import BaseAST 5 | from ..module import Module 6 | from .scope import * 7 | from ..exceptions import * 8 | from .config import * 9 | 10 | 11 | class BaseNodeAnalyzer(ABC): 12 | @abstractmethod 13 | def analyze(self, 14 | node: BaseNode, 15 | module: Module, 16 | ast: BaseAST, 17 | analyze_node_clb: callable, 18 | scope: Scope): ... 19 | 20 | 21 | @singleton 22 | class AssignNodeAnalyzer(BaseNodeAnalyzer): 23 | def analyze(self, 24 | node: AssignNode, 25 | module: Module, 26 | ast: BaseAST, 27 | analyze_node_clb: callable, 28 | scope: Scope): 29 | 30 | if not scope.already_defined(node.identifier.text): 31 | node.definition = True 32 | else: 33 | node.definition = False 34 | 35 | 36 | @singleton 37 | class FuncNodeAnalyzer(BaseNodeAnalyzer): 38 | @staticmethod 39 | def _analyze_args(arguments: list[AssignNode], analyze_node_clb: callable): 40 | for arg_node in arguments: 41 | analyze_node_clb(arg_node) 42 | 43 | @staticmethod 44 | def _analyze_body(body: list[BaseNode], analyze_node_clb: callable): 45 | for body_node in body: 46 | analyze_node_clb(body_node) 47 | 48 | def analyze(self, 49 | node: FuncNode, 50 | module: Module, 51 | ast: BaseAST, 52 | analyze_node_clb: callable, 53 | scope: Scope): 54 | 55 | if scope.already_defined(node.identifier.text): 56 | raise AnalyzingError(f'SemanticError: function with name "{node.identifier.text}" already defined') 57 | 58 | scope.push(node) 59 | self._analyze_args(node.arguments, analyze_node_clb) 60 | self._analyze_body(node.body, analyze_node_clb) 61 | 62 | 63 | @singleton 64 | class IfNodeAnalyzer(BaseNodeAnalyzer): 65 | 66 | @staticmethod 67 | def _analyze_body(body: list[BaseNode], analyze_node_clb: callable): 68 | for body_node in body: 69 | analyze_node_clb(body_node) 70 | 71 | def analyze(self, 72 | node: IfNode, 73 | module: Module, 74 | ast: BaseAST, 75 | analyze_node_clb: callable, 76 | scope: Scope): 77 | self._analyze_body(node.body, analyze_node_clb) 78 | 79 | 80 | @singleton 81 | class CallNodeAnalyzer(BaseNodeAnalyzer): 82 | @staticmethod 83 | def _analyze_args(arguments: list[BaseNode], analyze_node_clb: callable): 84 | for arg_node in arguments: 85 | analyze_node_clb(arg_node) 86 | 87 | def analyze(self, 88 | node: CallNode, 89 | module: Module, 90 | ast: BaseAST, 91 | analyze_node_clb: callable, 92 | scope: Scope): 93 | self._analyze_args(node.arguments, analyze_node_clb) 94 | 95 | if not scope.already_defined(node.identifier.text): 96 | raise AnalyzingError(f'SemanticError: function with name "{node.identifier.text}" does not exists') 97 | 98 | 99 | class ElseNodeAnalyzer(BaseNodeAnalyzer): 100 | @staticmethod 101 | def _analyze_body(body: list[BaseNode], analyze_node_clb: callable): 102 | for body_node in body: 103 | analyze_node_clb(body_node) 104 | 105 | def analyze(self, 106 | node: ElseNode, 107 | module: Module, 108 | ast: BaseAST, 109 | analyze_node_clb: callable, 110 | scope: Scope): 111 | self._analyze_body(node.body, analyze_node_clb) 112 | 113 | 114 | class WhileNodeAnalyzer(BaseNodeAnalyzer): 115 | def _analyze_condition(self): 116 | pass 117 | 118 | @staticmethod 119 | def _analyze_body(body: list[BaseNode], analyze_node_clb: callable): 120 | for body_node in body: 121 | analyze_node_clb(body_node) 122 | 123 | def analyze(self, 124 | node: WhileNode, 125 | module: Module, 126 | ast: BaseAST, 127 | analyze_node_clb: callable, 128 | scope: Scope): 129 | self._analyze_body(node.body, analyze_node_clb) 130 | -------------------------------------------------------------------------------- /fastpy/lexer/lexers.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from fastpy.import_tools import import_class 3 | from .detectors import BaseDetector 4 | from .special_symbols import * 5 | from .config import * 6 | from .tokens import BaseToken, create_token 7 | from ..log import Logger 8 | from ..module import Module 9 | from ..exceptions import * 10 | 11 | 12 | class BaseLexer(ABC): 13 | """Lexer interface""" 14 | 15 | @abstractmethod 16 | def __init__(self, module: Module): 17 | """ 18 | 19 | :param module: the module parameter contains information about the currently processed file. 20 | """ 21 | 22 | @abstractmethod 23 | def lex(self) -> list[BaseToken]: 24 | """Splits the code into a list of tokens""" 25 | 26 | 27 | class Lexer(BaseLexer): 28 | """Basic lexer implementation of FastPy""" 29 | 30 | @Logger.info(pattern='Lexer created ({module})') 31 | def __init__(self, module: Module): 32 | self._current_module = module 33 | self._code = module.source_code 34 | self._tokens: list[BaseToken] = [] 35 | self._token_detectors = { 36 | token_type: import_class(detection_info.get('detector'))() 37 | for token_type, detection_info in TOKEN_DETECTION.items() 38 | } 39 | 40 | def _detect_token(self, code_line: str, line_number: int, column_number: int) -> int: 41 | """Detects and extracts a token from a line of code""" 42 | 43 | start_symbol = code_line[column_number] 44 | 45 | if start_symbol in SPECIAL_SYMBOLS.keys(): 46 | token_type = SPECIAL_SYMBOLS.get(start_symbol) 47 | token = create_token( 48 | token_type=token_type, 49 | text=start_symbol, 50 | line=line_number, 51 | ) 52 | self._tokens.append(token) 53 | return len(token.text) + column_number - 1 54 | 55 | for token_type, detection_info in TOKEN_DETECTION.items(): 56 | supposed_token_type = TokenTypes.__getattr__(token_type) 57 | detector: BaseDetector = self._token_detectors.get(token_type) 58 | 59 | if supposed_token_type not in detector.detects: 60 | continue 61 | 62 | regexes = detection_info.get('regexes') 63 | 64 | for regex in regexes: 65 | token = detector.detect( 66 | code=code_line, 67 | line_number=line_number, 68 | column_number=column_number, 69 | regex_pattern=regex, 70 | supposed_token_type=supposed_token_type 71 | ) 72 | if token: 73 | self._tokens.append(token) 74 | return len(token.text) + column_number - 1 75 | 76 | return -1 77 | 78 | @Logger.info(pattern='Lexing: {line_number}: {code_line}') 79 | def _lex_line(self, code_line: str, line_number: int) -> None: 80 | """Splits each line of the code into tokens""" 81 | 82 | ignore_before = -1 83 | for column, char in enumerate(code_line): 84 | if char == COMMENT_START_SYMBOL: 85 | return 86 | 87 | if column <= ignore_before: 88 | continue 89 | 90 | ignore_before = self._detect_token( 91 | code_line=code_line, 92 | line_number=line_number, 93 | column_number=column, 94 | ) 95 | 96 | @Logger.info('Start lexing...', ending_message='Lexing completed in {time}') 97 | # @Logger.catch_errors() 98 | def lex(self) -> list[BaseToken]: 99 | for i, code_line in enumerate(self._code.split('\n')): 100 | if code_line == '' or code_line.startswith(COMMENT_START_SYMBOL): 101 | continue 102 | 103 | try: 104 | self._lex_line( 105 | code_line=code_line, 106 | line_number=i + 1 107 | ) 108 | except LexingError as e: 109 | Logger.log_critical(f'{self._current_module.filepath}: {i + 1}: {e}') 110 | os.system('pause') 111 | exit(-1) 112 | 113 | self._tokens.append(create_token( 114 | token_type=TokenTypes.endline, 115 | text='\n', 116 | line=i + 1, 117 | )) 118 | 119 | return self._tokens 120 | 121 | 122 | def create_lexer(module: Module) -> BaseLexer: 123 | """Lexer factory""" 124 | return import_class(LEXER_CLASS_PATH)( 125 | module=module, 126 | ) 127 | -------------------------------------------------------------------------------- /fastpy/dev_kit/transpiler.py: -------------------------------------------------------------------------------- 1 | from fastpy.filesystem import FileSystem as Fs 2 | from fastpy.log import Logger 3 | from fastpy.lexer import create_lexer, BaseToken 4 | from fastpy.parser import create_parser, BaseAST 5 | from fastpy.semantic_analyzer import create_analyzer 6 | from fastpy.parser.nodes import CallNode 7 | from fastpy.module import Module 8 | from fastpy.transpiler import create_transpiler 9 | from .config import BUILTIN_FUNCTIONS 10 | from fastpy.exceptions import * 11 | 12 | 13 | class TranspileAPI: 14 | def __init__(self, source: str, **kwargs): 15 | self.main_source_file = Fs.normalize_path(source) 16 | self.kwargs = kwargs 17 | 18 | @staticmethod 19 | def _lex_file(module: Module) -> list[BaseToken]: 20 | lexer = create_lexer( 21 | module=module 22 | ) 23 | 24 | tokens = lexer.lex() 25 | Logger.print_raw('|'.join(map(str, tokens)), 'TOKENS:') 26 | return tokens 27 | 28 | @staticmethod 29 | def _parse_file(module: Module, tokens: list[BaseToken]) -> BaseAST: 30 | parser = create_parser( 31 | module=module, 32 | tokens=tokens 33 | ) 34 | 35 | ast = parser.parse() 36 | Logger.print_raw(ast, 'ABSTRACT SYNTAX TREE:') 37 | return ast 38 | 39 | @staticmethod 40 | def _translate_file(module: Module, ast: BaseAST) -> str: 41 | transpiler = create_transpiler( 42 | module=module, 43 | ast=ast 44 | ) 45 | cpp_code = transpiler.transpile() 46 | Logger.print_raw(cpp_code, 'CPP CODE:') 47 | return cpp_code 48 | 49 | @staticmethod 50 | def _analyze_file(module: Module, ast: BaseAST): 51 | analyzer = create_analyzer( 52 | module=module, 53 | ast=ast 54 | ) 55 | analyzer.analyze() 56 | 57 | def _save_cpp_code(self, module: Module, code: str) -> str: 58 | out_folder = self.kwargs.get('output') or 'fastpy_build' 59 | Fs.makedirs(out_folder) 60 | out_folder = Fs.normalize_path(out_folder) 61 | Fs.makedirs(Fs.join(out_folder, 'bin')) 62 | Fs.makedirs(Fs.join(out_folder, 'src')) 63 | filepath = Fs.join(out_folder, 'src', 64 | Fs.replace_ext(module.filename, '.cpp' if module.name == '__main__' else '.hpp')) 65 | Fs.write_file(filepath, code) 66 | Logger.log_info(f'Code saved to "{filepath}"') 67 | return filepath 68 | 69 | def _compile(self, main_cpp_file: str) -> str: 70 | out_folder = self.kwargs.get('output') or 'fastpy_build' 71 | out_bin_filepath = Fs.join(out_folder, 'bin', 'main.exe') 72 | command = f'g++ {Fs.normalize_path(main_cpp_file)} -o {out_bin_filepath} -std=c++1z' 73 | out_code = Fs.execute(command) 74 | if out_code == 0: 75 | Logger.log_info(f'Compilation complete, binaries saved to "{Fs.join(out_folder, "bin")}"') 76 | return out_bin_filepath 77 | 78 | def _transpile_file(self, module: Module) -> str: 79 | 80 | # first step: lexing 81 | tokens = self._lex_file(module) 82 | 83 | # second step: parsing 84 | ast = self._parse_file(module, tokens) 85 | 86 | # importing modules are processed after parsing 87 | for node in ast.nodes(module.name): 88 | if isinstance(node, CallNode): 89 | if node.identifier.text == BUILTIN_FUNCTIONS['import']: 90 | for importing_file in node.arguments: 91 | try: 92 | importing_file = Fs.normalize_path(importing_file.value.text) 93 | self._transpile_file(Module(importing_file)) 94 | except FileNotFoundError as e: 95 | raise ParsingError('ImportError: ' + e.args[0].casefold()) 96 | 97 | # third step: semantic analyzing 98 | self._analyze_file(module, ast) 99 | 100 | # fourth step: transpiling 101 | cpp_code = self._translate_file(module, ast) 102 | return self._save_cpp_code(module, cpp_code) 103 | 104 | def _copy_reqs(self): 105 | out_folder = self.kwargs.get('output') or 'fastpy_build' 106 | libs_folder = Fs.join(out_folder, 'src', 'include') 107 | Fs.makedirs(libs_folder) 108 | Fs.copy_files('cpp_code/libs', libs_folder) 109 | 110 | def transpile(self) -> str: 111 | main_cpp_file = self._transpile_file(Module(self.main_source_file, '__main__')) 112 | self._copy_reqs() 113 | if self.kwargs.get('compile', False): 114 | return self._compile(main_cpp_file) 115 | return main_cpp_file 116 | -------------------------------------------------------------------------------- /fastpy/transpiler/transpilers.py: -------------------------------------------------------------------------------- 1 | import os 2 | from abc import abstractmethod, ABC 3 | from .config import * 4 | from ..import_tools import import_class 5 | from ..module import Module 6 | from ..parser import BaseAST, BaseNode 7 | from ..log import Logger 8 | from .node_transpilers import * 9 | from jinja2 import Environment, FileSystemLoader, Template 10 | from ..exceptions import * 11 | 12 | 13 | class BaseTranspiler(ABC): 14 | """Transpailer interface""" 15 | 16 | @abstractmethod 17 | def __init__(self, 18 | module: Module, 19 | ast: BaseAST): 20 | """ 21 | 22 | :param module: module: the module parameter contains information about the currently processed file 23 | :param ast: Abstract Syntax Tree - output of previous parsing stage 24 | """ 25 | 26 | @abstractmethod 27 | def transpile(self) -> str: 28 | """Transpile an Abstract Syntax Tree to source C++ code""" 29 | 30 | 31 | class Transpiler(BaseTranspiler): 32 | """Basic Transpailer implementation of FastPy""" 33 | 34 | @Logger.info(pattern='Transpiler created ({module})') 35 | def __init__(self, module: Module, ast: BaseAST): 36 | self._current_module = module 37 | self._ast = ast 38 | self._code_template: Template | None = None 39 | self._code = Code() 40 | self._transpilers = {} 41 | self._additional_includes = Code() 42 | 43 | self._load_template() 44 | self._load_transpilers() 45 | 46 | def _load_transpilers(self): 47 | for node_class_path, transpiler_class_path in NODE_TRANSPILING.items(): 48 | self._transpilers.update({ 49 | import_class(node_class_path): import_class(transpiler_class_path)() 50 | }) 51 | 52 | def _load_template(self): 53 | env = Environment( 54 | loader=FileSystemLoader(CPP_TEMPLATES_DIR) 55 | ) 56 | 57 | if self._current_module.name == '__main__': 58 | self._code_template = env.get_template(CPP_MAIN_TEMPLATE_PATH) 59 | else: 60 | self._code_template = env.get_template(CPP_TEMPLATE_PATH) 61 | 62 | def _transpile_node(self, node: BaseNode, **kwargs) -> BaseCode: 63 | Logger.log_info(f'Transpiling: {node.line}: {node}') 64 | transpiler: BaseNodeTranspiler = self._transpilers.get(node.__class__) 65 | if not transpiler: 66 | raise TranspilingError(f'You need to specify the node transpiler for "{node.__class__.__name__}"' 67 | f' in "transpiler.json" config file') 68 | 69 | code = transpiler.transpile( 70 | node=node, 71 | transpile_node_clb=self._transpile_node, 72 | **kwargs 73 | ) 74 | 75 | return code 76 | 77 | def _transpile_import(self, node: CallNode): 78 | for importing_file in node.arguments: 79 | if isinstance(importing_file, ValueNode): 80 | include_path = importing_file.value.text 81 | include_path = include_path.replace("'", '').replace('"', '') 82 | include_path = Fs.replace_ext(include_path, '.hpp') 83 | self._additional_includes.push_external( 84 | f'#include "{include_path}"', 85 | auto_semicolon=False, endl=True 86 | ) 87 | 88 | @Logger.info('Start transpiling...', ending_message='Transpiling completed in {time}') 89 | def transpile(self) -> str: 90 | for node in self._ast.nodes(self._current_module.name): 91 | if isinstance(node, CallNode) and node.identifier.text == BUILTIN_FUNCTIONS['import']: 92 | self._transpile_import(node) 93 | continue 94 | 95 | try: 96 | code = self._transpile_node( 97 | node=node 98 | ) 99 | except TranspilingError as e: 100 | Logger.log_critical(f'{self._current_module.filepath}: {node.line}: {e}') 101 | os.system('pause') 102 | exit(-1) 103 | 104 | self._code.push_internal(code.internal, auto_semicolon=False) 105 | self._code.push_external(code.external, auto_semicolon=False) 106 | 107 | return self._code_template.render( 108 | external_code=self._code.external, 109 | internal_code=self._code.internal, 110 | additional_includes=self._additional_includes.external 111 | ) 112 | 113 | 114 | def create_transpiler(module: Module, ast: BaseAST) -> BaseTranspiler: 115 | """Transpiler factory""" 116 | return import_class(TRANSPILER_CLASS_PATH)( 117 | module=module, 118 | ast=ast 119 | ) 120 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/src.dir/build.make: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | # Delete rule output on recipe failure. 5 | .DELETE_ON_ERROR: 6 | 7 | #============================================================================= 8 | # Special targets provided by cmake. 9 | 10 | # Disable implicit rules so canonical targets will work. 11 | .SUFFIXES: 12 | 13 | # Disable VCS-based implicit rules. 14 | % : %,v 15 | 16 | # Disable VCS-based implicit rules. 17 | % : RCS/% 18 | 19 | # Disable VCS-based implicit rules. 20 | % : RCS/%,v 21 | 22 | # Disable VCS-based implicit rules. 23 | % : SCCS/s.% 24 | 25 | # Disable VCS-based implicit rules. 26 | % : s.% 27 | 28 | .SUFFIXES: .hpux_make_needs_suffix_list 29 | 30 | # Command-line flag to silence nested $(MAKE). 31 | $(VERBOSE)MAKESILENT = -s 32 | 33 | #Suppress display of executed commands. 34 | $(VERBOSE).SILENT: 35 | 36 | # A target that is always out of date. 37 | cmake_force: 38 | .PHONY : cmake_force 39 | 40 | #============================================================================= 41 | # Set environment variables for the build. 42 | 43 | SHELL = cmd.exe 44 | 45 | # The CMake executable. 46 | CMAKE_COMMAND = "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" 47 | 48 | # The command to remove a file. 49 | RM = "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" -E rm -f 50 | 51 | # Escaping for special characters. 52 | EQUALS = = 53 | 54 | # The top-level source directory on which CMake was run. 55 | CMAKE_SOURCE_DIR = D:\Programming\Python\Projects\FastPy-public\fastpy_build\src 56 | 57 | # The top-level build directory on which CMake was run. 58 | CMAKE_BINARY_DIR = D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug 59 | 60 | # Include any dependencies generated for this target. 61 | include CMakeFiles/src.dir/depend.make 62 | # Include the progress variables for this target. 63 | include CMakeFiles/src.dir/progress.make 64 | 65 | # Include the compile flags for this target's objects. 66 | include CMakeFiles/src.dir/flags.make 67 | 68 | CMakeFiles/src.dir/main.cpp.obj: CMakeFiles/src.dir/flags.make 69 | CMakeFiles/src.dir/main.cpp.obj: ../main.cpp 70 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --progress-dir=D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles --progress-num=$(CMAKE_PROGRESS_1) "Building CXX object CMakeFiles/src.dir/main.cpp.obj" 71 | D:\Programming\C++\Compilers\mingw64\bin\g++.exe $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -o CMakeFiles\src.dir\main.cpp.obj -c D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\main.cpp 72 | 73 | CMakeFiles/src.dir/main.cpp.i: cmake_force 74 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Preprocessing CXX source to CMakeFiles/src.dir/main.cpp.i" 75 | D:\Programming\C++\Compilers\mingw64\bin\g++.exe $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -E D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\main.cpp > CMakeFiles\src.dir\main.cpp.i 76 | 77 | CMakeFiles/src.dir/main.cpp.s: cmake_force 78 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green "Compiling CXX source to assembly CMakeFiles/src.dir/main.cpp.s" 79 | D:\Programming\C++\Compilers\mingw64\bin\g++.exe $(CXX_DEFINES) $(CXX_INCLUDES) $(CXX_FLAGS) -S D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\main.cpp -o CMakeFiles\src.dir\main.cpp.s 80 | 81 | # Object files for target src 82 | src_OBJECTS = \ 83 | "CMakeFiles/src.dir/main.cpp.obj" 84 | 85 | # External object files for target src 86 | src_EXTERNAL_OBJECTS = 87 | 88 | src.exe: CMakeFiles/src.dir/main.cpp.obj 89 | src.exe: CMakeFiles/src.dir/build.make 90 | src.exe: CMakeFiles/src.dir/linklibs.rsp 91 | src.exe: CMakeFiles/src.dir/objects1.rsp 92 | src.exe: CMakeFiles/src.dir/link.txt 93 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --green --bold --progress-dir=D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles --progress-num=$(CMAKE_PROGRESS_2) "Linking CXX executable src.exe" 94 | $(CMAKE_COMMAND) -E cmake_link_script CMakeFiles\src.dir\link.txt --verbose=$(VERBOSE) 95 | 96 | # Rule to build all files generated by this target. 97 | CMakeFiles/src.dir/build: src.exe 98 | .PHONY : CMakeFiles/src.dir/build 99 | 100 | CMakeFiles/src.dir/clean: 101 | $(CMAKE_COMMAND) -P CMakeFiles\src.dir\cmake_clean.cmake 102 | .PHONY : CMakeFiles/src.dir/clean 103 | 104 | CMakeFiles/src.dir/depend: 105 | $(CMAKE_COMMAND) -E cmake_depends "MinGW Makefiles" D:\Programming\Python\Projects\FastPy-public\fastpy_build\src D:\Programming\Python\Projects\FastPy-public\fastpy_build\src D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles\src.dir\DependInfo.cmake --color=$(COLOR) 106 | .PHONY : CMakeFiles/src.dir/depend 107 | 108 | -------------------------------------------------------------------------------- /fastpy/parser/validators.py: -------------------------------------------------------------------------------- 1 | """Additional tools for tokens validation""" 2 | 3 | from ..lexer import BaseToken, TokenTypes 4 | 5 | 6 | class Validators: 7 | """ 8 | This class is just a wrapper for functions that are used for parsing 9 | """ 10 | 11 | @staticmethod 12 | def check_token_type(tokens: list[BaseToken], token_index: int, possible_types: list[int | str]) -> bool: 13 | """ 14 | 15 | :param tokens: list of tokens for check 16 | :param token_index: particular token index 17 | :param possible_types: possible types of particular token 18 | :return: some type matches with token name 19 | """ 20 | 21 | token_type = tokens[token_index].type 22 | 23 | if isinstance(possible_types[0], str): 24 | return token_type.name in possible_types 25 | else: 26 | return token_type.value in possible_types 27 | 28 | @staticmethod 29 | def check_token_name(tokens: list[BaseToken], token_index: int, possible_names: list[str]) -> bool: 30 | """ 31 | 32 | :param tokens: list of tokens for check 33 | :param token_index: particular token index 34 | :param possible_names: possible names of particular token 35 | :return: some name matches with token name 36 | """ 37 | 38 | token_name = tokens[token_index].name 39 | return token_name in possible_names 40 | 41 | @staticmethod 42 | def check_token_type_presence( 43 | tokens: list[BaseToken], 44 | required_types: list[str | int | TokenTypes] 45 | ): 46 | """ 47 | 48 | :param tokens: list of tokens for check 49 | :param required_types: types that must presence in the token list 50 | :return: present all required types 51 | """ 52 | 53 | types = tuple(map(lambda t: t.type.name, tokens)) \ 54 | if isinstance(required_types[0], str) \ 55 | else tuple(map(lambda t: t.type.value, tokens)) 56 | 57 | for required_type in required_types: 58 | if required_type not in types: 59 | return False 60 | return True 61 | 62 | @staticmethod 63 | def check_token_name_presence(tokens: list[BaseToken], required_names: list[str]): 64 | """ 65 | 66 | :param tokens: list of tokens for check 67 | :param required_names: names that must presence in the token list 68 | :return: present all required names 69 | """ 70 | 71 | names = tuple(map(lambda t: t.name, tokens)) 72 | for required_name in required_names: 73 | if required_name not in names: 74 | return False 75 | return True 76 | 77 | @staticmethod 78 | def check_token_types(tokens: list[BaseToken], types: list[int]) -> bool: 79 | """ 80 | 81 | :param tokens: list of tokens for check 82 | :param types: list of supposed token types in the same order as the tokens 83 | :return: match types 84 | """ 85 | 86 | for token, supposed_type in zip(tokens, types): 87 | if supposed_type is None: 88 | continue 89 | 90 | if token.type != supposed_type: 91 | return False 92 | 93 | return True 94 | 95 | @staticmethod 96 | def check_token_texts(tokens: list[BaseToken], texts: list[str]) -> bool: 97 | """ 98 | 99 | :param tokens: list of tokens for check 100 | :param texts: list of supposed token texts in the same order as the tokens 101 | :return: match texts 102 | """ 103 | 104 | for token, supposed_text in zip(tokens, texts): 105 | if supposed_text is None: 106 | continue 107 | 108 | if token.text != supposed_text: 109 | return False 110 | 111 | return True 112 | 113 | @staticmethod 114 | def check_token_names(tokens: list[BaseToken], names: list[str]) -> bool: 115 | """ 116 | 117 | :param tokens: list of tokens for check 118 | :param names: list of supposed token names in the same order as the tokens 119 | :return: match names 120 | """ 121 | 122 | for token, supposed_name in zip(tokens, names): 123 | if supposed_name is None: 124 | continue 125 | 126 | if token.name != supposed_name: 127 | return False 128 | 129 | return True 130 | 131 | @staticmethod 132 | def check_min_tokens_length(tokens: list[BaseToken], min_length: int): 133 | """ 134 | 135 | :param tokens: list of tokens for check 136 | :param min_length: min length of tokens list 137 | :return: len of tokens >= min length 138 | """ 139 | return len(tokens) >= min_length 140 | 141 | @staticmethod 142 | def check_fixed_tokens_length(tokens: list[BaseToken], length: int): 143 | """ 144 | 145 | :param tokens: list of tokens for check 146 | :param length: supposed length of tokens list 147 | :return: len of tokens == length 148 | """ 149 | return len(tokens) == length 150 | -------------------------------------------------------------------------------- /fastpy/lexer/detectors.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from .tokens import BaseToken, create_token 3 | from .token_types import TokenTypes 4 | from .config import * 5 | from ..exceptions import * 6 | import re 7 | import string 8 | 9 | 10 | class BaseDetector(ABC): 11 | """Token detector interface""" 12 | 13 | detects: tuple[TokenTypes] 14 | 15 | @abstractmethod 16 | def detect(self, 17 | code: str, 18 | line_number: int, 19 | column_number: int, 20 | regex_pattern: str, 21 | supposed_token_type: TokenTypes) -> BaseToken: 22 | """ 23 | Splits a portion of a FastPy code line into a token 24 | 25 | :param code: FastPy source code part 26 | :param line_number: 27 | :param column_number: 28 | :param regex_pattern: regular expression that helps to detect and extract token 29 | :param supposed_token_type: supposed type of token 30 | :return: extracted token 31 | """ 32 | 33 | 34 | class UniversalDetector(BaseDetector): 35 | """ 36 | Lets to detect and extract several types of token such number & identifier 37 | """ 38 | 39 | detects = (TokenTypes.number, TokenTypes.identifier) 40 | 41 | def detect(self, 42 | code: str, 43 | line_number: int, 44 | column_number: int, 45 | regex_pattern: str, 46 | supposed_token_type: TokenTypes) -> BaseToken: 47 | cut_string = code[column_number::] 48 | result = re.match(regex_pattern, cut_string) 49 | 50 | if result: 51 | result_string = result.group().strip() 52 | return create_token(supposed_token_type, result_string, line_number) 53 | 54 | 55 | class LiteralDetector(BaseDetector): 56 | """ 57 | Lets to detect and extract token of string literal type 58 | """ 59 | detects = (TokenTypes.literal,) 60 | 61 | @staticmethod 62 | def _escape_double_quote(literal: str) -> str: 63 | out_literal = '' 64 | 65 | for char in literal[1:-1]: 66 | match char: 67 | case '\\': 68 | pass 69 | case '"': 70 | out_literal += '\\"' 71 | case _: 72 | out_literal += char 73 | return '"' + out_literal + '"' 74 | 75 | @staticmethod 76 | def _extract_string_literal(code: str, pattern: str) -> str: 77 | start = code[0] 78 | 79 | result = re.search(pattern, code) 80 | literal = None 81 | if result: 82 | literal = result.group() 83 | 84 | if literal.count(start) > 2: 85 | ignore_next = False 86 | for i, char in enumerate(literal[1::]): 87 | if ignore_next: 88 | ignore_next = False 89 | continue 90 | 91 | if char == '\\': 92 | ignore_next = True 93 | if char == start: 94 | return '"' + literal[1:i + 1] + '"' 95 | 96 | return ('"' + literal[1:-1] + '"') if literal else None 97 | 98 | def detect(self, 99 | code: str, 100 | line_number: int, 101 | column_number: int, 102 | regex_pattern: str, 103 | supposed_token_type: TokenTypes) -> BaseToken | None: 104 | cut_string = code[column_number::] 105 | 106 | if cut_string[0] not in ['"', "'"]: 107 | return 108 | 109 | string_literal = self._extract_string_literal( 110 | code=cut_string, 111 | pattern=regex_pattern 112 | ) 113 | if not string_literal: 114 | return 115 | 116 | return create_token( 117 | token_type=supposed_token_type, 118 | text=self._escape_double_quote(string_literal), 119 | line=line_number, 120 | ) 121 | 122 | 123 | class OperatorDetector(BaseDetector): 124 | """ 125 | Lets to detect and extract token of operator type 126 | """ 127 | 128 | detects = (TokenTypes.operator,) 129 | 130 | def detect(self, 131 | code: str, 132 | line_number: int, 133 | column_number: int, 134 | regex_pattern: str, 135 | supposed_token_type: TokenTypes) -> BaseToken: 136 | 137 | cut_string = code[column_number::] 138 | 139 | start = cut_string[0] 140 | 141 | overlaps = [] 142 | 143 | for op, name in OPERATORS.items(): 144 | if start == op[0]: 145 | if op[0] in string.ascii_letters and name != 'else': 146 | if re.match(op + ' ', cut_string): 147 | overlaps.append(op) 148 | else: 149 | 150 | if re.match(re.escape(op), cut_string): 151 | overlaps.append(op) 152 | 153 | if len(overlaps) > 0: 154 | overlaps.sort(key=len) 155 | return create_token( 156 | supposed_token_type, 157 | overlaps[-1], 158 | line_number, 159 | OPERATORS.get(overlaps[-1]) 160 | ) 161 | -------------------------------------------------------------------------------- /config/code_highlighting.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 00# 01 02 03 04 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | ( ) , 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | fun return class interface 28 | -> - + * / ^ 29 | bool str int 30 | private public protected 31 | log_warning log_error log_info log import import_parts 32 | @ 33 | true True false False None null none 34 | if else elif match case default 35 | 00" 01 02" 03 04 05 06' 07 08' 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/Makefile: -------------------------------------------------------------------------------- 1 | # CMAKE generated file: DO NOT EDIT! 2 | # Generated by "MinGW Makefiles" Generator, CMake Version 3.20 3 | 4 | # Default target executed when no arguments are given to make. 5 | default_target: all 6 | .PHONY : default_target 7 | 8 | # Allow only one "make -f Makefile2" at a time, but pass parallelism. 9 | .NOTPARALLEL: 10 | 11 | #============================================================================= 12 | # Special targets provided by cmake. 13 | 14 | # Disable implicit rules so canonical targets will work. 15 | .SUFFIXES: 16 | 17 | # Disable VCS-based implicit rules. 18 | % : %,v 19 | 20 | # Disable VCS-based implicit rules. 21 | % : RCS/% 22 | 23 | # Disable VCS-based implicit rules. 24 | % : RCS/%,v 25 | 26 | # Disable VCS-based implicit rules. 27 | % : SCCS/s.% 28 | 29 | # Disable VCS-based implicit rules. 30 | % : s.% 31 | 32 | .SUFFIXES: .hpux_make_needs_suffix_list 33 | 34 | # Command-line flag to silence nested $(MAKE). 35 | $(VERBOSE)MAKESILENT = -s 36 | 37 | #Suppress display of executed commands. 38 | $(VERBOSE).SILENT: 39 | 40 | # A target that is always out of date. 41 | cmake_force: 42 | .PHONY : cmake_force 43 | 44 | #============================================================================= 45 | # Set environment variables for the build. 46 | 47 | SHELL = cmd.exe 48 | 49 | # The CMake executable. 50 | CMAKE_COMMAND = "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" 51 | 52 | # The command to remove a file. 53 | RM = "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" -E rm -f 54 | 55 | # Escaping for special characters. 56 | EQUALS = = 57 | 58 | # The top-level source directory on which CMake was run. 59 | CMAKE_SOURCE_DIR = D:\Programming\Python\Projects\FastPy-public\fastpy_build\src 60 | 61 | # The top-level build directory on which CMake was run. 62 | CMAKE_BINARY_DIR = D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug 63 | 64 | #============================================================================= 65 | # Targets provided globally by CMake. 66 | 67 | # Special rule for the target edit_cache 68 | edit_cache: 69 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "No interactive CMake dialog available..." 70 | "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" -E echo "No interactive CMake dialog available." 71 | .PHONY : edit_cache 72 | 73 | # Special rule for the target edit_cache 74 | edit_cache/fast: edit_cache 75 | .PHONY : edit_cache/fast 76 | 77 | # Special rule for the target rebuild_cache 78 | rebuild_cache: 79 | @$(CMAKE_COMMAND) -E cmake_echo_color --switch=$(COLOR) --cyan "Running CMake to regenerate build system..." 80 | "D:\Programs\CLion 2021.2.2\bin\cmake\win\bin\cmake.exe" --regenerate-during-build -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) 81 | .PHONY : rebuild_cache 82 | 83 | # Special rule for the target rebuild_cache 84 | rebuild_cache/fast: rebuild_cache 85 | .PHONY : rebuild_cache/fast 86 | 87 | # The main all target 88 | all: cmake_check_build_system 89 | $(CMAKE_COMMAND) -E cmake_progress_start D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\\CMakeFiles\progress.marks 90 | $(MAKE) $(MAKESILENT) -f CMakeFiles\Makefile2 all 91 | $(CMAKE_COMMAND) -E cmake_progress_start D:\Programming\Python\Projects\FastPy-public\fastpy_build\src\cmake-build-debug\CMakeFiles 0 92 | .PHONY : all 93 | 94 | # The main clean target 95 | clean: 96 | $(MAKE) $(MAKESILENT) -f CMakeFiles\Makefile2 clean 97 | .PHONY : clean 98 | 99 | # The main clean target 100 | clean/fast: clean 101 | .PHONY : clean/fast 102 | 103 | # Prepare targets for installation. 104 | preinstall: all 105 | $(MAKE) $(MAKESILENT) -f CMakeFiles\Makefile2 preinstall 106 | .PHONY : preinstall 107 | 108 | # Prepare targets for installation. 109 | preinstall/fast: 110 | $(MAKE) $(MAKESILENT) -f CMakeFiles\Makefile2 preinstall 111 | .PHONY : preinstall/fast 112 | 113 | # clear depends 114 | depend: 115 | $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles\Makefile.cmake 1 116 | .PHONY : depend 117 | 118 | #============================================================================= 119 | # Target rules for targets named src 120 | 121 | # Build rule for target. 122 | src: cmake_check_build_system 123 | $(MAKE) $(MAKESILENT) -f CMakeFiles\Makefile2 src 124 | .PHONY : src 125 | 126 | # fast build rule for target. 127 | src/fast: 128 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/build 129 | .PHONY : src/fast 130 | 131 | main.obj: main.cpp.obj 132 | .PHONY : main.obj 133 | 134 | # target to build an object file 135 | main.cpp.obj: 136 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/main.cpp.obj 137 | .PHONY : main.cpp.obj 138 | 139 | main.i: main.cpp.i 140 | .PHONY : main.i 141 | 142 | # target to preprocess a source file 143 | main.cpp.i: 144 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/main.cpp.i 145 | .PHONY : main.cpp.i 146 | 147 | main.s: main.cpp.s 148 | .PHONY : main.s 149 | 150 | # target to generate assembly for a file 151 | main.cpp.s: 152 | $(MAKE) $(MAKESILENT) -f CMakeFiles\src.dir\build.make CMakeFiles/src.dir/main.cpp.s 153 | .PHONY : main.cpp.s 154 | 155 | # Help Target 156 | help: 157 | @echo The following are some of the valid targets for this Makefile: 158 | @echo ... all (the default if no target is provided) 159 | @echo ... clean 160 | @echo ... depend 161 | @echo ... edit_cache 162 | @echo ... rebuild_cache 163 | @echo ... src 164 | @echo ... main.obj 165 | @echo ... main.i 166 | @echo ... main.s 167 | .PHONY : help 168 | 169 | 170 | 171 | #============================================================================= 172 | # Special targets to cleanup operation of make. 173 | 174 | # Special rule to run CMake to check the build system integrity. 175 | # No rule that depends on this can have commands that come from listfiles 176 | # because they might be regenerated. 177 | cmake_check_build_system: 178 | $(CMAKE_COMMAND) -S$(CMAKE_SOURCE_DIR) -B$(CMAKE_BINARY_DIR) --check-build-system CMakeFiles\Makefile.cmake 0 179 | .PHONY : cmake_check_build_system 180 | 181 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/CMakeFiles/3.20.2/CMakeCXXCompiler.cmake: -------------------------------------------------------------------------------- 1 | set(CMAKE_CXX_COMPILER "D:/Programming/C++/Compilers/mingw64/bin/g++.exe") 2 | set(CMAKE_CXX_COMPILER_ARG1 "") 3 | set(CMAKE_CXX_COMPILER_ID "GNU") 4 | set(CMAKE_CXX_COMPILER_VERSION "7.3.0") 5 | set(CMAKE_CXX_COMPILER_VERSION_INTERNAL "") 6 | set(CMAKE_CXX_COMPILER_WRAPPER "") 7 | set(CMAKE_CXX_STANDARD_COMPUTED_DEFAULT "14") 8 | set(CMAKE_CXX_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters;cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates;cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates;cxx_std_17") 9 | set(CMAKE_CXX98_COMPILE_FEATURES "cxx_std_98;cxx_template_template_parameters") 10 | set(CMAKE_CXX11_COMPILE_FEATURES "cxx_std_11;cxx_alias_templates;cxx_alignas;cxx_alignof;cxx_attributes;cxx_auto_type;cxx_constexpr;cxx_decltype;cxx_decltype_incomplete_return_types;cxx_default_function_template_args;cxx_defaulted_functions;cxx_defaulted_move_initializers;cxx_delegating_constructors;cxx_deleted_functions;cxx_enum_forward_declarations;cxx_explicit_conversions;cxx_extended_friend_declarations;cxx_extern_templates;cxx_final;cxx_func_identifier;cxx_generalized_initializers;cxx_inheriting_constructors;cxx_inline_namespaces;cxx_lambdas;cxx_local_type_template_args;cxx_long_long_type;cxx_noexcept;cxx_nonstatic_member_init;cxx_nullptr;cxx_override;cxx_range_for;cxx_raw_string_literals;cxx_reference_qualified_functions;cxx_right_angle_brackets;cxx_rvalue_references;cxx_sizeof_member;cxx_static_assert;cxx_strong_enums;cxx_thread_local;cxx_trailing_return_types;cxx_unicode_literals;cxx_uniform_initialization;cxx_unrestricted_unions;cxx_user_literals;cxx_variadic_macros;cxx_variadic_templates") 11 | set(CMAKE_CXX14_COMPILE_FEATURES "cxx_std_14;cxx_aggregate_default_initializers;cxx_attribute_deprecated;cxx_binary_literals;cxx_contextual_conversions;cxx_decltype_auto;cxx_digit_separators;cxx_generic_lambdas;cxx_lambda_init_captures;cxx_relaxed_constexpr;cxx_return_type_deduction;cxx_variable_templates") 12 | set(CMAKE_CXX17_COMPILE_FEATURES "cxx_std_17") 13 | set(CMAKE_CXX20_COMPILE_FEATURES "") 14 | set(CMAKE_CXX23_COMPILE_FEATURES "") 15 | 16 | set(CMAKE_CXX_PLATFORM_ID "MinGW") 17 | set(CMAKE_CXX_SIMULATE_ID "") 18 | set(CMAKE_CXX_COMPILER_FRONTEND_VARIANT "") 19 | set(CMAKE_CXX_SIMULATE_VERSION "") 20 | 21 | 22 | 23 | 24 | set(CMAKE_AR "D:/Programming/C++/Compilers/mingw64/bin/ar.exe") 25 | set(CMAKE_CXX_COMPILER_AR "D:/Programming/C++/Compilers/mingw64/bin/gcc-ar.exe") 26 | set(CMAKE_RANLIB "D:/Programming/C++/Compilers/mingw64/bin/ranlib.exe") 27 | set(CMAKE_CXX_COMPILER_RANLIB "D:/Programming/C++/Compilers/mingw64/bin/gcc-ranlib.exe") 28 | set(CMAKE_LINKER "D:/Programming/C++/Compilers/mingw64/bin/ld.exe") 29 | set(CMAKE_MT "") 30 | set(CMAKE_COMPILER_IS_GNUCXX 1) 31 | set(CMAKE_CXX_COMPILER_LOADED 1) 32 | set(CMAKE_CXX_COMPILER_WORKS TRUE) 33 | set(CMAKE_CXX_ABI_COMPILED TRUE) 34 | set(CMAKE_COMPILER_IS_MINGW 1) 35 | set(CMAKE_COMPILER_IS_CYGWIN ) 36 | if(CMAKE_COMPILER_IS_CYGWIN) 37 | set(CYGWIN 1) 38 | set(UNIX 1) 39 | endif() 40 | 41 | set(CMAKE_CXX_COMPILER_ENV_VAR "CXX") 42 | 43 | if(CMAKE_COMPILER_IS_MINGW) 44 | set(MINGW 1) 45 | endif() 46 | set(CMAKE_CXX_COMPILER_ID_RUN 1) 47 | set(CMAKE_CXX_SOURCE_FILE_EXTENSIONS C;M;c++;cc;cpp;cxx;m;mm;mpp;CPP) 48 | set(CMAKE_CXX_IGNORE_EXTENSIONS inl;h;hpp;HPP;H;o;O;obj;OBJ;def;DEF;rc;RC) 49 | 50 | foreach (lang C OBJC OBJCXX) 51 | if (CMAKE_${lang}_COMPILER_ID_RUN) 52 | foreach(extension IN LISTS CMAKE_${lang}_SOURCE_FILE_EXTENSIONS) 53 | list(REMOVE_ITEM CMAKE_CXX_SOURCE_FILE_EXTENSIONS ${extension}) 54 | endforeach() 55 | endif() 56 | endforeach() 57 | 58 | set(CMAKE_CXX_LINKER_PREFERENCE 30) 59 | set(CMAKE_CXX_LINKER_PREFERENCE_PROPAGATES 1) 60 | 61 | # Save compiler ABI information. 62 | set(CMAKE_CXX_SIZEOF_DATA_PTR "8") 63 | set(CMAKE_CXX_COMPILER_ABI "") 64 | set(CMAKE_CXX_BYTE_ORDER "LITTLE_ENDIAN") 65 | set(CMAKE_CXX_LIBRARY_ARCHITECTURE "") 66 | 67 | if(CMAKE_CXX_SIZEOF_DATA_PTR) 68 | set(CMAKE_SIZEOF_VOID_P "${CMAKE_CXX_SIZEOF_DATA_PTR}") 69 | endif() 70 | 71 | if(CMAKE_CXX_COMPILER_ABI) 72 | set(CMAKE_INTERNAL_PLATFORM_ABI "${CMAKE_CXX_COMPILER_ABI}") 73 | endif() 74 | 75 | if(CMAKE_CXX_LIBRARY_ARCHITECTURE) 76 | set(CMAKE_LIBRARY_ARCHITECTURE "") 77 | endif() 78 | 79 | set(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX "") 80 | if(CMAKE_CXX_CL_SHOWINCLUDES_PREFIX) 81 | set(CMAKE_CL_SHOWINCLUDES_PREFIX "${CMAKE_CXX_CL_SHOWINCLUDES_PREFIX}") 82 | endif() 83 | 84 | 85 | 86 | 87 | 88 | set(CMAKE_CXX_IMPLICIT_INCLUDE_DIRECTORIES "D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include/c++;D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include/c++/x86_64-w64-mingw32;D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include/c++/backward;D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include;D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0/include-fixed;D:/Programming/C++/Compilers/mingw64/x86_64-w64-mingw32/include") 89 | set(CMAKE_CXX_IMPLICIT_LINK_LIBRARIES "stdc++;mingw32;gcc_s;gcc;moldname;mingwex;pthread;advapi32;shell32;user32;kernel32;iconv;mingw32;gcc_s;gcc;moldname;mingwex") 90 | set(CMAKE_CXX_IMPLICIT_LINK_DIRECTORIES "D:/Programming/C++/Compilers/mingw64/lib/gcc/x86_64-w64-mingw32/7.3.0;D:/Programming/C++/Compilers/mingw64/lib/gcc;D:/Programming/C++/Compilers/mingw64/x86_64-w64-mingw32/lib;D:/Programming/C++/Compilers/mingw64/lib") 91 | set(CMAKE_CXX_IMPLICIT_LINK_FRAMEWORK_DIRECTORIES "") 92 | -------------------------------------------------------------------------------- /fastpy/parser/nodes.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..lexer import BaseToken 3 | from ..exceptions import * 4 | from ..filesystem import FileSystem as Fs 5 | 6 | 7 | class BaseNode(ABC): 8 | """Node interface""" 9 | 10 | @abstractmethod 11 | def __init__(self, *args, **kwargs): ... 12 | 13 | @property 14 | @abstractmethod 15 | def line(self) -> int: 16 | """ 17 | 18 | :return: line number from which the node was extracted 19 | """ 20 | 21 | 22 | class NodeWithBody(BaseNode, ABC): 23 | body = [] 24 | 25 | def push_node_to_body(self, node: BaseNode): 26 | self.body.append(node) 27 | 28 | 29 | class BasicNode(BaseNode, ABC): 30 | pass 31 | 32 | 33 | class NamedNode(BaseNode, ABC): 34 | identifier = None 35 | 36 | 37 | class NodeWithScope(NodeWithBody, NamedNode, ABC): 38 | identifier = None 39 | 40 | 41 | class PrintableNode(BaseNode, ABC): 42 | def __repr__(self): 43 | text = '<' 44 | for attr_name, attr_value in vars(self).items(): 45 | text += f'{attr_name}:{attr_value.text if isinstance(attr_value, BaseToken) else attr_value}, ' 46 | text += '>' 47 | return self.__class__.__name__ + text 48 | 49 | 50 | class VariableNode(BasicNode, PrintableNode, NamedNode): 51 | def __init__(self, identifier: BaseToken): 52 | self.identifier = identifier 53 | 54 | @property 55 | def line(self) -> int: 56 | return self.identifier.line 57 | 58 | 59 | class ValueNode(BasicNode, PrintableNode): 60 | def __init__(self, value: BaseToken): 61 | self.value = value 62 | 63 | @property 64 | def line(self) -> int: 65 | return self.value.line 66 | 67 | 68 | class AssignNode(BasicNode, PrintableNode, NamedNode): 69 | def __init__(self, 70 | identifier: BaseToken, 71 | value_type: BaseNode = None, 72 | value: BaseNode = None): 73 | self.identifier = identifier 74 | self.value_type = value_type 75 | self.value = value 76 | self.definition = True 77 | 78 | @property 79 | def line(self) -> int: 80 | return self.identifier.line 81 | 82 | 83 | class FuncNode(NodeWithScope, PrintableNode, NamedNode): 84 | def __init__(self, 85 | identifier: BaseToken, 86 | arguments: list[AssignNode] = None, 87 | body: list[BaseNode] = None, 88 | return_type: VariableNode = None, 89 | template: bool = False): 90 | self.identifier = identifier 91 | self.arguments = arguments or [] 92 | self.body = body or [] 93 | self.return_type = return_type 94 | self.template = template 95 | 96 | @property 97 | def line(self) -> int: 98 | return self.identifier.line 99 | 100 | 101 | class CallNode(BasicNode, PrintableNode): 102 | def __init__(self, 103 | identifier: BaseToken, 104 | arguments: list[BaseNode] = None): 105 | self.identifier = identifier 106 | self.arguments = arguments or [] 107 | 108 | @property 109 | def line(self) -> int: 110 | return self.identifier.line 111 | 112 | 113 | class LogicOpNode(BasicNode, PrintableNode): 114 | def __init__(self, 115 | left_operand: BaseNode = None, 116 | right_operand: BaseNode = None, 117 | operator: BaseToken = None): 118 | self.left_operand = left_operand 119 | self.right_operand = right_operand 120 | self.operator = operator 121 | self.in_brackets = False 122 | 123 | @property 124 | def line(self) -> int: 125 | if not self.left_operand: 126 | return -1 127 | return self.left_operand.line 128 | 129 | 130 | class ElseNode(NodeWithBody, PrintableNode): 131 | def __init__(self, body: list[BaseNode] = None, ): 132 | self.body = body or [] 133 | 134 | @property 135 | def line(self) -> int: 136 | if len(self.body) > 0: 137 | return self.body[0].line 138 | return -1 139 | 140 | 141 | class BinOpNode(BasicNode, PrintableNode): 142 | def __init__(self, 143 | left_operand: BaseNode = None, 144 | right_operand: BaseNode = None, 145 | operator: BaseToken = None, 146 | priority: int = None): 147 | self.left_operand = left_operand 148 | self.right_operand = right_operand 149 | self.operator = operator 150 | self.priority = priority 151 | self.in_brackets = False 152 | 153 | @staticmethod 154 | def _get_operand_len(operand: BaseNode): 155 | if isinstance(operand, (VariableNode, ValueNode)): 156 | return 1 157 | elif isinstance(operand, BinOpNode): 158 | return len(operand) 159 | return 0 160 | 161 | def __len__(self): 162 | tokens_number = 1 163 | tokens_number += self._get_operand_len(self.left_operand) 164 | tokens_number += self._get_operand_len(self.right_operand) 165 | return tokens_number 166 | 167 | def __repr__(self): 168 | op_text = f'{self.left_operand} {self.operator.text} {self.right_operand}' 169 | if self.in_brackets: 170 | op_text = '( ' + op_text + ' )' 171 | 172 | return self.__class__.__name__ + f'<{op_text}>' 173 | 174 | @property 175 | def line(self) -> int: 176 | if self.left_operand: 177 | return self.left_operand.line 178 | return -1 179 | 180 | 181 | class IfNode(NodeWithBody, PrintableNode): 182 | def __init__(self, 183 | condition: LogicOpNode | BinOpNode = None, 184 | body: list[BaseNode] = None, 185 | elif_cases: list = None, 186 | else_case: ElseNode = None, 187 | is_elif: bool = False): 188 | self.condition = condition 189 | self.body = body or [] 190 | self.elif_cases: list[IfNode.__init__] = elif_cases 191 | self.else_case = else_case 192 | self.is_elif = is_elif 193 | 194 | @property 195 | def line(self) -> int: 196 | return self.condition.line 197 | 198 | 199 | # class ForNode(BellyNode, PrintableNode): 200 | # def __init__(self, 201 | # body: list[BaseNode] = None, 202 | # else_body: list[BaseNode] = None 203 | # ): 204 | # pass 205 | 206 | 207 | class WhileNode(NodeWithBody, PrintableNode): 208 | def __init__(self, 209 | condition: LogicOpNode | BinOpNode = None, 210 | body: list[BaseNode] = None, 211 | else_body: list[BaseNode] = None): 212 | self.condition = condition 213 | self.else_body = else_body or [] 214 | self.body = body or [] 215 | 216 | @property 217 | def line(self) -> int: 218 | if not self.condition: 219 | return -1 220 | return self.condition.line 221 | 222 | 223 | class ReturnNode(BasicNode, PrintableNode): 224 | def __init__(self, node: BaseNode): 225 | self.node = node 226 | 227 | @property 228 | def line(self) -> int: 229 | if not self.node: 230 | return -1 231 | return self.node.line 232 | -------------------------------------------------------------------------------- /fastpy/parser/parsers.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..lexer import BaseToken, TokenTypes, code_from_tokens 3 | from ..module import Module 4 | from ..import_tools import import_class 5 | from .config import * 6 | from .ast import * 7 | from ..exceptions import ParsingError 8 | from ..log import Logger 9 | from .structure import * 10 | from .node_parsers import * 11 | 12 | 13 | class BaseParser(ABC): 14 | """Parser interface""" 15 | 16 | @abstractmethod 17 | def __init__(self, 18 | module: Module, 19 | tokens: list[BaseToken]): 20 | """ 21 | 22 | :param module: the module parameter contains information about the currently processed file 23 | :param tokens: list of tokens - output of previous stage 24 | """ 25 | 26 | @abstractmethod 27 | def parse(self) -> BaseAST: 28 | """Parses tokens and returns an Abstract Syntax Tree""" 29 | 30 | 31 | class Parser(BaseParser): 32 | """Basic parser implementation of FastPy""" 33 | 34 | @Logger.info(pattern='Parser created ({module})') 35 | def __init__(self, 36 | module: Module, 37 | tokens: list[BaseToken]): 38 | self._ast = create_ast() 39 | self._current_module = module 40 | self._current_struct: Structure | None = None 41 | self._structs: list[Structure] = [] 42 | self._structs: list[Structure] = [] 43 | self._tokens = tokens 44 | self._node_parsers = {} 45 | self._load_parsers() 46 | 47 | def _load_parsers(self): 48 | for node_name, parse_data in NODE_PARSING.items(): 49 | self._node_parsers.update({ 50 | import_class(parse_data.get('node_class')): 51 | { 52 | 'parser_instance': import_class(parse_data.get('parser_class'))(), 53 | 'cases': parse_data.get('cases') 54 | } 55 | }) 56 | 57 | def _parse_node(self, tokens: list[BaseToken], 58 | possible_node_types: list[type[BaseNode]] = None, 59 | parser: BaseNodeParser = None, 60 | **parser_data) -> BaseNode: 61 | if parser: 62 | for node_type in possible_node_types: 63 | cases = self._node_parsers.get(node_type).get('cases') 64 | for parser_args in cases: 65 | if parser.validate(tokens=tokens, 66 | supposed_node_type=node_type, 67 | **parser_args.get('validate_data'), 68 | **parser_data): 69 | node = parser.parse( 70 | tokens=tokens, 71 | supposed_node_type=node_type, 72 | parse_node_clb=self._parse_node, 73 | **parser_args.get('parse_data'), 74 | **parser_data 75 | ) 76 | 77 | return node 78 | for node_type, parser_info in self._node_parsers.items(): 79 | parser_instance: BaseNodeParser = parser_info.get('parser_instance') 80 | cases = parser_info.get('cases') 81 | 82 | if possible_node_types and node_type not in possible_node_types: 83 | continue 84 | 85 | if node_type not in parser_instance.parses: 86 | continue 87 | 88 | for parser_args in cases: 89 | if parser_instance.validate(tokens=tokens, 90 | supposed_node_type=node_type, 91 | **parser_args.get('validate_data')): 92 | node = parser_instance.parse( 93 | tokens=tokens, 94 | supposed_node_type=node_type, 95 | parse_node_clb=self._parse_node, 96 | **parser_args.get('parse_data') 97 | ) 98 | return node 99 | 100 | raise ParsingError(f'SyntaxError: failed to parse expression "{code_from_tokens(tokens)}"') 101 | 102 | def _detect_struct_start(self, node: BaseNode, level: int): 103 | if isinstance(node, NodeWithBody): 104 | self._current_struct = Structure( 105 | node=node, 106 | level=level + 4 107 | ) 108 | self._structs.append(self._current_struct) 109 | 110 | def _within_struct(self, level: int): 111 | return self._current_struct and self._current_struct.within_struct(level=level) 112 | 113 | def _get_struct(self, level: int) -> Structure: 114 | for struct in reversed(self._structs): 115 | if struct.within_struct(level=level): 116 | return struct 117 | 118 | @staticmethod 119 | def _check_expr_level(level: int): 120 | if level % 4 != 0: 121 | raise ParsingError('SyntaxError: invalid number of spaces, number of spaces must be a multiple of four') 122 | 123 | @Logger.info(pattern='Parsing: {expr_tokens[0].line}: {expr_tokens}: level: {expr_level}') 124 | def _parse_expression(self, expr_tokens: list[BaseToken], expr_level: int): 125 | """Parses each line of code split into tokens""" 126 | 127 | self._check_expr_level(level=expr_level) 128 | 129 | node = self._parse_node(expr_tokens) 130 | 131 | struct = self._get_struct(expr_level) 132 | if struct: 133 | struct.push_node(node) 134 | else: 135 | self._ast.push_node( 136 | module=self._current_module, 137 | node=node 138 | ) 139 | 140 | self._detect_struct_start(node, expr_level) 141 | 142 | @Logger.info('Start parsing...', ending_message='Parsing completed in {time}') 143 | # @Logger.catch_errors() 144 | def parse(self) -> BaseAST: 145 | expr_tokens = [] 146 | expr_level = 0 147 | code_started = False 148 | 149 | for token in self._tokens: 150 | 151 | if token.type in [TokenTypes.gap]: 152 | if not code_started: 153 | expr_level += 1 154 | continue 155 | 156 | elif token.type in [TokenTypes.tab]: 157 | if not code_started: 158 | expr_level += 4 159 | continue 160 | 161 | elif token.type in [TokenTypes.endline]: 162 | try: 163 | self._parse_expression( 164 | expr_tokens=expr_tokens, 165 | expr_level=expr_level 166 | ) 167 | except ParsingError as e: 168 | Logger.log_critical(f'{self._current_module.filepath}: {expr_tokens[0].line}: {e}') 169 | os.system('pause') 170 | exit(-1) 171 | except IndexError: 172 | pass 173 | 174 | expr_tokens.clear() 175 | expr_level = 0 176 | code_started = False 177 | continue 178 | 179 | code_started = True 180 | expr_tokens.append(token) 181 | 182 | return self._ast 183 | 184 | 185 | def create_parser(module: Module, 186 | tokens: list[BaseToken], ): 187 | return import_class(PARSER_CLASS_PATH)(module=module, tokens=tokens) 188 | -------------------------------------------------------------------------------- /fastpy_build/src/cmake-build-debug/src.cbp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 96 | 97 | -------------------------------------------------------------------------------- /fastpy/transpiler/node_transpilers.py: -------------------------------------------------------------------------------- 1 | from abc import ABC, abstractmethod 2 | from ..parser.nodes import * 3 | from .code import * 4 | from ..singleton import singleton 5 | from .config import * 6 | 7 | 8 | class BaseNodeTranspiler(ABC): 9 | """ 10 | Node Transpiler interface 11 | """ 12 | 13 | @abstractmethod 14 | def transpile(self, 15 | node: BaseNode, 16 | transpile_node_clb: callable, 17 | **extra_data) -> BaseCode: 18 | """ 19 | 20 | :param node: node to transpile 21 | :param transpile_node_clb: callback to transpile node body or arguments or condition 22 | :param extra_data: extra data, transmitted by another node transpiler 23 | :return: 24 | """ 25 | 26 | 27 | @singleton 28 | class AssignNodeTranspiler(BaseNodeTranspiler): 29 | def transpile(self, node: AssignNode, transpile_node_clb: callable, **extra_data) -> BaseCode: 30 | code = Code() 31 | value_node = node.value 32 | value = '' 33 | if value_node: 34 | value = transpile_node_clb( 35 | node=value_node, 36 | **extra_data, 37 | type=node.value_type if node.value_type else None 38 | ).internal 39 | 40 | var_type = '' 41 | if node.definition: 42 | # var_type = f'{node.value_type.text if node.value_type is not None else "auto"} ' 43 | if node.value_type: 44 | var_type = transpile_node_clb(node.value_type, endl=False, auto_semicolon=False) 45 | else: 46 | var_type = 'auto' 47 | 48 | code.push_internal( 49 | f'{var_type} ' 50 | f'{node.identifier.text}{" = " if value else ""}' 51 | f'{value}', 52 | **extra_data 53 | ) 54 | 55 | return code 56 | 57 | 58 | @singleton 59 | class ValueNodeTranspiler(BaseNodeTranspiler): 60 | def transpile(self, 61 | node: ValueNode, 62 | transpile_node_clb: callable, 63 | **extra_data) -> BaseCode: 64 | code = Code() 65 | 66 | value = node.value.text 67 | if isinstance(value, str): 68 | if value[0] == "'": 69 | value = '"' + value[1:-1] + '"' 70 | 71 | code.push_internal( 72 | f'{value}', 73 | **extra_data 74 | ) 75 | return code 76 | 77 | 78 | @singleton 79 | class VariableNodeTranspiler(BaseNodeTranspiler): 80 | def transpile(self, 81 | node: VariableNode, 82 | transpile_node_clb: callable, 83 | **extra_data) -> BaseCode: 84 | code = Code() 85 | code.push_internal( 86 | f'{node.identifier.text}', 87 | **extra_data 88 | ) 89 | return code 90 | 91 | 92 | @singleton 93 | class FuncNodeTranspiler(BaseNodeTranspiler): 94 | 95 | @staticmethod 96 | def _transpile_arguments(arguments: list[BaseNode], transpile_node_clb) -> str: 97 | code = '' 98 | 99 | for i, arg in enumerate(arguments): 100 | code += transpile_node_clb(arg, endl=False, auto_semicolon=False).internal 101 | if i <= len(arguments) - 2: 102 | code += ', ' 103 | 104 | return code 105 | 106 | @staticmethod 107 | def _transpile_body(body: list[BaseNode], transpile_node_clb) -> str: 108 | code = Code() 109 | 110 | for i, node in enumerate(body): 111 | code.push_internal( 112 | transpile_node_clb(node=node, ).internal, 113 | endl=False, auto_semicolon=False 114 | ) 115 | 116 | return code.internal 117 | 118 | def transpile(self, 119 | node: FuncNode, 120 | transpile_node_clb: callable, 121 | **extra_data) -> BaseCode: 122 | code = Code() 123 | arguments = self._transpile_arguments(node.arguments, transpile_node_clb) 124 | body = self._transpile_body(node.body, transpile_node_clb) 125 | 126 | return_type = node.return_type.identifier.text if node.return_type else 'void' 127 | func_code = f'{return_type} {node.identifier.text} ({arguments}){{\n{body}\n}}' 128 | code.push_external(func_code) 129 | return code 130 | 131 | 132 | @singleton 133 | class CallNodeTranspiler(BaseNodeTranspiler): 134 | @staticmethod 135 | def _transpile_arguments(arguments: list[BaseNode], transpile_node_clb) -> str: 136 | code = '' 137 | 138 | for i, arg in enumerate(arguments): 139 | code += transpile_node_clb(arg, endl=False, auto_semicolon=False).internal 140 | if i <= len(arguments) - 2: 141 | code += ', ' 142 | 143 | return code 144 | 145 | def transpile(self, 146 | node: CallNode, 147 | transpile_node_clb: callable, 148 | **extra_data) -> BaseCode: 149 | code = Code() 150 | 151 | cast_type: VariableNode = extra_data.get('type') 152 | specify_type = False 153 | 154 | if node.identifier.text == BUILTIN_FUNCTIONS['input']: 155 | specify_type = True 156 | 157 | code.push_internal( 158 | f'{node.identifier.text}' 159 | f'{("<" + cast_type.identifier.text + ">") if specify_type else ""}' 160 | f'({self._transpile_arguments(node.arguments, transpile_node_clb)})', 161 | **extra_data 162 | ) 163 | return code 164 | 165 | 166 | @singleton 167 | class OperationsNodeTranspiler(BaseNodeTranspiler): 168 | @staticmethod 169 | def _transpile_operator(operator: str) -> str: 170 | eq = OPERATORS_EQUIVALENTS.get(operator) 171 | if eq: 172 | return eq 173 | 174 | return operator 175 | 176 | def transpile(self, 177 | node: LogicOpNode | BinOpNode, 178 | transpile_node_clb: callable, 179 | **extra_data) -> BaseCode: 180 | code = Code() 181 | left_operand = transpile_node_clb(node.left_operand, auto_semicolon=False, endl=False) 182 | if not node.right_operand: 183 | match_expr = f'{left_operand}' 184 | else: 185 | right_operand = transpile_node_clb(node.right_operand, auto_semicolon=False, endl=False) 186 | match_expr = f'{left_operand} {self._transpile_operator(node.operator.text)} {right_operand}' 187 | 188 | if node.in_brackets: 189 | match_expr = '(' + match_expr + ')' 190 | 191 | code.push_internal(match_expr, **extra_data) 192 | return code 193 | 194 | 195 | @singleton 196 | class IfNodeTranspiler(BaseNodeTranspiler): 197 | @staticmethod 198 | def _transpile_condition(node: LogicOpNode, transpile_node_clb) -> str: 199 | return transpile_node_clb(node, endl=False, auto_semicolon=False).internal 200 | 201 | @staticmethod 202 | def _transpile_body(body: list[BaseNode], transpile_node_clb) -> str: 203 | code = Code() 204 | 205 | for i, node in enumerate(body): 206 | code.push_internal( 207 | transpile_node_clb(node=node, endl=False, auto_semicolon=False).internal, 208 | ) 209 | 210 | return code.internal 211 | 212 | def transpile(self, 213 | node: IfNode, 214 | transpile_node_clb: callable, 215 | **extra_data) -> BaseCode: 216 | code = Code() 217 | condition = self._transpile_condition(node.condition, transpile_node_clb) 218 | body = self._transpile_body(node.body, transpile_node_clb) 219 | 220 | code.push_internal( 221 | f'{"else if" if node.is_elif else "if"} ({condition}) {{\n{body}\n}}', 222 | auto_semicolon=False, 223 | endl=True 224 | ) 225 | 226 | return code 227 | 228 | 229 | class ElseNodeTranspiler(BaseNodeTranspiler): 230 | 231 | @staticmethod 232 | def _transpile_body(body: list[BaseNode], transpile_node_clb) -> str: 233 | code = Code() 234 | 235 | for i, node in enumerate(body): 236 | code.push_internal( 237 | transpile_node_clb(node=node, endl=False, auto_semicolon=False).internal, 238 | ) 239 | 240 | return code.internal 241 | 242 | def transpile(self, 243 | node: ElseNode, 244 | transpile_node_clb: callable, 245 | **extra_data) -> BaseCode: 246 | code = Code() 247 | body = self._transpile_body(node.body, transpile_node_clb) 248 | 249 | code.push_internal( 250 | f'else {{\n{body}\n}}', 251 | auto_semicolon=False, 252 | endl=True 253 | ) 254 | return code 255 | 256 | 257 | class WhileNodeTranspiler(BaseNodeTranspiler): 258 | @staticmethod 259 | def _transpile_body(body: list[BaseNode], transpile_node_clb) -> str: 260 | code = Code() 261 | 262 | for i, node in enumerate(body): 263 | code.push_internal( 264 | transpile_node_clb(node=node).internal, 265 | auto_semicolon=False, 266 | endl=True 267 | ) 268 | 269 | return code.internal 270 | 271 | @staticmethod 272 | def _transpile_condition(node: LogicOpNode, transpile_node_clb) -> str: 273 | return transpile_node_clb(node, endl=False, auto_semicolon=False).internal 274 | 275 | def transpile(self, 276 | node: WhileNode, 277 | transpile_node_clb: callable, 278 | **extra_data) -> BaseCode: 279 | code = Code() 280 | code.push_internal(f'while ({self._transpile_condition(node.condition, transpile_node_clb)})' 281 | f'\n{{\n{self._transpile_body(node.body, transpile_node_clb)}\n}}\n') 282 | return code 283 | 284 | 285 | class ReturnNodeTranspiler(BaseNodeTranspiler): 286 | def transpile(self, 287 | node: ReturnNode, 288 | transpile_node_clb: callable, 289 | **extra_data) -> BaseCode: 290 | code = Code() 291 | 292 | code.push_internal(f'return {transpile_node_clb(node.node).internal if node.node else ""}') 293 | return code 294 | -------------------------------------------------------------------------------- /fastpy/parser/node_parsers.py: -------------------------------------------------------------------------------- 1 | from .nodes import * 2 | from .validators import * 3 | from ..singleton import singleton 4 | from ..import_tools import import_class 5 | from ..lexer import TokenTypes, code_from_tokens 6 | from .config import * 7 | 8 | 9 | class BaseNodeParser(ABC): 10 | """Node Parser interface""" 11 | 12 | parses: tuple[type[BaseNode]] 13 | 14 | @abstractmethod 15 | def validate(self, 16 | tokens: list[BaseToken], 17 | supposed_node_type: type[BaseNode], 18 | **extra_data) -> bool: 19 | """ 20 | Validates tokens and returns whether the parser is ready to parse the provided tokens 21 | 22 | :param tokens: tokens to parse 23 | :param supposed_node_type: supposed type of node 24 | :param extra_data: data that helps to validate and parse tokens, 25 | can be loaded from config or transmitted from another node parser 26 | :return: parser readiness 27 | """ 28 | 29 | @abstractmethod 30 | def parse(self, 31 | tokens: list[BaseToken], 32 | parse_node_clb: callable, 33 | supposed_node_type: type[BaseNode], 34 | **extra_data) -> BaseNode | list[BaseNode]: 35 | """ 36 | Parses tokens and returns one or more obtained nodes. 37 | Should only run when the validate method returns True 38 | 39 | :param tokens: tokens to parse 40 | :param parse_node_clb: callback that can be useful to the parser to parse subnodes 41 | :param supposed_node_type: supposed type of node 42 | :param extra_data: data that helps to validate and parse tokens, 43 | can be loaded from config or transmitted from another node parser 44 | :return: 45 | """ 46 | 47 | 48 | @singleton 49 | class UniversalNodeParser(BaseNodeParser): 50 | parses = ( 51 | AssignNode, 52 | FuncNode, 53 | CallNode, 54 | ValueNode, 55 | VariableNode, 56 | IfNode, 57 | ElseNode, 58 | WhileNode, 59 | ReturnNode 60 | ) 61 | 62 | @staticmethod 63 | def _raise_exception(tokens: list[BaseToken], exception_data: dict): 64 | message = exception_data.get('message') 65 | raise ParsingError(message + f' "{code_from_tokens(tokens)}"') 66 | 67 | def validate(self, 68 | tokens: list[BaseToken], 69 | supposed_node_type: type[BaseNode], 70 | **extra_data) -> bool: 71 | 72 | methods = extra_data.get('methods') 73 | if not methods: 74 | return False 75 | 76 | for method_name, method_kwargs in methods.items(): 77 | exception = method_kwargs.get('exception') 78 | if exception: 79 | method_kwargs.pop('exception') 80 | 81 | if not getattr(Validators, method_name)( 82 | tokens=tokens, 83 | **method_kwargs 84 | ): 85 | if exception: 86 | self._raise_exception(tokens=tokens, exception_data=exception) 87 | 88 | return False 89 | 90 | return True 91 | 92 | @staticmethod 93 | def _parse_value(tokens: list[BaseToken], parse_node: callable, **value_data): 94 | index = value_data.get('index') 95 | parser_class_path = value_data.get('parser_class') 96 | nullable = value_data.get('nullable', True) 97 | value = value_data.get('value') 98 | if value: 99 | return value 100 | 101 | value = None 102 | 103 | if index is not None: 104 | value = tokens[index] 105 | 106 | elif parser_class_path: 107 | parser_class = import_class(parser_class_path) 108 | parser = parser_class() 109 | possible_node_types = tuple( 110 | import_class(node_class_path) 111 | for node_class_path in value_data.get('possible_node_classes') 112 | ) 113 | tokens_slice_data = value_data.get('tokens_slice') 114 | slice_start = tokens_slice_data.get('start_index') 115 | slice_end = tokens_slice_data.get('end_index') 116 | 117 | if slice_start: 118 | value = parse_node( 119 | tokens=tokens[slice_start::] if slice_end is None else tokens[slice_start:slice_end], 120 | possible_node_types=possible_node_types, 121 | parser=parser 122 | ) 123 | if not nullable and not value: 124 | raise ParsingError(value_data.get('error_message')) 125 | return value 126 | 127 | def parse(self, 128 | tokens: list[BaseToken], 129 | supposed_node_type: type[BaseNode], 130 | parse_node_clb: callable, 131 | **extra_data) -> BaseNode: 132 | 133 | node_arguments = {} 134 | for key, value_data in extra_data.items(): 135 | node_arguments.update({ 136 | key: self._parse_value( 137 | tokens=tokens, 138 | parse_node=parse_node_clb, 139 | **value_data) 140 | }) 141 | 142 | return supposed_node_type( 143 | **node_arguments 144 | ) 145 | 146 | 147 | @singleton 148 | class OperationNodeParser(BaseNodeParser): 149 | parses = (BinOpNode, LogicOpNode) 150 | 151 | def validate(self, 152 | tokens: list[BaseToken], 153 | supposed_node_type: type[BaseNode], 154 | **extra_data) -> bool: 155 | if supposed_node_type is LogicOpNode: 156 | if len(tokens) == 1 and tokens[0].type == TokenTypes.identifier: 157 | return True 158 | 159 | for token in tokens: 160 | if token.type == TokenTypes.operator and token.name in ['and', 'or', 'not']: 161 | return True 162 | 163 | elif supposed_node_type is BinOpNode: 164 | left_operand = None 165 | 166 | for token in tokens: 167 | if not left_operand and token.type in [TokenTypes.identifier, TokenTypes.number, TokenTypes.literal]: 168 | left_operand = token 169 | elif token.type == TokenTypes.operator \ 170 | and token.name in BIN_OP_NAMES: 171 | if left_operand: 172 | return True 173 | return False 174 | 175 | @staticmethod 176 | def _check_operand_fields(operand: BinOpNode) -> bool: 177 | if isinstance(operand, BinOpNode): 178 | return not any(( 179 | operand.right_operand is None, 180 | operand.left_operand is None, 181 | operand.operator is None 182 | )) 183 | return True 184 | 185 | @staticmethod 186 | def _create_op_node(left_operand: BaseNode, operator: BaseToken, right_operand: BaseNode): 187 | if not operator: 188 | return LogicOpNode( 189 | left_operand=left_operand, 190 | operator=operator, 191 | right_operand=right_operand 192 | ) 193 | if operator.name in ['and', 'or']: 194 | return LogicOpNode( 195 | left_operand=left_operand, 196 | operator=operator, 197 | right_operand=right_operand 198 | ) 199 | else: 200 | return BinOpNode( 201 | left_operand=left_operand, 202 | operator=operator, 203 | right_operand=right_operand 204 | ) 205 | 206 | def parse(self, 207 | tokens: list[BaseToken], 208 | parse_node_clb: callable, 209 | supposed_node_type: type[BaseNode], 210 | **extra_data) -> BinOpNode | tuple[int, BinOpNode] | LogicOpNode | tuple[int, LogicOpNode]: 211 | if len(tokens) == 1 and tokens[0].type == TokenTypes.identifier: 212 | return LogicOpNode(VariableNode(tokens[0])) 213 | 214 | left_operand, right_operand = None, None 215 | operator = None 216 | ignore_before = -1 217 | parenthesis_expected = extra_data.get('parenthesis_expected') 218 | 219 | for i, token in enumerate(tokens): 220 | if i <= ignore_before: 221 | continue 222 | 223 | match token.type: 224 | case TokenTypes.number | TokenTypes.literal: 225 | if not left_operand: 226 | left_operand = ValueNode(token) 227 | elif not right_operand: 228 | right_operand = ValueNode(token) 229 | else: 230 | left_operand = self._create_op_node( 231 | left_operand=left_operand, 232 | operator=operator, 233 | right_operand=right_operand 234 | ) 235 | right_operand = ValueNode(token) 236 | operator = None 237 | 238 | case TokenTypes.identifier: 239 | if not left_operand: 240 | left_operand = VariableNode(token) 241 | elif not right_operand: 242 | right_operand = VariableNode(token) 243 | else: 244 | left_operand = self._create_op_node( 245 | left_operand=left_operand, 246 | operator=operator, 247 | right_operand=right_operand 248 | ) 249 | right_operand = VariableNode(token) 250 | operator = None 251 | 252 | case TokenTypes.operator: 253 | if token.name not in BIN_OP_NAMES + LOGIC_OP_NAMES: 254 | break 255 | if not operator: 256 | operator = token 257 | else: 258 | left_operand = self._create_op_node( 259 | left_operand=left_operand, 260 | operator=operator, 261 | right_operand=right_operand 262 | ) 263 | operator = token 264 | right_operand = None 265 | 266 | case TokenTypes.start_parenthesis: 267 | 268 | checked, operand = self.parse( 269 | tokens[i + 1::], 270 | parse_node_clb, 271 | supposed_node_type, 272 | parenthesis_expected=True 273 | ) 274 | if isinstance(operand, (BinOpNode, LogicOpNode)): 275 | operand.in_brackets = True 276 | 277 | ignore_before = checked + i 278 | 279 | if not left_operand: 280 | left_operand = operand 281 | elif not right_operand: 282 | right_operand = operand 283 | 284 | case TokenTypes.end_parenthesis: 285 | if parenthesis_expected: 286 | parenthesis_expected = False 287 | if isinstance(left_operand, 288 | (BinOpNode, LogicOpNode)) and operator is None and right_operand is None: 289 | return i + 1, left_operand 290 | 291 | return i + 1, self._create_op_node( 292 | left_operand=left_operand, 293 | operator=operator, 294 | right_operand=right_operand 295 | ) 296 | else: 297 | raise ParsingError(f'SyntaxError: excess closing bracket "{code_from_tokens(tokens)}"') 298 | 299 | if parenthesis_expected: 300 | raise ParsingError(f'SyntaxError: close bracket expected') 301 | 302 | if not self._check_operand_fields(right_operand) or not self._check_operand_fields(left_operand): 303 | raise ParsingError(f'SyntaxError: unfinished expression "{code_from_tokens(tokens)}"') 304 | 305 | if isinstance(left_operand, (BinOpNode, LogicOpNode)) and operator is None and right_operand is None: 306 | return left_operand 307 | 308 | if operator and not right_operand or not operator and right_operand: 309 | raise ParsingError(f'SyntaxError: failed to parse math expression "{code_from_tokens(tokens)}"') 310 | 311 | return self._create_op_node( 312 | left_operand=left_operand, 313 | operator=operator, 314 | right_operand=right_operand 315 | ) 316 | 317 | 318 | @singleton 319 | class ArgumentsParser(BaseNodeParser): 320 | def validate(self, 321 | tokens: list[BaseToken], 322 | supposed_node_type: type[BaseNode], 323 | **extra_data) -> bool: 324 | return True 325 | 326 | def parse(self, 327 | tokens: list[BaseToken], 328 | parse_node_clb: callable, 329 | supposed_node_type: type[BaseNode], 330 | **extra_data) -> BaseNode | list[BaseNode]: 331 | 332 | if tokens[0].type == TokenTypes.end_parenthesis or tokens[0].type == TokenTypes.end_chevrons: 333 | return [] 334 | 335 | arguments = [] 336 | expr_tokens = [] 337 | opened_parenthesis_counter = 1 338 | for token in tokens: 339 | match token.type: 340 | case TokenTypes.start_parenthesis: 341 | opened_parenthesis_counter += 1 342 | case TokenTypes.end_parenthesis | TokenTypes.end_chevrons: 343 | opened_parenthesis_counter -= 1 344 | 345 | if opened_parenthesis_counter == 0: 346 | node = parse_node_clb(expr_tokens) 347 | arguments.append(node) 348 | return arguments 349 | case TokenTypes.comma: 350 | if opened_parenthesis_counter == 1: 351 | node = parse_node_clb(expr_tokens) 352 | arguments.append(node) 353 | expr_tokens.clear() 354 | continue 355 | 356 | expr_tokens.append(token) 357 | 358 | 359 | @singleton 360 | class ConditionParser(BaseNodeParser): 361 | def validate(self, 362 | tokens: list[BaseToken], 363 | supposed_node_type: type[BaseNode], 364 | **extra_data) -> bool: 365 | return True 366 | 367 | def parse(self, 368 | tokens: list[BaseToken], 369 | parse_node_clb: callable, 370 | supposed_node_type: type[BaseNode], 371 | **extra_data) -> BaseNode | list[BaseNode]: 372 | return parse_node_clb( 373 | tokens[0:-1], 374 | (LogicOpNode, BinOpNode), 375 | OperationNodeParser() 376 | ) 377 | 378 | 379 | @singleton 380 | class ComplexTypeNodeParser(BaseNodeParser): 381 | def validate(self, 382 | tokens: list[BaseToken], 383 | supposed_node_type: type[BaseNode], 384 | **extra_data) -> bool: 385 | return \ 386 | Validators.check_token_type_presence( 387 | tokens, 388 | (TokenTypes.start_chevrons, TokenTypes.end_chevrons) 389 | ) and Validators.check_token_types(tokens, (TokenTypes.identifier, TokenTypes.start_chevrons)) 390 | 391 | def parse(self, 392 | tokens: list[BaseToken], 393 | parse_node_clb: callable, 394 | supposed_node_type: type[BaseNode], 395 | **extra_data) -> BaseNode | list[BaseNode]: 396 | type_token = tokens[0] 397 | 398 | arguments = parse_node_clb( 399 | tokens[2::], 400 | (VariableNode,), 401 | ArgumentsParser() 402 | ) 403 | print(type_token, arguments) 404 | -------------------------------------------------------------------------------- /config/parser.json: -------------------------------------------------------------------------------- 1 | { 2 | "parser_class": "fastpy.parser.parsers.Parser", 3 | "ast_class": "fastpy.parser.ast.AST", 4 | "node_parsing": { 5 | "AssignNode": { 6 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 7 | "node_class": "fastpy.parser.nodes.AssignNode", 8 | "cases": [ 9 | { 10 | "validate_data": { 11 | "methods": { 12 | "check_min_tokens_length": { 13 | "min_length": 5 14 | }, 15 | "check_token_types": { 16 | "types": [ 17 | "identifier", 18 | "operator", 19 | "identifier", 20 | "operator" 21 | ] 22 | }, 23 | "check_token_names": { 24 | "names": [ 25 | null, 26 | "body_start", 27 | null, 28 | "assign" 29 | ] 30 | } 31 | } 32 | }, 33 | "parse_data": { 34 | "identifier": { 35 | "index": 0 36 | }, 37 | "value_type": { 38 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 39 | "possible_node_classes": [ 40 | "fastpy.parser.nodes.VariableNode" 41 | ], 42 | "tokens_slice": { 43 | "start_index": 2, 44 | "end_index": 3 45 | } 46 | }, 47 | "value": { 48 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 49 | "possible_node_classes": [ 50 | "fastpy.parser.nodes.ValueNode", 51 | "fastpy.parser.nodes.VariableNode", 52 | "fastpy.parser.nodes.BinOpNode", 53 | "fastpy.parser.nodes.CallNode" 54 | ], 55 | "tokens_slice": { 56 | "start_index": 4 57 | } 58 | } 59 | } 60 | }, 61 | { 62 | "validate_data": { 63 | "methods": { 64 | "check_min_tokens_length": { 65 | "min_length": 3 66 | }, 67 | "check_token_types": { 68 | "types": [ 69 | "identifier", 70 | "operator" 71 | ] 72 | }, 73 | "check_token_names": { 74 | "names": [ 75 | null, 76 | "assign" 77 | ] 78 | } 79 | } 80 | }, 81 | "parse_data": { 82 | "identifier": { 83 | "index": 0 84 | }, 85 | "value": { 86 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 87 | "possible_node_classes": [ 88 | "fastpy.parser.nodes.ValueNode", 89 | "fastpy.parser.nodes.VariableNode", 90 | "fastpy.parser.nodes.BinOpNode", 91 | "fastpy.parser.nodes.CallNode" 92 | ], 93 | "tokens_slice": { 94 | "start_index": 2 95 | } 96 | } 97 | } 98 | }, 99 | { 100 | "validate_data": { 101 | "methods": { 102 | "check_fixed_tokens_length": { 103 | "length": 3 104 | }, 105 | "check_token_types": { 106 | "types": [ 107 | "identifier", 108 | "operator", 109 | "identifier" 110 | ] 111 | }, 112 | "check_token_names": { 113 | "names": [ 114 | null, 115 | "body_start", 116 | null 117 | ] 118 | } 119 | } 120 | }, 121 | "parse_data": { 122 | "identifier": { 123 | "index": 0 124 | }, 125 | "value_type": { 126 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 127 | "possible_node_classes": [ 128 | "fastpy.parser.nodes.VariableNode" 129 | ], 130 | "tokens_slice": { 131 | "start_index": 2, 132 | "end_index": 3 133 | } 134 | } 135 | } 136 | }, 137 | { 138 | "validate_data": { 139 | "methods": { 140 | "check_min_tokens_length": { 141 | "min_length": 5 142 | }, 143 | "check_token_name_presence": { 144 | "required_names": [ 145 | "assign" 146 | ] 147 | }, 148 | "check_token_types": { 149 | "types": [ 150 | "identifier", 151 | "operator", 152 | "identifier" 153 | ] 154 | }, 155 | "check_token_names": { 156 | "names": [ 157 | null, 158 | "body_start" 159 | ] 160 | } 161 | } 162 | }, 163 | "parse_data": { 164 | "identifier": { 165 | "index": 0 166 | }, 167 | "value_type": { 168 | "parser_class": "fastpy.parser.node_parsers.ComplexTypeNodeParser", 169 | "possible_node_classes": [ 170 | "fastpy.parser.nodes.VariableNode" 171 | ], 172 | "tokens_slice": { 173 | "start_index": 2 174 | } 175 | } 176 | } 177 | } 178 | ] 179 | }, 180 | "VariableNode": { 181 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 182 | "node_class": "fastpy.parser.nodes.VariableNode", 183 | "cases": [ 184 | { 185 | "validate_data": { 186 | "methods": { 187 | "check_fixed_tokens_length": { 188 | "length": 1 189 | }, 190 | "check_token_types": { 191 | "types": [ 192 | "identifier" 193 | ] 194 | } 195 | } 196 | }, 197 | "parse_data": { 198 | "identifier": { 199 | "index": 0 200 | } 201 | } 202 | } 203 | ] 204 | }, 205 | "ValueNode": { 206 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 207 | "node_class": "fastpy.parser.nodes.ValueNode", 208 | "cases": [ 209 | { 210 | "validate_data": { 211 | "methods": { 212 | "check_fixed_tokens_length": { 213 | "length": 1 214 | }, 215 | "check_token_type": { 216 | "token_index": 0, 217 | "possible_types": [ 218 | "literal", 219 | "number" 220 | ] 221 | } 222 | } 223 | }, 224 | "parse_data": { 225 | "value": { 226 | "index": 0 227 | } 228 | } 229 | } 230 | ] 231 | }, 232 | "FuncNode": { 233 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 234 | "node_class": "fastpy.parser.nodes.FuncNode", 235 | "cases": [ 236 | { 237 | "validate_data": { 238 | "methods": { 239 | "check_min_tokens_length": { 240 | "min_length": 7 241 | }, 242 | "check_token_types": { 243 | "types": [ 244 | "operator", 245 | "identifier", 246 | "start_parenthesis" 247 | ] 248 | }, 249 | "check_token_names": { 250 | "names": [ 251 | "function" 252 | ] 253 | }, 254 | "check_token_name_presence": { 255 | "required_names": [ 256 | "return_type" 257 | ] 258 | }, 259 | "check_token_name": { 260 | "exception": { 261 | "message": "SyntaxError: body start operator expected" 262 | }, 263 | "token_index": -1, 264 | "possible_names": [ 265 | "body_start" 266 | ] 267 | }, 268 | "check_token_type_presence": { 269 | "exception": { 270 | "message": "SyntaxError: close bracket expected" 271 | }, 272 | "required_types": [ 273 | "end_parenthesis" 274 | ] 275 | } 276 | } 277 | }, 278 | "parse_data": { 279 | "identifier": { 280 | "index": 1 281 | }, 282 | "arguments": { 283 | "parser_class": "fastpy.parser.node_parsers.ArgumentsParser", 284 | "possible_node_classes": [ 285 | "fastpy.parser.nodes.AssignNode" 286 | ], 287 | "tokens_slice": { 288 | "start_index": 3 289 | } 290 | }, 291 | "return_type": { 292 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 293 | "possible_node_classes": [ 294 | "fastpy.parser.nodes.VariableNode" 295 | ], 296 | "tokens_slice": { 297 | "start_index": -2, 298 | "end_index": -1 299 | } 300 | } 301 | } 302 | }, 303 | { 304 | "validate_data": { 305 | "methods": { 306 | "check_min_tokens_length": { 307 | "min_length": 5 308 | }, 309 | "check_token_types": { 310 | "types": [ 311 | "operator", 312 | "identifier", 313 | "start_parenthesis" 314 | ] 315 | }, 316 | "check_token_names": { 317 | "names": [ 318 | "function" 319 | ] 320 | }, 321 | "check_token_name": { 322 | "exception": { 323 | "message": "SyntaxError: body start operator expected" 324 | }, 325 | "token_index": -1, 326 | "possible_names": [ 327 | "body_start" 328 | ] 329 | }, 330 | "check_token_type_presence": { 331 | "exception": { 332 | "message": "SyntaxError: close bracket expected" 333 | }, 334 | "required_types": [ 335 | "end_parenthesis" 336 | ] 337 | } 338 | } 339 | }, 340 | "parse_data": { 341 | "identifier": { 342 | "index": 1 343 | }, 344 | "arguments": { 345 | "parser_class": "fastpy.parser.node_parsers.ArgumentsParser", 346 | "possible_node_classes": [ 347 | "fastpy.parser.nodes.AssignNode" 348 | ], 349 | "tokens_slice": { 350 | "start_index": 3 351 | } 352 | } 353 | } 354 | } 355 | ] 356 | }, 357 | "CallNode": { 358 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 359 | "node_class": "fastpy.parser.nodes.CallNode", 360 | "cases": [ 361 | { 362 | "validate_data": { 363 | "methods": { 364 | "check_min_tokens_length": { 365 | "min_length": 3 366 | }, 367 | "check_token_types": { 368 | "types": [ 369 | "identifier", 370 | "start_parenthesis" 371 | ] 372 | }, 373 | "check_token_type": { 374 | "token_index": -1, 375 | "possible_types": [ 376 | "end_parenthesis" 377 | ], 378 | "exception": { 379 | "message": "SyntaxError: close bracket expected" 380 | } 381 | } 382 | } 383 | }, 384 | "parse_data": { 385 | "identifier": { 386 | "index": 0 387 | }, 388 | "arguments": { 389 | "parser_class": "fastpy.parser.node_parsers.ArgumentsParser", 390 | "possible_node_classes": [ 391 | "fastpy.parser.nodes.ValueNode", 392 | "fastpy.parser.nodes.VariableNode", 393 | "fastpy.parser.nodes.CallNode", 394 | "fastpy.parser.nodes.BinOpNode" 395 | ], 396 | "tokens_slice": { 397 | "start_index": 2 398 | } 399 | } 400 | } 401 | } 402 | ] 403 | }, 404 | "ElseNode": { 405 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 406 | "node_class": "fastpy.parser.nodes.ElseNode", 407 | "cases": [ 408 | { 409 | "validate_data": { 410 | "methods": { 411 | "check_token_names": { 412 | "names": [ 413 | "else" 414 | ] 415 | }, 416 | "check_token_name": { 417 | "exception": { 418 | "message": "SyntaxError: body start operator expected" 419 | }, 420 | "token_index": -1, 421 | "possible_names": [ 422 | "body_start" 423 | ] 424 | }, 425 | "check_fixed_tokens_length": { 426 | "length": 2 427 | } 428 | } 429 | }, 430 | "parse_data": { 431 | } 432 | } 433 | ] 434 | }, 435 | "IfNode": { 436 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 437 | "node_class": "fastpy.parser.nodes.IfNode", 438 | "cases": [ 439 | { 440 | "validate_data": { 441 | "methods": { 442 | "check_token_names": { 443 | "names": [ 444 | "if" 445 | ] 446 | }, 447 | "check_token_name": { 448 | "exception": { 449 | "message": "SyntaxError: body start operator expected" 450 | }, 451 | "token_index": -1, 452 | "possible_names": [ 453 | "body_start" 454 | ] 455 | }, 456 | "check_min_tokens_length": { 457 | "min_length": 3 458 | } 459 | } 460 | }, 461 | "parse_data": { 462 | "condition": { 463 | "parser_class": "fastpy.parser.node_parsers.ConditionParser", 464 | "possible_node_classes": [ 465 | "fastpy.parser.nodes.LogicOpNode", 466 | "fastpy.parser.nodes.BinOpNode" 467 | ], 468 | "tokens_slice": { 469 | "start_index": 1 470 | } 471 | } 472 | } 473 | }, 474 | { 475 | "validate_data": { 476 | "methods": { 477 | "check_token_names": { 478 | "names": [ 479 | "elif" 480 | ] 481 | }, 482 | "check_token_name": { 483 | "exception": { 484 | "message": "SyntaxError: body start operator expected" 485 | }, 486 | "token_index": -1, 487 | "possible_names": [ 488 | "body_start" 489 | ] 490 | }, 491 | "check_min_tokens_length": { 492 | "min_length": 3 493 | } 494 | } 495 | }, 496 | "parse_data": { 497 | "condition": { 498 | "parser_class": "fastpy.parser.node_parsers.ConditionParser", 499 | "possible_node_classes": [ 500 | "fastpy.parser.nodes.LogicOpNode", 501 | "fastpy.parser.nodes.BinOpNode" 502 | ], 503 | "tokens_slice": { 504 | "start_index": 1 505 | } 506 | }, 507 | "is_elif": { 508 | "value": true 509 | } 510 | } 511 | } 512 | ] 513 | }, 514 | "WhileNode": { 515 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 516 | "node_class": "fastpy.parser.nodes.WhileNode", 517 | "cases": [ 518 | { 519 | "validate_data": { 520 | "methods": { 521 | "check_min_tokens_length": { 522 | "min_length": 3 523 | }, 524 | "check_token_names": { 525 | "names": [ 526 | "while" 527 | ] 528 | }, 529 | "check_token_name": { 530 | "exception": { 531 | "message": "SyntaxError: body start operator expected" 532 | }, 533 | "token_index": -1, 534 | "possible_names": [ 535 | "body_start" 536 | ] 537 | } 538 | } 539 | }, 540 | "parse_data": { 541 | "condition": { 542 | "parser_class": "fastpy.parser.node_parsers.ConditionParser", 543 | "possible_node_classes": [ 544 | "fastpy.parser.nodes.LogicOpNode", 545 | "fastpy.parser.nodes.BinOpNode" 546 | ], 547 | "tokens_slice": { 548 | "start_index": 1 549 | } 550 | } 551 | } 552 | } 553 | ] 554 | }, 555 | "ReturnNode": { 556 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 557 | "node_class": "fastpy.parser.nodes.ReturnNode", 558 | "cases": [ 559 | { 560 | "validate_data": { 561 | "methods": { 562 | "check_token_names": { 563 | "names": [ 564 | "return" 565 | ] 566 | } 567 | } 568 | }, 569 | "parse_data": { 570 | "node": { 571 | "parser_class": "fastpy.parser.node_parsers.UniversalNodeParser", 572 | "possible_node_classes": [ 573 | "fastpy.parser.nodes.LogicOpNode", 574 | "fastpy.parser.nodes.BinOpNode", 575 | "fastpy.parser.nodes.VariableNode", 576 | "fastpy.parser.nodes.ValueNode", 577 | "fastpy.parser.nodes.CallNode" 578 | ], 579 | "tokens_slice": { 580 | "start_index": 1 581 | } 582 | } 583 | } 584 | } 585 | ] 586 | }, 587 | "LogicOpNode": { 588 | "parser_class": "fastpy.parser.node_parsers.OperationNodeParser", 589 | "node_class": "fastpy.parser.nodes.LogicOpNode", 590 | "cases": [ 591 | { 592 | "validate_data": { 593 | }, 594 | "parse_data": { 595 | } 596 | } 597 | ] 598 | }, 599 | "BinOpNode": { 600 | "parser_class": "fastpy.parser.node_parsers.OperationNodeParser", 601 | "node_class": "fastpy.parser.nodes.BinOpNode", 602 | "cases": [ 603 | { 604 | "validate_data": { 605 | }, 606 | "parse_data": { 607 | } 608 | } 609 | ] 610 | } 611 | } 612 | } --------------------------------------------------------------------------------