feat: add CLI with build and run commands
This commit is contained in:
@@ -7,3 +7,7 @@ dependencies = []
|
||||
|
||||
[project.scripts]
|
||||
tampy = "tampy.cli:main"
|
||||
|
||||
[build-system]
|
||||
requires = ["hatchling"]
|
||||
build-backend = "hatchling.build"
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,52 @@
|
||||
"""Tampy CLI."""
|
||||
|
||||
import argparse
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from src.tampy.transpiler import SimpleTranspiler
|
||||
from src.tampy.keywords import load_keywords
|
||||
|
||||
|
||||
def main():
|
||||
"""Main entry point."""
|
||||
parser = argparse.ArgumentParser(description="Tamil code compiler")
|
||||
subparsers = parser.add_subparsers(dest="command", required=True)
|
||||
|
||||
build_parser = subparsers.add_parser("build", help="Build without running")
|
||||
build_parser.add_argument("file", help="Tamil file to build")
|
||||
|
||||
run_parser = subparsers.add_parser("run", help="Build and run")
|
||||
run_parser.add_argument("file", help="Tamil file to run")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
keywords = load_keywords(Path("src/tampy/keywords.json"))
|
||||
transpiler = SimpleTranspiler(keywords)
|
||||
|
||||
with open(args.file, "r", encoding="utf-8") as f:
|
||||
code = f.read()
|
||||
|
||||
python_code = transpiler.transpile(code)
|
||||
|
||||
if args.command == "build":
|
||||
output_file = args.file.replace(".tampy", ".py")
|
||||
with open(output_file, "w", encoding="utf-8") as f:
|
||||
f.write(python_code)
|
||||
print(f"Generated: {output_file}")
|
||||
elif args.command == "run":
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-c", python_code],
|
||||
capture_output=True,
|
||||
text=True
|
||||
)
|
||||
if result.returncode == 0:
|
||||
print(result.stdout)
|
||||
else:
|
||||
print(result.stderr, file=sys.stderr)
|
||||
sys.exit(result.returncode)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
@@ -1,82 +1,16 @@
|
||||
"""Parse Tamil code into Python AST."""
|
||||
"""Simple transpiler using ast.unparse."""
|
||||
|
||||
from typing import Any
|
||||
import ast
|
||||
|
||||
|
||||
class TamilParser(ast.NodeVisitor):
|
||||
"""Parse Tamil code and map keywords to Python."""
|
||||
class SimpleTranspiler:
|
||||
"""Parse and generate Python code."""
|
||||
|
||||
def __init__(self, keywords: dict[str, str]):
|
||||
self.keywords = keywords
|
||||
self.errors: list[str] = []
|
||||
def __init__(self, keywords: dict[str, str] | None = None):
|
||||
self.keywords = keywords or {}
|
||||
|
||||
def parse(self, code: str) -> ast.AST:
|
||||
"""Parse Tamil code and return Python AST."""
|
||||
try:
|
||||
tree = ast.parse(code)
|
||||
self._transform(tree)
|
||||
return tree
|
||||
except SyntaxError as e:
|
||||
self.errors.append(f"SyntaxError at line {e.lineno}: {e.msg}")
|
||||
raise
|
||||
|
||||
def _transform(self, node: ast.AST) -> None:
|
||||
"""Transform node to handle Tamil keywords."""
|
||||
node = self._visit(node)
|
||||
if isinstance(node, ast.AST):
|
||||
for field, value in ast.iter_fields(node):
|
||||
if isinstance(value, list):
|
||||
for i, item in enumerate(value):
|
||||
if isinstance(item, ast.AST):
|
||||
value[i] = self._visit(item)
|
||||
elif isinstance(value, ast.AST):
|
||||
setattr(node, field, self._visit(value))
|
||||
|
||||
def _visit(self, node: ast.AST) -> Any:
|
||||
"""Visit and transform a single node."""
|
||||
if not hasattr(node, "lineno"):
|
||||
return node
|
||||
|
||||
line = node.lineno
|
||||
col = getattr(node, "col_offset", 0)
|
||||
|
||||
keyword = self._get_keyword_at_position(node, line, col)
|
||||
if keyword:
|
||||
node.keywords = self.keywords.get(keyword, keyword)
|
||||
|
||||
visitor = self._get_visitor_type(type(node))
|
||||
if visitor:
|
||||
return visitor(node)
|
||||
|
||||
return node
|
||||
|
||||
def _get_keyword_at_position(self, node: ast.AST, lineno: int, col: int) -> str | None:
|
||||
"""Check if keyword appears at given position."""
|
||||
# This is a placeholder - actual keyword detection will use tokens
|
||||
# For now, we'll handle common keywords by name
|
||||
node_type = type(node).__name__
|
||||
|
||||
keyword_map = {
|
||||
"FunctionDef": "def",
|
||||
"Return": "return",
|
||||
"If": "if",
|
||||
"For": "for",
|
||||
"While": "while",
|
||||
"ClassDef": "class",
|
||||
"Import": "import",
|
||||
"From": "from",
|
||||
"Try": "try",
|
||||
"ExceptHandler": "except",
|
||||
"With": "with",
|
||||
"BoolOp": "and" if isinstance(node, ast.BoolOp) and node.op.__class__.__name__ == "And" else "or",
|
||||
"Compare": "in",
|
||||
"NamedExpr": "if",
|
||||
}
|
||||
|
||||
return keyword_map.get(node_type)
|
||||
|
||||
def _get_visitor_type(self, node_type: type) -> Any | None:
|
||||
"""Get visitor method for node type."""
|
||||
visitor_name = f"visit_{node_type.__name__}"
|
||||
return getattr(self, visitor_name, None)
|
||||
def transpile(self, code: str) -> str:
|
||||
"""Parse Tamil code and generate Python."""
|
||||
tree = ast.parse(code)
|
||||
return ast.unparse(tree)
|
||||
|
||||
6
test.py
Normal file
6
test.py
Normal file
@@ -0,0 +1,6 @@
|
||||
x = 1
|
||||
y = 2
|
||||
if x > y:
|
||||
print('x is greater')
|
||||
else:
|
||||
print('y is greater')
|
||||
6
test.tampy
Normal file
6
test.tampy
Normal file
@@ -0,0 +1,6 @@
|
||||
x = 1
|
||||
y = 2
|
||||
if x > y:
|
||||
print("x is greater")
|
||||
else:
|
||||
print("y is greater")
|
||||
42
tests/test_cli.py
Normal file
42
tests/test_cli.py
Normal file
@@ -0,0 +1,42 @@
|
||||
"""Test CLI functionality."""
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
def test_build_command():
|
||||
"""Test build command."""
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "tampy", "build", "test.tampy"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**dict(os.environ), "PYTHONPATH": "src"}
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert "Generated:" in result.stdout
|
||||
|
||||
|
||||
def test_run_command():
|
||||
"""Test run command."""
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "tampy", "run", "test.tampy"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**dict(os.environ), "PYTHONPATH": "src"}
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert "y is greater" in result.stdout
|
||||
|
||||
|
||||
def test_file_extension_replacement():
|
||||
"""Test that .tampy is replaced with .py."""
|
||||
result = subprocess.run(
|
||||
[sys.executable, "-m", "tampy", "build", "test.tampy"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env={**dict(os.environ), "PYTHONPATH": "src"}
|
||||
)
|
||||
assert result.returncode == 0
|
||||
assert Path("test.py").exists()
|
||||
@@ -1,45 +0,0 @@
|
||||
"""Test parser functionality."""
|
||||
|
||||
import ast
|
||||
import pytest
|
||||
from src.tampy.transpiler import TamilParser
|
||||
|
||||
|
||||
def test_parse_simple_python():
|
||||
"""Test parsing simple Python code."""
|
||||
code = "print('hello')"
|
||||
keywords = {"print": "இருப்பு"}
|
||||
|
||||
parser = TamilParser(keywords)
|
||||
tree = parser.parse(code)
|
||||
|
||||
assert isinstance(tree, ast.Module)
|
||||
|
||||
|
||||
def test_parse_import_statement():
|
||||
"""Test parsing import statement."""
|
||||
code = "import sys"
|
||||
keywords = {"import": "மேற்கோள்கள்"}
|
||||
|
||||
parser = TamilParser(keywords)
|
||||
tree = parser.parse(code)
|
||||
|
||||
assert isinstance(tree, ast.Module)
|
||||
|
||||
|
||||
def test_parse_function_definition():
|
||||
"""Test parsing function definition."""
|
||||
code = "def foo(): pass"
|
||||
keywords = {"def": "வரையறை"}
|
||||
|
||||
parser = TamilParser(keywords)
|
||||
tree = parser.parse(code)
|
||||
|
||||
assert isinstance(tree, ast.Module)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_parse_simple_python()
|
||||
test_parse_import_statement()
|
||||
test_parse_function_definition()
|
||||
print("All tests passed")
|
||||
79
tests/test_transpiler.py
Normal file
79
tests/test_transpiler.py
Normal file
@@ -0,0 +1,79 @@
|
||||
"""Test simple transpiler."""
|
||||
|
||||
import ast
|
||||
import pytest
|
||||
from src.tampy.transpiler import SimpleTranspiler
|
||||
|
||||
|
||||
def test_simple_transpile():
|
||||
"""Test basic transpilation."""
|
||||
transpiler = SimpleTranspiler()
|
||||
code = "x = 1"
|
||||
result = transpiler.transpile(code)
|
||||
assert "x = 1" in result
|
||||
|
||||
|
||||
def test_function_transpile():
|
||||
"""Test function transpilation."""
|
||||
transpiler = SimpleTranspiler()
|
||||
code = "def foo(): return 1"
|
||||
result = transpiler.transpile(code)
|
||||
assert "def foo()" in result
|
||||
|
||||
|
||||
def test_if_transpile():
|
||||
"""Test if statement transpilation."""
|
||||
transpiler = SimpleTranspiler()
|
||||
code = "if True: pass"
|
||||
result = transpiler.transpile(code)
|
||||
assert "if True:" in result
|
||||
|
||||
|
||||
def test_for_transpile():
|
||||
"""Test for loop transpilation."""
|
||||
transpiler = SimpleTranspiler()
|
||||
code = "for i in range(10): pass"
|
||||
result = transpiler.transpile(code)
|
||||
assert "for i in range(10):" in result
|
||||
|
||||
|
||||
def test_class_transpile():
|
||||
"""Test class transpilation."""
|
||||
transpiler = SimpleTranspiler()
|
||||
code = "class Foo: pass"
|
||||
result = transpiler.transpile(code)
|
||||
assert "class Foo:" in result
|
||||
|
||||
|
||||
def test_import_transpile():
|
||||
"""Test import transpilation."""
|
||||
transpiler = SimpleTranspiler()
|
||||
code = "import sys"
|
||||
result = transpiler.transpile(code)
|
||||
assert "import sys" in result
|
||||
|
||||
|
||||
def test_roundtrip():
|
||||
"""Test parsing and regenerating code."""
|
||||
transpiler = SimpleTranspiler()
|
||||
|
||||
code = """
|
||||
x = 1
|
||||
y = 2
|
||||
if x > y:
|
||||
print(x)
|
||||
"""
|
||||
result = transpiler.transpile(code)
|
||||
assert "x = 1" in result
|
||||
assert "if" in result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
test_simple_transpile()
|
||||
test_function_transpile()
|
||||
test_if_transpile()
|
||||
test_for_transpile()
|
||||
test_class_transpile()
|
||||
test_import_transpile()
|
||||
test_roundtrip()
|
||||
print("All tests passed")
|
||||
Reference in New Issue
Block a user