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"