Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ jobs:
runners: '[
"ubuntu-24.04"
]'
min-version: "3.8"
min-version: "3.9"
include-pre-releases: true
implementations: '["CPython", "PyPy"]'

Expand Down
25 changes: 10 additions & 15 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,20 +1,15 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files
- id: trailing-whitespace
- id: end-of-file-fixer
- id: check-yaml
- id: check-added-large-files

- repo: https://github.com/psf/black
rev: 25.1.0
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.16.2
hooks:
- id: black
args: ["-l", "79"]

- repo: https://github.com/pycqa/flake8
rev: 7.3.0
hooks:
- id: flake8
args: ["--ignore", "E203,W503"]
- id: ruff-check
args: [ --fix ]
- id: ruff-format
9 changes: 6 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,21 @@ build-backend = "hatchling.build"

[project]
name = "yabi-bython"
version = "0.9.3"
dynamic = ["version"]
description = "Yet Another Bython (braced Python) Implementation"
authors = [{name = "Sóla Łuset"}]
readme = "README.md"
license = "MIT"
license-files = ["LICENSE.md"]
dependencies = ["pwcp>=0.12.2,<0.13"]
dependencies = ["pwcp~=0.13.0"]
optional-dependencies = {tests = ["pytest"]}
scripts = {yabi = "yabi:main", yabi-convert = "yabi:convert_main"}
scripts = {yabi = "yabi:main", yabi-convert = "yabi.convert:main"}

[project.urls]
Repository = "https://github.com/solaluset/yabi"

[tool.hatch.build.targets.wheel]
packages = ["yabi"]

[tool.hatch.version]
path = "yabi/version.py"
1 change: 1 addition & 0 deletions ruff.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
line-length = 79
96 changes: 82 additions & 14 deletions tests/test_yabi.py
Original file line number Diff line number Diff line change
@@ -1,30 +1,61 @@
import ast
import os
import sys
from io import StringIO
from unittest.mock import patch

from pytest import mark
from pytest import fixture, mark

sys.path.insert(0, os.path.dirname(os.path.dirname(__file__)))

from yabi import config, main, to_bython, to_pure_python # noqa: E402
from yabi.console import YabiConsole # noqa: E402

from yabi import config, main, to_bython, to_pure_python
from yabi.console import YabiConsole
from yabi.parser import _transform

sys.dont_write_bytecode = True
sys.ps1 = getattr(sys, "ps1", ">>> ")
sys.ps2 = getattr(sys, "ps2", "... ")


def _ok_err(func, *args):
try:
return func(*args), None
except Exception as e: # noqa: BLE001
return None, e


@fixture(scope="session", autouse=True)
def checked_transform():
def _transform_test(code: str, python: bool) -> str:
ret = _transform(code, python)

result1 = _transform(ret, True)
result2 = ret if python else _transform(result1, True)

ast1, err1 = _ok_err(ast.parse, result1)
ast2, err2 = _ok_err(ast.parse, result2)
# check if ASTs or their errors are equivalent
if ast1:
assert ast2 and ast.dump(ast1) == ast.dump(ast2)
else:
assert type(err1) is type(err2) and err1.args == err2.args

return ret

with patch("yabi.parser._transform", new=_transform_test):
yield


def check_file(file, expexted_output, *args):
with patch("sys.stdout", new=StringIO()):
main(args + (file,))
assert sys.stdout.getvalue() == expexted_output


def check_console(code, expexted_output):
with patch("sys.stdout", new=StringIO()), patch(
"sys.stdin", new=StringIO(code)
with (
patch("sys.stdout", new=StringIO()),
patch("sys.stdin", new=StringIO(code)),
):
YabiConsole().interact()
assert sys.stdout.getvalue() == expexted_output
Expand Down Expand Up @@ -126,6 +157,19 @@ def test_console_linecont():
check_console(code, expexted_output)


def test_console_preprocessing():
code = (
"""
#pragma pypp on
#define a 1
a
""".strip()
+ "\n"
)
expexted_output = ">>> >>> >>> 1\n>>> "
check_console(code, expexted_output)


def test_to_bython():
assert (
to_bython(
Expand All @@ -138,9 +182,7 @@ def test_to_bython():
!
/* these comments too */
print("No")
""".strip().replace(
"!", ""
)
""".strip().replace("!", "")
)
== """
for (i in {1, 2, 3}) {
Expand All @@ -154,10 +196,7 @@ def test_to_bython():
print("No")
}
}
""".strip().replace(
"!", ""
)
+ "\n"
""".strip().replace("!", "")
)


Expand All @@ -166,7 +205,17 @@ def test_comment_with_semicolon():


def test_semicolon_parse():
assert to_pure_python("; while True {}") == "while True:\n pass\n"
assert to_pure_python("; while True {}\n") == "while True:\n pass\n"
assert (
to_pure_python("if True: if True: pass; while True: pass")
== """
if True:
if True:
pass
while True:
pass
""".strip()
)


def test_inline_comment():
Expand All @@ -177,6 +226,25 @@ def test_inline_comment():
assert to_pure_python(code).strip() == code


def test_c_comment():
assert to_pure_python("/* test */") == "# test"
assert (
to_pure_python(
"""
/* one
more
test */
"""
)
== """
# one
# more
# test
""".strip()
+ "\n"
)


def test_preprocessing():
with open("tests/preprocess.by") as f:
code = f.read()
Expand Down
4 changes: 1 addition & 3 deletions yabi/__init__.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
__all__ = (
"__version__",
"main",
"convert_main",
"to_bython",
"to_pure_python",
)


from .runner import __version__, main
from .convert import convert_main
from .parser import to_bython, to_pure_python
from .runner import __version__, main
1 change: 0 additions & 1 deletion yabi/__main__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
from . import main


main()
29 changes: 9 additions & 20 deletions yabi/console.py
Original file line number Diff line number Diff line change
@@ -1,34 +1,26 @@
import sys
import ast
import sys
from code import InteractiveConsole

from pwcp.preprocessor import PyPreprocessor, PreprocessorError, preprocess
from pwcp.preprocessor import PreprocessorError, preprocess

from . import config
from .parser import UNCLOSED_BLOCK_ERROR, to_pure_python
from .parser import UNCLOSED_BLOCK_ERROR


class YabiConsole(InteractiveConsole):
def __init__(self):
super().__init__()
self.preprocessor = PyPreprocessor(
disabled=not config.ENABLE_PREPROCESSING
)
self.pwcp_data = {}

def runsource(self, source, filename="<input>", symbol="single") -> bool:
try:
parsed = to_pure_python(
preprocess(source, filename, self.preprocessor)[0]
)
except PreprocessorError:
self.showtraceback()
return False
except SyntaxError as e:
parsed = preprocess(source, filename, self.pwcp_data)[0]
except (PreprocessorError, SyntaxError) as e:
if e.args[0] == UNCLOSED_BLOCK_ERROR or e.args[0].startswith(
"Unterminated"
):
return True
self.showtraceback()
self.showsyntaxerror(filename)
return False
if not source.endswith("\n"):
parsed = parsed.rstrip("\n")
Expand Down Expand Up @@ -56,11 +48,11 @@ def runsource(self, source, filename="<input>", symbol="single") -> bool:

def runcode(self, code) -> bool:
try:
exec(code, self.locals)
exec(code, self.locals) # noqa: S102
return True
except SystemExit:
raise
except BaseException:
except BaseException: # noqa: BLE001
self.showtraceback()
return False

Expand All @@ -69,9 +61,6 @@ def _compiler(self, source, filename, symbol, original_source):
return None
try:
return self.compile(source, filename, symbol)
except PreprocessorError:
self.showtraceback()
return self.compile("", filename, symbol)
except SyntaxError:
if "\n" in original_source and not source.endswith("\n"):
return None
Expand Down
11 changes: 7 additions & 4 deletions yabi/convert.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import sys
import argparse
import sys

from .parser import to_pure_python, to_bython

from .parser import to_bython, to_pure_python

parser = argparse.ArgumentParser(
description="Python <==> Bython converter",
Expand All @@ -16,8 +15,12 @@
parser.add_argument("target")


def convert_main(args=sys.argv[1:]):
def main(args=sys.argv[1:]):
args = parser.parse_args(args)
converter = to_pure_python if args.to_python else to_bython
with open(args.target) as source:
print(converter(source.read()).rstrip("\n"))


if __name__ == "__main__":
main()
27 changes: 27 additions & 0 deletions yabi/hooks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from pwcp import PreprocessorHooks, PycType

from . import config
from .parser import to_pure_python
from .version import __version__


class YabiHooks(PreprocessorHooks):
def __init__(self):
super().__init__("yabi")

def process_source(
self, source: str, filename: str, state: None
) -> tuple[str, None]:
return to_pure_python(source), None

def create_pyc_data(self, data: None, pyc_type: PycType) -> dict:
return {
"version": __version__,
"preprocessing": config.ENABLE_PREPROCESSING,
}

def validate_pyc_data(self, pyc: dict, pyc_type: PycType) -> bool:
return (
pyc["version"] == __version__
and pyc["preprocessing"] == config.ENABLE_PREPROCESSING
)
Loading