Releases: sdiehl/prism
Releases · sdiehl/prism
v0.3.0
- New surface syntax, every form sugar over the existing core:
| Sugar | Desugars to |
|---|---|
x += e (and -=, *=, %=) |
x := x <op> e |
while cond do <block> |
repeat_while(\() -> cond, \() -> <block>) |
loop <block> (may break) |
repeat_while(\() -> true, \() -> <block>) |
loop <block> (no break) |
forever(\() -> <block>) (bottom-typed: never returns) |
break / continue |
final ctl perform of an internal Break/Continue effect, handled at the loop |
return e |
final ctl perform of an internal Return(a) effect, handled at the function body |
a ^ b |
pow(a, b), the Pow class method (Int instance bignum-multiplies, Float is pow_float) |
a[i] |
index(a, i), a failable read dispatched on a's head type (Elem ! {Fail}) |
a[i] := v |
a := index_set(a, i, v), the in-place (FBIP) setter, rebinding the var |
a[i] += e (and -=, *=) |
a[i] := a[i] + e (the read makes it ! {Fail}) |
f(args, using d) |
explicit instance selection (the old f[d](args)), f's callee wrapped in the chosen dict |
- Indexing
a[i]/a[i] := v/a[i] += eoverArray(Int key),HashMap(String key), read-onlyStringbyte, andList, dispatched at elaboration on the receiver's head type (no new class). A missing index performsFail(a[i] : Elem ! {Fail}), so it composes with??/?./default; writes are FBIP-in-place when uniquely owned, and nestedgrid[i][j] := vcomposes. - Explicit instance selection moves from
f[name]tof(args, using name), freeing[]for indexing and converging override with first-class dictionary passing;usingis reserved. - Instance coherence: each
(class, type-head)has one canonical instance that implicit resolution always selects, so ambiguity-at-use is gone. A lone instance is canonical by default; when several share a head one is named with a top-levelcanonical Class(Head) = name, and two undesignated instances are a coherence error at definition (caret plus designation hint).f(args, using name)stays the visible override, and anewtypeis the way to a different default. No Core, runtime, or backend change, so the parity oracle is byte-identical.canonicalis reserved. - A right-associative power operator
^(tighter than*), the method of a newPowclass:2 ^ 10is bignum-correctInt,2.0 ^ 10.0isFloat, mixedInt ^ Floata type error. The prior integerpowis nowint_pow. - Imperative loops (
while/loop) lower to a tail-recursive prelude driver (constant stack, no per-iteration allocation), an unconditionalloopto the bottom-typedforever.break/continue/returncompile to non-resumable performs of internal, fully-handled effects, so none surfaces in a function's row, and a loop installs a handler only for the keyword it uses. The prelude's oldwhileis nowrepeat_while. - Principal effect-row inference: a lambda delimits its effects onto its own arrow row, the arrow is covariant in that row (a pure function fits any effectful context via row subsumption), and the call-graph set pass is dropped as a row seed so the inferred row is the single source of truth. Builtins carry their effect row on the type, so inference attributes
IO/Exn/Faildirectly; rows display in canonical name-sorted order; and definitions are inferred in dependency-SCC order so a forward reference sees a generalized type. maskover the sole handler of an effect now leaves the operation genuinely unhandled (the label stays in the row) instead of inferring it pure and hitting an effect-reconciliation ICE at lowering.- Two source warnings (the prelude is exempt): an unused local binding and a name shadowing one in scope (a leading
_and a consuming rebindlet s = f(s)are exempt). Annotations are name-checked uniformly across parameters, returns, constraints, and rows: an undeclared effect or constructor is a hard error, and an annotation broader than the inferred row warns. - Standard library split into on-demand modules under
lib/std(Data.Char/List/Map/Maybe/Result/Set/String), shrinking the always-loaded prelude;Setgainsset_union/set_intersection/set_difference, and a project may replace the built-in prelude via[package] preludeinprism.toml. - CLI: a bare
prism program.prcompiles a single file to a native binary named after the source (-ooverrides,--mlirselects the backend);prism buildis the project verb (nearestprism.toml) and interpreting is explicit viaprism run. - Local monadification confines the free-monad form to the component entangled with an escaping effect, keeping unrelated functions on their fused evidence/state path and falling back to whole-program lowering only when the split is unclean. The fallback now warns on stderr by default (silence with
PRISM_QUIET), naming the functions that lost fusion. - Constant-stack effect handlers: a closed free-monad handler is driven by one self-tail-recursive
{n}@regionloop, so a tail-resumptive or function-answer state handler resumes viamusttailin constant native stack (aperf_gateStateloop goes from ~10^5 to millions of iterations in a 2 MB stack). On by default (opt out withPRISM_NATIVE_EFFECTS=0); it changes the driver, not the one-EOp-cell-per-operation reification. - Compiler-internal correctness, with no change to accepted programs or output: existential scope is enforced unconditionally at
solve; numeric defaulting is deferred to one pass at generalization; and FBIP reuse becomes a scopedWithReuse/Reusecore node, making free-once and spend-at-one-allocation well-formedness properties of the term. - The Lean core model (
models/) grows from a substitution small-step into a mechanized CEK machine proved to implement a big-step semantics, with unique normal forms, a progress trichotomy, and effect progress/unhandled lemmas, allsorry-free overpropext/Quot.sound. It runs as a third differential oracle via a newprism dump core-jsonphase, checked against the interpreter across a fixture corpus. - Hardening: the bignum allocator is overflow-checked like every cell, each builtin's calling convention is derived from an exhaustive
Builtinmethod (so a new builtin cannot ship without declaring its ABI), and the reference grammar is single-sourced inmodels/grammar.ebnf, sliced into the spec by mdbook anchors.
v0.2.0
0.2.0
- Fixed-width bitwise and shift builtins on the I64/U64 lanes:
i64_and/i64_or/i64_xor/i64_shl/i64_shrand theiru64_*counterparts. and/or/xor share one bit pattern across lanes;i64_shris arithmetic,u64_shrlogical; shift counts are taken modulo 64. system(String) -> Intruns a shell command and returns its exit code, andeprint/eprintlnwrite to stderr, so a program can drive external tools and emit diagnostics off the stdout stream.- Superclasses: a class may declare another as a superclass (
class Ord(a) given Eq(a)). Each instance carries a resolved superclass dictionary as a leading field of its dict cell, and agiven Ord(a)constraint discharges anEq(a)obligation by projecting it, found automatically from the instances in scope. The prelude'sOrdnow requiresEq. - Growable mutable
Array(a)(array_new/array_empty/array_len/array_get/array_set/array_push), an ordinary reference-counted heap cell so drops recurse into its elements.array_setandarray_pushwrite in place when the array is uniquely owned (FBIP) and copy otherwise;array_pushdoubles capacity when full, so appends are amortized O(1). The prelude addsarray_of_list. string_of_arrayand the preludeconcat_all/array_of_listbuild a string from many chunks in a single allocation, replacing the quadratic right-nestedconcatchain.- Prelude
HashMap(v): a separate-chaining hash table with String keys built on the growable array (hm_new/hm_insert/hm_lookup/hm_member/hm_get_or/hm_keys/hm_values/hm_size/hm_to_list/hm_delete/hm_from_list/hm_adjust), doubling its bucket count past load factor 1. Keys hash by a fixed-width FNV-1a written in the language, so iteration order is a deterministic function of the inserts. - O(1) byte access:
byte_at/byte_len(UTF-8 unaware) andstring_of_bytes, so a lexer or hash scans raw bytes in linear time.array_poprounds out the array API, andarray_foldl/array_to_listare added to the prelude. - Surface fixed-width arithmetic:
i64_*/u64_*add/sub/mul/div/rem/cmp(wrapping, no bignum promotion), enabling a real fixed-width hash in userland. - Higher-kinded types: a class parameter may range over a type constructor (kind
* -> *), applied asf(a)in method signatures, with instance resolution keyed on the head constructor. The prelude adds theFunctor/Applicative/Monad/Foldable/Traversabletower withListandOptioninstances.fmap/traverseare effect-polymorphic, so the per-element effect row threads through instead of anApplicativewrapper (effects, not do-notation). - String-utility prelude: character classifiers (
is_digit/is_alpha/is_alnum/is_space/is_upper/is_lower,to_upper_c/to_lower_c),starts_with/ends_with/contains/index_of,to_upper/to_lower/trim, andargs(). prism fmtseparates top-level declarations with a blank line.- Editor tooling: a dependency-free Neovim highlighter under
scripts/nvim/(anftdetect/filetype map for*.prplus asyntax/highlighter), with its keyword set mirrored fromsrc/lex/token.rsso it tracks the lexer. - A project and module system. A
prism.tomlmanifest ([package] name,[bin] entry) plus asrc/tree make a multi-file project thatprism run/buildcompiles, resolving module paths from the project root rather than the entry file's directory (single-file invocation is unchanged). Name resolution now canonicalizes every top-level definition to a module-qualified symbol (Data.Map.insertfor exports,Data.Map@helperfor privates), so qualified references (M.x) reach disjoint namespaces and two modules may export the same short name and coexist; selective imports (import M (a, b)) bring only the listed names into bare scope, andpubcontrols what an importer can reach. Instances record their defining module: an orphan instance (defined apart from both its class and its type) and instances overlapping across modules are reported as warnings, and an ambiguity names each candidate's module. - Module-system follow-ups:
pub import M (x)re-exports the named items through the importing module, with chains resolving transitively to the original definition; a module's full dotted path qualifies a reference (Geo.Util.one), not only its last component (Util.one); and orphan/overlapping-instance warnings render with a source caret when they point into the program being compiled. - Static
fip/fbipchecking of the FP^2 discipline.fbipproves zero fresh allocation and that an annotated body calls only annotated, allocation-free functions;fipadditionally proves linearity (each owned non-immediate binding consumed at most once, checked on the source term with scalars exempt) and bounded stack (every recursive call in the call-graph SCC is a tail call or a single tail-modulo-cons/-add). The tail/TRMC classification is shared with codegen (src/core/tailrec.rs), so an acceptedfipfunction always lowers to a loop;fipmay call onlyfip,fbipmay call either. Recursive accumulator/TRMC functions likerev_ontoandbumpnow type-check asfipand run in constant heap and stack. - A cyclic superclass hierarchy (
class A given B,class B given A) is now reported as asuperclass cycle: A -> B -> Aerror at class-construction time, instead of overflowing the stack the first time the chain is walked at a use site. - Pattern coverage now treats an irrefutable
if trueguard as always-matching: such an arm completes exhaustiveness like an unguarded one (no spurious non-exhaustive error) and shadows any later arm (which is reported unreachable). Fallible guards keep their conservative non-covering treatment. - The
fipbounded-stack rejection now explains when a function joins the tail-recursion group only because another function flows as a first-class value (a capture) rather than through a direct call cycle, pointing at the fix (call directly or annotatefbip). The set of accepted programs is unchanged; only the diagnostic is sharper. - Rank-N polymorphism is predicative: a
forallwritten directly as a type-constructor argument (List(forall a. (a) -> a)) is now rejected at the annotation with a clear "impredicative type" message naming the constructor, instead of surfacing later as a confusingexpected a, got Intmismatch from a leaked rigid variable. Higher-rank types remain available as function parameters, results, and declared data fields (a polymorphic field carries aforallthrough a generic container).
v0.1.0
0.1.0
Initial release.
- Strict, impure functional language with ML-family surface syntax: ADTs, pattern matching, parametric polymorphism, and a prelude of
Option/Result/List/Mapcombinators. - Hindley-Milner type inference with bidirectional, higher-rank (rank-N) checking and subsumption.
- Type classes by dictionary passing, with named instances and
deriving (Eq, Ord, Show). - Algebraic effects and handlers: inferred, extensible effect rows via row polymorphism; multishot
resume;final ctlnon-resumable clauses; scoped/masked handlers and forwarding. - Evidence-passing compilation of handlers, with tail-resumptive clauses lowered to direct calls and a free-monad fallback when effects escape tracking.
- Exhaustiveness and redundancy checking, plus decision-tree pattern-match compilation.
- First-class optics: record-update paths, view patterns, bidirectional pattern synonyms, and
deriving (Lens). - Stream fusion: effectful producer/transformer/consumer pipelines fuse to zero intermediate allocations.
- Verse-inspired failure model (
fail,guard,??,?.,transact) and structured error handling, both built on effect handlers. - Deterministic memory management via Perceus reference counting with in-place reuse (FBIP), no garbage collector.
- Call-by-push-value core in A-normal form with tail-call optimization and tail recursion modulo cons.
- Three backends kept byte-identical: a tree-walking interpreter, native code via LLVM, and a text-MLIR backend.
- Lean model of the core with a machine-checked determinism theorem.
- Compiles to WebAssembly, so the language runs in the browser.
- Tooling: interactive shell,
run,build,check,fmt, and phasedump.