added test and a test json
All checks were successful
CI / test (push) Successful in 11s

This commit is contained in:
2026-05-19 15:56:42 -05:00
parent d78a8235b5
commit d727e3d51e
2 changed files with 301 additions and 0 deletions

154
tests/test_engine.py Normal file
View File

@@ -0,0 +1,154 @@
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"

147
tests/test_pack.json Normal file
View File

@@ -0,0 +1,147 @@
{
"meta": {
"name": "Spanish",
"native_name": "Español",
"code": "es"
},
"keywords": {
"si": "if",
"sino": "else",
"osi": "elif",
"para": "for",
"mientras": "while",
"def": "def",
"clase": "class",
"importar": "import",
"de": "from",
"como": "as",
"retornar": "return",
"parar": "break",
"continuar": "continue",
"pasar": "pass",
"intentar": "try",
"excepto": "except",
"finalmente": "finally",
"lanzar": "raise",
"con": "with",
"en": "in",
"es": "is",
"y": "and",
"o": "or",
"no": "not",
"elim": "del",
"global": "global",
"nolocal": "nonlocal",
"afirmar": "assert",
"generar": "yield",
"esperar": "await",
"asinc": "async",
"lambda": "lambda",
"Verda": "True",
"Falso": "False",
"Nada": "None"
},
"builtins": {
"escribir": "print",
"imprimir": "print",
"entrada": "input",
"lon": "len",
"dist": "range",
"tipo": "type",
"ent": "int",
"dec": "float",
"texto": "str",
"lista": "list",
"dicc": "dict",
"conj": "set",
"tupla": "tuple",
"bool": "bool",
"abrir": "open",
"enumerar": "enumerate",
"map": "map",
"filtrar": "filter",
"ordenado": "sorted",
"invertido": "reversed",
"sum": "sum",
"min": "min",
"max": "max",
"abs": "abs",
"redondear": "round",
"rnd": "round",
"todos": "all",
"alguno": "any",
"esinstancia": "isinstance",
"teneatri": "hasattr",
"obtatri": "getattr",
"estabatri": "setattr",
"repr": "repr",
"formatear": "format",
"vars": "vars",
"sigue": "next",
"id": "id",
"car": "chr",
"hex": "hex",
"bin": "bin",
"oct": "oct"
},
"exceptions": {
"Excepcion": "Exception",
"ExcepcionBase": "BaseException",
"ErrorDeValor": "ValueError",
"ErrorDeTipo": "TypeError",
"ErrorDeClave": "KeyError",
"ErrorDeIndice": "IndexError",
"ErrorDeAtributo": "AttributeError",
"ErrorDeNombre": "NameError",
"ErrorDeImportacion": "ImportError",
"ErrorDelSistema": "OSError",
"ArchivoNoEncontrado": "FileNotFoundError",
"ErrorDeEjecucion": "RuntimeError",
"DetenerIteracion": "StopIteration",
"SalidaDelSistema": "SystemExit",
"InterrupcionDeTeclado": "KeyboardInterrupt",
"ErrorNoImplementado": "NotImplementedError",
"ErrorDeDivisionCero": "ZeroDivisionError",
"ErrorDeRecursion": "RecursionError",
"ErrorDeSintaxis": "SyntaxError",
"ErrorDeAfirmacion": "AssertionError",
"ErrorDeDesbordamiento": "OverflowError",
"ErrorDeMemoria": "MemoryError",
"ErrorDePermiso": "PermissionError",
"ErrorDeTiempoAgotado": "TimeoutError"
},
"error_messages": {
"SyntaxError": "Error de sintaxis",
"ValueError": "Error de valor",
"TypeError": "Error de tipo",
"KeyError": "Error de clave",
"IndexError": "Error de índice",
"AttributeError": "Error de atributo",
"NameError": "Error de nombre",
"ImportError": "Error de importación",
"FileNotFoundError": "Archivo no encontrado",
"ZeroDivisionError": "Error división por cero",
"RecursionError": "Error de recursión",
"RuntimeError": "Error de ejecución",
"MemoryError": "Error de memoria",
"OverflowError": "Error de desbordamiento",
"AssertionError": "Error de afirmación",
"NotImplementedError": "Error no implementado",
"StopIteration": "Detener iteración",
"KeyboardInterrupt": "Interrupción de teclado",
"PermissionError": "Error de permiso",
"TimeoutError": "Error de tiempo agotado"
},
"stdlib": {
"mate": "math",
"sis": "sys",
"fechahora": "datetime",
"tiempo": "time",
"aleatorio": "random",
"aleatoria": "random",
"colecciones": "collections",
"ruta": "pathlib",
"er": "re"
},
"postfix_keywords": []
}