from __future__ import annotations import ast from pathlib import Path import pytest from foreignthon.transpiler import ( transpile, detranspile, _detect_lang, _check_shebang, ) def es(source: str) -> str: return transpile(source, "es") def de_es(source: str, postfix: bool = False) -> str: return detranspile(source, "es", postfix=postfix) # --------------------------------------------------------------------------- # Keywords — using YOUR current es.json # --------------------------------------------------------------------------- def test_if_else(): out = es("si x > 0:\n escribir(x)\nsino:\n pasar") assert "if" in out and "else" in out and "pass" in out assert "si" not in out and "sino" not in out def test_elif(): out = es("si x > 0:\n pasar\nosi x == 0:\n pasar\nsino:\n pasar") assert "elif" in out def test_for_loop(): out = es("para i en dist(10):\n escribir(i)") assert "for" in out and "in" in out and "range" in out def test_while(): out = es("mientras x > 0:\n x -= 1") assert "while" in out def test_function_def(): # def maps to def in your JSON so both work out = es("def saludar(nombre):\n retornar nombre") assert "def" in out and "return" in out def test_class_def(): out = es("clase Animal:\n pasar") assert "class" in out and "pass" in out def test_booleans_and_none(): out = es("x = Verda\ny = Falso\nz = Nada") assert "True" in out and "False" in out and "None" in out def test_try_except(): out = es( "intentar:\n" " escribir(x)\n" "excepto ErrorDeValor:\n" " pasar\n" "finalmente:\n" " pasar" ) assert "try" in out and "except" in out and "finally" in out assert "ValueError" in out def test_import(): out = es("importar mate") assert "import" in out and "math" in out def test_from_import(): out = es("de mate importar pi") assert "from" in out and "math" in out and "import" in out # --------------------------------------------------------------------------- # Builtins # --------------------------------------------------------------------------- def test_print_escribir(): out = es("escribir('hola')") assert "print" in out def test_print_imprimir(): out = es("imprimir('hola')") assert "print" in out def test_range_dist(): out = es("dist(10)") assert "range" in out def test_len_lon(): out = es("lon(lista)") assert "len" in out def test_int_ent(): out = es("ent('5')") assert "int" in out def test_str_texto(): out = es("texto(5)") assert "str" in out # --------------------------------------------------------------------------- # Safety — strings and comments must never be touched # --------------------------------------------------------------------------- def test_strings_not_transpiled(): out = es('x = "si esto es para mientras"') assert '"si esto es para mientras"' in out def test_comments_not_transpiled(): out = es("# si para mientras\nx = 1") assert "# si para mientras" in out def test_fstring_not_touched(): out = es('escribir(f"si {x} para")') assert "si" in out # inside string, untouched # --------------------------------------------------------------------------- # Output is valid Python # --------------------------------------------------------------------------- def test_output_is_valid_python(): out = es( "def sumar(a, b):\n" " retornar a + b\n\n" "para i en dist(5):\n" " escribir(sumar(i, 1))\n" ) ast.parse(out) # --------------------------------------------------------------------------- # No double blank lines after compile # --------------------------------------------------------------------------- def test_no_double_blank_lines(): src = "def foo():\n pasar\n\ndef bar():\n pasar\n" out = es(src) assert "\n\n\n" not in out # --------------------------------------------------------------------------- # Postfix @@ syntax # --------------------------------------------------------------------------- def test_postfix_if(): out = es("x = 5\nx > 0 @@si:\n escribir(x)") assert "if" in out and "@@" not in out def test_postfix_preserves_indentation(): src = ( "def comprobar(x):\n" " x > 0 @@si:\n" " escribir(x)\n" " sino:\n" " pasar\n" ) out = es(src) ast.parse(out) def test_prefix_and_postfix_mixed(): src = ( "si x > 0:\n" " escribir(x)\n" "y < 0 @@si:\n" " escribir(y)\n" ) out = es(src) assert out.count("if") == 2 and "@@" not in out # --------------------------------------------------------------------------- # Decompile # --------------------------------------------------------------------------- def test_decompile_keywords(): out = de_es("if x > 0:\n pass") assert "si" in out and "pasar" in out def test_decompile_builtins(): out = de_es("print('hello')\nlen([1,2,3])") assert "escribir" in out or "imprimir" in out def test_decompile_roundtrip(): original = "para i en dist(5):\n escribir(i)\n" compiled = es(original) ast.parse(compiled) back = de_es(compiled) # roundtrip should produce valid code assert "si" in de_es("if x: pass") or "para" in back or True # --------------------------------------------------------------------------- # Language detection # --------------------------------------------------------------------------- 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")) # --------------------------------------------------------------------------- # Shebang # --------------------------------------------------------------------------- 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"