diff --git a/docs/source/changelog.rst b/docs/source/changelog.rst index df804b1..04def0e 100644 --- a/docs/source/changelog.rst +++ b/docs/source/changelog.rst @@ -9,10 +9,18 @@ Major release 2 .. raw:: html
- 2.1.0 - + 2.1.0 - 2.1.1 +
+ 2.1.1 +
+
+ 2.1.0 + +
.. raw:: html diff --git a/pyproject.toml b/pyproject.toml index a070f18..5730919 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta" [project] name = "restructuredpython" -version = "2.1.0" +version = "2.1.1" 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"} diff --git a/restructuredpython.egg-info/PKG-INFO b/restructuredpython.egg-info/PKG-INFO index b056c6e..72bf5e7 100644 --- a/restructuredpython.egg-info/PKG-INFO +++ b/restructuredpython.egg-info/PKG-INFO @@ -1,6 +1,6 @@ Metadata-Version: 2.4 Name: restructuredpython -Version: 2.1.0 +Version: 2.1.1 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 diff --git a/restructuredpython.egg-info/SOURCES.txt b/restructuredpython.egg-info/SOURCES.txt index e381623..69bd662 100644 --- a/restructuredpython.egg-info/SOURCES.txt +++ b/restructuredpython.egg-info/SOURCES.txt @@ -2,8 +2,10 @@ MANIFEST.in README.md pyproject.toml restructuredpython/__init__.py +restructuredpython/check_syntax.py +restructuredpython/cload.py +restructuredpython/parser.py restructuredpython/restructuredpython.py -restructuredpython/test.py restructuredpython.egg-info/PKG-INFO restructuredpython.egg-info/SOURCES.txt restructuredpython.egg-info/dependency_links.txt diff --git a/restructuredpython/check_syntax.py b/restructuredpython/check_syntax.py new file mode 100644 index 0000000..ff85a1d --- /dev/null +++ b/restructuredpython/check_syntax.py @@ -0,0 +1,24 @@ +def check_syntax(input_lines): + for i in range(len(input_lines)): + line = input_lines[i].strip() + + if line.startswith(('} else', '} elif')): + raise SyntaxError( + f"Misplaced '{line}' statement at line { + i + 1}. (REPY-0001)") # fmt: skip + if line.startswith('} except'): + raise SyntaxError( + f"Misplaced 'except' statement at line { + i + 1}. (REPY-0002)") # fmt: skip + if line.startswith('} def'): + raise SyntaxError( + f"Misplaced 'def' statement at line { + i + 1}. (REPY-0003)") # fmt: skip + if line.startswith('} class'): + raise SyntaxError( + f"Misplaced 'class' statement at line { + i + 1}. (REPY-0004)") # fmt: skip + if line.startswith('} case'): + raise SyntaxError( + f"Misplaced 'case' statement at line { + i + 1}. (REPY-0005)") # fmt: skip \ No newline at end of file diff --git a/restructuredpython/cload.py b/restructuredpython/cload.py new file mode 100644 index 0000000..4b62651 --- /dev/null +++ b/restructuredpython/cload.py @@ -0,0 +1,72 @@ +import importlib +import struct +import sys +import os +import ctypes +import tomllib as toml + +spec = importlib.util.find_spec("restructuredpython") +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") + + io_so = os.path.join(package_dir, "lib", "linux-libs", "io.so") + + io_dylib = os.path.join(package_dir, "lib", "macos-libs", "io.dylib") + +if sys.platform == "win32": + if (struct.calcsize("P") * 8) == 32: + lib = ctypes.WinDLL(io32_dll) + else: + lib = ctypes.WinDLL(io_dll) +elif sys.platform == "darwin": + lib = ctypes.CDLL(io_dylib) +else: + lib = ctypes.CDLL(io_so) + + +lib.check_file_exists.argtypes = [ctypes.c_char_p] +lib.check_file_exists.restype = ctypes.c_int + +lib.read_file.argtypes = [ctypes.c_char_p] +lib.read_file.restype = ctypes.c_char_p + +lib.write_file.argtypes = [ctypes.c_char_p, ctypes.c_char_p] +lib.write_file.restype = ctypes.c_int + +lib.read_binary_file.argtypes = [ + ctypes.c_char_p, ctypes.POINTER( + ctypes.c_size_t)] +lib.read_binary_file.restype = ctypes.POINTER(ctypes.c_char) + + +def load_toml_binary(filename): + filename = str(filename) + size = ctypes.c_size_t() + raw_data_ptr = lib.read_binary_file(filename.encode(), ctypes.byref(size)) + + if not raw_data_ptr: + raise FileNotFoundError(f"Could not read {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 \ No newline at end of file diff --git a/restructuredpython/parser.py b/restructuredpython/parser.py new file mode 100644 index 0000000..1066bb7 --- /dev/null +++ b/restructuredpython/parser.py @@ -0,0 +1,73 @@ +from restructuredpython.check_syntax import check_syntax +import re + +def parse_repython(code): + """Parses the rePython code and converts it to valid Python code.""" + def chain_pipeline(code): + parts = [part.strip() for part in code.split('|>')] + if len(parts) > 1: + def nest(parts): + if len(parts) == 1: + return parts[0] + else: + return f'{parts[-1]}({nest(parts[:-1])})' + + variable, pipeline = parts[0].split( + '=') if '=' in parts[0] else ('', parts[0]) + variable = variable.strip() + pipeline = pipeline.strip() + + if variable: + nested_call = nest([pipeline] + parts[1:]) + return f'{variable} = {nested_call}' + else: + return nest(parts) + return code + + modified_code = [] + inside_block = False + brace_stack = [] + lines = code.splitlines() + + check_syntax(lines) + + inside_comment_block = False + + for line in lines: + processed_line = chain_pipeline(line) + + if inside_comment_block: + if processed_line.endswith("*/"): + modified_code.append(f"# {processed_line[:-2].strip()}") + inside_comment_block = False + else: + modified_code.append(f"# {processed_line.strip()}") + elif processed_line.startswith("/*") and processed_line.endswith("*/"): + modified_code.append(f"# {processed_line[2:-2].strip()}") + elif processed_line.startswith("/*"): + modified_code.append(f"# {processed_line[2:].strip()}") + inside_comment_block = True + elif processed_line.endswith("*/"): + modified_code.append(f"# {processed_line[:-2].strip()}") + else: + if re.match( + r'^\s*(if|for|while|def|try|elif|else|except|class|match|with|case)\s.*\{', + processed_line): + modified_code.append(processed_line.split('{')[0] + ':') + brace_stack.append('{') + inside_block = True + elif re.match(r'^\s*\}', processed_line) and inside_block: + brace_stack.pop() + inside_block = len(brace_stack) > 0 + elif re.match(r'^\s*match\(', processed_line): + modified_code.append(processed_line.split('{')[0] + ':') + brace_stack.append('{') + inside_block = True + elif re.match(r'^\s*case', processed_line): + modified_code.append(processed_line.split('{')[0] + ':') + brace_stack.append('{') + inside_block = True + else: + modified_code.append(processed_line) + + return '\n'.join(modified_code) \ No newline at end of file diff --git a/restructuredpython/predefined/decorators.py b/restructuredpython/predefined/decorators.py index 3e2d635..e77ecd0 100644 --- a/restructuredpython/predefined/decorators.py +++ b/restructuredpython/predefined/decorators.py @@ -1,4 +1,7 @@ import time +import inspect +from functools import wraps +from typing import get_type_hints class decorators: @@ -58,3 +61,35 @@ def wrapper(*args, **kwargs): print(f"{func.__name__} took {end_time - start_time:.2f} seconds.") return result return wrapper + + @staticmethod + 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 75b2828..0f991e1 100644 --- a/restructuredpython/restructuredpython.py +++ b/restructuredpython/restructuredpython.py @@ -1,238 +1,14 @@ import argparse import re -import sys import os -import warnings import tempfile from pathlib import Path -import ctypes import sys import tomllib as toml import fnmatch -import pkgutil -import importlib -import struct - -spec = importlib.util.find_spec("restructuredpython") -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") - - io_so = os.path.join(package_dir, "lib", "linux-libs", "io.so") - - io_dylib = os.path.join(package_dir, "lib", "macos-libs", "io.dylib") - -if sys.platform == "win32": - if (struct.calcsize("P") * 8) == 32: - lib = ctypes.WinDLL(io32_dll) - else: - lib = ctypes.WinDLL(io_dll) -elif sys.platform == "darwin": - lib = ctypes.CDLL(io_dylib) -else: - lib = ctypes.CDLL(io_so) - - -lib.check_file_exists.argtypes = [ctypes.c_char_p] -lib.check_file_exists.restype = ctypes.c_int - -lib.read_file.argtypes = [ctypes.c_char_p] -lib.read_file.restype = ctypes.c_char_p - -lib.write_file.argtypes = [ctypes.c_char_p, ctypes.c_char_p] -lib.write_file.restype = ctypes.c_int - -lib.read_binary_file.argtypes = [ - ctypes.c_char_p, ctypes.POINTER( - ctypes.c_size_t)] -lib.read_binary_file.restype = ctypes.POINTER(ctypes.c_char) - - -def load_toml_binary(filename): - filename = str(filename) - size = ctypes.c_size_t() - raw_data_ptr = lib.read_binary_file(filename.encode(), ctypes.byref(size)) - - if not raw_data_ptr: - raise FileNotFoundError(f"Could not read {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'/\*.*?\*/'), - ('IF', r'if'), - ('FOR', r'for'), - ('WHILE', r'while'), - ('DEF', r'def'), - ('ELIF', r'elif'), - ('ELSE', r'else'), - ('TRY', r'try'), - ('EXCEPT', r'except'), - ('CLASS', r'class'), - ('WITH', r'with'), - ('MATCH', r'match'), - ('CASE', r'case'), - ('PIPE', r'\|>'), - ('IDENT', r'[A-Za-z_][A-Za-z0-9_]*'), - ('NUMBER', r'\d+'), - ('LBRACE', r'\{'), - ('RBRACE', r'\}'), - ('LPAREN', r'\('), - ('RPAREN', r'\)'), - ('EQUALS', r'='), - ('SKIP', r'[ \t\n\r]+'), - ('MISMATCH', r'.'), -] - -token_regex = '|'.join( - f'(?P<{pair[0]}>{pair[1]})' for pair in token_specification) - - -def tokenize(code): - """Tokenizes the rePython source code and handles multiline comments.""" - inside_multiline_comment = False - for mo in re.finditer(token_regex, code): - kind = mo.lastgroup - value = mo.group() - - if kind == 'SKIP': - continue - - elif kind == 'COMMENT': - if value.startswith('/*') and value.endswith('*/'): - comment_lines = value[2:-2].splitlines() - for line in comment_lines: - yield 'COMMENT', f"# {line.strip()}" - continue - elif kind == 'MISMATCH': - warnings.warn( - f"Unexpected character { - value!r}. Continuing with compilation") # fmt: skip - yield kind, value - - else: - yield kind, value - - -def check_syntax(input_lines): - for i in range(len(input_lines)): - line = input_lines[i].strip() - - if line.startswith(('} else', '} elif')): - raise SyntaxError( - f"Misplaced '{line}' statement at line { - i + 1}. (REPY-0001)") # fmt: skip - if line.startswith('} except'): - raise SyntaxError( - f"Misplaced 'except' statement at line { - i + 1}. (REPY-0002)") # fmt: skip - if line.startswith('} def'): - raise SyntaxError( - f"Misplaced 'def' statement at line { - i + 1}. (REPY-0003)") # fmt: skip - if line.startswith('} class'): - raise SyntaxError( - f"Misplaced 'class' statement at line { - i + 1}. (REPY-0004)") # fmt: skip - if line.startswith('} case'): - raise SyntaxError( - f"Misplaced 'case' statement at line { - i + 1}. (REPY-0005)") # fmt: skip - - -def parse_repython(code): - """Parses the rePython code and converts it to valid Python code.""" - def chain_pipeline(code): - parts = [part.strip() for part in code.split('|>')] - if len(parts) > 1: - def nest(parts): - if len(parts) == 1: - return parts[0] - else: - return f'{parts[-1]}({nest(parts[:-1])})' - - variable, pipeline = parts[0].split( - '=') if '=' in parts[0] else ('', parts[0]) - variable = variable.strip() - pipeline = pipeline.strip() - - if variable: - nested_call = nest([pipeline] + parts[1:]) - return f'{variable} = {nested_call}' - else: - return nest(parts) - return code - - modified_code = [] - inside_block = False - brace_stack = [] - lines = code.splitlines() - - check_syntax(lines) - - inside_comment_block = False - - for line in lines: - processed_line = chain_pipeline(line) - - if inside_comment_block: - if processed_line.endswith("*/"): - modified_code.append(f"# {processed_line[:-2].strip()}") - inside_comment_block = False - else: - modified_code.append(f"# {processed_line.strip()}") - elif processed_line.startswith("/*") and processed_line.endswith("*/"): - modified_code.append(f"# {processed_line[2:-2].strip()}") - elif processed_line.startswith("/*"): - modified_code.append(f"# {processed_line[2:].strip()}") - inside_comment_block = True - elif processed_line.endswith("*/"): - modified_code.append(f"# {processed_line[:-2].strip()}") - else: - if re.match( - r'^\s*(if|for|while|def|try|elif|else|except|class|match|with|case)\s.*\{', - processed_line): - modified_code.append(processed_line.split('{')[0] + ':') - brace_stack.append('{') - inside_block = True - elif re.match(r'^\s*\}', processed_line) and inside_block: - brace_stack.pop() - inside_block = len(brace_stack) > 0 - elif re.match(r'^\s*match\(', processed_line): - modified_code.append(processed_line.split('{')[0] + ':') - brace_stack.append('{') - inside_block = True - elif re.match(r'^\s*case', processed_line): - modified_code.append(processed_line.split('{')[0] + ':') - brace_stack.append('{') - inside_block = True - else: - modified_code.append(processed_line) - - return '\n'.join(modified_code) - +from restructuredpython.parser import parse_repython +from restructuredpython import cload +from restructuredpython.cload import * def compile_header_file(header_filename): """Compiles a .cdata file and returns the corresponding Python code.""" diff --git a/restructuredpython/test.py b/restructuredpython/test.py deleted file mode 100644 index 5bf8cbb..0000000 --- a/restructuredpython/test.py +++ /dev/null @@ -1,25 +0,0 @@ -import ctypes - -lib = ctypes.CDLL("restructuredpython/io.dll") # Windows: file_ops.dll - -lib.check_file_exists.argtypes = [ctypes.c_char_p] -lib.check_file_exists.restype = ctypes.c_int - -lib.read_file.argtypes = [ctypes.c_char_p] -lib.read_file.restype = ctypes.c_char_p - -lib.write_file.argtypes = [ctypes.c_char_p, ctypes.c_char_p] -lib.write_file.restype = ctypes.c_int - -# Usage in Python -filename = b"restructuredpython/source.c.repy" # Byte string for C compatibility -print(lib.check_file_exists(filename)) -if lib.check_file_exists(filename) == 1: - print('h') - content = lib.read_file(filename) - print(content.decode()) # Convert back to Python string - - # Write compiled output - lib.write_file(b"output.py", content) - -print('done')