jerboa-gerbil: converter from jerboa-emacs .ss to gerbil-emacs
ober
86719f019af8d001b5ba7201e3d6894652f4f9de
new file mode 100644 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +.gerbil/ +*.o +*.o1 +*.o1.dSYM/ +*~ +manifest.ss new file mode 100644 --- /dev/null +++ b/README.md @@ -0,0 +1,53 @@ +# jerboa-gerbil + +Tooling to port [`jerboa-emacs`](https://git.sr.ht/~lisp) features to +[`gerbil-emacs`](https://git.cons.io/ober/), written in Gerbil Scheme. + +The two editors are forks of one ancestor and share the same Gerbil surface +syntax, so porting a feature is mostly **renaming import module paths** and +pointing Jerboa's "chez-powers" stdlib surface at [`jerboa-compat`](../jerboa-compat). + +## convert.ss + +Rewrites a jerboa-emacs `.ss` source's imports to gerbil-emacs, textually +(comments/formatting preserved), and warns about residual `:jerboa-*` refs that +need a human: + +```bash +gxi convert.ss INPUT.ss [-o OUTPUT.ss] # default: stdout +``` + +Rename rules (specific before generic): + +| from | to | +|---|---| +| `:jerboa-emacs/chez-powers` | `:jerboa-compat/powers` | +| `:jerboa-emacs/atom` | `:jerboa-compat/atom` | +| `:jerboa-scintilla/…` | `:gerbil-scintilla/…` | +| `:jerboa-qt/…` | `:gerbil-qt/…` | +| `:jerboa-aws/…` | `:gerbil-aws/…` (no Gerbil lib yet — flagged) | +| `:jerboa-emacs/…` | `:gemacs/…` | + +Library use: `(import :jerboa-gerbil/convert)` → `(convert-string text)` returns +`(values new-text report residual-count)`. + +## Testing Gerbil from this session + +`gerbil-mcp` can't be registered as an MCP server here (the jerboa-mcp loader +occupies that slot), so drive it over stdio with the bundled client: + +```bash +cd ~/mine/gerbil-mcp +node gmcp.mjs list +node gmcp.mjs gerbil_compile_check '{"file_path":"…","loadpath":["/Users/user/mine"]}' +node gmcp.mjs gerbil_eval '{"imports":[":jerboa-compat/powers"],"loadpath":["/Users/user/mine"],"expression":"…"}' +``` + +It pins `GERBIL_MCP_GXI_PATH=/opt/gerbil/bin/gxi`. + +## Status + +Foundation complete: `jerboa-compat` (atom, lru-cache, stm, engine, powers) and +this converter, both built and tested on Gerbil. End-to-end proof in +`../proof-port`. Remaining: port the ~484 `cmd-*` features that exist in +jerboa-emacs but not gerbil-emacs (git/forge, org, editing, games, …). new file mode 100644 --- /dev/null +++ b/convert.ss @@ -0,0 +1,80 @@ +;;; -*- Gerbil -*- +;;; jerboa-gerbil/convert — rewrite jerboa-emacs .ss import paths to gerbil-emacs. +;;; +;;; jerboa-emacs and gerbil-emacs share the same Gerbil surface syntax; porting a +;;; feature is mostly renaming import module paths and pointing Jerboa's +;;; "chez-powers" stdlib surface at :jerboa-compat. This applies those renames +;;; textually (preserving comments/formatting) and reports residual :jerboa-* +;;; references that need a human (e.g. libraries not yet ported to Gerbil). +;;; +;;; gxi convert.ss INPUT.ss [-o OUTPUT.ss] # default: write to stdout +;;; +;;; Library use: (import :jerboa-gerbil/convert) then (convert-string text). + +(export convert-string convert-file! +rename-rules+ main) + +(import :std/srfi/13 :std/misc/ports :std/format) + +;; (name old new) — order matters: specific :jerboa-emacs/* before the generic. +(def +rename-rules+ + '(("chez-powers" ":jerboa-emacs/chez-powers" ":jerboa-compat/powers") + ("atom" ":jerboa-emacs/atom" ":jerboa-compat/atom") + ("scintilla" ":jerboa-scintilla/" ":gerbil-scintilla/") + ("qt" ":jerboa-qt/" ":gerbil-qt/") + ("aws" ":jerboa-aws/" ":gerbil-aws/") + ("emacs" ":jerboa-emacs/" ":gemacs/"))) + +;; literal (non-regex) count + replace, with an optional start offset +(def (count-sub sub s) + (let (n (string-length sub)) + (let loop ((start 0) (c 0)) + (let (i (string-contains s sub start)) + (if i (loop (+ i n) (+ c 1)) c))))) + +(def (replace-all s old new) + (let (ol (string-length old)) + (let loop ((start 0) (acc '())) + (let (i (string-contains s old start)) + (if i + (loop (+ i ol) (cons new (cons (substring s start i) acc))) + (string-concatenate (reverse (cons (substring s start (string-length s)) acc)))))))) + +;; convert-string : text -> (values new-text report residual-count) +;; report is a list of (rule-name . count); residual-count is the number of +;; ":jerboa-" tokens still present after the renames (should be 0). +(def (convert-string text) + (let loop ((rules +rename-rules+) (s text) (report '())) + (if (null? rules) + ;; residual = leftover :jerboa-* refs, excluding our intended :jerboa-compat target + (values s (reverse report) (- (count-sub ":jerboa-" s) (count-sub ":jerboa-compat" s))) + (let* ((rule (car rules)) + (name (car rule)) (old (cadr rule)) (new (caddr rule)) + (c (count-sub old s))) + (loop (cdr rules) + (if (> c 0) (replace-all s old new) s) + (cons (cons name c) report)))))) + +(def (convert-file! in (out #f)) + (let-values (((txt report residual) (convert-string (read-file-string in)))) + (if out + (call-with-output-file out (lambda (p) (display txt p))) + (display txt)) + (values report residual))) + +(def (main . args) + (let loop ((args args) (out #f) (in #f)) + (cond + ((null? args) + (unless in (error "usage: gxi convert.ss INPUT.ss [-o OUTPUT.ss]")) + (let-values (((report residual) (convert-file! in out))) + (let (ep (current-error-port)) + (fprintf ep "jerboa-gerbil: converted ~a~a\n" in (if out (format " -> ~a" out) "")) + (for-each (lambda (kv) + (when (> (cdr kv) 0) + (fprintf ep " ~a: ~a\n" (car kv) (cdr kv)))) + report) + (when (> residual 0) + (fprintf ep " WARNING: ~a residual :jerboa-* reference(s) need manual review\n" + residual))))) + ((equal? (car args) "-o") (loop (cddr args) (cadr args) in)) + (else (loop (cdr args) out (car args)))))) new file mode 100644 --- /dev/null +++ b/gerbil.pkg @@ -0,0 +1 @@ +(package: jerboa-gerbil)