diff --git a/examples/manual_code/circle_pure.impl.jac b/examples/manual_code/circle_pure.impl.jac new file mode 100644 index 000000000..c93225739 --- /dev/null +++ b/examples/manual_code/circle_pure.impl.jac @@ -0,0 +1,27 @@ +"""Enum for shape types""" + +:enum:ShapeType { + CIRCLE = "Circle", + UNKNOWN = "Unknown" +} + +"""Function to calculate the area of a circle.""" +:can:calculate_area +(radius: float) -> float { + return math.pi * radius * radius; +} + +:obj:Circle:can: (radius: float) { + self.radius = radius; + .(ShapeType.CIRCLE); +} + +"""Overridden method to calculate the area of the circle.""" +:obj:Circle:can:area -> float { + return math.pi * .radius * .radius; +} + +:can:main_run { + print(f"Area of a circle with radius {RAD} using function: {calculate_area(RAD)}"); + print(f"Area of a {c.shape_type.value} with radius {RAD} using class: {c.area()}"); +} diff --git a/examples/manual_code/circle_pure.jac b/examples/manual_code/circle_pure.jac new file mode 100644 index 000000000..841abf7f8 --- /dev/null +++ b/examples/manual_code/circle_pure.jac @@ -0,0 +1,28 @@ +""" +This module demonstrates a simple circle class and a function to calculate +the area of a circle in all of Jac's glory. +""" +import:py math; + +enum ShapeType; +can calculate_area(radius: float) -> float; +can main_run; + +"""Base class for a shape.""" +obj Shape { + has shape_type: ShapeType; + can area -> float abs; +} + +"""Circle class inherits from Shape.""" +obj Circle:Shape: { + has radius: float; + can (radius: float); + can area -> float; +} + +# Radius of the demo circle +glob RAD = 5, c = Circle(radius=RAD); + +"""Here we run the main program.""" +with entry:__main__ { main_run(); } \ No newline at end of file diff --git a/examples/manual_code/circle_pure.test.jac b/examples/manual_code/circle_pure.test.jac new file mode 100644 index 000000000..4274ea09f --- /dev/null +++ b/examples/manual_code/circle_pure.test.jac @@ -0,0 +1,4 @@ +glob expected_area = 78.53981633974483; +test { check.AlmostEqual(calculate_area(RAD), expected_area); } +test { c = Circle(RAD); check.AlmostEqual(c.area(), expected_area); } +test { c = Circle(RAD); check.Equal(c.shape_type, ShapeType.CIRCLE); } \ No newline at end of file diff --git a/jaclang/compiler/absyntree.py b/jaclang/compiler/absyntree.py index 21a9a6382..3243397e4 100644 --- a/jaclang/compiler/absyntree.py +++ b/jaclang/compiler/absyntree.py @@ -271,12 +271,16 @@ def __init__( body: Sequence[ElementStmt | String | EmptyToken], is_imported: bool, kid: Sequence[AstNode], + impl_mod: Optional[Module] = None, + test_mod: Optional[Module] = None, ) -> None: """Initialize whole program node.""" self.name = name self.source = source self.body = body self.is_imported = is_imported + self.impl_mod = impl_mod + self.test_mod = test_mod self.mod_deps: dict[str, Module] = {} AstNode.__init__(self, kid=kid) AstDocNode.__init__(self, doc=doc) diff --git a/jaclang/compiler/passes/main/def_use_pass.py b/jaclang/compiler/passes/main/def_use_pass.py index d4be26169..bee4dbf96 100644 --- a/jaclang/compiler/passes/main/def_use_pass.py +++ b/jaclang/compiler/passes/main/def_use_pass.py @@ -17,14 +17,6 @@ class DefUsePass(SymTabPass): def after_pass(self) -> None: """After pass.""" - # for i in self.unlinked: - # if not i.sym_name.startswith("[") and type(i.parent) in [ - # ast.AtomTrailer, - # ]: - # self.warning( - # f"{i.sym_name} used before being defined.", - # node_override=i, - # ) def enter_architype(self, node: ast.Architype) -> None: """Sub objects. diff --git a/jaclang/compiler/passes/main/import_pass.py b/jaclang/compiler/passes/main/import_pass.py index b753ebdec..10af05f81 100644 --- a/jaclang/compiler/passes/main/import_pass.py +++ b/jaclang/compiler/passes/main/import_pass.py @@ -27,6 +27,7 @@ def before_pass(self) -> None: def enter_module(self, node: ast.Module) -> None: """Run Importer.""" self.cur_node = node + self.annex_impl(node) self.terminate() # Turns off auto traversal for deliberate traversal self.run_again = True while self.run_again: @@ -35,25 +36,44 @@ def enter_module(self, node: ast.Module) -> None: for i in all_imports: if i.lang.tag.value == "jac" and not i.sub_module: self.run_again = True - mod = ( - self.import_module( - node=i, - mod_path=node.loc.mod_path, - ) - if i.lang.tag.value == "jac" - else self.import_py_module(node=i, mod_path=node.loc.mod_path) + mod = self.import_module( + node=i, + mod_path=node.loc.mod_path, ) if not mod: self.run_again = False continue + self.annex_impl(mod) i.sub_module = mod i.add_kids_right([mod], pos_update=False) # elif i.lang.tag.value == "py": # self.import_py_module(node=i, mod_path=node.loc.mod_path) self.enter_import(i) SubNodeTabPass(prior=self, input_ir=node) + self.annex_impl(node) node.mod_deps = self.import_table + def annex_impl(self, node: ast.Module) -> None: + """Annex impl and test modules.""" + if not node.loc.mod_path: + self.error("Module has no path") + if node.loc.mod_path.endswith(".jac") and path.exists( + f"{node.loc.mod_path[:-4]}.impl.jac" + ): + mod = self.import_mod_from_file(f"{node.loc.mod_path[:-4]}.impl.jac") + if mod: + node.impl_mod = mod + node.add_kids_right([mod], pos_update=False) + mod.parent = node + if node.loc.mod_path.endswith(".jac") and path.exists( + f"{node.loc.mod_path[:-4]}.test.jac" + ): + mod = self.import_mod_from_file(f"{node.loc.mod_path[:-4]}.test.jac") + if mod: + node.test_mod = mod + node.add_kids_right([mod], pos_update=False) + mod.parent = node + def enter_import(self, node: ast.Import) -> None: """Sub objects. @@ -73,19 +93,22 @@ def enter_import(self, node: ast.Import) -> None: def import_module(self, node: ast.Import, mod_path: str) -> ast.Module | None: """Import a module.""" - from jaclang.compiler.transpiler import jac_file_to_pass - from jaclang.compiler.passes.main import SubNodeTabPass - self.cur_node = node # impacts error reporting target = import_target_to_relative_path( node.path.path_str, path.dirname(node.loc.mod_path) ) + return self.import_mod_from_file(target) - if target in self.import_table: - return self.import_table[target] + def import_mod_from_file(self, target: str) -> ast.Module | None: + """Import a module from a file.""" + from jaclang.compiler.transpiler import jac_file_to_pass + from jaclang.compiler.passes.main import SubNodeTabPass if not path.exists(target): - self.error(f"Could not find module {target}", node_override=node) + self.error(f"Could not find module {target}") + return None + if target in self.import_table: + return self.import_table[target] try: mod_pass = jac_file_to_pass(file_path=target, target=SubNodeTabPass) self.errors_had += mod_pass.errors_had @@ -99,9 +122,7 @@ def import_module(self, node: ast.Import, mod_path: str) -> ast.Module | None: mod.is_imported = True return mod else: - self.error( - f"Module {target} is not a valid Jac module.", node_override=node - ) + self.error(f"Module {target} is not a valid Jac module.") return None def import_py_module(self, node: ast.Import, mod_path: str) -> Optional[ast.Module]: diff --git a/jaclang/compiler/passes/main/sym_tab_build_pass.py b/jaclang/compiler/passes/main/sym_tab_build_pass.py index 79c1eca17..98930cfc6 100644 --- a/jaclang/compiler/passes/main/sym_tab_build_pass.py +++ b/jaclang/compiler/passes/main/sym_tab_build_pass.py @@ -254,6 +254,17 @@ def exit_module(self, node: ast.Module) -> None: is_imported: bool, """ self.pop_scope() + if ( + isinstance(node.parent, ast.Module) + and node + in [ + node.parent.impl_mod, + node.parent.test_mod, + ] + and node.sym_tab + ): + for v in node.sym_tab.tab.values(): + self.def_insert(v.decl, table_override=self.cur_scope()) def enter_global_vars(self, node: ast.GlobalVars) -> None: """Sub objects. diff --git a/jaclang/compiler/passes/tool/jac_formatter_pass.py b/jaclang/compiler/passes/tool/jac_formatter_pass.py index 8a7d1d706..48d9a6773 100644 --- a/jaclang/compiler/passes/tool/jac_formatter_pass.py +++ b/jaclang/compiler/passes/tool/jac_formatter_pass.py @@ -1764,8 +1764,8 @@ def exit_test(self, node: ast.Test) -> None: elif isinstance(i, ast.Semi): self.emit(node, i.gen.jac) elif isinstance(i, ast.Name): - # if not i.value.startswith("test"): - self.emit(node, f" {i.value} ") + if not i.value.startswith("test_t"): + self.emit(node, f" {i.value} ") else: if start: self.emit(node, i.gen.jac) diff --git a/jaclang/compiler/passes/tool/tests/test_jac_format_pass.py b/jaclang/compiler/passes/tool/tests/test_jac_format_pass.py index fc0e7b7a1..417fb54dd 100644 --- a/jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +++ b/jaclang/compiler/passes/tool/tests/test_jac_format_pass.py @@ -87,7 +87,7 @@ def micro_suite_test(self, filename: str) -> None: target=PyastGenPass, schedule=without_format, ) - if "circle_clean_tests.jac" in filename: + if "circle_clean_tests.jac" in filename or "circle_pure.test.jac" in filename: tokens = code_gen_format.ir.gen.jac.split() num_test = 0 for i in range(len(tokens)): diff --git a/jaclang/tests/test_man_code.py b/jaclang/tests/test_man_code.py index 90a0f0608..f3c1e9d45 100644 --- a/jaclang/tests/test_man_code.py +++ b/jaclang/tests/test_man_code.py @@ -65,6 +65,24 @@ def test_clean_circle_jac(self) -> None: stdout_value, ) + def test_pure_circle_jac(self) -> None: + """Basic test for pass.""" + captured_output = io.StringIO() + sys.stdout = captured_output + + # Execute the function + cli.run(self.fixture_abs_path("../../../examples/manual_code/circle_pure.jac")) # type: ignore + + sys.stdout = sys.__stdout__ + stdout_value = captured_output.getvalue() + + # Assertions or verifications + self.assertEqual( + "Area of a circle with radius 5 using function: 78.53981633974483\n" + "Area of a Circle with radius 5 using class: 78.53981633974483\n", + stdout_value, + ) + def test_clean_circle_jac_test(self) -> None: """Basic test for pass.""" captured_output = io.StringIO()