155 lines
4.5 KiB
Python
155 lines
4.5 KiB
Python
from __future__ import annotations
|
|
|
|
import ast
|
|
import json
|
|
import textwrap
|
|
from pathlib import Path
|
|
|
|
import pytest
|
|
|
|
from foreignthon.transpiler import _check_shebang, _detect_lang, detranspile, transpile
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Setup: Load the local test JSON fixture
|
|
# ---------------------------------------------------------------------------
|
|
|
|
TEST_PACK_PATH = Path(__file__).parent / "test_pack.json"
|
|
TEST_PACK = json.loads(TEST_PACK_PATH.read_text(encoding="utf-8"))
|
|
|
|
|
|
def core_transpile(src: str) -> str:
|
|
# We pass "es" to match the JSON's meta code, but we feed it the local pack
|
|
return transpile(textwrap.dedent(src).strip() + "\n", "es", pack=TEST_PACK)
|
|
|
|
|
|
def core_detranspile(src: str, postfix: bool = False) -> str:
|
|
return detranspile(
|
|
textwrap.dedent(src).strip() + "\n", "es", postfix=postfix, pack=TEST_PACK
|
|
)
|
|
|
|
|
|
def valid(src: str) -> bool:
|
|
try:
|
|
ast.parse(src)
|
|
return True
|
|
except SyntaxError:
|
|
return False
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 1. Core Mechanics: Translation & AST Validity
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_engine_basic_translation():
|
|
# Ensures the engine reads the dictionary and swaps the words
|
|
src = """
|
|
para i en dist(5):
|
|
escribir(i)
|
|
"""
|
|
out = core_transpile(src)
|
|
assert "for" in out and "in" in out and "range" in out and "print" in out
|
|
assert valid(out)
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 2. Core Mechanics: Safety Boundaries (Strings & Comments)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_engine_preserves_strings():
|
|
# The engine MUST NOT translate keywords hidden inside strings
|
|
out = core_transpile('mensaje = "si para mientras def clase"')
|
|
assert '"si para mientras def clase"' in out
|
|
|
|
|
|
def test_engine_preserves_comments():
|
|
# The engine MUST NOT translate keywords hidden in comments
|
|
out = core_transpile("# si para mientras\nx = 1")
|
|
assert "# si para mientras" in out
|
|
|
|
|
|
def test_engine_preserves_fstrings():
|
|
out = core_transpile('escribir(f"valor si={42}")')
|
|
assert "si" in out # 'si' survives because it is inside the string
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 3. Core Mechanics: Postfix Syntax (@@)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_engine_postfix_reversal():
|
|
# Tests the engine's ability to move the keyword to the front
|
|
src = """
|
|
x = 5
|
|
x > 0 @@si:
|
|
escribir(x)
|
|
"""
|
|
out = core_transpile(src)
|
|
assert "if x > 0:" in out
|
|
assert "@@" not in out
|
|
assert valid(out)
|
|
|
|
|
|
def test_engine_mixed_prefix_postfix():
|
|
src = """
|
|
si x > 0:
|
|
escribir(x)
|
|
y < 0 @@si:
|
|
escribir(y)
|
|
"""
|
|
out = core_transpile(src)
|
|
assert out.count("if") == 2
|
|
assert "@@" not in out
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 4. Core Mechanics: Decompilation (Round Trip)
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_engine_detranspile():
|
|
# Standard Python should turn back into foreignthon syntax
|
|
src = """
|
|
if x > 0:
|
|
pass
|
|
"""
|
|
out = core_detranspile(src)
|
|
assert "si" in out and "pasar" in out
|
|
|
|
|
|
def test_engine_roundtrip():
|
|
# foreignthon -> Python -> foreignthon
|
|
original = "para i en dist(5):\n escribir(i)\n"
|
|
compiled = core_transpile(original)
|
|
assert valid(compiled)
|
|
|
|
back = core_detranspile(compiled)
|
|
assert "para" in back and "dist" in back
|
|
# Accept either valid translation for 'print'
|
|
assert "escribir" in back or "imprimir" in back
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# 5. Core Utilities: Detection & Shebangs
|
|
# ---------------------------------------------------------------------------
|
|
|
|
|
|
def test_detect_lang_from_extension():
|
|
assert _detect_lang(Path("script.es.py")) == "es"
|
|
assert _detect_lang(Path("script.ta.py")) == "ta"
|
|
|
|
|
|
def test_detect_lang_bad_extension():
|
|
with pytest.raises(ValueError):
|
|
_detect_lang(Path("script.py"))
|
|
|
|
|
|
def test_shebang_override():
|
|
assert _check_shebang("# foreignthon: fr\nsi x:\n pasar", "es") == "fr"
|
|
|
|
|
|
def test_shebang_default_when_absent():
|
|
assert _check_shebang("si x:\n pasar", "es") == "es"
|