783 lines
22 KiB
Markdown
783 lines
22 KiB
Markdown
# 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<T>` (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<T> // owned, linear — see §4
|
|
Rc<T> // shared, refcounted, linear handle — see §4
|
|
```
|
|
|
|
### Arrays and generics
|
|
|
|
```rust
|
|
[u32; 10]
|
|
Vec<T>
|
|
Map<K, V>
|
|
```
|
|
|
|
### 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<T>` (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<T>` and
|
|
`Rc<T>` handles.
|
|
|
|
- A `Box<T>` 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<T>` 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<T>` follows the same linear-handle discipline. `.clone()` consumes a `&Rc<T>` 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<Point> {
|
|
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<Point>, 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<T> {
|
|
data: Box<[T]>,
|
|
len: usize,
|
|
}
|
|
|
|
impl<T> Vec<T> {
|
|
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<T> { Ready(T), Pending }
|
|
|
|
trait Task {
|
|
type Output;
|
|
fn poll(&mut self) -> Poll<Self::Output>;
|
|
}
|
|
```
|
|
|
|
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<u8> {
|
|
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<Box<dyn Task<Output = ()>>>) -> ! {
|
|
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<Closure>` 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<T>` — no null, ever. `T?` is pure sugar for `Option<T>` in **type position**
|
|
only:
|
|
|
|
```rust
|
|
let x: i32? = None; // sugar for Option<i32>
|
|
```
|
|
|
|
`?` as a postfix operator remains reserved for Rust-style error/`None` propagation in
|
|
**expression position**:
|
|
|
|
```rust
|
|
fn parse(s: str) -> Option<i32> {
|
|
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<T> {
|
|
data: Box<[T]>,
|
|
len: usize,
|
|
}
|
|
|
|
impl<T> Vec<T> {
|
|
fn new(alloc: &Allocator, capacity: usize) -> Vec<T> {
|
|
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<i32> = 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<u8> {
|
|
if UART_READY {
|
|
UART_READY = false;
|
|
Poll::Ready(uart_read_byte())
|
|
} else {
|
|
Poll::Pending
|
|
}
|
|
}
|
|
}
|
|
|
|
fn kernel_main() -> ! {
|
|
let mut tasks: Vec<Box<dyn Task<Output = ()>>> = 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<Config, IoError> {
|
|
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<type> *)
|
|
|
|
(* 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<T>` | `Option<T>` / `T?` sugar | `Option<T>` |
|
|
| 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*
|