jaclang 0.8.8__py3-none-any.whl → 0.8.10__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of jaclang might be problematic. Click here for more details.
- jaclang/cli/cli.py +194 -10
- jaclang/cli/cmdreg.py +144 -8
- jaclang/compiler/__init__.py +6 -1
- jaclang/compiler/codeinfo.py +16 -1
- jaclang/compiler/constant.py +33 -8
- jaclang/compiler/jac.lark +154 -62
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +656 -149
- jaclang/compiler/passes/__init__.py +2 -1
- jaclang/compiler/passes/ast_gen/__init__.py +5 -0
- jaclang/compiler/passes/ast_gen/base_ast_gen_pass.py +54 -0
- jaclang/compiler/passes/ast_gen/jsx_processor.py +344 -0
- jaclang/compiler/passes/ecmascript/__init__.py +25 -0
- jaclang/compiler/passes/ecmascript/es_unparse.py +576 -0
- jaclang/compiler/passes/ecmascript/esast_gen_pass.py +2068 -0
- jaclang/compiler/passes/ecmascript/estree.py +972 -0
- jaclang/compiler/passes/ecmascript/tests/__init__.py +1 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/advanced_language_features.jac +170 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.impl.jac +30 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/class_separate_impl.jac +14 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/client_jsx.jac +89 -0
- jaclang/compiler/passes/ecmascript/tests/fixtures/core_language_features.jac +195 -0
- jaclang/compiler/passes/ecmascript/tests/test_esast_gen_pass.py +167 -0
- jaclang/compiler/passes/ecmascript/tests/test_js_generation.py +239 -0
- jaclang/compiler/passes/main/__init__.py +0 -3
- jaclang/compiler/passes/main/annex_pass.py +23 -1
- jaclang/compiler/passes/main/def_use_pass.py +1 -0
- jaclang/compiler/passes/main/pyast_gen_pass.py +413 -255
- jaclang/compiler/passes/main/pyast_load_pass.py +48 -11
- jaclang/compiler/passes/main/pyjac_ast_link_pass.py +2 -0
- jaclang/compiler/passes/main/sym_tab_build_pass.py +18 -1
- jaclang/compiler/passes/main/tests/fixtures/autoimpl.cl.jac +7 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_arity.jac +3 -0
- jaclang/compiler/passes/main/tests/fixtures/checker_class_construct.jac +33 -0
- jaclang/compiler/passes/main/tests/fixtures/defuse_modpath.jac +7 -0
- jaclang/compiler/passes/main/tests/fixtures/member_access_type_resolve.jac +2 -1
- jaclang/compiler/passes/main/tests/test_checker_pass.py +31 -3
- jaclang/compiler/passes/main/tests/test_def_use_pass.py +12 -0
- jaclang/compiler/passes/main/tests/test_import_pass.py +23 -4
- jaclang/compiler/passes/main/tests/test_predynamo_pass.py +13 -14
- jaclang/compiler/passes/main/tests/test_pyast_gen_pass.py +25 -0
- jaclang/compiler/passes/main/type_checker_pass.py +7 -0
- jaclang/compiler/passes/tool/doc_ir_gen_pass.py +219 -20
- jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
- jaclang/compiler/passes/tool/jac_formatter_pass.py +2 -2
- jaclang/compiler/passes/tool/tests/fixtures/import_fmt.jac +7 -1
- jaclang/compiler/passes/tool/tests/fixtures/tagbreak.jac +135 -29
- jaclang/compiler/passes/tool/tests/test_jac_format_pass.py +4 -1
- jaclang/compiler/passes/transform.py +9 -1
- jaclang/compiler/passes/uni_pass.py +5 -7
- jaclang/compiler/program.py +27 -26
- jaclang/compiler/tests/test_client_codegen.py +113 -0
- jaclang/compiler/tests/test_importer.py +12 -10
- jaclang/compiler/tests/test_parser.py +249 -3
- jaclang/compiler/type_system/type_evaluator.jac +1078 -0
- jaclang/compiler/type_system/type_utils.py +1 -1
- jaclang/compiler/type_system/types.py +6 -0
- jaclang/compiler/unitree.py +438 -82
- jaclang/langserve/engine.jac +224 -288
- jaclang/langserve/sem_manager.jac +12 -8
- jaclang/langserve/server.jac +48 -48
- jaclang/langserve/tests/fixtures/greet.py +17 -0
- jaclang/langserve/tests/fixtures/md_path.jac +22 -0
- jaclang/langserve/tests/fixtures/user.jac +15 -0
- jaclang/langserve/tests/test_server.py +66 -371
- jaclang/lib.py +17 -0
- jaclang/runtimelib/archetype.py +25 -25
- jaclang/runtimelib/client_bundle.py +169 -0
- jaclang/runtimelib/client_runtime.jac +586 -0
- jaclang/runtimelib/constructs.py +4 -2
- jaclang/runtimelib/machine.py +308 -139
- jaclang/runtimelib/meta_importer.py +111 -22
- jaclang/runtimelib/mtp.py +15 -0
- jaclang/runtimelib/server.py +1089 -0
- jaclang/runtimelib/tests/fixtures/client_app.jac +18 -0
- jaclang/runtimelib/tests/fixtures/custom_access_validation.jac +1 -1
- jaclang/runtimelib/tests/fixtures/savable_object.jac +4 -5
- jaclang/runtimelib/tests/fixtures/serve_api.jac +75 -0
- jaclang/runtimelib/tests/test_client_bundle.py +55 -0
- jaclang/runtimelib/tests/test_client_render.py +63 -0
- jaclang/runtimelib/tests/test_serve.py +1069 -0
- jaclang/settings.py +0 -3
- jaclang/tests/fixtures/attr_pattern_case.jac +18 -0
- jaclang/tests/fixtures/funccall_genexpr.jac +7 -0
- jaclang/tests/fixtures/funccall_genexpr.py +5 -0
- jaclang/tests/fixtures/iife_functions.jac +142 -0
- jaclang/tests/fixtures/iife_functions_client.jac +143 -0
- jaclang/tests/fixtures/multistatement_lambda.jac +116 -0
- jaclang/tests/fixtures/multistatement_lambda_client.jac +113 -0
- jaclang/tests/fixtures/needs_import_dup.jac +6 -4
- jaclang/tests/fixtures/py2jac_empty.py +0 -0
- jaclang/tests/fixtures/py_run.py +7 -5
- jaclang/tests/fixtures/pyfunc_fstr.py +2 -2
- jaclang/tests/fixtures/simple_lambda_test.jac +12 -0
- jaclang/tests/test_cli.py +134 -18
- jaclang/tests/test_language.py +120 -32
- jaclang/tests/test_reference.py +20 -3
- jaclang/utils/NonGPT.py +375 -0
- jaclang/utils/helpers.py +64 -20
- jaclang/utils/lang_tools.py +31 -4
- jaclang/utils/tests/test_lang_tools.py +5 -16
- jaclang/utils/treeprinter.py +8 -3
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/RECORD +106 -71
- jaclang/compiler/passes/main/binder_pass.py +0 -594
- jaclang/compiler/passes/main/tests/fixtures/sym_binder.jac +0 -47
- jaclang/compiler/passes/main/tests/test_binder_pass.py +0 -111
- jaclang/compiler/type_system/type_evaluator.py +0 -844
- jaclang/langserve/tests/session.jac +0 -294
- jaclang/langserve/tests/test_dev_server.py +0 -80
- jaclang/runtimelib/importer.py +0 -351
- jaclang/tests/test_typecheck.py +0 -542
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
- {jaclang-0.8.8.dist-info → jaclang-0.8.10.dist-info}/entry_points.txt +0 -0
jaclang/cli/cli.py
CHANGED
|
@@ -22,9 +22,6 @@ from jaclang.settings import settings
|
|
|
22
22
|
from jaclang.utils.helpers import debugger as db
|
|
23
23
|
from jaclang.utils.lang_tools import AstTool
|
|
24
24
|
|
|
25
|
-
Jac.create_cmd()
|
|
26
|
-
Jac.setup()
|
|
27
|
-
|
|
28
25
|
|
|
29
26
|
@cmd_registry.register
|
|
30
27
|
def format(path: str, outfile: str = "", to_screen: bool = False) -> None:
|
|
@@ -62,7 +59,7 @@ def format(path: str, outfile: str = "", to_screen: bool = False) -> None:
|
|
|
62
59
|
if path.endswith(".jac"):
|
|
63
60
|
if not path_obj.exists():
|
|
64
61
|
print(f"Error: File '{path}' does not exist.", file=sys.stderr)
|
|
65
|
-
|
|
62
|
+
exit(1)
|
|
66
63
|
formatted_code = JacProgram.jac_file_formatter(str(path_obj))
|
|
67
64
|
write_formatted_code(formatted_code, str(path_obj))
|
|
68
65
|
return
|
|
@@ -79,6 +76,7 @@ def format(path: str, outfile: str = "", to_screen: bool = False) -> None:
|
|
|
79
76
|
|
|
80
77
|
# Case 3: Invalid path
|
|
81
78
|
print(f"Error: '{path}' is not a .jac file or directory.", file=sys.stderr)
|
|
79
|
+
exit(1)
|
|
82
80
|
|
|
83
81
|
|
|
84
82
|
def proc_file_sess(
|
|
@@ -104,6 +102,7 @@ def proc_file_sess(
|
|
|
104
102
|
"Not a valid file!\nOnly supports `.jac`, `.jir`, and `.py`",
|
|
105
103
|
file=sys.stderr,
|
|
106
104
|
)
|
|
105
|
+
exit(1)
|
|
107
106
|
mach = ExecutionContext(session=session, root=root)
|
|
108
107
|
Jac.set_context(mach)
|
|
109
108
|
return base, mod, mach
|
|
@@ -148,7 +147,11 @@ def run(
|
|
|
148
147
|
lng=lng,
|
|
149
148
|
)
|
|
150
149
|
except Exception as e:
|
|
151
|
-
|
|
150
|
+
from jaclang.utils.helpers import dump_traceback
|
|
151
|
+
|
|
152
|
+
print(dump_traceback(e), file=sys.stderr)
|
|
153
|
+
mach.close()
|
|
154
|
+
exit(1)
|
|
152
155
|
elif filename.endswith(".jir"):
|
|
153
156
|
try:
|
|
154
157
|
with open(filename, "rb") as f:
|
|
@@ -160,7 +163,11 @@ def run(
|
|
|
160
163
|
lng=lng,
|
|
161
164
|
)
|
|
162
165
|
except Exception as e:
|
|
163
|
-
|
|
166
|
+
from jaclang.utils.helpers import dump_traceback
|
|
167
|
+
|
|
168
|
+
print(dump_traceback(e), file=sys.stderr)
|
|
169
|
+
mach.close()
|
|
170
|
+
exit(1)
|
|
164
171
|
|
|
165
172
|
mach.close()
|
|
166
173
|
|
|
@@ -208,6 +215,8 @@ def get_object(filename: str, id: str, session: str = "", main: bool = True) ->
|
|
|
208
215
|
data = obj.__jac__.__getstate__()
|
|
209
216
|
else:
|
|
210
217
|
print(f"Object with id {id} not found.", file=sys.stderr)
|
|
218
|
+
mach.close()
|
|
219
|
+
exit(1)
|
|
211
220
|
mach.close()
|
|
212
221
|
return data
|
|
213
222
|
|
|
@@ -238,6 +247,9 @@ def build(filename: str, typecheck: bool = False) -> None:
|
|
|
238
247
|
for alrt in out.errors_had + out.warnings_had:
|
|
239
248
|
print(alrt.pretty_print(), file=sys.stderr)
|
|
240
249
|
|
|
250
|
+
if errs > 0:
|
|
251
|
+
exit(1)
|
|
252
|
+
|
|
241
253
|
with open(filename[:-4] + ".jir", "wb") as f:
|
|
242
254
|
pickle.dump(out, f)
|
|
243
255
|
|
|
@@ -273,8 +285,11 @@ def bind(filename: str, typecheck: bool = False) -> None:
|
|
|
273
285
|
divider = "=" * 40
|
|
274
286
|
print(f"{divider}\n{header}\n{divider}\n{mods.sym_tab.sym_pp()}")
|
|
275
287
|
print(f"Errors: {errs}, Warnings: {warnings}")
|
|
288
|
+
if errs > 0:
|
|
289
|
+
exit(1)
|
|
276
290
|
else:
|
|
277
291
|
print("Not a .jac/.py file.", file=sys.stderr)
|
|
292
|
+
exit(1)
|
|
278
293
|
|
|
279
294
|
|
|
280
295
|
@cmd_registry.register
|
|
@@ -301,8 +316,11 @@ def check(filename: str, print_errs: bool = True) -> None:
|
|
|
301
316
|
for e in prog.errors_had:
|
|
302
317
|
print("Error:", e, file=sys.stderr)
|
|
303
318
|
print(f"Errors: {errs}, Warnings: {warnings}")
|
|
319
|
+
if errs > 0:
|
|
320
|
+
exit(1)
|
|
304
321
|
else:
|
|
305
322
|
print("Not a .jac file.", file=sys.stderr)
|
|
323
|
+
exit(1)
|
|
306
324
|
|
|
307
325
|
|
|
308
326
|
@cmd_registry.register
|
|
@@ -376,6 +394,8 @@ def enter(
|
|
|
376
394
|
(loaded_mod,) = ret_module
|
|
377
395
|
if not loaded_mod:
|
|
378
396
|
print("Errors occurred while importing the module.", file=sys.stderr)
|
|
397
|
+
mach.close()
|
|
398
|
+
exit(1)
|
|
379
399
|
else:
|
|
380
400
|
archetype = getattr(loaded_mod, entrypoint)(*args)
|
|
381
401
|
|
|
@@ -466,6 +486,7 @@ def tool(tool: str, args: Optional[list] = None) -> None:
|
|
|
466
486
|
raise e
|
|
467
487
|
else:
|
|
468
488
|
print(f"Ast tool {tool} not found.", file=sys.stderr)
|
|
489
|
+
exit(1)
|
|
469
490
|
|
|
470
491
|
|
|
471
492
|
@cmd_registry.register
|
|
@@ -503,8 +524,10 @@ def debug(filename: str, main: bool = True, cache: bool = False) -> None:
|
|
|
503
524
|
print("Done debugging.")
|
|
504
525
|
else:
|
|
505
526
|
print(f"Error while generating bytecode in {filename}.", file=sys.stderr)
|
|
527
|
+
exit(1)
|
|
506
528
|
else:
|
|
507
529
|
print("Not a .jac file.", file=sys.stderr)
|
|
530
|
+
exit(1)
|
|
508
531
|
|
|
509
532
|
|
|
510
533
|
@cmd_registry.register
|
|
@@ -575,6 +598,7 @@ def dot(
|
|
|
575
598
|
jac_machine.close()
|
|
576
599
|
else:
|
|
577
600
|
print("Not a .jac file.", file=sys.stderr)
|
|
601
|
+
exit(1)
|
|
578
602
|
|
|
579
603
|
|
|
580
604
|
@cmd_registry.register
|
|
@@ -607,17 +631,20 @@ def py2jac(filename: str) -> None:
|
|
|
607
631
|
print(formatted_code)
|
|
608
632
|
else:
|
|
609
633
|
print("Error converting Python code to Jac.", file=sys.stderr)
|
|
634
|
+
exit(1)
|
|
610
635
|
else:
|
|
611
636
|
print("Not a .py file.")
|
|
637
|
+
exit(1)
|
|
612
638
|
|
|
613
639
|
|
|
614
640
|
@cmd_registry.register
|
|
615
641
|
def jac2py(filename: str) -> None:
|
|
616
642
|
"""Convert a Jac file to Python code.
|
|
617
643
|
|
|
618
|
-
Translates Jac source code to equivalent Python code.
|
|
619
|
-
|
|
620
|
-
|
|
644
|
+
Translates Jac source code to equivalent Python code. The generated Python
|
|
645
|
+
uses direct imports from jaclang.lib, making the output clean and suitable
|
|
646
|
+
for use as a standalone library or for integrating Jac components with
|
|
647
|
+
Python projects.
|
|
621
648
|
|
|
622
649
|
Args:
|
|
623
650
|
filename: Path to the .jac file to convert
|
|
@@ -627,9 +654,166 @@ def jac2py(filename: str) -> None:
|
|
|
627
654
|
"""
|
|
628
655
|
if filename.endswith(".jac"):
|
|
629
656
|
code = JacProgram().compile(file_path=filename).gen.py
|
|
630
|
-
|
|
657
|
+
if code:
|
|
658
|
+
print(code)
|
|
659
|
+
else:
|
|
660
|
+
exit(1)
|
|
661
|
+
else:
|
|
662
|
+
print("Not a .jac file.", file=sys.stderr)
|
|
663
|
+
exit(1)
|
|
664
|
+
|
|
665
|
+
|
|
666
|
+
@cmd_registry.register
|
|
667
|
+
def js(filename: str) -> None:
|
|
668
|
+
"""Convert a Jac file to JavaScript code.
|
|
669
|
+
|
|
670
|
+
Translates Jac source code to equivalent JavaScript/ECMAScript code using
|
|
671
|
+
the ESTree AST specification. This allows Jac programs to run in JavaScript
|
|
672
|
+
environments like Node.js or web browsers.
|
|
673
|
+
|
|
674
|
+
Args:
|
|
675
|
+
filename: Path to the .jac file to convert
|
|
676
|
+
|
|
677
|
+
Examples:
|
|
678
|
+
jac js myprogram.jac > myprogram.js
|
|
679
|
+
jac js myprogram.jac
|
|
680
|
+
"""
|
|
681
|
+
if filename.endswith(".jac"):
|
|
682
|
+
try:
|
|
683
|
+
prog = JacProgram()
|
|
684
|
+
ir = prog.compile(file_path=filename)
|
|
685
|
+
|
|
686
|
+
if prog.errors_had:
|
|
687
|
+
for error in prog.errors_had:
|
|
688
|
+
print(f"Error: {error}", file=sys.stderr)
|
|
689
|
+
exit(1)
|
|
690
|
+
js_output = ir.gen.js or ""
|
|
691
|
+
if not js_output.strip():
|
|
692
|
+
print(
|
|
693
|
+
"ECMAScript code generation produced no output.",
|
|
694
|
+
file=sys.stderr,
|
|
695
|
+
)
|
|
696
|
+
exit(1)
|
|
697
|
+
print(js_output)
|
|
698
|
+
except Exception as e:
|
|
699
|
+
print(f"Error generating JavaScript: {e}", file=sys.stderr)
|
|
700
|
+
import traceback
|
|
701
|
+
|
|
702
|
+
traceback.print_exc()
|
|
703
|
+
exit(1)
|
|
631
704
|
else:
|
|
632
705
|
print("Not a .jac file.", file=sys.stderr)
|
|
706
|
+
exit(1)
|
|
707
|
+
|
|
708
|
+
|
|
709
|
+
# Register core commands first (before plugins load)
|
|
710
|
+
# These can be overridden by plugins with higher priority
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
@cmd_registry.register
|
|
714
|
+
def serve(
|
|
715
|
+
filename: str,
|
|
716
|
+
session: str = "",
|
|
717
|
+
port: int = 8000,
|
|
718
|
+
main: bool = True,
|
|
719
|
+
faux: bool = False,
|
|
720
|
+
) -> None:
|
|
721
|
+
"""Start a REST API server for the specified .jac file.
|
|
722
|
+
|
|
723
|
+
Executes the target module and turns all functions into authenticated REST API
|
|
724
|
+
endpoints. Function signatures are introspected to create the API interface.
|
|
725
|
+
Walkers are converted to REST APIs where their fields become the interface,
|
|
726
|
+
with an additional target_node field for spawning location.
|
|
727
|
+
|
|
728
|
+
Each user gets their own persistent root node that persists across runs.
|
|
729
|
+
Users must create an account and authenticate to access the API.
|
|
730
|
+
|
|
731
|
+
Args:
|
|
732
|
+
filename: Path to the .jac file to serve
|
|
733
|
+
session: Session identifier for persistent state (default: auto-generated)
|
|
734
|
+
port: Port to run the server on (default: 8000)
|
|
735
|
+
main: Treat the module as __main__ (default: True)
|
|
736
|
+
faux: Perform introspection and print endpoint docs without starting server (default: False)
|
|
737
|
+
|
|
738
|
+
Examples:
|
|
739
|
+
jac serve myprogram.jac
|
|
740
|
+
jac serve myprogram.jac --port 8080
|
|
741
|
+
jac serve myprogram.jac --session myapp.session
|
|
742
|
+
jac serve myprogram.jac --faux
|
|
743
|
+
"""
|
|
744
|
+
from jaclang.runtimelib.server import JacAPIServer
|
|
745
|
+
|
|
746
|
+
# Process file and session
|
|
747
|
+
base, mod, mach = proc_file_sess(filename, session)
|
|
748
|
+
lng = filename.split(".")[-1]
|
|
749
|
+
Jac.set_base_path(base)
|
|
750
|
+
|
|
751
|
+
# Import the module
|
|
752
|
+
if filename.endswith((".jac", ".py")):
|
|
753
|
+
try:
|
|
754
|
+
Jac.jac_import(
|
|
755
|
+
target=mod,
|
|
756
|
+
base_path=base,
|
|
757
|
+
lng=lng,
|
|
758
|
+
)
|
|
759
|
+
except Exception as e:
|
|
760
|
+
print(f"Error loading {filename}: {e}", file=sys.stderr)
|
|
761
|
+
mach.close()
|
|
762
|
+
exit(1)
|
|
763
|
+
elif filename.endswith(".jir"):
|
|
764
|
+
try:
|
|
765
|
+
with open(filename, "rb") as f:
|
|
766
|
+
Jac.attach_program(pickle.load(f))
|
|
767
|
+
Jac.jac_import(
|
|
768
|
+
target=mod,
|
|
769
|
+
base_path=base,
|
|
770
|
+
lng=lng,
|
|
771
|
+
)
|
|
772
|
+
except Exception as e:
|
|
773
|
+
print(f"Error loading {filename}: {e}", file=sys.stderr)
|
|
774
|
+
mach.close()
|
|
775
|
+
exit(1)
|
|
776
|
+
|
|
777
|
+
# Create and start the API server
|
|
778
|
+
# Use session path for persistent storage across user sessions
|
|
779
|
+
session_path = session if session else os.path.join(base, f"{mod}.session")
|
|
780
|
+
|
|
781
|
+
server = JacAPIServer(
|
|
782
|
+
module_name=mod,
|
|
783
|
+
session_path=session_path,
|
|
784
|
+
port=port,
|
|
785
|
+
base_path=base,
|
|
786
|
+
)
|
|
787
|
+
|
|
788
|
+
# If faux mode, print endpoint documentation and exit
|
|
789
|
+
if faux:
|
|
790
|
+
try:
|
|
791
|
+
server.print_endpoint_docs()
|
|
792
|
+
mach.close()
|
|
793
|
+
return
|
|
794
|
+
except Exception as e:
|
|
795
|
+
print(f"Error generating endpoint documentation: {e}", file=sys.stderr)
|
|
796
|
+
mach.close()
|
|
797
|
+
exit(1)
|
|
798
|
+
|
|
799
|
+
# Don't close the context - keep the module loaded for the server
|
|
800
|
+
# mach.close()
|
|
801
|
+
|
|
802
|
+
try:
|
|
803
|
+
server.start()
|
|
804
|
+
except KeyboardInterrupt:
|
|
805
|
+
print("\nServer stopped.")
|
|
806
|
+
mach.close() # Close on shutdown
|
|
807
|
+
except Exception as e:
|
|
808
|
+
print(f"Server error: {e}", file=sys.stderr)
|
|
809
|
+
mach.close()
|
|
810
|
+
exit(1)
|
|
811
|
+
|
|
812
|
+
|
|
813
|
+
Jac.create_cmd()
|
|
814
|
+
Jac.setup()
|
|
815
|
+
|
|
816
|
+
cmd_registry.finalize()
|
|
633
817
|
|
|
634
818
|
|
|
635
819
|
def start_cli() -> None:
|
jaclang/cli/cmdreg.py
CHANGED
|
@@ -4,23 +4,47 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import argparse
|
|
6
6
|
import inspect
|
|
7
|
+
import os
|
|
7
8
|
import re
|
|
9
|
+
import sys
|
|
8
10
|
from dataclasses import fields as dataclass_fields
|
|
11
|
+
from enum import IntEnum
|
|
9
12
|
from typing import Callable, Dict, Optional
|
|
10
13
|
|
|
11
14
|
from jaclang.settings import Settings as JacSettings
|
|
12
15
|
|
|
13
16
|
|
|
17
|
+
class CommandPriority(IntEnum):
|
|
18
|
+
"""Priority levels for command registration.
|
|
19
|
+
|
|
20
|
+
Higher values take precedence when multiple commands with the same name are registered.
|
|
21
|
+
This allows plugins to override core commands in a controlled manner.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
CORE = 100 # Core jaclang commands (lowest priority, can be overridden)
|
|
25
|
+
PLUGIN = 200 # Plugin-provided commands (override core)
|
|
26
|
+
USER = 300 # User-defined commands (highest priority, override everything)
|
|
27
|
+
|
|
28
|
+
|
|
14
29
|
class Command:
|
|
15
30
|
"""Represents a command in the command line interface."""
|
|
16
31
|
|
|
17
32
|
func: Callable
|
|
18
33
|
sig: inspect.Signature
|
|
19
|
-
|
|
20
|
-
|
|
34
|
+
priority: CommandPriority
|
|
35
|
+
source: str # Source plugin/module name
|
|
36
|
+
|
|
37
|
+
def __init__(
|
|
38
|
+
self,
|
|
39
|
+
func: Callable,
|
|
40
|
+
priority: CommandPriority = CommandPriority.CORE,
|
|
41
|
+
source: str = "core",
|
|
42
|
+
) -> None:
|
|
21
43
|
"""Initialize a Command instance."""
|
|
22
44
|
self.func = func
|
|
23
45
|
self.sig = inspect.signature(func)
|
|
46
|
+
self.priority = priority
|
|
47
|
+
self.source = source
|
|
24
48
|
|
|
25
49
|
def call(self, *args: list, **kwargs: dict) -> str:
|
|
26
50
|
"""Call the associated function with the specified arguments and keyword arguments."""
|
|
@@ -82,13 +106,17 @@ class CommandRegistry:
|
|
|
82
106
|
"""Registry for managing commands in the command line interface."""
|
|
83
107
|
|
|
84
108
|
registry: dict[str, Command]
|
|
109
|
+
pending_commands: dict[str, list[Command]] # Commands waiting to be bound
|
|
85
110
|
sub_parsers: argparse._SubParsersAction
|
|
86
111
|
parser: argparse.ArgumentParser
|
|
87
112
|
args: argparse.Namespace
|
|
113
|
+
_finalized: bool # Whether command registration has been finalized
|
|
88
114
|
|
|
89
115
|
def __init__(self) -> None:
|
|
90
116
|
"""Initialize a CommandRegistry instance."""
|
|
91
117
|
self.registry = {}
|
|
118
|
+
self.pending_commands = {}
|
|
119
|
+
self._finalized = False
|
|
92
120
|
self.parser = argparse.ArgumentParser(
|
|
93
121
|
prog="jac",
|
|
94
122
|
description="Jac Programming Language CLI - A tool for working with Jac programs",
|
|
@@ -150,11 +178,55 @@ class CommandRegistry:
|
|
|
150
178
|
help=f"str - Override setting '{name}'",
|
|
151
179
|
)
|
|
152
180
|
|
|
153
|
-
def register(
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
181
|
+
def register(
|
|
182
|
+
self,
|
|
183
|
+
func: Callable | None = None,
|
|
184
|
+
*,
|
|
185
|
+
priority: CommandPriority = CommandPriority.CORE,
|
|
186
|
+
source: str = "core",
|
|
187
|
+
) -> Callable:
|
|
188
|
+
"""Register a command in the registry.
|
|
189
|
+
|
|
190
|
+
This method supports both decorator syntax with and without arguments:
|
|
191
|
+
@cmd_registry.register
|
|
192
|
+
def my_cmd(): ...
|
|
193
|
+
|
|
194
|
+
@cmd_registry.register(priority=CommandPriority.PLUGIN, source="my-plugin")
|
|
195
|
+
def my_cmd(): ...
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
func: The command function to register
|
|
199
|
+
priority: Priority level for conflict resolution
|
|
200
|
+
source: Source plugin/module name for introspection
|
|
201
|
+
|
|
202
|
+
Returns:
|
|
203
|
+
The original function (for decorator usage)
|
|
204
|
+
"""
|
|
205
|
+
|
|
206
|
+
def _register(f: Callable) -> Callable:
|
|
207
|
+
"""Inner registration function."""
|
|
208
|
+
name = f.__name__
|
|
209
|
+
cmd = Command(f, priority=priority, source=source)
|
|
210
|
+
|
|
211
|
+
# Add to pending commands for priority resolution
|
|
212
|
+
if name not in self.pending_commands:
|
|
213
|
+
self.pending_commands[name] = []
|
|
214
|
+
self.pending_commands[name].append(cmd)
|
|
215
|
+
|
|
216
|
+
# If already finalized, bind immediately (late registration)
|
|
217
|
+
if self._finalized:
|
|
218
|
+
self._resolve_and_bind_command(name)
|
|
219
|
+
|
|
220
|
+
return f
|
|
221
|
+
|
|
222
|
+
# Support both @register and @register(...) syntax
|
|
223
|
+
if func is not None:
|
|
224
|
+
return _register(func)
|
|
225
|
+
return _register
|
|
226
|
+
|
|
227
|
+
def _bind_command_to_argparse(self, name: str, cmd: Command) -> None:
|
|
228
|
+
"""Bind a command to argparse subparser."""
|
|
229
|
+
func = cmd.func
|
|
158
230
|
# Extract the first paragraph from the docstring for brief description
|
|
159
231
|
doc = func.__doc__ or ""
|
|
160
232
|
brief_desc = doc.split("\n\n")[0].strip()
|
|
@@ -264,7 +336,51 @@ class CommandRegistry:
|
|
|
264
336
|
else param.annotation
|
|
265
337
|
),
|
|
266
338
|
)
|
|
267
|
-
|
|
339
|
+
|
|
340
|
+
def _resolve_and_bind_command(self, name: str) -> None:
|
|
341
|
+
"""Resolve command conflicts by priority and bind to argparse."""
|
|
342
|
+
if name not in self.pending_commands:
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
commands = self.pending_commands[name]
|
|
346
|
+
if not commands:
|
|
347
|
+
return
|
|
348
|
+
|
|
349
|
+
# Sort by priority (highest first)
|
|
350
|
+
commands.sort(key=lambda c: c.priority, reverse=True)
|
|
351
|
+
|
|
352
|
+
# Winner is the highest priority command
|
|
353
|
+
winner = commands[0]
|
|
354
|
+
|
|
355
|
+
# Warn about conflicts if multiple commands with different priorities
|
|
356
|
+
# Only warn if JAC_CLI_VERBOSE environment variable is set
|
|
357
|
+
if len(commands) > 1 and os.getenv("JAC_CLI_VERBOSE"):
|
|
358
|
+
conflicts = [f"{c.source} (priority={c.priority})" for c in commands[1:]]
|
|
359
|
+
print(
|
|
360
|
+
f"Warning: Command '{name}' registered by multiple sources. "
|
|
361
|
+
f"Using {winner.source} (priority={winner.priority}). "
|
|
362
|
+
f"Overriding: {', '.join(conflicts)}",
|
|
363
|
+
file=sys.stderr,
|
|
364
|
+
)
|
|
365
|
+
|
|
366
|
+
# Register the winner
|
|
367
|
+
self.registry[name] = winner
|
|
368
|
+
self._bind_command_to_argparse(name, winner)
|
|
369
|
+
|
|
370
|
+
def finalize(self) -> None:
|
|
371
|
+
"""Finalize command registration by resolving conflicts and binding to argparse.
|
|
372
|
+
|
|
373
|
+
This should be called after all plugins have had a chance to register commands.
|
|
374
|
+
"""
|
|
375
|
+
if self._finalized:
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
# Resolve all pending commands
|
|
379
|
+
for name in list(self.pending_commands.keys()):
|
|
380
|
+
self._resolve_and_bind_command(name)
|
|
381
|
+
|
|
382
|
+
self._finalized = True
|
|
383
|
+
self.pending_commands.clear()
|
|
268
384
|
|
|
269
385
|
def get(self, name: str) -> Optional[Command]:
|
|
270
386
|
"""Get the Command instance for a given command name."""
|
|
@@ -279,6 +395,26 @@ class CommandRegistry:
|
|
|
279
395
|
all_commands[name] = (doc, args)
|
|
280
396
|
return all_commands
|
|
281
397
|
|
|
398
|
+
def has_command(self, name: str) -> bool:
|
|
399
|
+
"""Check if a command is already registered."""
|
|
400
|
+
return name in self.registry
|
|
401
|
+
|
|
402
|
+
def list_commands(self) -> dict[str, dict[str, CommandPriority | str]]:
|
|
403
|
+
"""List all registered commands with metadata.
|
|
404
|
+
|
|
405
|
+
Returns:
|
|
406
|
+
Dictionary mapping command names to metadata including source and priority
|
|
407
|
+
"""
|
|
408
|
+
return {
|
|
409
|
+
name: {
|
|
410
|
+
"source": cmd.source,
|
|
411
|
+
"priority": cmd.priority,
|
|
412
|
+
"priority_name": cmd.priority.name,
|
|
413
|
+
"doc": cmd.func.__doc__ or "No documentation",
|
|
414
|
+
}
|
|
415
|
+
for name, cmd in self.registry.items()
|
|
416
|
+
}
|
|
417
|
+
|
|
282
418
|
|
|
283
419
|
cmd_registry = CommandRegistry()
|
|
284
420
|
|
jaclang/compiler/__init__.py
CHANGED
|
@@ -36,7 +36,12 @@ except ModuleNotFoundError:
|
|
|
36
36
|
generate_static_parser(force=True)
|
|
37
37
|
from jaclang.compiler.larkparse import jac_parser as jac_lark
|
|
38
38
|
|
|
39
|
-
jac_lark
|
|
39
|
+
if not hasattr(jac_lark, "Lark_StandAlone"):
|
|
40
|
+
generate_static_parser(force=True)
|
|
41
|
+
from jaclang.compiler.larkparse import jac_parser as jac_lark
|
|
42
|
+
|
|
43
|
+
with contextlib.suppress(AttributeError):
|
|
44
|
+
jac_lark.logger.setLevel(logging.DEBUG)
|
|
40
45
|
contextlib.suppress(ModuleNotFoundError)
|
|
41
46
|
|
|
42
47
|
TOKEN_MAP = {
|
jaclang/compiler/codeinfo.py
CHANGED
|
@@ -3,12 +3,25 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import ast as ast3
|
|
6
|
-
from
|
|
6
|
+
from dataclasses import dataclass, field
|
|
7
|
+
from typing import Any, Optional, TYPE_CHECKING
|
|
7
8
|
|
|
8
9
|
if TYPE_CHECKING:
|
|
10
|
+
from jaclang.compiler.passes.ecmascript.estree import Node as EsNode
|
|
9
11
|
from jaclang.compiler.unitree import Source, Token
|
|
10
12
|
|
|
11
13
|
|
|
14
|
+
@dataclass
|
|
15
|
+
class ClientManifest:
|
|
16
|
+
"""Client-side rendering manifest metadata."""
|
|
17
|
+
|
|
18
|
+
exports: list[str] = field(default_factory=list)
|
|
19
|
+
globals: list[str] = field(default_factory=list)
|
|
20
|
+
params: dict[str, list[str]] = field(default_factory=dict)
|
|
21
|
+
globals_values: dict[str, Any] = field(default_factory=dict)
|
|
22
|
+
has_client: bool = False
|
|
23
|
+
|
|
24
|
+
|
|
12
25
|
class CodeGenTarget:
|
|
13
26
|
"""Code generation target."""
|
|
14
27
|
|
|
@@ -20,8 +33,10 @@ class CodeGenTarget:
|
|
|
20
33
|
self.jac: str = ""
|
|
21
34
|
self.doc_ir: doc.DocType = doc.Text("")
|
|
22
35
|
self.js: str = ""
|
|
36
|
+
self.client_manifest: ClientManifest = ClientManifest()
|
|
23
37
|
self.py_ast: list[ast3.AST] = []
|
|
24
38
|
self.py_bytecode: Optional[bytes] = None
|
|
39
|
+
self.es_ast: Optional[EsNode] = None
|
|
25
40
|
|
|
26
41
|
|
|
27
42
|
class CodeLocInfo:
|
jaclang/compiler/constant.py
CHANGED
|
@@ -197,7 +197,6 @@ class Tokens(str, Enum):
|
|
|
197
197
|
BW_AND_EQ = "BW_AND_EQ"
|
|
198
198
|
BW_OR_EQ = "BW_OR_EQ"
|
|
199
199
|
BW_XOR_EQ = "BW_XOR_EQ"
|
|
200
|
-
BW_NOT_EQ = "BW_NOT_EQ"
|
|
201
200
|
LSHIFT_EQ = "LSHIFT_EQ"
|
|
202
201
|
RSHIFT_EQ = "RSHIFT_EQ"
|
|
203
202
|
WALRUS_EQ = "WALRUS_EQ"
|
|
@@ -248,6 +247,7 @@ class Tokens(str, Enum):
|
|
|
248
247
|
KW_OVERRIDE = "KW_OVERRIDE"
|
|
249
248
|
KW_MATCH = "KW_MATCH"
|
|
250
249
|
KW_CASE = "KW_CASE"
|
|
250
|
+
KW_CLIENT = "KW_CLIENT"
|
|
251
251
|
PLUS = "PLUS"
|
|
252
252
|
MINUS = "MINUS"
|
|
253
253
|
STAR_MUL = "STAR_MUL"
|
|
@@ -296,15 +296,40 @@ class Tokens(str, Enum):
|
|
|
296
296
|
RETURN_HINT = "RETURN_HINT"
|
|
297
297
|
NULL_OK = "NULL_OK"
|
|
298
298
|
DECOR_OP = "DECOR_OP"
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
299
|
+
JSX_TEXT = "JSX_TEXT"
|
|
300
|
+
JSX_OPEN_START = "JSX_OPEN_START"
|
|
301
|
+
JSX_SELF_CLOSE = "JSX_SELF_CLOSE"
|
|
302
|
+
JSX_TAG_END = "JSX_TAG_END"
|
|
303
|
+
JSX_CLOSE_START = "JSX_CLOSE_START"
|
|
304
|
+
JSX_FRAG_OPEN = "JSX_FRAG_OPEN"
|
|
305
|
+
JSX_FRAG_CLOSE = "JSX_FRAG_CLOSE"
|
|
306
|
+
JSX_NAME = "JSX_NAME"
|
|
306
307
|
COMMENT = "COMMENT"
|
|
307
308
|
WS = "WS"
|
|
309
|
+
F_DQ_START = "F_DQ_START"
|
|
310
|
+
F_SQ_START = "F_SQ_START"
|
|
311
|
+
F_TDQ_START = "F_TDQ_START"
|
|
312
|
+
F_TSQ_START = "F_TSQ_START"
|
|
313
|
+
RF_DQ_START = "RF_DQ_START"
|
|
314
|
+
RF_SQ_START = "RF_SQ_START"
|
|
315
|
+
RF_TDQ_START = "RF_TDQ_START"
|
|
316
|
+
RF_TSQ_START = "RF_TSQ_START"
|
|
317
|
+
F_DQ_END = "F_DQ_END"
|
|
318
|
+
F_SQ_END = "F_SQ_END"
|
|
319
|
+
F_TDQ_END = "F_TDQ_END"
|
|
320
|
+
F_TSQ_END = "F_TSQ_END"
|
|
321
|
+
F_TEXT_DQ = "F_TEXT_DQ"
|
|
322
|
+
F_TEXT_SQ = "F_TEXT_SQ"
|
|
323
|
+
F_TEXT_TDQ = "F_TEXT_TDQ"
|
|
324
|
+
F_TEXT_TSQ = "F_TEXT_TSQ"
|
|
325
|
+
RF_TEXT_DQ = "RF_TEXT_DQ"
|
|
326
|
+
RF_TEXT_SQ = "RF_TEXT_SQ"
|
|
327
|
+
RF_TEXT_TDQ = "RF_TEXT_TDQ"
|
|
328
|
+
RF_TEXT_TSQ = "RF_TEXT_TSQ"
|
|
329
|
+
D_LBRACE = "D_LBRACE"
|
|
330
|
+
D_RBRACE = "D_RBRACE"
|
|
331
|
+
CONV = "CONV"
|
|
332
|
+
F_FORMAT_TEXT = "F_FORMAT_TEXT"
|
|
308
333
|
|
|
309
334
|
def __str__(self) -> str:
|
|
310
335
|
return self.value
|