BjoManual/_04.Appendices.sz

417 lines
28 KiB
Text
Raw Normal View History

@section{Appendices}
@subsection{Appendix A — Coming from Scheme or ML}
Bjolang sits at the crossroads of two major functional programming lineages. From the Scheme and Lisp tradition, it draws its uniform S-expression syntax, prefix notation, code-as-data perspective, and hygienic macro system. From the ML family—specifically OCaml and F#—it inherits static Hindley-Milner type inference, algebraic data types, compile-time exhaustiveness checking, traits, and high-performance immutable data structures.
Depending on which background you bring to Bjolang, parts of the language will feel like second nature while other parts will require shifting your mental habits.
@subsubsection{Coming from Scheme}
If you have written Scheme, Racket, or Common Lisp, the visual rhythm of Bjolang will feel immediately comfortable:
@read-list{
- S-expression syntax and prefix notation: Expressions are nested parenthesized lists, and operators are ordinary functions called in prefix position: @code{(+ 1 2 (* 3 4))}.
- Familiar control forms: Core branching and binding forms like @code{let}, @code{let*}, @code{cond}, @code{when}, @code{unless}, and @code{begin} behave as you expect.
- First-class closures: Functions are first-class values created with @code{fun} (or @code{defun} at top level), capturing lexical scope, and passing into or returning from higher-order functions.
- Persistent linked lists: The @code{List} type provides standard singly-linked functional lists with @code{Cons} and @code{Nil}, along with familiar combinators such as @code{list-head}, @code{list-tail}, @code{list-map}, and @code{list-filter}.
- Hygienic macros: Bjolang features a compile-time syntactic macro system based on pattern matching with @code{syntax-match}, syntax quotation with @code{#'(...)}, and explicit identifier injection with @code{inject}.
}
However, several fundamental differences will quickly become apparent:
@read-list{
- Static typing and mandatory top-level signatures: Dynamic typing is absent. While types inside function bodies are inferred automatically using Hindley-Milner type inference, every top-level definition and exported value requires an explicit signature: @code{(: name type)}. Type errors are caught at compile time before any code runs.
- No truthiness tricks or @code{#f}-as-nil: In Scheme, functions like @code{memq} or @code{assoc} return either a value or @code{#f} to signal absence, and @code{#f} is often treated as a general falsey sentinel. In Bjolang, absent values are strictly represented by the @code{(Option %a)} union (@code{Some} or @code{None}), and operations that may fail return @code{(Result %e %a)} (@code{Ok} or @code{Err}). Booleans (@code{#t} and @code{#f}) are strictly booleans; conditionals do not coerce non-boolean values.
- Vectors and maps are persistent by default: In Scheme, vectors are mutable flat arrays. In Bjolang, @code{(Vec %a)} is an immutable Relaxed Radix Balanced (RRB) tree with efficient O(log n) random indexing, updates, and concatenation. Similarly, @code{(Map %k %v)} is an immutable Compressed Hash-Array Mapped Prefix-tree (CHAMP). In-place mutation is never the default: mutable state requires explicit declaration with @code{def/mutable} and modification with @code{set!}.
- Structural pattern matching over @code{car}/@code{cdr}: Destructuring lists, records, and unions is done using the @code{match} construct rather than long chains of @code{car}, @code{cdr}, or @code{cadr}. The compiler checks pattern matches for exhaustiveness and warns or errors if any potential value is unaccounted for.
- Concurrency without @code{call/cc}: Scheme systems often implement coroutines or thread libraries using first-class continuations (@code{call/cc}). Bjolang does not provide @code{call/cc}. Instead, it offers direct-style cooperative fibers (bjoroutines), typed channels, and Concurrent ML (CML) event combinators running on a dedicated fiber scheduler.
}
@subsubsection{Coming from F# or OCaml}
If you come from F#, OCaml, or Haskell, the conceptual model of Bjolang's type system and runtime behavior will feel very natural:
@read-list{
- Hindley-Milner type inference: Types within local expressions and function bodies are completely inferred. Polymorphic functions declare type variables with a leading percent sign, such as @code{%a} or @code{%elem}.
- Algebraic data types: Data structures are modeled as sum types (@code{Union}) and product types (@code{Record} on the heap, or value-type @code{Struct} on the stack).
- Exhaustive pattern matching: The compiler tracks which variants of a union or struct combination have been handled in a @code{match} or @code{def} binding, rejecting patterns that fail to cover every possible case.
- Immutability by default: Data structures, record fields, and bindings are immutable unless explicitly declared mutable.
- Traits and ad-hoc polymorphism: Bjolang's @code{def/trait} and @code{impl} system works much like Haskell typeclasses or Rust traits. Traits can define default method implementations, require super-traits, and specify associated types.
- Direct-style concurrency: Concurrency is written in direct style without callback pyramids, closely resembling F#'s async computations or OCaml 5 effect handlers.
}
The main shifts you will encounter coming from ML are:
@read-list{
- Syntax and parentheses: There is no indentation-based syntax (like F#) or @code{let ... in} keyword framing (like OCaml). Everything is an S-expression with prefix notation. While this takes an afternoon to get used to, it makes code structure uniform and simplifies automated tooling.
- First-class compile-time macros: Rather than relying on external preprocessors like OCaml's PPX or compiler plugins, Bjolang allows writing hygienic macros in the language itself using @code{def/macro}. These transformers run inside the compiler process during the expansion phase.
- Concurrent ML events instead of raw tasks: Rather than manipulating raw asynchronous tasks or promises directly, Bjolang builds upon Concurrent ML. The primitive unit of asynchronous composition is the @code{(Event %a)}. Events can be combined with @code{choose}, wrapped with transformations via @code{wrap}, and selectively synchronized using @code{sync}.
- Automatic colour twins: In many typed asynchronous languages, asynchronous functions have distinct types (@code{Task<'T>} or @code{Async<'T>}) that cannot be passed to standard higher-order functions like @code{List.map} without creating asynchronous duplicates (@code{List.mapAsync}). Bjolang solves this "function colouring problem" at the compiler level: higher-order functions declaring @code{-?->} parameters automatically generate both synchronous and suspending twins, allowing the exact same @code{list-map} to be used whether your callback performs I/O or pure computation.
}
@subsection{Appendix B — Reading compiler errors}
Bjolang's frontend is designed to catch structural, typing, and concurrency errors early, providing descriptive diagnostics that point directly to the source of the problem before code reaches the C# compiler. Below are some of the most common compiler messages you will encounter, what causes them, and how to resolve them.
@subsubsection{1. Missing top-level type signature}
@codeblock[#:lang "bjolang"]|{
(defun (greet name)
(println (str "Hello, " name)))
}|
When compiled, the frontend rejects this with:
@codeblock[#:lang "text"]|{
Type Error: Function 'greet' requires a type signature (: greet ...) at hello.bjo:1:1
}|
@strong{Why it happens:} Unlike local helper functions defined inside another function, every top-level definition in a module must carry an explicit type signature. The compiler requires this so that module interfaces are self-documenting and can be emitted into the compiled assembly's public metadata without requiring whole-program inference across files. The only exception is @code{main}, which has a fixed signature known to the compiler.
@strong{How to fix it:} Add an explicit signature right before the definition:
@codeblock[#:lang "bjolang"]|{
(: greet (-> string unit))
(defun (greet name)
(println (str "Hello, " name)))
}|
@subsubsection{2. Calling a bjoroutine from a synchronous function}
@codeblock[#:lang "bjolang"]|{
(: fetch-data (-> string string))
(defbjo (fetch-data url)
(http-get url))
(: process-request (-> string string))
(defun (process-request url)
(fetch-data url))
}|
The compiler detects that a synchronous function is attempting to suspend:
@codeblock[#:lang "text"]|{
Type Error at server.bjo:7:10: calling 'fetch-data' is a yield point, and a yield point is not allowed here. 'process-request' is defined with (defun ...). Define it with (defbjo ...).
}|
@strong{Why it happens:} In Bjolang, calls to bjoroutines look identical to ordinary function calls—there is no explicit @code{await} keyword. However, a bjoroutine can yield or suspend its fiber. A function defined with @code{defun} compiles to a regular synchronous C# method and cannot suspend. Allowing a synchronous method to call a suspending bjoroutine directly would require blocking the thread or breaking call-stack invariants.
@strong{How to fix it:} If the caller needs to perform suspending operations, define it using @code{defbjo} instead of @code{defun}:
@codeblock[#:lang "bjolang"]|{
(: process-request (-> string string))
(defbjo (process-request url)
(fetch-data url))
}|
If the caller must remain synchronous, spawn the bjoroutine inside an isolated scope or handle it asynchronously using the concurrency runtime.
@subsubsection{3. Mismatch between signature and argument count}
@codeblock[#:lang "bjolang"]|{
(: render-card (-> Card Suit string))
(defun (render-card card)
(card->string card))
}|
The compiler flags the arity divergence immediately:
@codeblock[#:lang "text"]|{
Type Error: Function 'render-card' has 1 mandatory args but signature specifies 2 at cards.bjo:2:1
}|
@strong{Why it happens:} The parameter list in the @code{defun} head does not match the arrow type declared in the preceding @code{(: ...)} form. Here, the signature promised two mandatory arguments (@code{Card} and @code{Suit}), but the definition provided only one parameter (@code{card}).
@strong{How to fix it:} Align the parameter list with the signature, either by updating the type signature or by adding the missing parameters to the function head.
@subsubsection{4. Non-exhaustive pattern matching}
@codeblock[#:lang "bjolang"]|{
(type (: Suit (Union Clubs Diamonds Hearts Spades)))
(: suit-color (-> Suit string))
(defun (suit-color s)
(match s
(Hearts "red")
(Diamonds "red")))
}|
The exhaustiveness checker discovers the missing cases:
@codeblock[#:lang "text"]|{
Pattern Error at game.bjo:6:3: this match does not cover every value. Clubs reaches no clause.
}|
@strong{Why it happens:} Bjolang verifies that pattern matches cover every possible variant of a union or structure. If an unhandled case is matched at runtime, there would be no clause to execute. The error message includes a concrete witness (such as @code{Clubs}) showing an unhandled value that would fail to match.
@strong{How to fix it:} Add the missing cases to the @code{match} expression, or provide a wildcard fallback @code{_} if other cases should receive default handling:
@codeblock[#:lang "bjolang"]|{
(: suit-color (-> Suit string))
(defun (suit-color s)
(match s
(Hearts "red")
(Diamonds "red")
((or Clubs Spades) "black")))
}|
@subsubsection{5. Exporting a binding that references a private type}
Suppose you define a module @code{player.bjo} where a type is private, but an exported function returns it:
@codeblock[#:lang "bjolang"]|{
(export make-player)
(type (: Player (Record (: name string) (: score int))))
(: make-player (-> string Player))
(defun (make-player name)
(Player name 0))
}|
Compiling @code{player.bjo} fails with:
@codeblock[#:lang "text"]|{
Export Error: the exported binding 'make-player' names the type 'Player', which this module declares and does not export. A type crosses a module boundary only when it is named in an (export ...), so an importer has no way to resolve this. Write (export Player), or (export Player) with the declaration marked #:opaque to keep its representation to this module's code.
}|
@strong{Why it happens:} If another file imports @code{make-player}, the calling module will receive a value of type @code{Player}. But because @code{Player} was not exported, the importing module cannot name the type in signatures or inspect its fields.
@strong{How to fix it:} Export the type in the module header:
@codeblock[#:lang "bjolang"]|{
(export Player make-player)
}|
If you want to keep the internal fields and constructor private to @code{player.bjo}, mark the type as @code{#:opaque}:
@codeblock[#:lang "bjolang"]|{
(export Player make-player)
(type #:opaque (: Player (Record (: name string) (: score int))))
}|
@subsubsection{6. Accessing fields of an opaque type}
Following from the previous example, suppose an importing module attempts to read a field from an opaque record:
@codeblock[#:lang "bjolang"]|{
(import "player.bjo")
(defun (main args)
(let ((p (make-player "Alice")))
(println (int->string (record-ref p score))))
0)
}|
The compiler prevents the private access:
@codeblock[#:lang "text"]|{
Type Error at main.bjo:5:37: 'score' cannot be read here. player/Player is exported #:opaque.
}|
@strong{Why it happens:} When a type is exported with @code{#:opaque}, its record layout and constructor are visible only to the module that defined it. Outside modules may pass values of the type around, but they cannot directly read or write fields with @code{record-ref} or pattern match on internal components.
@strong{How to fix it:} Expose a public accessor function in the defining module (e.g. @code{player-score}) and export that function alongside the type.
@subsubsection{7. Using a macro where it is defined}
@codeblock[#:lang "bjolang"]|{
(import (std prelude))
(import (std syntax-match))
(def/macro (def/thunk form inject compare)
(syntax-match form
((_ name body)
#'(defun (,name) ,body))))
(def/thunk answer 42)
}|
When compiled, the macro expander reports:
@codeblock[#:lang "text"]|{
'def/thunk' is a macro defined in this module, and a macro cannot be used where it is defined, at thunk.bjo:9:1. Its transformer runs inside the compiler, so it has to be compiled before whatever uses it is read — which cannot be true of the file it is written in. Move it to a module of its own and import that. An (include ...) will not do: an included file becomes part of this one.
}|
@strong{Why it happens:} Bjolang macros are not textual string replacements. A macro transformer is written in Bjolang, compiled into a .NET assembly, and executed dynamically inside the compiler process to expand syntax trees. Because the macro must already be compiled before the compiler can read and expand code using it, a file cannot use macros defined within its own source text.
@strong{How to fix it:} Move your macro definitions into a dedicated module (such as @code{macros.bjo}), and then import that module into the files where you wish to use them:
@codeblock[#:lang "bjolang"]|{
;; In main.bjo:
(import "macros.bjo")
(def/thunk answer 42)
}|
@subsection{Appendix C — How it compiles}
Bjolang targets the .NET 10 CLR by compiling to readable, high-performance C# 12 code, which is then compiled into managed assemblies by the Roslyn compiler. Rather than running an interpreter or emitting raw IL bytes, compiling through C# allows Bjolang to leverage RyuJIT's advanced optimization pipeline, benefit from modern runtime features like value types and spans, and interoperate seamlessly with the broader .NET ecosystem.
For the curious engineer, this appendix explains what happens under the hood when you build a Bjolang program.
@subsubsection{The compilation pipeline}
When you run @code{bjo build} or @code{bjo run}, the compiler processes source files through several distinct phases:
@read-list{
- Parsing and macro expansion: Source text is read into untyped S-expression ASTs. Imported macro modules are loaded dynamically, and macro transformers are executed to expand custom syntactic forms into core language primitives.
- Colour twin generation: Higher-order functions with polymorphic effect arrows (@code{-?->}) and functions that call dual-mode primitives are duplicated as source ASTs before type checking, producing both synchronous and suspending versions.
- Hindley-Milner type inference: Types of all expressions are inferred, top-level signatures are checked against function bodies, pattern match exhaustiveness is verified, and trait constraints (@code{where} clauses) are gathered.
- Monomorphisation: Generic functions constrained by traits are specialized at concrete call sites (such as @code{int} or custom types) to eliminate virtual interface dispatch.
- Trait inlining: Direct trait method calls on known types are rewritten into primitive C# operations (such as @code{==}, @code{<}, and arithmetic operators).
- C# codegen: The typed AST is emitted as standard C# source files, wrapping functions in static module classes and mapping Bjolang identifiers to legal C# names.
- Roslyn compilation: The emitted C# code is handed to Roslyn (either directly or via the persistent @code{VBCSCompiler} server), producing a @code{.dll} or @code{.exe}.
}
@subsubsection{The C# output model and runtime assemblies}
Each Bjolang source file compiles to a static C# class named after the module: @code{deck.bjo} becomes @code{deck_Module}.
Because Bjolang allows characters in identifiers that are invalid in C# (such as hyphens, exclamation marks, and question marks), the code generator applies deterministic name mangling:
@read-list{
- Hyphens become @code{sub}: @code{card-points} becomes @code{cardsubpoints}.
- Exclamation marks become @code{bang}: @code{set!} becomes @code{setbang}.
- Question marks become @code{p}: @code{empty?} becomes @code{emptyp}.
- Type variable sigils (@code{%}) become @code{tv_}: @code{%elem} becomes @code{tv_elem}.
}
Every compiled program links against a small set of optimized runtime assemblies:
@read-table{
| Assembly | Role in compiled programs |
|----------|---------------------------|
| @code{BjolangRuntime} | Core primitives, string formatting, dynamic environment, and conversions |
| @code{Collections} | Persistent @code{(Vec %a)} based on Relaxed Radix Balanced (RRB) trees |
| @code{SchemeList} | Persistent singly-linked @code{(List %a)} with @code{Cons} and @code{Nil} |
| @code{Map} | Persistent @code{(Map %k %v)} based on Compressed Hash-Array Mapped Prefix-trees (CHAMP) |
| @code{BjoSet} | Persistent hash set @code{(Set %a)} |
| @code{BjoOrderedMap} | Balanced ordered map @code{(OrderedMap %k %v)} |
| @code{BjoOrderedSet} | Balanced ordered set @code{(OrderedSet %a)} |
| @code{Bjoml} | Concurrency runtime: lightweight fibers, channels, promises, and CML scheduler |
}
These assemblies reside once in the Bjolang installation directory. A compiled program does not copy these DLLs next to its binary; instead, it registers a custom assembly resolver on startup that points directly back to the runtime installation directory.
@subsubsection{Colour twins: solving the function colouring problem}
In languages with asynchronous functions (such as JavaScript, Python, C#, or Rust), functions are typically divided into two "colours": synchronous functions that return @code{T}, and asynchronous functions that return @code{Task<T>} or @code{Promise<T>}. This dichotomy often splits standard libraries in two: an author must write @code{map} for synchronous callbacks, and a separate @code{mapAsync} for asynchronous callbacks.
Bjolang solves this problem at the compiler level using @em{colour twins}:
1. The polymorphic arrow @code{-?->}: When writing a higher-order function that accepts a callback, you can declare its parameter with @code{-?->} rather than a fixed arrow:
@codeblock[#:lang "bjolang"]|{
(: list-map (-> (List %a) (-?-> %a %b) (List %b)))
}|
2. Source-level twin generation: Before type checking occurs, the @code{ColourTwins} pass scans all declarations. When it detects a function with a @code{-?->} parameter, it automatically generates a second definition from the same source body, repainted with suspending signatures. Both definitions are checked independently. This ensures that effects and await points are fully verified without needing error-prone AST copying after inference.
3. Reaching twins: Any function defined with @code{defun} that calls a dual-mode function (such as @code{defbjouble} primitives that provide both synchronous and fiber implementations) automatically receives a suspending twin as well. If an author explicitly wants a function to remain strictly synchronous, they can mark it with @code{#:sync}.
4. Automatic call-site selection: When a function is called, the compiler checks the colour of the caller. If an ordinary @code{defun} calls @code{list-map}, it calls the synchronous twin. If a bjoroutine (@code{defbjo}) calls @code{list-map} with a suspending callback, it automatically links to the suspending twin. The author writes the higher-order function once, and the language transparently provides both versions.
@subsubsection{Monomorphisation and trait inlining}
Generic code bounded by traits (such as @code{(where (Eq %a))}) is initially desugared using dictionary passing:
@codeblock[#:lang "bjolang"]|{
(: least (-> %a %a %a) (where (Ord %a)))
(defun (least a b)
(if (<= a b) a b))
}|
Under standard dictionary passing, @code{<=} is compiled into an interface method call: @code{_dict_Ord_a.le(a, b)}. While flexible, virtual calls through interface dictionaries carry notable overhead: they prevent inlining, require heap-allocated dictionary references, and force value types (like @code{int} or structs) to be boxed.
To eliminate this cost, Bjolang employs @em{monomorphisation}:
@read-list{
- Call-site specialization: Before trait inlining runs, the compiler inspects every call to a constrained generic function. When a function like @code{least} is called with concrete types—for instance, @code{(least 3 5)}—the compiler generates a specialized copy of @code{least} tailored specifically for @code{int}.
- Stable naming: Specialized copies are named deterministically using an FNV-1a hash of their concrete type arguments. This ensures that incremental builds generate stable symbol names across separate runs.
- Trait inlining: Once a copy is checked at a concrete type, @code{TraitInline} replaces the dictionary method call with the direct C# operation. For @code{int}, @code{_dict_Ord_int.le(a, b)} becomes a direct @code{a <= b} primitive comparison in the generated C# code.
- Cross-module monomorphisation: What if @code{least} is exported from a library and called from an application module? An exported constrained function embeds its untyped source AST directly into the compiled @code{.dll}'s metadata. When the importing module calls @code{least} with its own local types, it reads the AST from the imported metadata and monomorphises it locally against the caller's own trait registry.
}
As a result, generic functional abstractions in Bjolang compile down to the same tight loops and direct machine instructions as hand-written C#.
@subsubsection{Incremental builds and .bjobuild}
To keep development fast, Bjolang includes an incremental compilation system designed for sub-50ms turnarounds.
When a compilation finishes successfully, the compiler writes a build manifest named @code{<source>.bjobuild} beside the source file:
@codeblock[#:lang "text"]|{
mode release
output /home/linus/game.exe
compiler /home/linus/bjolang/bin/Release/net10.0/Bjolang.dll
source /home/linus/game.bjo
source /home/linus/deck.bjo
dep /home/linus/bjolang/lib/std/prelude.dll
dep /home/linus/bjolang/BjolangRuntime/bin/Release/net10.0/BjolangRuntime.dll
}|
When you run @code{bjo run game.bjo} again:
@read-list{
- @code{bjo} reads @code{game.bjobuild} and checks the file timestamps of every input: the compiler binary, each transitive source file, and linked dependencies.
- If nothing has changed, it executes the existing binary immediately without launching dotnet or the compiler.
- If any source file, dependency, or the compiler itself is newer than the binary, it triggers a rebuild.
}
You can pass @code{-c} to force an unconditional rebuild, or @code{-d} to create an unoptimized debug build with AST and generated C# dumps.
@subsection{Appendix D — Editor support}
Editing Bjolang code is most pleasant in Emacs, where specialized major modes support both Bjolang source files and Samizdat documentation.
@subsubsection{Bjolang mode: bjomode.el}
@code{bjomode.el} is located in the root of the Bjolang repository. It provides syntax highlighting, indentation, and structural navigation tailored to Bjolang's syntax.
To use it in your Emacs configuration, add the repository directory to your @code{load-path} and require the mode:
@codeblock[#:lang "elisp"]|{
(add-to-list 'load-path "/path/to/bjolang")
(require 'bjo-mode)
}|
@code{bjo-mode} associates automatically with @code{*.bjo} and @code{*.protobjo} files.
Key capabilities of the mode include:
@read-list{
- Comprehension brace pairing: Bjolang uses curly braces for list and vector comprehensions: @code{{for x in xs :yield (* x 2)}}. In @code{bjo-mode}, braces are configured in the syntax table as paired delimiters. Structural navigation commands like @code{C-M-f} (@code{forward-sexp}), @code{C-M-b} (@code{backward-sexp}), and @code{show-paren-mode} treat curly braces exactly like parentheses.
- Syntax propertizing for character literals: In Lisp syntax tables, a semicolon normally starts a line comment. The literal @code{#\\;} is recognized by a custom syntax-propertize rule so that the semicolon is treated as a character literal rather than swallowing the remainder of the line in comment coloring. Similarly, @code{#\\(} is recognized as a character rather than an unmatched opening parenthesis.
- Multiline raw string fences: Triply-quoted raw strings (@code{"""..."""}) are detected and marked with string fence syntax, ensuring that quotes inside raw strings do not disrupt subsequent syntax highlighting.
- Scheme-style indentation: Indentation follows Scheme conventions. Forms with bodies (@code{defun}, @code{defbjo}, @code{let}, @code{match}, @code{type}, @code{when}, @code{unless}) indent their bodies by 2 spaces. Branching arms in @code{if} forms indent by 4 spaces. Standard function calls and comprehension clauses line their arguments up under the first expression.
}
@subsubsection{Samizdat mode: samizdat-mode.el}
All documentation for Bjolang—including this manual—is written in Samizdat (@code{.sz}). The major mode @code{samizdat-mode.el} provides syntax highlighting and editing commands for Samizdat documents.
To configure it:
@codeblock[#:lang "elisp"]|{
(add-to-list 'load-path "/path/to/samizdat")
(require 'samizdat-mode)
(add-to-list 'auto-mode-alist '("\\.sz\\'" . samizdat-mode))
}|
Key editing features and keybindings include:
@read-list{
- Command highlighting: Samizdat directives like @code{@"@"section}, @code{@"@"subsection}, @code{@"@"codeblock}, and @code{@"@"include} are highlighted distinctly from regular prose text.
- Verbatim block handling: Verbatim code blocks enclosed in @code{|{ ... }|} and comments enclosed in @code{@"@";{ ... }} are syntax-propertized so that code symbols or @code{@"@"} characters inside code are not mistaken for Samizdat markup.
- @code{C-c C-s}: Insert a section or subsection header at point, prompting for the heading level and title.
- @code{C-c C-e}: Insert a Samizdat command at point, offering autocompletion over standard directive names. If a region is active, wraps the selected text in the command.
- @code{C-c C-b}: Wrap the selected region in a verbatim code block (@code{@"@"codeblock|{ ... }|}), prompting for an optional @code{#:lang} specifier.
- @code{C-c C-l}: Wrap the selected region in a link command (@code{@link[...]}).
- @code{C-c '}: Edit the verbatim code block at point in a dedicated indirect buffer using that block's native major mode (such as @code{bjo-mode}, @code{sh-mode}, or @code{emacs-lisp-mode}). Pressing @code{C-c '} again commits the changes back to the document.
- @code{C-c C-o}: Follow the reference at point, opening the target URL or jumping directly to an included file.
- Document navigation: Seamless integration with @code{outline-minor-mode} and @code{imenu} allows folding sections and navigating complex documents through hierarchical headings.
}