Skip to content

Commit 536e4c9

Browse files
Update extension handling to support new .asmxt pointer files and module-qualified operators
1 parent 2aa7205 commit 536e4c9

File tree

9 files changed

+39
-19
lines changed

9 files changed

+39
-19
lines changed

SPECIFICATION.html

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -245,13 +245,20 @@
245245
246246
- Extensions: the interpreter may also accept zero or more *extension* arguments that load Python extension modules before parsing and execution. Extensions may add new operators, new runtime types, and runtime hooks (including custom REPL implementations), but MUST NOT replace or modify existing built-in operators or types.
247247
- A Python extension is a `.py` file that defines `ASM_LANG_EXTENSION_API_VERSION = 1` (optional; defaults to 1) and a callable `asm_lang_register(ext)` entrypoint.
248-
- A pointer file is a `.asmx` text file containing one extension path per line. Lines are trimmed; blank lines are ignored; lines beginning with `#` are comments. Relative paths are resolved relative to the `.asmx` file's directory; when a referenced path is not found there the interpreter will also try the current working directory and, as a final fallback, the interpreter's own `ext/` subdirectory.
249-
- If a `.asmx` file is supplied as an argument, all of the linked extensions are loaded.
250-
- If no explicit extension arguments are provided, the interpreter will automatically look for a pointer file named `.asmx` in the current working directory. When a program path is being executed (not when `-source` is used), the interpreter also checks the program's directory for a `.asmx` pointer file and will additionally accept a pointer file that shares the program's basename but ends with `.asmx` (for example `program.asmln` alongside `program.asmx`). If any pointer file is found the extensions listed in that pointer file are loaded as if supplied on the command line.
248+
- A pointer file is a `.asmxt` text file containing one extension path per line. Lines are trimmed; blank lines are ignored; lines beginning with `#` are comments. Relative paths are resolved relative to the `.asmxt` file's directory; when a referenced path is not found there the interpreter will also try the current working directory and, as a final fallback, the interpreter's own `ext/` subdirectory.
249+
- If a `.asmxt` file is supplied as an argument, all of the linked extensions are loaded.
250+
- If no explicit extension arguments are provided, the interpreter will automatically look for a pointer file named `.asmxt` in the current working directory. When a program path is being executed (not when `-source` is used), the interpreter also checks the program's directory for a `.asmxt` pointer file and will additionally accept a pointer file that shares the program's basename but ends with `.asmxt` (for example `program.asmln` alongside `program.asmxt`). If any pointer file is found the extensions listed in that pointer file are loaded as if supplied on the command line.
251251
- Extensions are loaded before parsing so that extension-defined type names are recognized in typed assignments and function signatures.
252252
- If the only supplied positional inputs are extensions (and no program is supplied), the interpreter runs the REPL with the loaded extensions.
253253
- Hook surfaces exposed by the reference implementation include:
254254
- Operators: additional call names dispatched like built-ins.
255+
Extensions may opt into module-qualifying all of their operators by
256+
defining the module-level flag `ASM_LANG_EXTENSION_ASMODULE = True`.
257+
When an extension sets this flag every operator it registers will be
258+
exposed under the extension's name as a dotted prefix (for example, an
259+
extension named `mymod` registering `FOO` will expose `mymod.FOO`). This
260+
mirrors imported module-qualified bindings and avoids global name
261+
collisions.
255262
- Per-N-steps rules: `every_n_steps(N, handler)` runs the handler after state-log step indices where `step_index % N == 0`.
256263
- Event-bound rules: `on_event(name, handler)` for the following event names:
257264
- `program_start(interpreter, program, env)`
@@ -270,7 +277,7 @@
270277
- File mode: `asm-lang program.asmln`
271278
- Source-string mode: `asm-lang -source "foo = INPUT\nPRINT(foo)" -verbose`
272279
- With extensions (file mode): `asm-lang myext.py program.asmln`
273-
- With pointer file: `asm-lang extensions.asmx program.asmln`
280+
- With pointer file: `asm-lang extensions.asmxt program.asmln`
274281
275282
- REPL / Interactive mode: `asm-lang` (no program argument), or `asm-lang myext.py` (extensions only)
276283

asm-lang.exe

1.01 KB
Binary file not shown.

asm-lang.py

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ def _output_sink(text: str) -> None:
122122

123123
def run_cli(argv: Optional[List[str]] = None) -> int:
124124
parser = argparse.ArgumentParser(description="ASM-Lang reference interpreter")
125-
parser.add_argument("inputs", nargs="*", help="Program path/source and/or extension files (.py/.asmx)")
126-
parser.add_argument("--ext", action="append", default=[], help="Extension path (.py) or pointer file (.asmx)")
125+
parser.add_argument("inputs", nargs="*", help="Program path/source and/or extension files (.py/.asmxt)")
126+
parser.add_argument("--ext", action="append", default=[], help="Extension path (.py) or pointer file (.asmxt)")
127127
parser.add_argument("-source", "--source", dest="source_mode", action="store_true", help="Treat program argument as literal source text")
128128
parser.add_argument("-verbose", "--verbose", dest="verbose", action="store_true", help="Emit env snapshots in tracebacks")
129129
parser.add_argument("--traceback-json", action="store_true", help="Also emit JSON traceback")
@@ -134,7 +134,7 @@ def run_cli(argv: Optional[List[str]] = None) -> int:
134134
remaining: List[str] = []
135135
for item in inputs:
136136
lower = item.lower()
137-
if lower.endswith(".py") or lower.endswith(".asmx"):
137+
if lower.endswith(".py") or lower.endswith(".asmxt"):
138138
ext_paths.append(item)
139139
else:
140140
remaining.append(item)
@@ -143,26 +143,26 @@ def run_cli(argv: Optional[List[str]] = None) -> int:
143143
program: Optional[str] = remaining[0] if remaining else None
144144

145145
# If the caller didn't specify any extensions, look for a pointer file named
146-
# ".asmx" in the current working directory or (when a program file was
146+
# ".asmxt" in the current working directory or (when a program file was
147147
# provided) in the program's directory. If found, use it as the extension
148148
# pointer file so the interpreter loads the extensions it points to.
149149
if not ext_paths:
150-
cwd_asmx = os.path.abspath(".asmx")
150+
cwd_asmx = os.path.abspath(".asmxt")
151151
if os.path.exists(cwd_asmx):
152152
ext_paths.append(cwd_asmx)
153153
else:
154154
# If a program path was given (and isn't literal source text),
155-
# also check the program's directory for a .asmx pointer file.
155+
# also check the program's directory for a .asmxt pointer file.
156156
if program and not args.source_mode:
157157
program_dir = os.path.dirname(os.path.abspath(program))
158-
program_asmx = os.path.join(program_dir, ".asmx")
158+
program_asmx = os.path.join(program_dir, ".asmxt")
159159
if os.path.exists(program_asmx):
160160
ext_paths.append(program_asmx)
161161
else:
162162
# Also accept a pointer file that shares the program's
163-
# basename but uses the .asmx extension instead of the
164-
# program extension (e.g. program.asmln -> program.asmx).
165-
program_alt_asmx = os.path.splitext(os.path.abspath(program))[0] + ".asmx"
163+
# basename but uses the .asmxt extension instead of the
164+
# program extension (e.g. program.asmln -> program.asmxt).
165+
program_alt_asmx = os.path.splitext(os.path.abspath(program))[0] + ".asmxt"
166166
if os.path.exists(program_alt_asmx):
167167
ext_paths.append(program_alt_asmx)
168168

ext/gc.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
ASM_LANG_EXTENSION_NAME = "gc"
2020
ASM_LANG_EXTENSION_API_VERSION = 1
21+
ASM_LANG_EXTENSION_ASMODULE = False
2122

2223

2324
# ---- Interpreter monkeypatching ----

ext/networking.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737

3838
ASM_LANG_EXTENSION_NAME = "networking"
3939
ASM_LANG_EXTENSION_API_VERSION = 1
40+
ASM_LANG_EXTENSION_ASMODULE = True
4041

4142

4243
# ---- Value helpers (import lazily inside operators to avoid cycles) ----

ext/wasapi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424

2525
ASM_LANG_EXTENSION_API_VERSION = 1
2626
ASM_LANG_EXTENSION_NAME = "wasapi"
27+
ASM_LANG_EXTENSION_ASMODULE = True
2728

2829

2930
if sys.platform != "win32":

ext/win32.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
ASM_LANG_EXTENSION_NAME = "win32"
2929
ASM_LANG_EXTENSION_API_VERSION = 1
30+
ASM_LANG_EXTENSION_ASMODULE = True
3031

3132

3233
def _ensure_windows() -> None:

ext/windows.asmxt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
win32.py
2+
wasapi.py

extensions.py

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,10 @@ class RuntimeServices:
153153

154154

155155
class ExtensionAPI:
156-
def __init__(self, *, services: RuntimeServices, ext_name: str) -> None:
156+
def __init__(self, *, services: RuntimeServices, ext_name: str, asmodule: bool = False) -> None:
157157
self._services = services
158158
self._ext_name = ext_name
159+
self._asmodule = bool(asmodule)
159160

160161
# ---- metadata ----
161162
def metadata(self, *, name: str, version: str = "0.0.0", requires_api: int = EXTENSION_API_VERSION) -> None:
@@ -173,7 +174,12 @@ def register_operator(
173174
) -> None:
174175
if not name:
175176
raise ASMExtensionError("Operator name must be non-empty")
176-
self._services.operators.append((name, int(min_args), None if max_args is None else int(max_args), impl, doc))
177+
qualified = name
178+
if self._asmodule:
179+
prefix = str(self._ext_name)
180+
if not qualified.startswith(prefix + "."):
181+
qualified = f"{prefix}.{qualified}"
182+
self._services.operators.append((qualified, int(min_args), None if max_args is None else int(max_args), impl, doc))
177183

178184
def operator(self, name: str, min_args: int, max_args: Optional[int] = None, *, doc: str = ""):
179185
def deco(fn: Callable[..., Any]) -> Callable[..., Any]:
@@ -287,7 +293,7 @@ def load_extension_module(path: str) -> Any:
287293

288294
def read_asmx(pointer_file: str) -> List[str]:
289295
if not os.path.exists(pointer_file):
290-
raise ASMExtensionError(f".asmx file not found: {pointer_file}")
296+
raise ASMExtensionError(f".asmxt file not found: {pointer_file}")
291297
base_dir = os.path.dirname(os.path.abspath(pointer_file))
292298
out: List[str] = []
293299
with open(pointer_file, "r", encoding="utf-8") as handle:
@@ -328,7 +334,7 @@ def read_asmx(pointer_file: str) -> List[str]:
328334
def gather_extension_paths(paths: Sequence[str]) -> List[str]:
329335
expanded: List[str] = []
330336
for p in paths:
331-
if p.lower().endswith(".asmx"):
337+
if p.lower().endswith(".asmxt"):
332338
# Resolve pointer file path (absolute if possible) and expand it.
333339
pointer = p
334340
if not os.path.isabs(pointer):
@@ -372,6 +378,7 @@ def load_runtime_services(paths: Sequence[str]) -> RuntimeServices:
372378
if register is None or not callable(register):
373379
raise ASMExtensionError(f"Extension {path} must define callable asm_lang_register(ext)")
374380
ext_name = getattr(module, "ASM_LANG_EXTENSION_NAME", os.path.splitext(os.path.basename(path))[0])
375-
ext = ExtensionAPI(services=services, ext_name=str(ext_name))
381+
ext_asmodule = bool(getattr(module, "ASM_LANG_EXTENSION_ASMODULE", False))
382+
ext = ExtensionAPI(services=services, ext_name=str(ext_name), asmodule=ext_asmodule)
376383
register(ext)
377384
return services

0 commit comments

Comments
 (0)