-
Notifications
You must be signed in to change notification settings - Fork 0
Phase 31 asl plan
Part of the Amiga port design log.
amiga.asl C sub-module wrapping asl.library's
AslRequest(ASL_FileRequest, ...). Native Amiga file chooser
dialog usable from both CLI and Workbench launches — modal and
aware of every mounted volume / assign:
from amiga import asl
path = asl.file_request(title="Pick a script",
initial_drawer="Work:scripts/",
pattern="#?.py")
if path is None:
print("cancelled")
# Save dialog
out = asl.file_request(title="Save as", save=True,
initial_file="output.txt")
# Multi-select returns list
paths = asl.file_request(multi=True)
# Drawer-only
drawer = asl.file_request(drawers_only=True)The single-pick form returns the full path as a str; multi-select
returns a list[str]; cancel returns None. Drawer + file are
joined via AddPart() so the volume-separator (:) lands correctly
no matter what the user typed.
The module is shared across all three variants — asl.library
ships with AmigaOS 2.0+ (v36), no external SDK, and the size cost
is small (~2 KB text).
-
file_request(...)keyword args:title,initial_drawer,initial_file,pattern,save=False,multi=False,drawers_only=False - Returns
strfor single-pick,list[str]formulti=True,Nonefor user-cancelled - Paths joined via
dos.library AddPart()so volume separators come out right on AmigaOS - Latin-1 codec for filenames
- Font / screen / draw mode / palette requesters — file is the only one with clear practical use from a scripted REPL
- Custom hooks / per-entry filter callbacks —
patternis enough
One entry point, behaviour driven entirely by kwargs:
| Kwargs | Returns |
|---|---|
(title, initial_drawer, initial_file, pattern) |
str — full path of the picked file |
save=True |
str — full path (editable filename gadget) |
drawers_only=True |
str — drawer path (no file component) |
multi=True |
list[str] — one full path per shift-clicked file |
| user clicked Cancel | None |
multi=True + save=True
|
ValueError |
asl.library is famously stack-hungry: a default ~4 KB shell stack
trips a CHK exception (0x80000006) on the post-pick code path
even though the dialog renders fine. file_request runs the bare
AllocAslRequest + AslRequest calls on a 32 KB scratch stack
via StackSwap so callers don't have to remember Stack 32768
at the shell prompt; the path-build and string allocation happen
back on the original stack so the GC's stack-scan range stays
correct.
Path buffer is 1024 bytes (vs the 512 used by older modamiga.c
surfaces) to comfortably accommodate long-name filesystems
(SFS / PFS3 / FFS2).
This section is the step-by-step ship plan — how to chunk the work into landable PRs.
Step 1: single-pick file_request → Step 2: save / drawers_only / multi
↓
Step 3: docs + smoke
| # | Step | Output | On-target smoke |
|---|---|---|---|
| 1 |
modasl.c opens asl.library lazily, exposes file_request(title, initial_drawer, initial_file, pattern) — single-pick, no flags. Latin-1 codec on the strings; AddPart() to join drawer + file into the full path. Returns str or None. |
New _asl C module wired into the build, registered with MP_REGISTER_MODULE(MP_QSTR__asl, ...). |
A no-arg file_request() under Amiberry — confirms the dialog renders, the OK path returns a path, Cancel returns None. |
| 2 | Add save=False (ASLFR_DoSaveMode), drawers_only=False (ASLFR_DrawersOnly), multi=False (ASLFR_DoMultiSelect). multi changes the return type to list[str]; iterate fr_ArgList joining each entry against fr_Drawer with AddPart. |
Same C file extended; amiga.py adds import _asl as asl. |
Save dialog (save=True + initial_file="foo.txt") and a multi=True pick under Amiberry. |
| 3 | Docs flip, manifest verification, arg-shape tests. |
docs/amiga.md Phase 31 → ✅, docs/amiga-testing.md gains a short ASL subsection. |
vamos-runnable arg-shape / module-alias test; Amiberry visual confirmation noted as interactive. |
Each step is small (~80 LOC C + a one-line Python alias). Step 3 is paperwork.
-
ports/amiga/modasl.c— new C source. - Lazy
AslBase = OpenLibrary("asl.library", 36)cached in a static; opened on first call. Same lifecycle pattern as Phase 30: no explicit close (the library is system-wide; AmigaOS reaps it on process exit). Wireamiga_asl_close()into main.c shutdown for symmetry — cheap. -
ports/amiga/Makefileaddsmodasl.ctoSRC_CandSRC_QSTR. - The C entry:
Defined as
static mp_obj_t mod_asl_file_request( size_t n_args, const mp_obj_t *pos_args, mp_map_t *kw_args);
MP_DEFINE_CONST_FUN_OBJ_KWwith anmp_arg_ttable so kwargs work cleanly and step 2 can extend without breaking callers.- Tags built into a
struct TagItem[]on the stack (max ~12 slots covering every Step 1/2 kwarg + TAG_DONE). - Call
AllocAslRequestTags(ASL_FileRequest, tags)→ returns astruct FileRequester *or NULL. - Call
AslRequest(req, NULL)→ returnsBOOL(TRUE if user picked, FALSE if cancelled). - On TRUE: walk
req->fr_Drawer+req->fr_File, join viaAddPart(buf, drawer, sizeof buf)thenAddPart(buf, file, sizeof buf). Buffer is 1024 bytes — larger than the 512 used byamiga.match/ WBStartup-arg handling, because long-name filesystems (SFS / PFS3 / FFS2) allow ~105-byte filenames per component, so a deeply-nested path can plausibly exceed 512. 1024 covers the worst case with comfortable headroom and the stack cost is negligible. - Return
mp_obj_new_str(buf, strlen(buf)). - On FALSE: return
mp_const_none. - Always
FreeAslRequest(req)in a clean-up path.
- Tags built into a
| Kwarg | Tag | Type | Default |
|---|---|---|---|
title |
ASLFR_TitleText |
str |
"" (no title) |
initial_drawer |
ASLFR_InitialDrawer |
str | "" |
initial_file |
ASLFR_InitialFile |
str | "" |
pattern |
ASLFR_InitialPattern + ASLFR_DoPatterns=TRUE
|
str |
"" (no pattern, no filter) |
pattern implies ASLFR_DoPatterns=TRUE. Empty pattern leaves
the filter off entirely.
AllocAslRequest(type, ...) walks varargs from va_list; same
caller-frame issue we hit in Phase 30 with
EasyRequest. The Tags variant takes an explicit TagItem
array, which is what we'd construct anyway.
Visual check under Amiberry from the interactive REPL:
>>> from amiga import asl
>>> asl.file_request(title="Pick a script", initial_drawer="Sys:")
'Sys:Prefs/Workbench' # if you picked Sys:Prefs/Workbench
>>> asl.file_request()
None # if you clicked Cancel
>>> asl.file_request(pattern="#?.py")
'Work:scripts/foo.py' # only .py files visible in the list| Failure | Behaviour |
|---|---|
asl.library won't open |
OSError(MP_ENOENT) from file_request first call |
AllocAslRequestTags returns NULL |
OSError(MP_ENOMEM) |
| Non-string kwargs |
mp_obj_str_get_data raises TypeError automatically |
Path components contain \0
|
Truncated at the embedded NUL (AmigaOS C-string limit). Document; don't pre-validate. |
| User Cancels |
None (not an error — explicit "no choice" outcome) |
Same modasl.c, extended kwargs:
| Kwarg | Tag | Effect |
|---|---|---|
save=False |
ASLFR_DoSaveMode=TRUE |
Save dialog: editable filename field instead of file picker. |
drawers_only=False |
ASLFR_DrawersOnly=TRUE |
List/pick drawers; the "Files" pane is hidden. Result is just the drawer path. |
multi=False |
ASLFR_DoMultiSelect=TRUE |
User can shift-click multiple files. Result type changes to list[str]. |
multi=True post-processing:
- After
AslRequestreturns TRUE, walkreq->fr_NumArgs/req->fr_ArgList(astruct WBArg[]). - For each
WBArg, ignorewa_Lock(always 0 from ASL) and usewa_Nameas the file component, joined againstreq->fr_DrawerviaAddPartinto a fresh 512-byte buffer. - Append each full path to a Python list.
- Return the list.
multi=False + drawers_only=True returns just req->fr_Drawer
(no file component to join in).
Validation:
-
multi=True+save=True→ValueError: nonsensical. ASL itself would silently ignore one or the other, but we'd rather the user know. -
drawers_only=True+pattern="..."→ ignorepatternsilently (matches ASL's own behaviour).
import _asl as asl # noqa: F401 (re-exported as amiga.asl)So from amiga import asl and amiga.asl.file_request(...) both
work, matching the _amiga → amiga, _intuition → amiga.intuition
convention from Phase 17 and
Phase 30.
>>> asl.file_request(title="Save as", save=True, initial_file="out.txt")
'Ram Disk:out.txt'
>>> asl.file_request(multi=True, title="Pick scripts", pattern="#?.py")
['Work:scripts/foo.py', 'Work:scripts/bar.py']
>>> asl.file_request(drawers_only=True, title="Pick a folder")
'Work:scripts'-
docs/amiga.mdPhase 31 status → ✅; the section gains a "Status — done" block summarising the kwargs/return-type matrix. -
docs/amiga-testing.mdgains an ASL subsection under the Amiberry runner: interactive-only verification with REPL snippets for save / multi / drawers_only. -
tests/ports/amiga/test_asl_smoke.py— argument-validation + module-alias chain test, runnable under vamos. Covers:_asl is amiga.asl-
multi=True+save=TrueraisesValueError - Non-string kwargs raise
TypeError - On vamos the
OpenLibrary("asl.library", 36)may succeed against vamos's stub or raiseOSError(ENOENT)— accept either.
All three shipped variants include the module — asl.library is
part of AmigaOS 2.0+ and there's no external SDK to fetch. Size
cost: ~2 KB text.
-
Modal blocking.
AslRequestis fully modal — the call doesn't return until the user clicks OK or Cancel. Matches the design intent. ASL has an async hook mechanism (ASLFR_IntuiMsgFunc) for receiving IDCMP messages mid-request; out of scope. -
Stack-hungry — runs on a 32 KB scratch stack via
StackSwap. The default AmigaShell stack (often 4 KB) is too small for ASL's directory-listing / font-loading code — the dialog renders fine, but the post-pick code path trips a CHK exception (0x80000006) inside ASL. Doing the swap insidefile_requestmeans callers don't need to rememberStack 32768at the shell. Only the bareAllocAslRequest/AslRequestcalls run on the scratch stack; path-building andmp_obj_new_strhappen back on the original stack so MicroPython's GC stack-scan range (gc_stack_topin main.c) stays correct. - Screen independence. Same as Phase 30: ASL renders on the default public screen, opening its own if none is available. No Workbench dependency.
-
Path joining via
AddPart. AmigaOS volumes use:as the separator (Work:scripts/foo.py), subdirs use/. Naive concatenationdrawer + "/" + filebreaks on drawer values like"Work:"(would yieldWork:/foo.py).AddPartfromdos.libraryhandles every case correctly. - Latin-1 codec for strings. Same rationale as Phase 30 (AmigaOS Topaz is CP1252-ish). Filenames with non-ASCII bytes pass through as-is.
-
Pre-allocated 1024-byte path buffers. Long-name filesystems
(SFS, PFS3, FFS2) allow ~105-byte filenames per component, so a
deeply-nested path on a modern volume can plausibly approach
500+ bytes. 1024 covers that with headroom; stack cost is
negligible.
AddParttruncates safely on overflow if the caller does somehow exceed it, and we treat that overflow as the caller's problem (no pre-validation). -
Memory lifecycle.
AllocAslRequestTagsreturns a request struct that must beFreeAslRequest'd. Tag and string buffers are caller-owned stack memory; ASL copies what it needs during the call. OnceAslRequestreturns, only thefr_Drawer/fr_File/fr_ArgListfields of the request struct hold live data (those are inside the request itself), and they all go away onFreeAslRequest.
- Font / screen / draw-mode / palette requesters —
asl.librarysupports them but the file requester is the one with practical use from a scripted REPL. Addingfont_request()etc. is a follow-on phase if needed. - Custom hooks / per-entry filter callbacks
(
ASLFR_FilterFunc/ASLFR_AcceptPattern) —patterncovers the 95% case; callbacks would need a Hook trampoline. - Modal-but-with-progress / async surfaces — needs
BuildAslRequest- IDCMP wiring.
- Returning extra metadata (file size, date) — ASL doesn't expose
these; would require a separate
dos.library Examinepass per file, which the caller can do already.
ports/amiga/modasl.c — C module (~280 LOC)
ports/amiga/modules/amiga.py — adds `import _asl as asl`
ports/amiga/Makefile — modasl.c into SRC_C / SRC_QSTR
tests/ports/amiga/test_asl_smoke.py — vamos arg-shape smoke test
docs/amiga.md — Phase 31 status → ✅
docs/amiga-testing.md — ASL subsection under Amiberry runner
Variants: all three shipped variants. ~1.2–2 KB text cost per variant.