5 Commits

3 changed files with 78 additions and 59 deletions

View File

@@ -5,12 +5,13 @@ from pathlib import Path
import pytest import pytest
from foreignthon.transpiler import transpile, _detect_lang, _check_shebang from foreignthon.transpiler import _check_shebang, _detect_lang, transpile
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# All tests use the real foreignthon-es pack — no mocks # All tests use the real foreignthon-es pack — no mocks
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def es(source: str) -> str: def es(source: str) -> str:
return transpile(source, "es") return transpile(source, "es")
@@ -19,27 +20,33 @@ def es(source: str) -> str:
# Keywords # Keywords
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_if_else(): def test_if_else():
out = es("si x > 0:\n imprimir(x)\nsino:\n pasar") out = es("si x > 0:\n imprimir(x)\nsino:\n pasar")
assert "if" in out and "else" in out and "pass" in out assert "if" in out and "else" in out and "pass" in out
assert "si" not in out and "sino" not in out assert "si" not in out and "sino" not in out
def test_for_loop(): def test_for_loop():
out = es("para i en rango(10):\n imprimir(i)") out = es("para i en dist(10):\n imprimir(i)")
assert "for" in out and "in" in out and "range" in out assert "for" in out and "in" in out and "range" in out
def test_function_def(): def test_function_def():
out = es("definir saludar(nombre):\n retornar nombre") out = es("def saludar(nombre):\n retornar nombre")
assert "def" in out and "return" in out assert "def" in out and "return" in out
def test_class_def(): def test_class_def():
out = es("clase Animal:\n pasar") out = es("clase Animal:\n pasar")
assert "class" in out and "pass" in out assert "class" in out and "pass" in out
def test_booleans_and_none(): def test_booleans_and_none():
out = es("x = Verdadero\ny = Falso\nz = Nada") out = es("x = Verda\ny = Falso\nz = Nada")
assert "True" in out and "False" in out and "None" in out assert "True" in out and "False" in out and "None" in out
def test_try_except(): def test_try_except():
out = es( out = es(
"intentar:\n" "intentar:\n"
@@ -52,54 +59,66 @@ def test_try_except():
assert "try" in out and "except" in out and "finally" in out assert "try" in out and "except" in out and "finally" in out
assert "ValueError" in out assert "ValueError" in out
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Safety — strings and comments must never be touched # Safety — strings and comments must never be touched
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_strings_not_transpiled(): def test_strings_not_transpiled():
out = es('x = "si esto es para mientras definir"') out = es('x = "si esto es para mientras def"')
assert '"si esto es para mientras definir"' in out assert '"si esto es para mientras def"' in out
def test_comments_not_transpiled(): def test_comments_not_transpiled():
out = es("# si para mientras\nx = 1") out = es("# si para mientras\nx = 1")
assert "# si para mientras" in out assert "# si para mientras" in out
def test_fstring_not_touched(): def test_fstring_not_touched():
out = es('imprimir(f"si {x} para")') out = es('imprimir(f"si {x} para")')
assert "si" in out # inside the string, untouched assert "si" in out # inside the string, untouched
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Output is always valid Python # Output is always valid Python
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_output_is_valid_python(): def test_output_is_valid_python():
out = es( out = es(
"definir sumar(a, b):\n" "def sumar(a, b):\n"
" retornar a + b\n\n" " retornar a + b\n\n"
"para i en rango(5):\n" "para i en dist(5):\n"
" imprimir(sumar(i, 1))\n" " imprimir(sumar(i, 1))\n"
) )
ast.parse(out) # raises if invalid ast.parse(out) # raises if invalid
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Language detection # Language detection
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_detect_lang_from_extension(): def test_detect_lang_from_extension():
assert _detect_lang(Path("script.es.py")) == "es" assert _detect_lang(Path("script.es.py")) == "es"
assert _detect_lang(Path("script.ta.py")) == "ta" assert _detect_lang(Path("script.ta.py")) == "ta"
def test_detect_lang_bad_extension(): def test_detect_lang_bad_extension():
with pytest.raises(ValueError): with pytest.raises(ValueError):
_detect_lang(Path("script.py")) _detect_lang(Path("script.py"))
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
# Shebang override # Shebang override
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_shebang_override(): def test_shebang_override():
assert _check_shebang("# foreignthon: fr\nsi x:\n pasar", "es") == "fr" assert _check_shebang("# foreignthon: fr\nsi x:\n pasar", "es") == "fr"
def test_shebang_default_when_absent(): def test_shebang_default_when_absent():
assert _check_shebang("si x:\n pasar", "es") == "es" assert _check_shebang("si x:\n pasar", "es") == "es"
@@ -108,14 +127,16 @@ def test_shebang_default_when_absent():
# Postfix @@ syntax # Postfix @@ syntax
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
def test_postfix_if(): def test_postfix_if():
out = es("x = 5\nx > 0 @@si:\n imprimir(x)") out = es("x = 5\nx > 0 @@si:\n imprimir(x)")
assert "if" in out assert "if" in out
assert "@@" not in out assert "@@" not in out
def test_postfix_preserves_indentation(): def test_postfix_preserves_indentation():
src = ( src = (
"definir comprobar(x):\n" "def comprobar(x):\n"
" x > 0 @@si:\n" " x > 0 @@si:\n"
" imprimir(x)\n" " imprimir(x)\n"
" sino:\n" " sino:\n"
@@ -124,13 +145,9 @@ def test_postfix_preserves_indentation():
out = es(src) out = es(src)
ast.parse(out) # fails if indentation is broken ast.parse(out) # fails if indentation is broken
def test_prefix_still_works_alongside_postfix(): def test_prefix_still_works_alongside_postfix():
src = ( src = "si x > 0:\n" " imprimir(x)\n" "y < 0 @@si:\n" " imprimir(y)\n"
"si x > 0:\n"
" imprimir(x)\n"
"y < 0 @@si:\n"
" imprimir(y)\n"
)
out = es(src) out = es(src)
assert out.count("if") == 2 assert out.count("if") == 2
assert "@@" not in out assert "@@" not in out

View File

@@ -4,18 +4,17 @@ build-backend = "hatchling.build"
[project] [project]
name = "foreignthon-es" name = "foreignthon-es"
version = "0.2.10" version = "0.3.0"
description = "Spanish language pack for ForeignThon." description = "Spanish language pack for ForeignThon."
license = { text = "GPL v3" } license = { text = "GPL v3" }
requires-python = ">=3.9" requires-python = ">=3.9"
authors = [ authors = [
{ name = "Keshav Anand", email = "keshavanand.dev@gmail.com" } { name = "Keshav Anand", email = "keshavanand.dev@gmail.com" },
{ name = "Cody Trainer" },
] ]
keywords = ["foreignthon", "spanish", "español"] keywords = ["foreignthon", "spanish", "español"]
dependencies = [ dependencies = ["foreignthon>=0.4.0"]
"foreignthon>=0.4.0",
]
[project.entry-points."foreignthon.langs"] [project.entry-points."foreignthon.langs"]
es = "foreignthon_es" es = "foreignthon_es"

View File

@@ -9,16 +9,16 @@
"keywords": { "keywords": {
"si": "if", "si": "if",
"sino": "else", "sino": "else",
"sino_si": "elif", "osi": "elif",
"para": "for", "para": "for",
"mientras": "while", "mientras": "while",
"definir": "def", "def": "def",
"clase": "class", "clase": "class",
"importar": "import", "importar": "import",
"desde": "from", "de": "from",
"como": "as", "como": "as",
"retornar": "return", "retornar": "return",
"romper": "break", "parar": "break",
"continuar": "continue", "continuar": "continue",
"pasar": "pass", "pasar": "pass",
"intentar": "try", "intentar": "try",
@@ -31,58 +31,60 @@
"y": "and", "y": "and",
"o": "or", "o": "or",
"no": "not", "no": "not",
"eliminar": "del", "elim": "del",
"global": "global", "global": "global",
"nolocal": "nonlocal", "nolocal": "nonlocal",
"afirmar": "assert", "afirmar": "assert",
"generar": "yield", "generar": "yield",
"esperar": "await", "esperar": "await",
"asincrono": "async", "asinc": "async",
"lambda": "lambda", "lambda": "lambda",
"Verdadero": "True", "Verda": "True",
"Falso": "False", "Falso": "False",
"Nada": "None" "Nada": "None"
}, },
"builtins": { "builtins": {
"escribir": "print",
"imprimir": "print", "imprimir": "print",
"entrada": "input", "entrada": "input",
"longitud": "len", "lon": "len",
"rango": "range", "dist": "range",
"tipo": "type", "tipo": "type",
"entero": "int", "ent": "int",
"decimal": "float", "dec": "float",
"cadena": "str", "texto": "str",
"lista": "list", "lista": "list",
"diccionario": "dict", "dicc": "dict",
"conjunto": "set", "conj": "set",
"tupla": "tuple", "tupla": "tuple",
"booleano": "bool", "bool": "bool",
"abrir": "open", "abrir": "open",
"enumerar": "enumerate", "enumerar": "enumerate",
"mapear": "map", "map": "map",
"filtrar": "filter", "filtrar": "filter",
"ordenado": "sorted", "ordenado": "sorted",
"invertido": "reversed", "invertido": "reversed",
"suma": "sum", "sum": "sum",
"minimo": "min", "min": "min",
"maximo": "max", "max": "max",
"absoluto": "abs", "abs": "abs",
"redondear": "round", "redondear": "round",
"rnd": "round",
"todos": "all", "todos": "all",
"alguno": "any", "alguno": "any",
"es_instancia": "isinstance", "esinstancia": "isinstance",
"tiene_atributo": "hasattr", "teneatri": "hasattr",
"obtener_atributo": "getattr", "obtatri": "getattr",
"establecer_atributo": "setattr", "estabatri": "setattr",
"representar": "repr", "repr": "repr",
"formatear": "format", "formatear": "format",
"variables": "vars", "vars": "vars",
"siguiente": "next", "sigue": "next",
"identificador": "id", "id": "id",
"caracter": "chr", "car": "chr",
"hexadecimal": "hex", "hex": "hex",
"binario": "bin", "bin": "bin",
"octal": "oct" "oct": "oct"
}, },
"exceptions": { "exceptions": {
"Excepcion": "Exception", "Excepcion": "Exception",
@@ -120,27 +122,28 @@
"NameError": "Error de nombre", "NameError": "Error de nombre",
"ImportError": "Error de importación", "ImportError": "Error de importación",
"FileNotFoundError": "Archivo no encontrado", "FileNotFoundError": "Archivo no encontrado",
"ZeroDivisionError": "Error: división por cero", "ZeroDivisionError": "Error división por cero",
"RecursionError": "Error de recursión", "RecursionError": "Error de recursión",
"RuntimeError": "Error de ejecución", "RuntimeError": "Error de ejecución",
"MemoryError": "Error de memoria", "MemoryError": "Error de memoria",
"OverflowError": "Error de desbordamiento", "OverflowError": "Error de desbordamiento",
"AssertionError": "Error de afirmación", "AssertionError": "Error de afirmación",
"NotImplementedError": "Error: no implementado", "NotImplementedError": "Error no implementado",
"StopIteration": "Detener iteración", "StopIteration": "Detener iteración",
"KeyboardInterrupt": "Interrupción de teclado", "KeyboardInterrupt": "Interrupción de teclado",
"PermissionError": "Error de permiso", "PermissionError": "Error de permiso",
"TimeoutError": "Error de tiempo agotado" "TimeoutError": "Error de tiempo agotado"
}, },
"stdlib": { "stdlib": {
"matematicas": "math", "mate": "math",
"sistema": "sys", "sis": "sys",
"fecha_hora": "datetime", "fechahora": "datetime",
"tiempo": "time", "tiempo": "time",
"aleatorio": "random", "aleatorio": "random",
"aleatoria": "random",
"colecciones": "collections", "colecciones": "collections",
"ruta": "pathlib", "ruta": "pathlib",
"expresion_regular": "re" "er": "re"
}, },
"postfix_keywords": [] "postfix_keywords": []
} }