jaclang 0.8.9__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 +147 -25
- jaclang/cli/cmdreg.py +144 -8
- jaclang/compiler/__init__.py +6 -1
- jaclang/compiler/codeinfo.py +16 -1
- jaclang/compiler/constant.py +33 -13
- jaclang/compiler/jac.lark +130 -31
- jaclang/compiler/larkparse/jac_parser.py +2 -2
- jaclang/compiler/parser.py +567 -176
- 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/pyast_gen_pass.py +324 -234
- jaclang/compiler/passes/main/pyast_load_pass.py +46 -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 -2
- 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_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 +115 -0
- jaclang/compiler/passes/tool/fuse_comments_pass.py +1 -10
- 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 +22 -25
- 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 +169 -50
- jaclang/compiler/type_system/type_utils.py +1 -1
- jaclang/compiler/type_system/types.py +6 -0
- jaclang/compiler/unitree.py +430 -84
- 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 +1 -1
- jaclang/runtimelib/client_bundle.py +169 -0
- jaclang/runtimelib/client_runtime.jac +586 -0
- jaclang/runtimelib/constructs.py +2 -0
- jaclang/runtimelib/machine.py +259 -100
- 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 -2
- 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/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 +1 -1
- jaclang/tests/test_language.py +10 -39
- jaclang/tests/test_reference.py +17 -2
- jaclang/utils/NonGPT.py +375 -0
- jaclang/utils/helpers.py +44 -16
- jaclang/utils/lang_tools.py +31 -4
- jaclang/utils/tests/test_lang_tools.py +1 -1
- jaclang/utils/treeprinter.py +8 -3
- {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/METADATA +3 -3
- {jaclang-0.8.9.dist-info → jaclang-0.8.10.dist-info}/RECORD +96 -66
- 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/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.9.dist-info → jaclang-0.8.10.dist-info}/WHEEL +0 -0
- {jaclang-0.8.9.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:
|
|
@@ -150,7 +147,9 @@ def run(
|
|
|
150
147
|
lng=lng,
|
|
151
148
|
)
|
|
152
149
|
except Exception as e:
|
|
153
|
-
|
|
150
|
+
from jaclang.utils.helpers import dump_traceback
|
|
151
|
+
|
|
152
|
+
print(dump_traceback(e), file=sys.stderr)
|
|
154
153
|
mach.close()
|
|
155
154
|
exit(1)
|
|
156
155
|
elif filename.endswith(".jir"):
|
|
@@ -164,7 +163,9 @@ def run(
|
|
|
164
163
|
lng=lng,
|
|
165
164
|
)
|
|
166
165
|
except Exception as e:
|
|
167
|
-
|
|
166
|
+
from jaclang.utils.helpers import dump_traceback
|
|
167
|
+
|
|
168
|
+
print(dump_traceback(e), file=sys.stderr)
|
|
168
169
|
mach.close()
|
|
169
170
|
exit(1)
|
|
170
171
|
|
|
@@ -640,9 +641,10 @@ def py2jac(filename: str) -> None:
|
|
|
640
641
|
def jac2py(filename: str) -> None:
|
|
641
642
|
"""Convert a Jac file to Python code.
|
|
642
643
|
|
|
643
|
-
Translates Jac source code to equivalent Python code.
|
|
644
|
-
|
|
645
|
-
|
|
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.
|
|
646
648
|
|
|
647
649
|
Args:
|
|
648
650
|
filename: Path to the .jac file to convert
|
|
@@ -662,38 +664,158 @@ def jac2py(filename: str) -> None:
|
|
|
662
664
|
|
|
663
665
|
|
|
664
666
|
@cmd_registry.register
|
|
665
|
-
def
|
|
666
|
-
"""Convert a Jac file to
|
|
667
|
+
def js(filename: str) -> None:
|
|
668
|
+
"""Convert a Jac file to JavaScript code.
|
|
667
669
|
|
|
668
|
-
Translates Jac source code to equivalent
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
use as a standalone library.
|
|
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.
|
|
672
673
|
|
|
673
674
|
Args:
|
|
674
675
|
filename: Path to the .jac file to convert
|
|
675
676
|
|
|
676
677
|
Examples:
|
|
677
|
-
jac
|
|
678
|
+
jac js myprogram.jac > myprogram.js
|
|
679
|
+
jac js myprogram.jac
|
|
678
680
|
"""
|
|
679
681
|
if filename.endswith(".jac"):
|
|
680
|
-
# Temporarily enable library mode
|
|
681
|
-
original_library_mode = settings.library_mode
|
|
682
|
-
settings.library_mode = True
|
|
683
682
|
try:
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
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)
|
|
688
689
|
exit(1)
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
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)
|
|
692
704
|
else:
|
|
693
705
|
print("Not a .jac file.", file=sys.stderr)
|
|
694
706
|
exit(1)
|
|
695
707
|
|
|
696
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()
|
|
817
|
+
|
|
818
|
+
|
|
697
819
|
def start_cli() -> None:
|
|
698
820
|
"""
|
|
699
821
|
Start the command line interface.
|
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
|
@@ -247,6 +247,7 @@ class Tokens(str, Enum):
|
|
|
247
247
|
KW_OVERRIDE = "KW_OVERRIDE"
|
|
248
248
|
KW_MATCH = "KW_MATCH"
|
|
249
249
|
KW_CASE = "KW_CASE"
|
|
250
|
+
KW_CLIENT = "KW_CLIENT"
|
|
250
251
|
PLUS = "PLUS"
|
|
251
252
|
MINUS = "MINUS"
|
|
252
253
|
STAR_MUL = "STAR_MUL"
|
|
@@ -295,21 +296,40 @@ class Tokens(str, Enum):
|
|
|
295
296
|
RETURN_HINT = "RETURN_HINT"
|
|
296
297
|
NULL_OK = "NULL_OK"
|
|
297
298
|
DECOR_OP = "DECOR_OP"
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
FSTR_PIECE = "FSTR_PIECE"
|
|
307
|
-
FSTR_SQ_PIECE = "FSTR_SQ_PIECE"
|
|
308
|
-
FSTR_TRIPLE_PIECE = "FSTR_TRIPLE_PIECE"
|
|
309
|
-
FSTR_SQ_TRIPLE_PIECE = "FSTR_SQ_TRIPLE_PIECE"
|
|
310
|
-
FSTR_BESC = "FSTR_BESC"
|
|
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"
|
|
311
307
|
COMMENT = "COMMENT"
|
|
312
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"
|
|
313
333
|
|
|
314
334
|
def __str__(self) -> str:
|
|
315
335
|
return self.value
|