diff --git a/.gitattributes b/.gitattributes index cf6aa2b..eba1110 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,7 +1,2 @@ # Auto detect text files and perform LF normalization -* text=auto - -docs/** -linguist-documentation - -*.rst linguist-detectable -*.ps1 linguist-detectable +* text=auto \ No newline at end of file diff --git a/.github/workflows/pr-bot.yml b/.github/workflows/pr-bot.yml new file mode 100644 index 0000000..8166a24 --- /dev/null +++ b/.github/workflows/pr-bot.yml @@ -0,0 +1,57 @@ +name: reStructuredPython automated PR Labeling +on: + issue_comment: + types: [created] + +jobs: + update-labels: + runs-on: ubuntu-latest + steps: + - name: Parse comment and update PR labels + uses: actions/github-script@v6 + with: + script: | + const comment = context.payload.comment.body; + const prNumber = context.payload.issue.number; + const labelsToAdd = []; + const labelsToRemove = []; + + const status = { + windows: false, + mac: false, + ubuntu: false + }; + + if (comment.includes("!windows-passing")) status.windows = true; + if (comment.includes("!windows-failing")) status.windows = false; + if (comment.includes("!mac-passing")) status.mac = true; + if (comment.includes("!mac-failing")) status.mac = false; + if (comment.includes("!ubuntu-passing")) status.ubuntu = true; + if (comment.includes("!ubuntu-failing")) status.ubuntu = false; + + const allPassing = status.windows && status.mac && status.ubuntu; + const anyFailing = !status.windows || !status.mac || !status.ubuntu; + + if (allPassing) { + labelsToAdd.push("awaiting merge"); + labelsToRemove.push("DO-NOT-MERGE"); + } else if (anyFailing) { + labelsToAdd.push("DO-NOT-MERGE"); + labelsToRemove.push("awaiting merge"); + } + + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + labels: labelsToAdd + }); + + for (const label of labelsToRemove) { + await github.rest.issues.removeLabel({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + name: label + }).catch(() => {}); + } diff --git a/compile.bat b/compile.bat index ae9725e..ff9c181 100644 --- a/compile.bat +++ b/compile.bat @@ -15,11 +15,16 @@ xcopy "%SRC_DIR%\*" "%DEST%" /E /H /C /I /exclude:exclude.txt pushd "C:\TempBuild" cl /LD restructuredpython/include/io.c -for %%F in (*.dll) do xcopy "%%F" "%SRC_DIR%/restructuredpython/lib" /Y +set /p arch="Was this run from an x64 tools prompt (y/n) >" +if "%arch%"=="y" ( +for %%F in (io.dll) do xcopy "%%F" "%SRC_DIR%/restructuredpython/lib/windows-libs/io64.dll" /Y +) else ( +for %%F in (io.dll) do xcopy "%%F" "%SRC_DIR%/restructuredpython/lib/windows-libs/io32.dll" /Y +) echo Deleting copied files -del /Q "%DEST%\*.*" +del /Q C:\TempBuild popd diff --git a/docs/source/conf.py b/docs/source/conf.py index f1cd847..0c74aba 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -10,7 +10,7 @@ copyright = '2025, Rihaan Meher' author = 'Rihaan Meher' -release = '2.0.0' +release = '2.1.0' html_favicon = "_static/icon.png" # -- General configuration --------------------------------------------------- @@ -26,3 +26,84 @@ html_theme = 'furo' html_static_path = ['_static'] +html_theme_options = { + "light_css_variables": { + "color-brand-primary": "red", + "color-brand-secondary": "blue", + "color-brand-content": "#CC3333", + "color-admonition-background": "orange", + "color-background-primary": "#e3f2fd", + }, + "dark_css_variables": { + "color-foreground-primary": "black", + "color-brand-secondary": "blue", + "color-foreground-secondary": "#5a5c63", + "color-foreground-muted": "#6b6f76", + "color-foreground-border": "#878787", + "color-brand-primary": "red", + "color-brand-content": "#CC3333", + "color-admonition-background": "orange", + "color-background-primary": "#e3f2fd", + "color-background-secondary": "#f8f9fb", + "color-background-hover": "#efeff4ff", + "color-background-border": "#eeebee" + }, + "announcement": ''' + + + + ''', +} \ No newline at end of file diff --git a/docs/source/index.rst b/docs/source/index.rst index 5a0c034..3992d18 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -1,7 +1,9 @@ reStructuredPython Documentation ================================ -Welcome to the reStructuredPython documentation! reStructuredPython, also known as 'rePython', is a Python variant that introduces JavaScript-like syntax for a cleaner and more readable coding experience. It compiles seamlessly into standard Python code. +Welcome to the reStructuredPython documentation! reStructuredPython, also known as 'rePython', is a superset of python with full compatibility with existing python libaries that compiles back into standard python! + +https://github.com/sharktide/restructuredpython Getting Started --------------- @@ -31,6 +33,7 @@ Features * **Seamless Compilation:** reStructuredPython code compiles directly into Python, ensuring compatibility with existing Python libraries and frameworks. * **Enhanced Readability:** The syntax enhancements which include multiline comments aim to improve code readability and reduce verbosity. * **repyconfig.toml:** Easily control the build of your projects with a repyconfig.toml +* **Strict Types** Enhance your python projects wil strict types in functions with the builtin strict_types decorator Documentation ------------- diff --git a/docs/source/reference/Builtin_Decorators.rst b/docs/source/reference/Builtin_Decorators.rst deleted file mode 100644 index c151b27..0000000 --- a/docs/source/reference/Builtin_Decorators.rst +++ /dev/null @@ -1,198 +0,0 @@ -Builtin Decorators -================== - -This list is a complete list of decorators builtin to the reStructuredPython compiler and how to use them. - -How to include a decorator/function ------------------------------------ - -To include a decorator via the ``include`` keyword, simply add it to the top of your file where you would include ``cdata`` header files. - -For example, to include the timer decorator, you can import it and use it like this: - -.. code-block:: repy - - include 'decorators.timer' - - @timer - def myfunction() { - time.sleep(1) - } - -Or you could do this: - -.. code-block:: repy - - include 'decorators' - - @decorators.timer - def myfunction() { - pass - } - -Decorators ----------- - -This is the list of built in decorators avalible in reStructuredPython. - -1. **decorators.timer** - -Times the execution of the decorated function and prints the output. - -Main use: debugging. - -Output format: - -.. code-block:: python - - print(f"{func.__name__} took {end_time - start_time:.2f} seconds.") - -How to include: - -.. code-block:: repy - - include 'decorators.timer' - - @timer - def myfunction() { - time.sleep(1) - } - -Or you could do this: - -.. code-block:: repy - - include 'decorators' - - @decorators.timer - def myfunction() { - pass - } - -2. **decorators.logging** - -Logs all arguments passed and returned by a function - -Main use: debugging - -Output format: - -.. code-block:: python - - print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") - result = func(*args, **kwargs) - print(f"{func.__name__} returned {result}") - -How to include: - -.. code-block:: repy - - include 'decorators.logging' - - @logging - def myfunction(*args, **kwargs) { - pass - } - -Or you could do this: - -.. code-block:: repy - - include 'decorators' - - @decorators.logging - def myfunction(*args, **kwargs) { - pass - } - -3. **decorators.memoization** - -If a function is called repeatedly with the same arguments, this decorator will not run the funciton and check in its cache if the result already exists and return that. - -Main use: Preformance - -How to include: - -.. code-block:: repy - - include 'decorators.memoization' - - @logging - def myfunction(*args, **kwargs) { - result = "something" - return result - } - -Or you could do this: - -.. code-block:: repy - - include 'decorators' - - @decorators.logging - def myfunction(*args, **kwargs) { - result = "something" - return result - } - -4. **decorators.retry** - -Retry a function a set amount of times if a error occurs with a set delay. - -Arguments: -.. code-block:: python - retry(retries=3, delay=1) - -*Defaults to 3 retries and 1 second delay. - -.. code-block:: repy - - include 'decorators.memoization' - - @retry(3, 1) - def myfunction(*args, **kwargs) { - result = "something" - return result - } - -Or you could do this: - -.. code-block:: repy - - include 'decorators' - - @decorators.retry(3, 1) - def myfunction(*args, **kwargs) { - result = "something" - return result - } - -5. **decorators.access_control - -Allows only certain user roles to use a function - -Example: - -.. code-block:: repy - - include 'decorators.access_control' - - @access_control(allowed_roles=['admin', 'moderator']) - def delete_user_account(user_role, username) { - print(f"User '{username}' has been deleted by '{user_role}'.") - - # Simulated role-based access - delete_user_account('admin', 'john_doe') # This works. - delete_user_account('guest', 'john_doe') # This raises an exception. - -.. code-block:: repy - - include 'decorators' - - @decorators.access_control(allowed_roles=['admin', 'moderator']) - def delete_user_account(user_role, username) { - print(f"User '{username}' has been deleted by '{user_role}'.") - - # Simulated role-based access - delete_user_account('admin', 'john_doe') # This works. - delete_user_account('guest', 'john_doe') # This raises an exception. diff --git a/docs/source/reference/Builtin_Decorators/access_control.rst b/docs/source/reference/Builtin_Decorators/access_control.rst new file mode 100644 index 0000000..148edaa --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/access_control.rst @@ -0,0 +1,30 @@ +decorators.access_control +========================= + +Allows only certain user roles to use a function + +Example: + +.. code-block:: repy + + include 'decorators.access_control' + + @access_control(allowed_roles=['admin', 'moderator']) + def delete_user_account(user_role, username) { + print(f"User '{username}' has been deleted by '{user_role}'.") + + # Simulated role-based access + delete_user_account('admin', 'john_doe') # This works. + delete_user_account('guest', 'john_doe') # This raises an exception. + +.. code-block:: repy + + include 'decorators' + + @decorators.access_control(allowed_roles=['admin', 'moderator']) + def delete_user_account(user_role, username) { + print(f"User '{username}' has been deleted by '{user_role}'.") + + # Simulated role-based access + delete_user_account('admin', 'john_doe') # This works. + delete_user_account('guest', 'john_doe') # This raises an exception. \ No newline at end of file diff --git a/docs/source/reference/Builtin_Decorators/index.rst b/docs/source/reference/Builtin_Decorators/index.rst new file mode 100644 index 0000000..ae5da47 --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/index.rst @@ -0,0 +1,54 @@ +Builtin Decorators +================== + +This list is a complete list of decorators builtin to the reStructuredPython compiler and how to use them. + +How to include a decorator/function +----------------------------------- + +To include a decorator via the ``include`` keyword, simply add it to the top of your file where you would include ``cdata`` header files. + +For example, to include the timer decorator, you can import it and use it like this: + +.. code-block:: repy + + include 'decorators.timer' + + @timer + def myfunction() { + time.sleep(1) + } + +Or you could do this: + +.. code-block:: repy + + include 'decorators' + + @decorators.timer + def myfunction() { + pass + } + +Decorators +---------- + +This is the list of built in decorators avalible in reStructuredPython. + +5. **decorators.access_control + +Allows only certain user roles to use a function + +Example: + + + +.. toctree:: + :maxdepth: 2 + + access_control + logging + memoization + retry + strict_types + timer \ No newline at end of file diff --git a/docs/source/reference/Builtin_Decorators/logging.rst b/docs/source/reference/Builtin_Decorators/logging.rst new file mode 100644 index 0000000..975bfc9 --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/logging.rst @@ -0,0 +1,36 @@ +decorators.logging +================== + +Logs all arguments passed and returned by a function + +Main use: debugging + +Output format: + +.. code-block:: python + + print(f"Calling {func.__name__} with args: {args}, kwargs: {kwargs}") + result = func(*args, **kwargs) + print(f"{func.__name__} returned {result}") + +How to include: + +.. code-block:: repy + + include 'decorators.logging' + + @logging + def myfunction(*args, **kwargs) { + pass + } + +Or you could do this: + +.. code-block:: repy + + include 'decorators' + + @decorators.logging + def myfunction(*args, **kwargs) { + pass + } \ No newline at end of file diff --git a/docs/source/reference/Builtin_Decorators/memoization.rst b/docs/source/reference/Builtin_Decorators/memoization.rst new file mode 100644 index 0000000..858c999 --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/memoization.rst @@ -0,0 +1,30 @@ +decorators.memoization +====================== + +If a function is called repeatedly with the same arguments, this decorator will not run the funciton and check in its cache if the result already exists and return that. + +Main use: Preformance + +How to include: + +.. code-block:: repy + + include 'decorators.memoization' + + @logging + def myfunction(*args, **kwargs) { + result = "something" + return result + } + +Or you could do this: + +.. code-block:: repy + + include 'decorators' + + @decorators.logging + def myfunction(*args, **kwargs) { + result = "something" + return result + } \ No newline at end of file diff --git a/docs/source/reference/Builtin_Decorators/retry.rst b/docs/source/reference/Builtin_Decorators/retry.rst new file mode 100644 index 0000000..21ec888 --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/retry.rst @@ -0,0 +1,37 @@ +decorators.retry +================ + +Retry a function a set amount of times if a error occurs with a set delay. + +Arguments: +.. code-block:: python + + retry(retries=3, delay=1) + +*Defaults to 3 retries and 1 second delay. + +.. code-block:: repy + + include 'decorators.retry' + + @retry(3, 1) + def myfunction(*args, **kwargs) { + result = "something" + if mycondition() { + raise SyntaxError('Retry this!') + return result + } + +Or you could do this: + +.. code-block:: repy + + include 'decorators' + + @decorators.retry(3, 1) + def myfunction(*args, **kwargs) { + result = "something" + if mycondition() { + raise SyntaxError('Retry this!') + return result + } \ No newline at end of file diff --git a/docs/source/reference/Builtin_Decorators/strict_types.rst b/docs/source/reference/Builtin_Decorators/strict_types.rst new file mode 100644 index 0000000..3845304 --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/strict_types.rst @@ -0,0 +1,30 @@ +decorators.strict_types +======================= + +Allows strict type checking for functions + +Example: + +.. code-block:: repy + + include 'decorators.strict_types' + @strict_types + def add(a: int, b: int) -> int: + return a + b + + print(add(2, 3)) + print(add(2, 'hi')) # raises an error + +Or + +.. code-block:: repy + + include 'decorators' + @decorators.strict_types + def add(a: int, b: int) -> int: + return a + b + + print(add(2, 3)) + print(add(2, 'hi')) # raises an error + +Notes: This works with the return value, and with different types of input, even though not displayed in example. \ No newline at end of file diff --git a/docs/source/reference/Builtin_Decorators/timer.rst b/docs/source/reference/Builtin_Decorators/timer.rst new file mode 100644 index 0000000..285b16a --- /dev/null +++ b/docs/source/reference/Builtin_Decorators/timer.rst @@ -0,0 +1,34 @@ +decorators.timer +================ + +Times the execution of the decorated function and prints the output. + +Main use: debugging. + +Output format: + +.. code-block:: python + + print(f"{func.__name__} took {end_time - start_time:.2f} seconds.") + +How to include: + +.. code-block:: repy + + include 'decorators.timer' + + @timer + def myfunction() { + time.sleep(1) + } + +Or you could do this: + +.. code-block:: repy + + include 'decorators' + + @decorators.timer + def myfunction() { + pass + } \ No newline at end of file diff --git a/docs/source/reference/index.rst b/docs/source/reference/index.rst index f17384e..6ea0e65 100644 --- a/docs/source/reference/index.rst +++ b/docs/source/reference/index.rst @@ -4,8 +4,8 @@ Reference The reference section provides detailed information about the built-in functions and syntax of reStructuredPython. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 Syntax_Guide - Builtin_Decorators + Builtin_Decorators/index repyconfig.toml diff --git a/docs/source/roadmap.rst b/docs/source/roadmap.rst index ed94126..ffc2617 100644 --- a/docs/source/roadmap.rst +++ b/docs/source/roadmap.rst @@ -1,11 +1,6 @@ Roadmap ======= -1.2.0 ------ +Nothing to see here! -Possibly add built-in functions, not only decorators. This will definetly happen, we just aren't sure if it will happen in 1.1.1 - -Expected release dates: - -TBD +The content of releases 2.2.0 2.3.0 and 2.4.0 has not been decided yet diff --git a/pyproject.toml b/pyproject.toml index c46b496..a070f18 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta" [project] name = "restructuredpython" -version = "2.0.0" -description = "A superset of Python with many new features, including multiline comments, header files, and optional curly brackets around control statements" +version = "2.1.0" +description = "A superset of Python with many new features, including full JS integration, multiline comments, header files, and optional curly brackets around control statements" authors = [{name = "Rihaan Meher", email = "meherrihaan@gmail.com"}] license = {text = "Apache-2.0"} classifiers = [ diff --git a/restructuredpython.egg-info/PKG-INFO b/restructuredpython.egg-info/PKG-INFO index 3497d27..b056c6e 100644 --- a/restructuredpython.egg-info/PKG-INFO +++ b/restructuredpython.egg-info/PKG-INFO @@ -1,7 +1,7 @@ Metadata-Version: 2.4 Name: restructuredpython -Version: 2.0.0 -Summary: A superset of Python with many new features, including multiline comments, header files, and optional curly brackets around control statements +Version: 2.1.0 +Summary: A superset of Python with many new features, including full JS integration, multiline comments, header files, and optional curly brackets around control statements Author-email: Rihaan Meher License: Apache-2.0 Classifier: Programming Language :: Python :: 3 diff --git a/restructuredpython.egg-info/SOURCES.txt b/restructuredpython.egg-info/SOURCES.txt index cde8563..e381623 100644 --- a/restructuredpython.egg-info/SOURCES.txt +++ b/restructuredpython.egg-info/SOURCES.txt @@ -10,11 +10,13 @@ restructuredpython.egg-info/dependency_links.txt restructuredpython.egg-info/entry_points.txt restructuredpython.egg-info/top_level.txt restructuredpython/include/io.c -restructuredpython/lib/io.dll +restructuredpython/lib/DO_NOT_MODIFY_THIS_DIR.txt +restructuredpython/lib/windows-libs/io64.dll restructuredpython/predefined/__init__.py restructuredpython/predefined/decorators.py restructuredpython/predefined/decorators/access_control.py restructuredpython/predefined/decorators/logging.py restructuredpython/predefined/decorators/memoization.py restructuredpython/predefined/decorators/retry.py +restructuredpython/predefined/decorators/strict_types.py restructuredpython/predefined/decorators/timer.py \ No newline at end of file diff --git a/restructuredpython/predefined/decorators/strict_types.py b/restructuredpython/predefined/decorators/strict_types.py new file mode 100644 index 0000000..7dddc46 --- /dev/null +++ b/restructuredpython/predefined/decorators/strict_types.py @@ -0,0 +1,34 @@ +import inspect +from functools import wraps +from typing import get_type_hints + +def strict_types(func): + sig = inspect.signature(func) + type_hints = get_type_hints(func) + + @wraps(func) + def wrapper(*args, **kwargs): + bound_args = sig.bind(*args, **kwargs) + bound_args.apply_defaults() + + # Check argument types + for name, value in bound_args.arguments.items(): + expected_type = type_hints.get(name) + if expected_type and not isinstance(value, expected_type): + raise TypeError( + f"Argument '{name}' expected {expected_type.__name__}, got {type(value).__name__}" + ) + + # Call the function + result = func(*args, **kwargs) + + # Check return type + expected_return = type_hints.get('return') + if expected_return and not isinstance(result, expected_return): + raise TypeError( + f"Return value expected {expected_return.__name__}, got {type(result).__name__}" + ) + + return result + + return wrapper diff --git a/restructuredpython/restructuredpython.py b/restructuredpython/restructuredpython.py index c923034..d80f72a 100644 --- a/restructuredpython/restructuredpython.py +++ b/restructuredpython/restructuredpython.py @@ -17,11 +17,9 @@ if spec and spec.origin: package_dir = os.path.dirname(spec.origin) io_dll = os.path.join(package_dir, "lib", "windows-libs", "io64.dll") - io32_dll = os.path.join( - package_dir, - "lib", - "windows-lib", - "io32.dll") # Adjust based on platform + + io32_dll = os.path.join(package_dir, "lib", "windows-lib", "io32.dll") + io_so = os.path.join(package_dir, "lib", "linux-libs", "io.so") io_dylib = os.path.join(package_dir, "lib", "macos-libs", "io.dylib") @@ -37,7 +35,6 @@ lib = ctypes.CDLL(io_so) -# Define argument & return types lib.check_file_exists.argtypes = [ctypes.c_char_p] lib.check_file_exists.restype = ctypes.c_int @@ -63,10 +60,26 @@ def load_toml_binary(filename): raw_data = ctypes.string_at(raw_data_ptr, size.value) return toml.loads(raw_data.decode()) +def read_file_utf8(filename: str) -> str: + size = ctypes.c_size_t() + filename_bytes = filename.encode('utf-8') + + ptr = lib.read_binary_file(filename_bytes, ctypes.byref(size)) + if not ptr: + raise FileNotFoundError(f"File not found: {filename}") + + raw_bytes = ctypes.string_at(ptr, size.value) + + try: + text = raw_bytes.decode('utf-8') + except UnicodeDecodeError as e: + raise ValueError(f"File is not valid UTF-8: {e}") + + return text token_specification = [ - ('COMMENT', r'/\*.*?\*/'), # Multiline comment pattern + ('COMMENT', r'/\*.*?\*/'), ('IF', r'if'), ('FOR', r'for'), ('WHILE', r'while'), @@ -79,8 +92,8 @@ def load_toml_binary(filename): ('WITH', r'with'), ('MATCH', r'match'), ('CASE', r'case'), - ('PIPE', r'\|>'), # pipeline operator - ('IDENT', r'[A-Za-z_][A-Za-z0-9_]*'), # variable or function name + ('PIPE', r'\|>'), + ('IDENT', r'[A-Za-z_][A-Za-z0-9_]*'), ('NUMBER', r'\d+'), ('LBRACE', r'\{'), ('RBRACE', r'\}'), @@ -226,7 +239,7 @@ def compile_header_file(header_filename): if lib.check_file_exists(header_filename.encode()) == 0: raise FileNotFoundError(f"Header file {header_filename} not found.") try: - header_code = lib.read_file(header_filename.encode()).decode() + header_code = read_file_utf8(header_filename) if not header_code.strip(): raise ValueError(f"Header file {header_filename} is empty.") except Exception as e: @@ -247,15 +260,14 @@ def process_includes(code, input_file): header_code = "" for include in includes: - # Handle predefined headers + # predefined predefined_path = os.path.join( PREDEFINED_HEADERS_DIR, include.replace( '.', os.sep) + '.py') if lib.check_file_exists(predefined_path.encode()): header_code += compile_header_file(predefined_path) + "\n" continue - - # Handle user-defined headers + # userdefined if not os.path.isabs(include): include = os.path.join( os.path.dirname( @@ -374,7 +386,6 @@ def launch(): final_code = header_code + python_code - # Execute the compiled code directly try: execute_code_temporarily(final_code) except Exception as e: