Skip to content

Release v0.3.0#14

Merged
skydread1 merged 31 commits into
mainfrom
develop
Jun 1, 2026
Merged

Release v0.3.0#14
skydread1 merged 31 commits into
mainfrom
develop

Conversation

@skydread1
Copy link
Copy Markdown
Contributor

v0.3.0 - 2026-06-01

Completes the Clojure 1.10 stdlib surface and unifies the compiler config behind magic.flags.

Clojure 1.10 stdlib (the marked-1.10 port is now complete)

  • ex-message / ex-cause added to clojure.core - Fix nasser/magic#238
  • symbol arity-1 now converts a Var (qualified) or Keyword
  • tap> / add-tap / remove-tap (the tap system) ported to clojure.core
  • read+string ported to clojure.core, backed by string capture in LineNumberingTextReader
  • PrintWriter-on ported to clojure.core - Closes #8
  • Throwable->map brought to the 1.10 shape: conditional :message/:cause and the :phase key; StackTraceElement->vec and the StackFrame print-method derive the class from the frame's declaring type - part of #10
  • ex-triage / ex-str ported to clojure.main; repl-caught rewired to the 1.10 Throwable->map -> ex-triage -> ex-str path - part of #10
  • prepl / io-prepl / remote-prepl ported to clojure.core.server - Closes #10
  • renumbering-read ported to clojure.main; repl-read rewired to it - Closes #12
  • defprotocol :extend-via-metadata dispatch implemented (direct defs -> fully-qualified-symbol metadata -> extend table) - Closes #13

Compiler

  • magic.flags is now the single config surface: every compilation knob is a dynamic var there, and spells (*lift-vars*, *lift-keywords*, *sparse-case*) are flags too. Removed magic.core/*spells*, bind-spells!, the load-time global mutation, and the dead magic.spells.protocols spell; active-spells derives the spell fns from the flags - Fix nasser/magic#233
  • throw of a let/loop-local introduced inside a catch now compiles (the thrown expression was recompiled with a stale captured compilers map) - Closes #7

Runtime

  • LineNumberingTextReader captures read text for read+string
  • LispReader MetaReader preserves an explicit :line/:column/:source-span instead of clobbering it with positional values (uses positional only as a default), matching JVM 1.10 and ClojureCLR - part of #12
  • MethodImplCache carries the protocol fn symbol needed for :extend-via-metadata dispatch - part of #13

Tooling

  • bb prepl-server / bb prepl-eval: live runtime eval against a warm nostrand-hosted MAGIC runtime (a socket io-prepl), the runtime complement to bb pipeline - Closes #11

Deps & docs

  • deps.edn switched to monorepo paths and flybot-sg/clr.test.check
  • Component READMEs refreshed; hardcoded version pins dropped
  • magic-unity/package.json metadata fixed and version synced from version.edn

skydread1 added 30 commits May 25, 2026 15:31
catch-throw-compiler recompiled the thrown expression with a stale captured compilers map instead of the threaded cmplrs, dropping let/loop locals introduced inside the catch. Thread cmplrs through.

Closes #7
ex-info and ex-data were ported but ex-message and ex-cause were not.
Add both using the stdlib's .NET idiom: .getMessage maps to .Message,
.getCause to .InnerException.

Add regression tests in magic.test.control.

Closes nasser/magic#238
…asser/magic#238)

Recompiled clojure.core.clj.dll deployed to nostrand/references and
magic-unity Export.
CaptureString starts buffering every char read; GetString returns and clears the buffer. Buffered chars are dropped on unread. Enables read+string in clojure.core.
…lojure.core

symbol arity-1 now converts Vars (qualified) and Keywords, throwing ArgumentException on anything else.

add-tap, remove-tap and tap> back a tap set with a BlockingCollection worker thread started lazily on first use. BlockingCollection is in System.dll on net471, so no assembly-load is needed.

read+string returns the object read plus the trimmed source text.

Tests live in magic.test.stdlib (compiler suite) and smoke.stdlib-1-10 (IL2CPP regression).
…stdlib additions

Rebuilt Clojure.dll (reader capture) and clojure.core.clj.dll (symbol, tap>, read+string).
PrintWriter-on returns a TextWriter that buffers writes and calls flush-fn
with the accumulated string on Flush, and close-fn (when non-nil) on Close.
Implemented as a proxy over System.IO.StringWriter.

Closes #8
Make :message and :cause conditional, add the :phase key read from
:clojure.error/phase ex-data, and derive each stack frame's class symbol
from the method's declaring type instead of the StackFrame type itself.

Refs #10.
)

Rebuilt clojure.core_print.clj.dll in nostrand/references and the Unity
Export dir for the 1.10 Throwable->map shape.

Refs #10.
Port the 1.10 ex-triage and ex-str error-triage pair and rewire repl-caught
to the 1.10 form (Throwable->map -> ex-triage -> ex-str). err->msg and
:clojure.error/path are post-1.10 and are intentionally not ported. Tests
mirror clojure.test-clojure.main from ClojureCLR.

Refs #10
…server (#10)

Port the 1.10 prepl trio. Drop three post-1.10 details from the ClojureCLR
reference: the 3-arity PrintWriter-on autoflush call (our PrintWriter-on is
2-arity), the read+string :read-cond opts form, and the valf wrap in the
io-prepl catch. prepl evaluates at runtime, so it runs under Mono/nostrand
only, never under IL2CPP AOT.

Closes #10
bb pipeline shows the compiled IL for a form but never runs it, so there was
no way to see what a form returns at runtime. These tasks add that, as the
runtime complement to pipeline:

  bb prepl-server [port]   start a socket prepl on a warm nostrand-hosted
                           MAGIC runtime (the MAGIC analogue of an nREPL)
  bb prepl-eval '<form>'   send a form to it and print the structured reply:
                           {:tag :ret :val .. :ms ..}, separate {:tag :out ..},
                           or an :exception true :ret with Throwable->map data

The server is MAGIC Clojure (clojure.core.server/io-prepl, from #10) running
on the CLR; the client is plain babashka, so each eval is fast. Global defs
persist across calls because Vars live in the runtime. Mono/nostrand only:
prepl needs runtime eval, so it has no IL2CPP path. Binds the listener to
IPAddress/Loopback so start-server skips the Dns resolution that can otherwise
bind off 127.0.0.1.

Closes #11
…ader

MetaReader unconditionally overwrote a read seq's :line/:column/:source-span
with the reader's positional values, clobbering explicit ^{:line N} metadata.
JVM 1.10.0 and ClojureCLR keep the explicit value and use the positional value
only as a default via RT.get. Match that.

renumbering-read (clojure.main, 1.10) depends on this: it re-reads a captured
form to stabilize line numbers, which requires explicit :line metadata to
survive the read.

Refs #12
Add renumbering-read and rewire repl-read to use it, matching Clojure 1.10.0.
repl-read previously read directly via (read {:read-cond :allow} *in*); 1.10
wraps that in renumbering-read, which captures the form via read+string then
re-reads it from a fresh LineNumberingTextReader with the line number reset,
giving REPL-read forms stable :line metadata.

Closes #12
…ng-read

Rebuilt Clojure.dll (MetaReader explicit-meta fix) and clojure.main.clj.dll
(renumbering-read, repl-read).

Refs #12
…ta (#13)

Clojure 1.10 :extend-via-metadata needs the protocol method's
fully-qualified symbol as the metadata lookup key. Add it as a sym
field with sym-carrying constructors, matching ClojureCLR.

Keep the sym-less constructors as a bootstrap bridge: the checked-in
core_deftype.clj.dll still emits newobj against them. Dropped once the
bootstrap DLLs are rebuilt.
Port the Clojure 1.10 :extend-via-metadata weave. When a protocol opts
in, emit-method-builder checks the target's metadata for the
fully-qualified protocol-fn symbol before the external extend table,
giving the order: direct definitions, then metadata, then extend.
Thread the method symbol through expand-method-impl-cache and
-reset-methods, and pass :extend-via-metadata from emit-protocol.

Closes #13
…or :extend-via-metadata (#13)

Regenerate the core_deftype and clojure.core.protocols bootstrap DLLs
and the Export Clojure.dll for the metadata dispatch weave. Clojure.dll
still carries the transitional sym-less constructors; dropped in the
follow-up.
)

The rebuilt bootstrap DLLs emit newobj against the sym-carrying
constructors, so the sym-less bridge is dead. Remove it, leaving
MethodImplCache matching ClojureCLR.
)

Rebuild the Export Clojure.dll without the sym-less MethodImplCache
constructors.
…gic#233)

Spells become flags. magic.flags gains *lift-vars*, *lift-keywords*, and
*sparse-case*; magic.core/active-spells derives the active spell set from them
and get-compilers uses it. This replaces the externally-set *spells* list and
the global bind-spells!/alter-var-root mutation, so a spell is toggled like any
other flag.

Delete bind-spells!/bind-basic-spells! and the dead magic.spells.protocols
spell. build.clj enables an extra spell by binding its flag and loading its
namespace. Existing binding-based callers are unaffected: the flag names and the
default spells are unchanged.

Document the flag surface in the root and compiler READMEs, and cover
active-spells in magic.test.flags (spell identity and order, the sparse-case
branch, and a standalone require of the spell namespaces).

Closes nasser/magic#233
…ic#233)

Regenerate the magic.flags, magic.core, and magic.api compiler DLLs in
nostrand/references for the flag-based spell configuration. Compiler-only
namespaces, so not part of the magic-unity Export set.
@skydread1 skydread1 self-assigned this Jun 1, 2026
@skydread1 skydread1 merged commit 695f33d into main Jun 1, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant