diff --git a/.idea/dsa2.iml b/.idea/dsa2.iml
index 3f76210..5b6d67e 100644
--- a/.idea/dsa2.iml
+++ b/.idea/dsa2.iml
@@ -20,6 +20,8 @@
+
+
diff --git a/doc/DSC/dscx.md b/doc/DSC/dscx.md
deleted file mode 100644
index e69de29..0000000
diff --git a/doc/DSA State Machine Semantics.md b/doc/notes/DSA State Machine Semantics.md
similarity index 100%
rename from doc/DSA State Machine Semantics.md
rename to doc/notes/DSA State Machine Semantics.md
diff --git a/doc/dsc.md b/doc/notes/dsc.md
similarity index 100%
rename from doc/dsc.md
rename to doc/notes/dsc.md
diff --git a/doc/notes/dscs-dscx-language-spec.md b/doc/notes/dscs-dscx-language-spec.md
new file mode 100644
index 0000000..4abe1f4
--- /dev/null
+++ b/doc/notes/dscs-dscx-language-spec.md
@@ -0,0 +1,782 @@
+# DSCS & DSCX Language Specification (Draft v0.1)
+
+**DSCS** (Damn Simple C — Systems) and **DSCX** (Damn Simple C — eXtended) — a two-tier
+language pair for the DSA architecture. DSCS is a small, explicit systems language for
+kernels and drivers. DSCX is a Rust/Kotlin-flavored OOP language that transpiles to DSCS.
+Every valid DSCS program is a valid DSCX program.
+
+---
+
+## Table of Contents
+
+1. [Introduction](#introduction)
+2. [Lexical Structure](#lexical-structure)
+3. [DSCS: Types](#dscs-types)
+4. [DSCS: Ownership](#dscs-ownership)
+5. [DSCS: Structs, Traits, Generics](#dscs-structs-traits-generics)
+6. [DSCS: Functions, Control Flow, Memory](#dscs-functions-control-flow-memory)
+7. [DSCS: Concurrency (Task/Poll)](#dscs-concurrency)
+8. [DSCX: Classes and Inheritance](#dscx-classes-and-inheritance)
+9. [DSCX: Properties](#dscx-properties)
+10. [DSCX: Trailing Lambdas](#dscx-trailing-lambdas)
+11. [DSCX: Pattern Matching and `is`](#dscx-pattern-matching-and-is)
+12. [DSCX: Optionals](#dscx-optionals)
+13. [DSCX: Objects (Singletons) and Coroutines](#dscx-objects-and-coroutines)
+14. [Example Programs](#example-programs)
+15. [Grammar Summary (partial)](#grammar-summary)
+16. [Appendix A: DSCS vs DSCX vs Rust](#appendix-a-dscs-vs-dscx-vs-rust)
+
+---
+
+## 1. Introduction
+
+### Design goals
+
+- **DSCS is the floor.** It compiles to an IR, which lowers to DSA assembly. It has no
+ runtime, no hidden allocation, no dynamic dispatch. If you can write it in DSCS, you
+ can point at the exact assembly it becomes.
+- **DSCX is the ceiling.** It parses to its own AST, type-checks with full inference,
+ then transpiles to DSCS source (or DSCS-level IR) before continuing through the same
+ backend. DSCX never bypasses DSCS — everything DSCX does must be expressible as
+ ordinary DSCS underneath.
+- **DSCS ⊂ DSCX.** A `.dscs` file is always a legal `.dscx` file. Dropping to manual,
+ explicit low-level code is always available, never a special mode.
+
+### Two-language boundary, one detail that matters
+
+DSCS functions returning owned memory use `Box` (owned, linear) or `&T` (borrowed) in
+their signatures — never a bare untyped pointer for anything crossing a function
+boundary. This is what lets DSCX's compiler automatically decide how to manage memory
+it receives from DSCS calls, without documentation or guesswork (see
+[§4](#dscs-ownership)).
+
+---
+
+## 2. Lexical Structure
+
+Comments, identifiers, and literals are unchanged from the original DSC spec.
+
+```rust
+// single-line comment
+/* multi-line
+ comment */
+
+valid_identifier
+_private
+MyStruct
+
+42 0x2A 0b101010 0o52
+"a string" "\n \t \\ \""
+true false
+```
+
+Semicolons are mandatory statement terminators in both languages.
+
+### Reserved keywords (union of both languages)
+
+```
+as async await break class
+const continue defer dyn else
+enum false fn for if
+impl include is let loop
+match mut object priv pub
+return self Self spawn static
+struct trait true while
+```
+
+DSCX-only: `class`, `object`, `trait` (as a type-position bound), `async`, `await`,
+`spawn`, `is`, `match` (no-scrutinee form). All are usable in DSCS only where noted below
+(DSCS has its own, smaller `trait`).
+
+---
+
+## 3. DSCS: Types
+
+### Primitives
+
+```rust
+i8 i16 i32 i64 isize
+u8 u16 u32 u64 usize
+bool
+str // pointer to a length-prefixed byte sequence
+void // absence of value, return-type only
+```
+
+### Pointers and ownership
+
+```rust
+&T // borrowed, immutable
+&mut T // borrowed, mutable
+Box // owned, linear — see §4
+Rc // shared, refcounted, linear handle — see §4
+```
+
+### Arrays and generics
+
+```rust
+[u32; 10]
+Vec
+Map
+```
+
+### Algebraic types
+
+```rust
+enum Shape {
+ Circle(isize),
+ Rect(isize, isize),
+ Point,
+}
+```
+
+Enums carry data per variant, matched exhaustively with `match` (§6). No implicit null —
+absence is always `Option` (an ordinary two-variant enum in the standard library).
+
+---
+
+## 4. DSCS: Ownership
+
+DSCS has no borrow checker and no lifetimes. It adds exactly one piece of static
+checking beyond plain C-like pointers: **linear ("used-once") tracking** for `Box` and
+`Rc` handles.
+
+- A `Box` value must be consumed exactly once along every path out of its scope —
+ either freed explicitly (`.drop()`) or moved out (returned, stored, passed to a
+ function taking ownership).
+- `Box` is conceptually a fat pointer: `{ ptr: *T, alloc: &Allocator }`. `.drop()`
+ always frees through the allocator it was created with — the caller never needs to
+ remember or re-pass the allocator.
+- `Rc` follows the same linear-handle discipline. `.clone()` consumes a `&Rc` and
+ produces a new, independent owned handle (bumping the refcount); `.drop()` decrements,
+ freeing at zero.
+- Plain `&T` / `&mut T` are just borrows — no linearity tracking, no aliasing rules. The
+ programmer is responsible for not creating dangling references, exactly as in C.
+
+```rust
+fn make_point(alloc: &Allocator) -> Box {
+ Box::new(alloc, Point { x: 0, y: 0 })
+}
+
+fn peek(p: &Point) -> isize {
+ p.x // borrowed, no ownership implications
+}
+
+fn main() {
+ let alloc = Allocator::system();
+ let p = make_point(&alloc); // p: Box, owned
+ print(peek(&p));
+ p.drop(); // required — linear checker enforces this
+}
+```
+
+This is the entire mechanism DSCX relies on to automate memory management across the
+language boundary: ownership is legible from the type, not from a comment.
+
+---
+
+## 5. DSCS: Structs, Traits, Generics
+
+### Structs
+
+```rust
+struct Point {
+ x: isize,
+ y: isize,
+}
+
+impl Point {
+ fn new(x: isize, y: isize) -> Point {
+ Point { x: x, y: y }
+ }
+ fn len(&self) -> isize {
+ sqrt(self.x * self.x + self.y * self.y)
+ }
+}
+```
+
+Structs are value types by default (copied on assignment unless explicitly boxed).
+
+### Traits — static dispatch only
+
+```rust
+trait AsUnit {
+ fn unit(&self) -> Self;
+}
+
+impl AsUnit for Point {
+ fn unit(&self) -> Self {
+ let len = self.len();
+ Point { x: self.x / len, y: self.y / len }
+ }
+}
+```
+
+DSCS traits are monomorphized at compile time (like Rust generics with trait bounds).
+**There is no `dyn`, no vtable, no runtime dispatch in DSCS itself.** Where kernel code
+needs runtime-polymorphic behavior (e.g. driver interfaces), build it explicitly as a
+struct of function pointers — the same pattern C kernels use:
+
+```rust
+struct FileOps {
+ read: fn(&File, &mut [u8]) -> usize,
+ write: fn(&File, &[u8]) -> usize,
+}
+```
+
+This manual pattern is exactly what DSCX's `dyn Trait` compiles down to (§8) — DSCS
+users can build it by hand for the same effect, with full visibility into the indirection.
+
+### Generics
+
+```rust
+struct Vec {
+ data: Box<[T]>,
+ len: usize,
+}
+
+impl Vec {
+ fn push(&mut self, item: T) { /* ... */ }
+}
+```
+
+Simple, monomorphized generics — no higher-kinded types, no const generics (yet), no
+specialization.
+
+---
+
+## 6. DSCS: Functions, Control Flow, Memory
+
+```rust
+fn add(a: i32, b: i32) -> i32 {
+ a + b // implicit return of trailing expression
+}
+```
+
+### Control flow
+
+```rust
+if x < 10 { ... } else { ... }
+while i < len { ... }
+loop { ... } // infinite, break to exit
+
+match shape {
+ Shape::Circle(r) => 3 * r * r,
+ Shape::Rect(w, h) => w * h,
+ Shape::Point => 0,
+}
+```
+
+DSCS `match` requires a scrutinee and must be exhaustive over the enum's variants — no
+`is`, no dynamic-dispatch matching (those are DSCX-only, see §11).
+
+### Memory: allocator passing + `defer`
+
+```rust
+fn read_file(alloc: &Allocator, path: str) -> Box<[u8]> {
+ let buf = alloc.alloc(1024);
+ defer buf.drop(); // runs on every exit path
+ // ...
+ buf
+}
+```
+
+`defer` schedules an expression to run at scope exit, in reverse declaration order,
+regardless of which `return` path is taken — identical to the original DSC spec.
+
+---
+
+## 7. DSCS: Concurrency (Task/Poll)
+
+DSA has no true parallelism; concurrency is cooperative and single-threaded. DSCS
+exposes this as a plain trait, not language syntax:
+
+```rust
+enum Poll { Ready(T), Pending }
+
+trait Task {
+ type Output;
+ fn poll(&mut self) -> Poll;
+}
+```
+
+Only operations that can genuinely suspend implement `Task`. Ordinary DSCS code is
+unaffected and stays synchronous.
+
+```rust
+struct UartRead;
+impl Task for UartRead {
+ type Output = u8;
+ fn poll(&mut self) -> Poll {
+ if uart_has_byte() { Poll::Ready(uart_read_byte()) } else { Poll::Pending }
+ }
+}
+```
+
+A kernel scheduler polls all live tasks each loop iteration, and idles the core with the
+`hlt()` intrinsic when nothing made progress, resuming on the next interrupt:
+
+```rust
+fn run(tasks: &mut Vec>>) -> ! {
+ loop {
+ let mut progressed = false;
+ for t in tasks.iter_mut() {
+ if let Poll::Ready(()) = t.poll() { progressed = true; }
+ }
+ if !progressed { hlt(); }
+ }
+}
+```
+
+ISRs never run task logic directly — they flip a flag or push to a queue that the
+relevant `Task::poll()` checks (e.g. `UART_READY`). Shared state touched by both an ISR
+and the scheduler must have interrupts masked during access, since a single core can
+still be preempted mid-operation by an interrupt.
+
+DSCX's `async`/`await`/coroutines (§13) compile down to hand-written-equivalent `Task`
+state machines using exactly this vocabulary.
+
+---
+
+## 8. DSCX: Classes and Inheritance
+
+```rust
+class Point2 {
+ let x: isize,
+ let y: isize,
+ fn len(self) -> isize {
+ sqrt(self.x * self.x + self.y * self.y)
+ }
+}
+
+// single class inheritance + multiple trait conformance
+class Point3: Point2 + AsUnit {
+ let z: isize,
+ fn len(self) -> isize {
+ sqrt(self.x * self.x + self.y * self.y + self.z * self.z)
+ }
+ fn AsUnit.unit(self) -> Self {
+ let len = self.len();
+ Self { x: self.x / len, y: self.y / len, z: self.z / len }
+ }
+}
+```
+
+- Classes are **reference types** by default (Kotlin-style) — unlike DSCS structs, which
+ are value types. `self`/`&self` collapse to the same meaning at the class level.
+ Actual by-value semantics belong to DSCS structs.
+ Method receivers use bare `self`.
+- A class may inherit from **one** class and implement **any number** of traits
+ (`Class: Base + TraitA + TraitB`).
+- Trait-method qualification (`fn AsUnit.unit(self)`) disambiguates a method name that
+ collides with an inherited or another trait's method; it's only required where a
+ collision actually exists.
+- Memory is never manual here — DSCX auto-inserts alloc/free for provably single-owner
+ values, and falls back to refcounting when a value's escape analysis shows multiple
+ live aliases or a thread/coroutine boundary crossing. Both paths lower to explicit
+ `Box`/`Rc` + `defer` in the generated DSCS.
+- `dyn Trait` (implicit for bare trait-typed parameters, e.g. `fn f(val: AsUnit)`)
+ compiles to the manual vtable-struct pattern from §5.
+
+---
+
+## 9. DSCX: Properties
+
+```rust
+class Circle(val r: i32) {
+ var area: i32
+ get() = 3 * r * r
+ set(value) { r = sqrt(value / 3) }
+}
+
+let c = Circle(5)
+let a = c.area // desugars to c.get_area()
+c.area = 20 // desugars to c.set_area(20)
+```
+
+Compound assignment through a property (`c.area += 1`) desugars to
+`c.set_area(c.get_area() + 1)`, evaluating a non-trivial receiver expression exactly
+once via a compiler-generated temporary.
+
+---
+
+## 10. DSCX: Trailing Lambdas
+
+A bare `{ }` immediately following a call is a trailing lambda argument — never `|params|`
+in that position (which would collide with bitwise-or):
+
+```rust
+items.map { i -> i + 1 }
+items.map { it + 1 } // implicit single parameter `it`
+```
+
+Non-trailing closures still use `|params| expr` where no ambiguity exists:
+
+```rust
+let f = |i| i + 1;
+```
+
+Optional callback/lambda parameters are handled with **default parameter values**, not
+`Option`, when the callee doesn't need to branch on the argument's presence:
+
+```rust
+fn sort_by(self, cmp: (T, T) -> i32 = natural_order) { ... }
+list.sort_by() // uses default
+list.sort_by { a, b -> a - b } // trailing lambda overrides default
+```
+
+`Option` is reserved for cases where absence is itself meaningful data the
+callee must `match` on.
+
+**Grammar note:** any construct introduced by a bare keyword followed by `{` (`if`,
+`while`, `match` with a scrutinee) disallows a bare top-level `{`-starting expression —
+including a trailing-lambda call — directly in that position; wrap in parens if needed.
+`match` itself (being a keyword, not an identifier) can never be mistaken for a
+trailing-lambda call target, so `match { ... }` (no scrutinee, see §11) is unambiguous.
+
+---
+
+## 11. DSCX: Pattern Matching and `is`
+
+### Ordinary exhaustive match (enums — identical to DSCS)
+
+```rust
+match shape {
+ Shape::Circle(r) => ...,
+ Shape::Rect(w, h) => ...,
+ Shape::Point => ...,
+}
+```
+
+### `is` — hierarchy-aware type test
+
+```rust
+match val {
+ is Point3 => true, // true only for Point3 (no known subclasses)
+ is Point2 => true, // true for Point2 and any subclass, incl. Point3
+ is AsUnit => true, // true for any implementor
+}
+```
+
+`dyn`-trait / class-hierarchy matches are **not** provably exhaustive, so a wildcard arm
+is mandatory:
+
+```rust
+match val {
+ is Point3 => ...,
+ _ => ...,
+}
+```
+
+Binding a narrowed value reuses ordinary `name: Type` syntax rather than a tuple-struct-
+like `Type(name)` (which would collide with real tuple-struct patterns) or an `as`/`@`
+suffix:
+
+```rust
+match val {
+ is p: Point3 => p.some_point3_only_method(), // p: Point3, explicitly bound
+ is Point2 => true,
+}
+```
+
+No implicit flow-sensitive smart-casting — narrowing always requires the explicit
+`is name: Type` bind, deliberately trading Kotlin's automatic narrowing for a much
+smaller type-checker feature.
+
+### No-scrutinee `match` — if/else-if chain, sugar
+
+```rust
+match {
+ do_some_processing() == 1 => println("success!"),
+ something_else() == 1 => println("success"),
+ val is Point3 => { return true; },
+ false => unreachable(),
+}
+```
+
+Every arm's left-hand side is an ordinary `bool`-valued expression (`is` included, since
+`is` is itself just a boolean-returning infix operator) — arms are evaluated top to
+bottom, lazily, first match wins. **Commas between arms are mandatory**, even when an
+arm's right-hand side is a `{ }` block, to avoid reintroducing whitespace-sensitive
+parsing.
+
+---
+
+## 12. DSCX: Optionals
+
+`Option` — no null, ever. `T?` is pure sugar for `Option` in **type position**
+only:
+
+```rust
+let x: i32? = None; // sugar for Option
+```
+
+`?` as a postfix operator remains reserved for Rust-style error/`None` propagation in
+**expression position**:
+
+```rust
+fn parse(s: str) -> Option {
+ let n = try_parse(s)?; // propagates None early
+ Some(n)
+}
+```
+
+No Kotlin-style `?.` safe-call or `!!` non-null assertion — those don't exist since
+there's no null to guard against.
+
+---
+
+## 13. DSCX: Objects and Coroutines
+
+### Singletons via `object`
+
+```rust
+object Logger {
+ var count: i32 = 0
+ fn log(msg: String) {
+ count += 1
+ println(msg)
+ }
+}
+
+Logger.log("started")
+```
+
+Lowers to a lazily-initialized static DSCS struct instance plus accessor functions —
+the object itself is not a class instance the user constructs.
+
+### Coroutines
+
+```rust
+async fn read_line() -> String {
+ let b = uart.read_byte().await;
+ // ...
+}
+
+let handle = spawn { heavy_computation() }
+let result = handle.await()
+```
+
+- **Explicit `await`** at every suspension point (not Kotlin's implicit `suspend fun`) —
+ this is what makes the transpile to DSCS mechanical: every `.await` is a known split
+ point for the state-machine transform.
+- The compiler lowers an `async fn` into a generated struct (holding whatever locals
+ must survive across suspension points) implementing DSCS's `Task` trait (§7), with
+ `poll()` dispatching via a generated resume-point jump table — the same shape as
+ Rust's async transform, targeting DSCS's plain trait instead of a compiler-magic type.
+- `spawn { }` hands a block to the runtime's scheduler as a new `Task`; `.await()` on the
+ returned handle polls until ready.
+
+---
+
+## 14. Example Programs
+
+### 14.1 DSCS — pointer-heavy: a manual vector
+
+```rust
+struct Vec {
+ data: Box<[T]>,
+ len: usize,
+}
+
+impl Vec {
+ fn new(alloc: &Allocator, capacity: usize) -> Vec {
+ Vec { data: Box::new_slice(alloc, capacity), len: 0 }
+ }
+
+ fn push(&mut self, item: T) {
+ if self.len == self.data.len() {
+ self.grow();
+ }
+ self.data[self.len] = item;
+ self.len += 1;
+ }
+
+ fn get(&self, i: usize) -> &T {
+ &self.data[i]
+ }
+
+ fn drop(self) {
+ self.data.drop();
+ }
+}
+
+fn main() {
+ let alloc = Allocator::system();
+ let mut v: Vec = Vec::new(&alloc, 4);
+ defer v.drop();
+
+ v.push(1);
+ v.push(2);
+
+ let mut i: usize = 0;
+ while i < v.len {
+ print(*v.get(i));
+ i += 1;
+ }
+}
+```
+
+### 14.2 DSCS — kernel: UART driver as a Task
+
+```rust
+static UART_READY: bool = false;
+
+extern "isr" fn uart_isr() {
+ UART_READY = true;
+}
+
+struct UartRead;
+impl Task for UartRead {
+ type Output = u8;
+ fn poll(&mut self) -> Poll {
+ if UART_READY {
+ UART_READY = false;
+ Poll::Ready(uart_read_byte())
+ } else {
+ Poll::Pending
+ }
+ }
+}
+
+fn kernel_main() -> ! {
+ let mut tasks: Vec>> = Vec::new(&Allocator::kernel(), 8);
+ // register drivers as tasks...
+ loop {
+ let mut progressed = false;
+ for t in tasks.iter_mut() {
+ if let Poll::Ready(()) = t.poll() { progressed = true; }
+ }
+ if !progressed { hlt(); }
+ }
+}
+```
+
+### 14.3 DSCX — application UI (from the working draft)
+
+```rust
+/* uilib.dscx */
+class UiBuilder {
+ fn text_entry(field: String) -> Interaction
+ fn button(label: str) -> Interaction
+ fn vertical(body: () -> void)
+}
+class Interaction {
+ fn clicked() -> bool
+}
+
+/* app.dscx */
+pub enum Action {
+ SubmitForm(PasswordForm)
+}
+
+class PasswordForm {
+ let username: String,
+ let password: String,
+}
+
+class UserInterface {
+ const MESSAGE: str = "Run Emulator"
+ let form: PasswordForm
+ let state: EmulatorState
+
+ fn new(state: EmulatorState) -> Self {
+ Self {
+ form: PasswordForm { username: String::new(), password: String::new() },
+ state,
+ }
+ }
+
+ fn ui(self, ui: UiBuilder) -> Action {
+ ui_lib::Form(self.form, ui, "login") { form, ui ->
+ ui.text_entry(form.username) { view -> view.placeholder = "enter username" }
+ ui.text_entry(form.password) // placeholder has a default value
+ if ui.button("Submit").clicked() {
+ return Action::SubmitForm(form)
+ }
+ }
+ ui.vertical {
+ my_lib::StatusIndicator(self.state.status, ui)
+ if ui.button("Run Emulator").clicked() {
+ self.state.run()
+ }
+ }
+ }
+}
+```
+
+### 14.4 DSCX — coroutine-based file read
+
+```rust
+async fn read_config(path: String) -> Result {
+ let bytes = fs::read(path).await?
+ Config::parse(bytes)
+}
+
+fn main() {
+ let handle = spawn { read_config("app.toml") }
+ match handle.await() {
+ Ok(cfg) => println("loaded: ${cfg.name}"),
+ Err(e) => println("failed: ${e}"),
+ }
+}
+```
+
+---
+
+## 15. Grammar Summary (partial — DSCX additions over DSCS)
+
+```ebnf
+(* DSCX no-scrutinee match *)
+match_expr = "match" [ scrutinee ] "{" match_arm { "," match_arm } [ "," ] "}"
+match_arm = pattern_or_bool "=>" expression
+
+pattern_or_bool = bool_expr
+ | "is" [ identifier ":" ] type_path
+ | "_"
+
+(* DSCX is-test as a standalone boolean expression *)
+is_expr = expression "is" [ identifier ":" ] type_path
+
+(* trailing lambda *)
+trailing_lambda = call_expr "{" [ lambda_params "->" ] { statement } "}"
+lambda_params = identifier { "," identifier }
+
+(* class definition *)
+class_def = "class" identifier [ ":" inherit_list ] "{" { class_member } "}"
+inherit_list = type_path { "+" type_path }
+
+(* property *)
+property_decl = ("val" | "var") identifier ":" type
+ [ "get" "(" ")" ( "=" expression | block ) ]
+ [ "set" "(" identifier ")" block ]
+
+(* optional sugar *)
+optional_type = type "?" (* == Option *)
+
+(* ownership-bearing DSCS types *)
+owned_type = "Box" "<" type ">"
+shared_type = "Rc" "<" type ">"
+borrowed_type = "&" [ "mut" ] type
+```
+
+---
+
+## Appendix A: DSCS vs DSCX vs Rust
+
+| Feature | DSCS | DSCX | Rust |
+|---|---|---|---|
+| Encapsulation | `struct` + `impl` | `class` (ref type) | `struct`/`enum` + `impl` |
+| Default aggregate semantics | value type | reference type | value type (move) |
+| Inheritance | none | single class + multi-trait | none (trait composition only) |
+| Trait dispatch | static/monomorphized only | static or `dyn` (manual-vtable lowering) | static or `dyn` |
+| Memory management | manual (`Box`, `defer`, explicit allocator) | automatic (inferred drop / refcount) | borrow-checked, compile-time |
+| Borrow checking | none — linear `Box`/`Rc` tracking only | none (inherited from DSCS output) | full borrow checker + lifetimes |
+| Pattern matching | exhaustive `match` over enums | `match` (enum) + `is` (hierarchy, non-exhaustive) | exhaustive `match` |
+| Optionals | `Option` | `Option` / `T?` sugar | `Option` |
+| Concurrency | `Task`/`Poll` trait, hand-written state machines | `async`/`await`/`spawn`, compiler-generated state machines | `Future`/`async`/`await` |
+| Closures | none (function pointers only) | trailing lambdas, `|params| expr` | closures, `Fn`/`FnMut`/`FnOnce` |
+| Singletons | manual `static` | `object` keyword | no direct equivalent (`static`/`lazy_static`) |
+
+---
+
+*End of DSCS & DSCX Language Specification Draft v0.1*
diff --git a/doc/notes/dscs_reference_draft.md b/doc/notes/dscs_reference_draft.md
new file mode 100644
index 0000000..874b2cb
--- /dev/null
+++ b/doc/notes/dscs_reference_draft.md
@@ -0,0 +1,253 @@
+# DSCS reference (Draft v0.2)
+
+**DSCS - Damn Simple Code (Systems)**
+
+## Design goals
+
+- a simple, explicit systems language with all the capabilities of a language like C, with improved safety and ergonomics.
+
+## Lexical structure
+
+```rust
+// single line comment...
+/* multi
+ line
+ comment */
+
+// variables:
+fn main() { // function names
+ let local: Type; // local variables
+}
+
+const CONST: Type = val; // strictly cannot change, computed at compile time.
+static STATIC: Type = val; // a global value that can change. fixed size.
+
+fn types() {
+ let boolean: bool = false; // or true;
+ let integer: u32 = 37; // or i32, i16, i8, u16, u8;
+ let hex: u8 = 0xAA;
+ let oct: u8 = 0o5;
+ let binary: u8 = 0b01000011;
+
+ // declare an array as so:
+ // because the size is stored with the type, the compiler can infer the length
+ let arr: [u32; 6] = [0, 1, 2, 3, 4, 5];
+ // = [1; 6]; // alternative syntax for [1, 1, 1, 1, 1, 1]
+
+ // pointers. these are not references so care should be taken to use them safely.
+ let ptr: * u32 = &integer;
+ let mut_ptr: *mut u32 = &mut integer;
+}
+
+// structs
+struct Point {
+ // declare a field with the "field" keyword, and end with a semicolon
+ field x: u32;
+ field y: u32;
+
+ // make a field public with the pub keyword.
+ pub field id: u32;
+
+ // consts can also be stored within struct definitions.
+ // this associated constant can be accessed with Vec2::SOME_SIZE
+ const SOME_SIZE: u32 = 0;
+
+ // you can also use associated statics.
+ // as with statics in other languages,
+ // you should use an atomic type when dealing with them in concurrent code,
+ // even on a single core architecture like DSA.
+ static COUNT: u32 = 0;
+
+ pub fn new(x: u32, y: u32) -> Self {
+ // not thread safe!
+ let id = COUNT;
+ COUNT += 1;
+
+ Self {
+ x,
+ y,
+ id
+ }
+ }
+
+ // methods can be declared inside struct definitions.
+ // methods starting with a *self access the struct immutably
+ pub fn get_x(*self) -> u32 {
+ // implicit return on the last expression of a function body;
+ self.x
+ }
+
+ // the *mut self argument means the method can mutate the instance of the struct it is performed on.
+ pub fn set_y(*mut self, y: u32) {
+ self.y = y;
+ }
+
+ // returns a unit vector from the origin in the direction of the point.
+ // taking self as a parameter implicitly moves self into the function body.
+ // this means the value is no longer in the scope of the caller and is therefore inaccessible.
+ // any pointers to the value that still exist may be dangling and should not be used.
+ pub fn unit(self) -> Self {
+ Self {
+ x: self.x / (self.x + self.y),
+ y: self.y / (self.x + self.y),
+ }
+ }
+}
+
+// you can use enums to represent various different types at the same time.
+// useful for state machines.
+enum Shape {
+ variant Circle(u32);
+ variant Rectangle(u32, u32);
+ variant Square(u32);
+ variant Point; // doesn't have a size so we use the unit variant.
+
+ const PI: u32 = 3; // the lack of floating point values isn't ideal.
+
+ pub fn is_square(*self) -> bool {
+ // pattern matching with match!
+ match *self {
+ Self::Square(_) => true,
+ _ => false,
+ }
+ }
+}
+
+// DSCS also supports simple generics with static dispatch.
+// below is an example of a struct using generics, that also uses heap memory.
+struct Vec {
+ field data: *mut T;
+ field capacity: u32;
+ field length: u32;
+
+ // with_capacity takes a generic T and returns an instance of Self with that type T.
+ // DSCS is a language with manual memory management, so you must pass an allocator whenever you intend to
+ // allocate dynamic memory.
+ fn with_capacity(alloc: *mut Allocator, capacity: u32) -> Self {
+ let ptr = alloc.alloc(capacity) as *mut T;
+ Self {
+ data: ptr,
+ capacity,
+ length: 0
+ }
+ }
+
+ fn length(*self) -> u32 {
+ self.length
+ }
+
+ fn resize(*mut self, alloc: *mut Allocator, new_size: u32) {
+ self.data = alloc.realloc(self.data, u32);
+ }
+
+ // consumes self and frees its memory.
+ fn drop(self, alloc: *mut Allocator) {
+ alloc.free(self.data);
+ }
+}
+
+// in the above example, allocator is also a statically dispatched opaque type.
+// the Allocator given could essentially be anything. all we know as the user is that the allocator implements the Allocator trait:
+
+// traits
+
+trait Allocator {
+ // allocates a new block of memory of the given size and returns a pointer to it.
+ // a void pointer must be explicitly cast by the caller to the type that they intend to use it as, before it can be dereferenced.
+ // dereferencing a void pointer is a compiler error.
+ fn alloc(*mut self, size: u32) -> *mut void;
+
+ // frees the memory allocated to `ptr`
+ // this also sets the pointer to a null pointer so that the user can check it has been freed.
+ // while we cannot set it to zero by dereferencing it,
+ // we can cast it to a *null which implicitly sets the pointer to zero.
+ fn free(*mut self, ptr: *mut *void);
+
+ // this will probably also move the memory at that pointer to the new location.
+ fn realloc(*mut self, ptr: *mut void);
+}
+
+trait DivisibleByN {
+ fn is_divisible(*self, n: u32) {
+ // the integer_div method belongs to the Integer trait.
+ self.integer_div(n) == 0;
+ }
+}
+
+// memory management
+fn main() {
+ // we have a few different ways to manage memory.
+ let mut alloc = ArenaAllocator::new(HEAP_START);
+
+ // method 1
+ // the pattern below is commonly used in C, however it's strongly discouraged in this language!
+ // the data is not a valid instance of SomeType, and is not guaranteed to be zero initialised.
+ // please don't do this, it makes it very easy for UB to happen!
+ let ptr = alloc.alloc(Point::size()) as *mut Point;
+ ptr.id = 1;
+
+ alloc.free(ptr); // don't use pointer now!
+
+ // method 2: put it in a box!
+ let mut point = Box::new(Point::new(2, 3));
+ // Box::drop(self, alloc) uses the passed allocator to free its contents when called.
+ // deferring execution of a function means it is executed as soon as the enclosing function returns.
+ defer ptr.drop(alloc);
+
+
+ // Box is implemented as a transparent type so we can simply use Box as if it were a *mut T!
+ point.set_y(5);
+}
+
+// more traits
+trait Task {
+ type Output;
+ fn poll(*mut self) -> Poll;
+}
+
+struct Keyboard;
+impl Task for Keyboard {
+ type Output = u8;
+ fn poll(*mut self) -> Poll {
+ if KEYBOARD.has_byte() { Poll::Ready(KEYBOARD.read()) } else { Poll::Pending }
+ }
+}
+
+// function types!
+fn add(x: u32, y: u32) -> u32 {
+ return x + y;
+}
+
+
+// in systems level programming we sometimes need to write ISRs to handle interrupts.
+fn invalid_instruction_fault() {
+ // handle the interrupt
+}
+// but we can't just put a function in our code and expect interrupts to go to it.
+// the machine needs to know where to find our interrupt handlers.
+
+// this means functions also need to be a type!
+// C got around this using void pointers, but we can do better here.
+
+// a function pointer has the type signature `Func` with the extra hardcoded constraint that A must be a tuple of values.
+let handler: Func<(), void> = invalid_instruction_fault;
+let func: Func<(y32, u32), u32> = add;
+
+
+
+
+// like rust, error handling is done with Result and optionals with Option.
+// an owned value in DSCS cannot be a null or none type, however a pointer can be, if explicitly cast to *null;
+```
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/doc/DSC/dscx.txt b/doc/notes/dscx.txt
similarity index 100%
rename from doc/DSC/dscx.txt
rename to doc/notes/dscx.txt
diff --git a/doc/dsi.md b/doc/notes/dsi.md
similarity index 100%
rename from doc/dsi.md
rename to doc/notes/dsi.md
diff --git a/doc/interrupts.md b/doc/notes/interrupts.md
similarity index 100%
rename from doc/interrupts.md
rename to doc/notes/interrupts.md
diff --git a/doc/reference/dscs-reference.md b/doc/reference/dscs-reference.md
new file mode 100644
index 0000000..faefcd9
--- /dev/null
+++ b/doc/reference/dscs-reference.md
@@ -0,0 +1,633 @@
+# DSCS Reference (Draft v0.3)
+
+**DSCS — Damn Simple Code (Systems)**
+
+## Introduction
+
+DSCS is the low-level half of the DSC language family — a simple, explicit systems
+language with the capabilities of a language like C, but with improved safety and
+ergonomics. It compiles down to DSA assembly and is meant to be usable directly for
+things like kernels and drivers.
+
+DSCS is also the foundation DSCX (the higher-level, OOP-flavoured member of the
+family) transpiles down to: every valid DSCS program is a valid DSCX program. This
+document covers DSCS on its own.
+
+### Design goals
+
+- A simple, explicit systems language with all the capabilities of a language like C,
+ with improved safety and ergonomics.
+- **Static dispatch only.** Generics are monomorphized at compile time; there is no
+ built-in `dyn Trait`, no vtables, and no hidden indirect calls. Where genuine
+ runtime polymorphism is needed, you build it explicitly — see
+ [Runtime Polymorphism](#runtime-polymorphism-without-dynamic-dispatch).
+- **Manual memory management.** No garbage collector, no automatic reference
+ counting. Allocators are passed explicitly wherever allocation happens.
+- **No borrow checker, no lifetimes.** DSCS trusts the programmer. See
+ [Ownership & Memory Safety](#ownership--memory-safety).
+
+---
+
+## Lexical Structure
+
+```rust
+// single line comment...
+/* multi
+ line
+ comment */
+```
+
+---
+
+## Variables, Constants & Statics
+
+```rust
+fn main() {
+ let local: Type; // local variables
+}
+
+const CONST: Type = val; // strictly cannot change, computed at compile time.
+static STATIC: Type = val; // a global value that can change. fixed size.
+```
+
+---
+
+## Primitive Types & Literals
+
+```rust
+fn types() {
+ let boolean: bool = false; // or true;
+ let integer: u32 = 37; // or i32, i16, i8, u16, u8;
+ let hex: u8 = 0xAA;
+ let oct: u8 = 0o5;
+ let binary: u8 = 0b01000011;
+}
+```
+
+### Arrays
+
+Because the size is stored with the type, the compiler can infer the length:
+
+```rust
+let arr: [u32; 6] = [0, 1, 2, 3, 4, 5];
+// = [1; 6]; // alternative syntax for [1, 1, 1, 1, 1, 1]
+```
+
+### Pointers
+
+```rust
+// pointers. these are not references, so care should be taken to use them safely.
+let ptr: *u32 = &integer;
+let mut_ptr: *mut u32 = &mut integer;
+```
+
+`*T` is read-only; `*mut T` is read/write. Full rules governing when a pointer is
+safe to use are covered in [Ownership & Memory Safety](#ownership--memory-safety) —
+raw pointers carry no compiler-enforced validity guarantee at all.
+
+---
+
+## Control Flow
+
+`if`/`else` are expressions — the same "no trailing semicolon on the last line is the
+return value" rule applies as in function bodies:
+
+```rust
+let sign = if x > 0 { 1 } else if x < 0 { -1 } else { 0 };
+```
+
+For a single-statement body, `=>` can be used instead of braces, aligning with
+`match`'s own arm syntax:
+
+```rust
+if condition => do_y();
+```
+
+The same rule applies to `else`, and the two forms compose — a branch can use
+whichever form fits it:
+
+```rust
+if condition => do_y(); else { a(); b(); }
+```
+
+As an expression, the arrow form has no trailing semicolon on either branch — `else`
+(a keyword) is what tells the parser where a branch's expression ends, same as any
+other expression grammar stopping at a token it can't extend through:
+
+```rust
+let sign = if x > 0 => 1 else if x < 0 => -1 else => 0;
+```
+
+**Dangling-else rule:** when `if` statements nest without an explicit `else` on the
+inner one, `else` binds to the *nearest* unmatched `if` — the same convention C and
+C++ use. This is a well-known ambiguity class (it's the specific reason Rust
+mandates braces on `if` bodies), reintroduced here deliberately in exchange for the
+more compact single-statement form; use explicit braces on the inner `if` whenever
+the binding could be unclear:
+
+```rust
+if a => if b => x(); else => y(); // else binds to `if b`, not `if a`
+if a { if b => x(); } else => y(); // braces make the outer binding explicit instead
+```
+
+`while` loops:
+
+```rust
+let mut i: u32 = 0;
+while i < 10 {
+ i += 1;
+}
+```
+
+There's no `for` loop construct defined yet — worth deciding later whether it's
+built as sugar over `while` or added as its own construct.
+
+---
+
+## Structs
+
+```rust
+struct Point {
+ // declare a field with the "field" keyword, and end with a semicolon
+ field x: u32;
+ field y: u32;
+
+ // make a field public with the pub keyword.
+ pub field id: u32;
+
+ // consts can also be stored within struct definitions.
+ // this associated constant can be accessed with Point::SOME_SIZE
+ const SOME_SIZE: u32 = 0;
+
+ // you can also use associated statics.
+ // as with statics in other languages,
+ // you should use an atomic type when dealing with them in concurrent code,
+ // even on a single core architecture like DSA.
+ static COUNT: u32 = 0;
+
+ pub fn new(x: u32, y: u32) -> Self {
+ // not thread safe!
+ let id = COUNT;
+ COUNT += 1;
+
+ Self {
+ x,
+ y,
+ id
+ }
+ }
+
+ // methods can be declared inside struct definitions.
+ // methods starting with a *self access the struct immutably
+ pub fn get_x(*self) -> u32 {
+ // implicit return on the last expression of a function body;
+ self.x
+ }
+
+ // the *mut self argument means the method can mutate the instance of the struct it is performed on.
+ pub fn set_y(*mut self, y: u32) {
+ self.y = y;
+ }
+
+ // returns a unit vector from the origin in the direction of the point.
+ // taking self as a parameter implicitly moves self into the function body.
+ // this means the value is no longer in the scope of the caller and is therefore inaccessible.
+ // any pointers to the value that still exist may be dangling and should not be used.
+ pub fn unit(self) -> Self {
+ Self {
+ x: self.x / (self.x + self.y),
+ y: self.y / (self.x + self.y),
+ }
+ }
+}
+```
+
+---
+
+## Enums
+
+You can use enums to represent various different types at the same time — useful for
+state machines.
+
+```rust
+enum Shape {
+ variant Circle(u32);
+ variant Rectangle(u32, u32);
+ variant Square(u32);
+ variant Point; // doesn't have a size so we use the unit variant.
+
+ const PI: u32 = 3; // the lack of floating point values isn't ideal.
+
+ pub fn is_square(*self) -> bool {
+ // pattern matching with match!
+ match *self {
+ Self::Square(_) => true,
+ _ => false,
+ }
+ }
+}
+```
+
+---
+
+## Generics
+
+DSCS supports simple generics with static dispatch — every generic function or
+struct is monomorphized once per concrete type it's used with at compile time. There
+is no runtime type erasure and no hidden indirection.
+
+```rust
+struct Vec {
+ field data: *mut T;
+ field capacity: u32;
+ field length: u32;
+
+ // with_capacity takes a generic T and an allocator, and returns an instance of
+ // Self holding that type T. DSCS is a language with manual memory management, so
+ // you must pass an allocator whenever you intend to allocate dynamic memory.
+ fn with_capacity(alloc: *mut A, capacity: u32) -> Self {
+ let ptr = alloc.alloc(capacity) as *mut T;
+ Self {
+ data: ptr,
+ capacity,
+ length: 0
+ }
+ }
+
+ fn length(*self) -> u32 {
+ self.length
+ }
+
+ fn resize(*mut self, alloc: *mut A, new_size: u32) {
+ self.data = alloc.realloc(self.data as *mut void, new_size) as *mut T;
+ self.capacity = new_size;
+ }
+
+ // consumes self and frees its memory.
+ fn drop(self, alloc: *mut A) {
+ alloc.free(self.data as *mut *void);
+ }
+}
+```
+
+> **Note on the original draft:** the earlier version of this example passed
+> `alloc: *mut Allocator` directly, with `Allocator` used as a bare trait type rather
+> than an explicit generic bound. This is intentional, not an oversight — see
+> [Bare Trait Names as Types](#bare-trait-names-as-types) below for the rule.
+
+---
+
+## Traits
+
+```rust
+trait Allocator {
+ // allocates a new block of memory of the given size and returns a pointer to it.
+ // a void pointer must be explicitly cast by the caller to the type that they intend to use it as, before it can be dereferenced.
+ // dereferencing a void pointer is a compiler error.
+ fn alloc(*mut self, size: u32) -> *mut void;
+
+ // frees the memory allocated to `ptr`
+ // this also sets the pointer to a null pointer so that the user can check it has been freed.
+ // while we cannot set it to zero by dereferencing it,
+ // we can cast it to a *null which implicitly sets the pointer to zero.
+ fn free(*mut self, ptr: *mut *void);
+
+ // reallocates the block at `ptr` to be `new_size` bytes, and returns a pointer
+ // to the (possibly moved) block.
+ fn realloc(*mut self, ptr: *mut void, new_size: u32) -> *mut void;
+}
+
+trait DivisibleByN {
+ // the integer_div method belongs to the Integer trait.
+ fn is_divisible(*self, n: u32) -> bool {
+ self.integer_div(n) == 0
+ }
+}
+```
+
+Because traits are always statically dispatched, a bound like `` or
+`` is resolved and monomorphized entirely at compile time — calling a
+trait method never involves a runtime lookup.
+
+### Bare Trait Names as Types
+
+Since DSCS has no dynamic dispatch, a bare trait name used directly in a type
+position is unambiguous — there's no competing `dyn Trait` reading it could be
+confused with — so it's allowed as shorthand for an implicit static-dispatch generic
+parameter, the same idea as Rust's `impl Trait` in argument position, just without
+the `impl` keyword:
+
+```rust
+fn alloc_point(a: *mut Allocator) -> Box { ... }
+// exactly equivalent to:
+fn alloc_point(a: *mut A) -> Box { ... }
+```
+
+**Each occurrence gets its own independent, anonymous type parameter** — this
+matches Rust's actual `impl Trait` semantics exactly, including the consequence that
+two bare-trait-typed parameters in the same signature are *not* required to be the
+same concrete type:
+
+```rust
+fn transfer(src: *mut Allocator, dst: *mut Allocator) { ... }
+// src and dst may be DIFFERENT concrete Allocator types — nothing here forces them
+// to agree.
+```
+
+If two or more parameters need to share the same concrete type, use an explicit
+named generic bound instead:
+
+```rust
+fn transfer(src: *mut A, dst: *mut A) { ... }
+// src and dst are now forced to be the same concrete allocator type.
+```
+
+This sugar is currently defined for parameter position only. Return-position bare
+trait names (Rust's `impl Trait` return-position equivalent) raise a separate
+question — an opaque return type's exact size wouldn't be visible to the caller,
+which matters more in a systems language than it does in Rust — and aren't covered
+by this rule yet.
+
+---
+
+## Function Types
+
+Functions are values too — this matters in systems programming, since interrupt
+handlers need to be installed as data the machine can jump to:
+
+```rust
+fn add(x: u32, y: u32) -> u32 {
+ return x + y;
+}
+
+// in systems level programming we sometimes need to write ISRs to handle interrupts.
+fn invalid_instruction_fault() {
+ // handle the interrupt
+}
+// but we can't just put a function in our code and expect interrupts to go to it.
+// the machine needs to know where to find our interrupt handlers.
+
+// this means functions also need to be a type!
+// C got around this using void pointers, but we can do better here.
+
+// A function pointer has the type `Func<(ArgTypes...), RetType>`, with the extra
+// hardcoded constraint that the argument list must be written as a tuple type, even
+// for a single argument.
+let handler: Func<(), void> = invalid_instruction_fault;
+let func: Func<(u32, u32), u32> = add;
+
+func(1, 2); // == 3
+```
+
+A `Func<...>` value is nothing more than the address of the function it points to —
+one machine word, `Copy`, no heap allocation. It **cannot** capture or close over
+local variables; it's purely a code address. If you need a callable value that also
+carries its own state, see the next section.
+
+---
+
+## Runtime Polymorphism (without dynamic dispatch)
+
+Sometimes you genuinely need runtime polymorphism — for example, a driver table
+where the concrete device type isn't known until the kernel is running. DSCS has no
+built-in `dyn Trait` for this. Instead, you build the vtable yourself, as an ordinary
+struct of function pointers:
+
+```rust
+struct ShapeOps {
+ field area: Func<(*void), u32>;
+}
+
+fn circle_area(self: *void) -> u32 {
+ let c = self as *Circle;
+ 3 * (*c).radius * (*c).radius
+}
+
+const CIRCLE_OPS: ShapeOps = ShapeOps { area: circle_area };
+
+// A caller holding a `*void` payload pointer and a `ShapeOps` table can now call
+// `(ops.area)(payload)` without ever knowing the concrete type behind the pointer —
+// this is exactly what `dyn Trait` does in other languages, just fully visible and
+// under your control instead of compiler-generated.
+```
+
+This is also the pattern to reach for whenever you need something closure-shaped: a
+`Func<...>` paired with its own `*void` (or concretely-typed) state pointer, bundled
+into a small struct.
+
+---
+
+## Memory Management
+
+You have a few different ways to manage memory.
+
+### Manual alloc/free (discouraged)
+
+```rust
+fn main() {
+ let mut alloc = ArenaAllocator::new(HEAP_START);
+
+ // the pattern below is commonly used in C, however it's strongly discouraged in this language!
+ // the data is not a valid instance of SomeType, and is not guaranteed to be zero initialised.
+ // please don't do this, it makes it very easy for UB to happen!
+ let ptr = alloc.alloc(Point::size()) as *mut Point;
+ ptr.id = 1;
+
+ alloc.free(ptr as *mut *void); // don't use pointer now!
+}
+```
+
+### `defer`
+
+Deferring execution of a statement means it's executed as soon as the enclosing
+function returns — regardless of which return path was taken. Multiple `defer`
+statements in the same function run in reverse order (last deferred, first executed),
+the same as Zig.
+
+### `Box`
+
+```rust
+fn main() {
+ let mut point = Box::new(alloc, Point::new(2, 3));
+ // Box::drop(self, alloc) uses the passed allocator to free its contents when called.
+ defer point.drop(alloc);
+
+ // Box is implemented as a transparent type so we can simply use Box as if it were a *mut T!
+ // method calls on a Box auto-dereference through it exactly as they would
+ // through a raw *mut T receiver.
+ point.set_y(5);
+}
+```
+
+`Box` has exactly one owner. Cloning a `Box` is a **deep** clone — it allocates
+fresh memory and copies the underlying value, which is why cloning a `Box` requires
+passing an allocator explicitly, same as any other allocation.
+
+### `Rc`
+
+`Rc` is for values that need multiple simultaneous owners. Unlike `Box`,
+cloning an `Rc` is cheap: it doesn't touch or copy the underlying data at all, it
+just increments a reference count and hands back a new, independent `Rc` handle to
+the *same* memory.
+
+```rust
+fn main() {
+ let a = Rc::new(alloc, Point::new(1, 2));
+ let b = a.clone(); // cheap: bumps the refcount, no allocation, no data copy
+
+ defer a.drop(); // decrements the refcount
+ defer b.drop(); // decrements the refcount; frees the Point once it reaches zero
+}
+```
+
+Like `Box`, each individual `Rc` *handle* is still linear — once you've dropped
+or moved a given handle, that specific handle is gone and must not be used again.
+Nothing stops you from calling `.clone()` first, though, to mint another independent
+handle before doing so.
+
+---
+
+## Ownership & Memory Safety
+
+DSCS's ownership model is loosely inspired by Rust's, with one major simplification:
+**there are no references, only pointers.** `*T`/`*mut T` are not tracked or
+validity-checked by the compiler at all.
+
+The rules that *do* apply:
+
+- Passing a value **by value** (e.g. `fn unit(self) -> Self`, not `*self`) moves it
+ into the function. After the call, the caller's copy is gone — any pointers still
+ pointing at it may now be dangling and must not be used.
+- Once a value has been dropped (via `defer`, an explicit `.drop()` call, or a raw
+ `alloc.free(...)`) or moved out of its original binding, **you must not create new
+ pointers to it**, and any pointers created before that point are no longer valid to
+ dereference.
+- There is **no borrow checker and no lifetimes**. The compiler does not verify any
+ of the above for you — it trusts the programmer entirely. This is a deliberate
+ simplicity/performance tradeoff relative to Rust: DSCX, the higher-level language
+ built on top of DSCS, is where automatic safety is intended to live.
+
+```rust
+fn dangling_example(alloc: *mut Allocator) -> *u32 {
+ let boxed = Box::new(alloc, 5);
+ let p: *u32 = &*boxed; // p now points at boxed's memory
+ boxed.drop(alloc); // the memory is freed here
+
+ p // DANGLING: p now points at freed memory. DSCS will not stop you from
+ // returning or dereferencing it — this is undefined behaviour.
+}
+```
+
+---
+
+## Error Handling
+
+Like Rust, error handling is done with `Result` and optionals with `Option`.
+
+```rust
+fn parse_digit(c: u8) -> Result {
+ if c >= '0' && c <= '9' {
+ Result::Ok((c - '0') as u32)
+ } else {
+ Result::Err(ParseError::NotADigit)
+ }
+}
+
+fn find(arr: [u32; 6], target: u32) -> Option {
+ let mut i: u32 = 0;
+ while i < 6 {
+ if arr[i] == target {
+ return Option::Some(i);
+ }
+ i += 1;
+ }
+ Option::None
+}
+```
+
+An **owned** value in DSCS can never be null or none. Only a pointer can be null, and
+only once it's been explicitly cast:
+
+```rust
+let p: *u32 = *null; // must be explicit; there is no implicit null for owned values
+```
+
+---
+
+## Concurrency
+
+DSCS has no threads and no language-level `async`/`await`. Concurrency is
+cooperative and single-threaded, built from two small pieces — `Poll` and `Task`
+are ordinary library-level types, not compiler magic:
+
+```rust
+enum Poll {
+ variant Ready(T);
+ variant Pending;
+}
+
+trait Task {
+ type Output;
+ fn poll(*mut self) -> Poll;
+}
+
+struct Keyboard;
+impl Task for Keyboard {
+ type Output = u8;
+ fn poll(*mut self) -> Poll {
+ if KEYBOARD.has_byte() { Poll::Ready(KEYBOARD.read()) } else { Poll::Pending }
+ }
+}
+```
+
+A scheduler is ordinary DSCS code that polls a task on a loop, halting the CPU (via
+the `hlt` intrinsic — real hardware idling, not a busy spin) whenever there's nothing
+left to do:
+
+```rust
+fn run_scheduler(mut task: Keyboard) {
+ loop {
+ match task.poll() {
+ Poll::Ready(byte) => { /* handle it, then reschedule or exit */ }
+ Poll::Pending => hlt(), // idle until the next interrupt wakes us
+ }
+ }
+}
+```
+
+A scheduler juggling several different concrete task types at once needs the same
+manual-vtable pattern from [Runtime Polymorphism](#runtime-polymorphism-without-dynamic-dispatch)
+— a struct pairing a `Func<(*void), Poll<...>>` with its own state pointer — since
+DSCS has no `dyn Trait` to erase each task's concrete type automatically.
+
+### Interrupt Service Routines
+
+An ISR should do as little as possible — typically just flip a flag that the
+corresponding `Task`'s `poll()` checks on its next pass. It must never run task logic
+directly:
+
+```rust
+static UART_READY: bool = false;
+
+fn uart_isr() {
+ UART_READY = true;
+}
+
+struct UartRead;
+impl Task for UartRead {
+ type Output = u8;
+ fn poll(*mut self) -> Poll {
+ if UART_READY {
+ UART_READY = false;
+ Poll::Ready(uart_read_byte())
+ } else {
+ Poll::Pending
+ }
+ }
+}
+```
+
+Because an ISR can preempt the scheduler at any instruction boundary — even on a
+single-core machine like DSA — any state touched by both an ISR and ordinary code
+(like `UART_READY` above) needs interrupts masked around the access if it isn't a
+single atomic instruction, to avoid a torn read/write.