8 Commits

8 changed files with 256 additions and 103 deletions

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "foreignthon" name = "foreignthon"
version = "0.2.0" version = "0.4.0"
description = "Write Python in any language. Transpiles foreign-language .xx.py files to standard Python." description = "Write Python in any language. Transpiles foreign-language .xx.py files to standard Python."
license = { text = "GPL v3" } license = { text = "GPL v3" }
requires-python = ">=3.9" requires-python = ">=3.9"

View File

@@ -9,15 +9,17 @@ from . import __version__
from .errors import activate from .errors import activate
from .transpiler import run_transpiled, transpile_file from .transpiler import run_transpiled, transpile_file
CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"])
@click.group()
@click.group(context_settings=CONTEXT_SETTINGS)
@click.version_option(__version__, prog_name="fpy") @click.version_option(__version__, prog_name="fpy")
def main(): def main():
"""ForeignThon — write Python in any language.""" """ForeignThon — write Python in any language."""
pass pass
@main.command() @main.command(context_settings=CONTEXT_SETTINGS)
@click.argument("file", type=click.Path(exists=True, path_type=Path)) @click.argument("file", type=click.Path(exists=True, path_type=Path))
@click.option("--lang", "-l", default=None, help="Override language code (e.g. es, ta)") @click.option("--lang", "-l", default=None, help="Override language code (e.g. es, ta)")
@click.option("--keep", is_flag=True, help="Keep the compiled .py alongside the source") @click.option("--keep", is_flag=True, help="Keep the compiled .py alongside the source")
@@ -29,8 +31,6 @@ def run(file: Path, lang: str | None, keep: bool):
source = f"# foreignthon: {lang}\n" + source source = f"# foreignthon: {lang}\n" + source
file.write_text(source, encoding="utf-8") file.write_text(source, encoding="utf-8")
# Activate error hook BEFORE transpiling so even transpile
# errors get shown in the foreign language
detected_lang = lang or _lang_from_file(file) detected_lang = lang or _lang_from_file(file)
activate(detected_lang) activate(detected_lang)
@@ -44,22 +44,41 @@ def run(file: Path, lang: str | None, keep: bool):
run_transpiled(file, transpiled) run_transpiled(file, transpiled)
@main.command() @main.command(context_settings=CONTEXT_SETTINGS)
@click.argument("file", type=click.Path(exists=True, path_type=Path)) @click.argument("file", type=click.Path(exists=True, path_type=Path))
@click.option("--output", "-o", default=None, help="Output path (default: beside source)") @click.option(
"--output", "-o", default=None,
help="Output file or directory. Defaults to same directory as source."
)
def compile(file: Path, output: str | None): def compile(file: Path, output: str | None):
"""Transpile a file to standard Python without running it.""" """
Transpile a foreign-language file to standard Python.
Output can be a file path or a directory:
\b
fpy compile script.es.py # → script.compiled.py
fpy compile script.es.py -o out/ # → out/script.compiled.py
fpy compile script.es.py -o out.py # → out.py
"""
transpiled = transpile_file(file) transpiled = transpile_file(file)
out_path = ( if output is None:
Path(output) if output out_path = file.with_suffix("").with_suffix(".compiled.py")
else file.with_suffix("").with_suffix(".compiled.py") else:
) out = Path(output)
if out.is_dir() or str(output).endswith("/"):
out.mkdir(parents=True, exist_ok=True)
out_path = out / file.with_suffix("").with_suffix(".compiled.py").name
else:
out_path = out
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(transpiled, encoding="utf-8") out_path.write_text(transpiled, encoding="utf-8")
click.echo(f"Compiled: {out_path}") click.echo(f"Compiled: {out_path}")
@main.command() @main.command(context_settings=CONTEXT_SETTINGS)
@click.argument("file", type=click.Path(exists=True, path_type=Path)) @click.argument("file", type=click.Path(exists=True, path_type=Path))
def check(file: Path): def check(file: Path):
"""Validate a foreign-language file without running it.""" """Validate a foreign-language file without running it."""
@@ -77,7 +96,7 @@ def check(file: Path):
sys.exit(1) sys.exit(1)
@main.command("pack") @main.command("pack", context_settings=CONTEXT_SETTINGS)
@click.argument("json_file", type=click.Path(exists=True, path_type=Path)) @click.argument("json_file", type=click.Path(exists=True, path_type=Path))
def validate_pack(json_file: Path): def validate_pack(json_file: Path):
"""Validate a language pack JSON file.""" """Validate a language pack JSON file."""
@@ -96,8 +115,48 @@ def validate_pack(json_file: Path):
click.echo(f"✓ Pack '{data['meta']['name']}' is valid.") click.echo(f"✓ Pack '{data['meta']['name']}' is valid.")
@main.command(context_settings=CONTEXT_SETTINGS)
@click.argument("file", type=click.Path(exists=True, path_type=Path))
@click.option("--lang", "-l", required=True, help="Target language code (e.g. es, ta)")
@click.option("--postfix", is_flag=True, help="Use @@ postfix style for if/elif/while")
@click.option("--output", "-o", default=None, help="Output file or directory")
def decompile(file: Path, lang: str, postfix: bool, output: str | None):
"""
Convert standard Python back to a foreign language.
Keywords and builtins are translated. Variable names are untouched.
\b
fpy decompile script.py --lang es
fpy decompile script.py --lang ta --postfix
fpy decompile script.py --lang es -o out/
"""
from .transpiler import detranspile_file
result = detranspile_file(file, lang, postfix=postfix)
ext = f".{lang}.py"
if output is None:
stem = file.stem if not file.stem.endswith(f".{lang}") else file.stem
out_path = file.with_name(stem + ext)
else:
out = Path(output)
if out.is_dir() or str(output).endswith("/"):
out.mkdir(parents=True, exist_ok=True)
out_path = out / (file.stem + ext)
else:
out_path = out
out_path.parent.mkdir(parents=True, exist_ok=True)
out_path.write_text(result, encoding="utf-8")
click.echo(f"Decompiled: {out_path}")
def _lang_from_file(path: Path) -> str: def _lang_from_file(path: Path) -> str:
suffixes = path.suffixes suffixes = path.suffixes
if len(suffixes) >= 2 and suffixes[-1] == ".py": if len(suffixes) >= 2 and suffixes[-1] == ".py":
return suffixes[-2].lstrip(".") return suffixes[-2].lstrip(".")
return "en" return "en"

View File

@@ -9,11 +9,6 @@ from .pack import load_pack
def _apply_postfix_syntax(source: str, mapping: dict) -> str: def _apply_postfix_syntax(source: str, mapping: dict) -> str:
"""
Pre-tokenizer pass: handle postfix @@ keyword syntax.
x > 0 @@ஆனால்: → ஆனால் x > 0:
Indentation is preserved by separating it before rewriting.
"""
if "@@" not in source: if "@@" not in source:
return source return source
@@ -28,28 +23,50 @@ def _apply_postfix_syntax(source: str, mapping: dict) -> str:
result.append(line) result.append(line)
continue continue
# Separate indentation from content so we never lose it
stripped = line.lstrip() stripped = line.lstrip()
indent = line[: len(line) - len(stripped)] indent = line[: len(line) - len(stripped)]
ending = "\n" if stripped.endswith("\n") else "" ending = "\n" if stripped.endswith("\n") else ""
content = stripped.rstrip("\n") content = stripped.rstrip("\n")
def _replace(m: re.Match) -> str: def _replace(m: re.Match) -> str:
expr = m.group(1).strip() return f"{m.group(2)} {m.group(1).strip()}"
kw = m.group(2)
return f"{kw} {expr}"
rewritten = indent + postfix_re.sub(_replace, content) + ending result.append(indent + postfix_re.sub(_replace, content) + ending)
result.append(rewritten)
return "".join(result)
def _apply_postfix_output(source: str, en_to_foreign: dict, postfix_english: set) -> str:
"""
Post-pass for decompile: rewrite foreign keyword lines to @@ postfix.
postfix_english comes from the language pack's postfix_keywords list.
"""
postfix_foreign = {en_to_foreign[k] for k in postfix_english if k in en_to_foreign}
lines = source.splitlines(keepends=True)
result = []
for line in lines:
stripped = line.lstrip()
indent = line[: len(line) - len(stripped)]
ending = "\n" if line.endswith("\n") else ""
content = stripped.rstrip("\n")
matched = False
for fkw in postfix_foreign:
if content.startswith(fkw + " ") and content.endswith(":"):
expr = content[len(fkw): -1].strip()
result.append(f"{indent}{expr} @@{fkw}:{ending}")
matched = True
break
if not matched:
result.append(line)
return "".join(result) return "".join(result)
def transpile(source: str, lang_code: str) -> str: def transpile(source: str, lang_code: str) -> str:
"""
Transpile foreign-language Python source to standard Python.
Uses the tokenizer so strings and comments are never touched.
"""
pack = load_pack(lang_code) pack = load_pack(lang_code)
mapping: dict[str, str] = {} mapping: dict[str, str] = {}
@@ -86,6 +103,47 @@ def transpile(source: str, lang_code: str) -> str:
return "".join(result) return "".join(result)
def detranspile(source: str, lang_code: str, postfix: bool = False) -> str:
pack = load_pack(lang_code)
en_to_foreign: dict[str, str] = {}
for section in ("keywords", "builtins", "exceptions", "stdlib"):
for foreign, english in pack[section].items():
en_to_foreign[english] = foreign
tokens_in = tokenize.generate_tokens(io.StringIO(source).readline)
result: list[str] = []
prev_end = (1, 0)
for tok in tokens_in:
tok_type, tok_string, tok_start, tok_end, _ = tok
start_row, start_col = tok_start
end_row, end_col = prev_end
if start_row == end_row:
result.append(" " * (start_col - end_col))
else:
result.append("\n" * (start_row - end_row))
result.append(" " * start_col)
if tok_type == tokenize.NAME and tok_string in en_to_foreign:
result.append(en_to_foreign[tok_string])
else:
result.append(tok_string)
prev_end = tok_end
output = "".join(result)
if postfix:
# Use pack-defined list, fallback to sensible defaults
postfix_english = set(pack.get("postfix_keywords", ["if", "elif", "while"]))
output = _apply_postfix_output(output, en_to_foreign, postfix_english)
return output
def transpile_file(path: Path) -> str: def transpile_file(path: Path) -> str:
lang_code = _detect_lang(path) lang_code = _detect_lang(path)
source = path.read_text(encoding="utf-8") source = path.read_text(encoding="utf-8")
@@ -93,6 +151,11 @@ def transpile_file(path: Path) -> str:
return transpile(source, lang_code) return transpile(source, lang_code)
def detranspile_file(path: Path, lang_code: str, postfix: bool = False) -> str:
source = path.read_text(encoding="utf-8")
return detranspile(source, lang_code, postfix=postfix)
def run_transpiled(original_path: Path, transpiled: str) -> None: def run_transpiled(original_path: Path, transpiled: str) -> None:
import linecache import linecache

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.1.1" 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.1.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,26 +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": []
} }

View File

@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
[project] [project]
name = "foreignthon-ta" name = "foreignthon-ta"
version = "0.1.1" version = "0.2.1"
description = "Tamil language pack for ForeignThon." description = "Tamil language pack for ForeignThon."
license = { text = "GPL v3" } license = { text = "GPL v3" }
requires-python = ">=3.9" requires-python = ">=3.9"
@@ -12,7 +12,7 @@ authors = [
{ name = "Keshav Anand", email = "keshavanand.dev@gmail.com" } { name = "Keshav Anand", email = "keshavanand.dev@gmail.com" }
] ]
keywords = ["foreignthon", "tamil", "தமிழ்"] keywords = ["foreignthon", "tamil", "தமிழ்"]
dependencies = ["foreignthon>=0.1.0"] dependencies = ["foreignthon>=0.4.0"]
[project.entry-points."foreignthon.langs"] [project.entry-points."foreignthon.langs"]
ta = "foreignthon_ta" ta = "foreignthon_ta"

View File

@@ -8,19 +8,19 @@
}, },
"keywords": { "keywords": {
"ஆனால்": "if", "ஆனால்": "if",
"இல்லையெனில்": "else", "மற்றபடி": "else",
"இல்லெனில்": "elif", "இல்லைஆனால்": "elif",
"ஒவ்வொன்றும்": "for", "ஆக": "for",
"தொடர்": "while", "வரை": "while",
"வரையறு": "def", "நிரல்பாகம்": "def",
"வகுப்பு": "class", "கோப்பு": "class",
"திரும்ப": "return", "பின்கோடு": "return",
"நிறுத்து": "break", "நிறுத்து": "break",
"தொடரவும்": "continue", "தொடர்": "continue",
"கடந்துசெல்": "pass", "கடந்துசெல்": "pass",
"முயற்சி": "try", "முயற்சி": "try",
"தவிர": "except", "தவிர": "except",
"இறுதியில்": "finally", "கடைசியில்": "finally",
"எழுப்பு": "raise", "எழுப்பு": "raise",
"உடன்": "with", "உடன்": "with",
"இறக்கு": "import", "இறக்கு": "import",
@@ -44,13 +44,13 @@
"ஒன்றுமில்லை": "None" "ஒன்றுமில்லை": "None"
}, },
"builtins": { "builtins": {
"அச்சிடு": "print", "பதிப்பி": "print",
"உள்ளீடு": "input", "உள்ளீடு": "input",
"நீளம்": "len", "நீளம்": "len",
"வரம்பு": "range", "வரம்பு": "range",
"வகை": "type", "வகை": "type",
"முழுஎண்": "int", "முழுஎண்": "int",
"மிதவை": "float", "தசமஎண்": "float",
"சரம்": "str", "சரம்": "str",
"பட்டியல்": "list", "பட்டியல்": "list",
"அகராதி": "dict", "அகராதி": "dict",
@@ -126,5 +126,16 @@
"தொகுப்புகள்": "collections", "தொகுப்புகள்": "collections",
"பாதை": "pathlib", "பாதை": "pathlib",
"வழக்கமொழி": "re" "வழக்கமொழி": "re"
} },
"postfix_keywords": [
"if",
"elif",
"while",
"class",
"with",
"try",
"except",
"finally",
"from"
]
} }