# 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*