diff --git a/.gitignore b/.gitignore index 10f3ee21..0f4d9a26 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,7 @@ target CLAUDE.md .agents/skills/agents-update .claude/skills/agents-update + +# docs site +node_modules +.pnpm-store \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 00000000..6a524b55 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Debug vihaco demo", + "type": "lldb", + "request": "launch", + "program": "${workspaceFolder}/target/debug/examples/demo", + "cwd": "${workspaceFolder}", + "preLaunchTask": "build demo", + "sourceLanguages": ["rust"] + } + ] +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 00000000..ebfb6fa6 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,11 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build demo", + "type": "shell", + "command": "cargo build --package vihaco-demos --example demo", + "problemMatcher": ["$rustc"] + } + ] +} diff --git a/Cargo.lock b/Cargo.lock index f02fe6b0..6a28a23e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -680,6 +680,18 @@ dependencies = [ "vihaco-parser-derive", ] +[[package]] +name = "vihaco-demos" +version = "0.2.0" +dependencies = [ + "chumsky", + "eyre", + "vihaco", + "vihaco-cpu", + "vihaco-parser", + "vihaco-parser-derive", +] + [[package]] name = "vihaco-doctests" version = "0.2.0" @@ -732,7 +744,9 @@ dependencies = [ "vihaco-abi", "vihaco-bytecode", "vihaco-module", + "vihaco-parser", "vihaco-runtime-derive", + "vihaco-syntax", ] [[package]] @@ -760,6 +774,7 @@ version = "0.2.0" dependencies = [ "chumsky", "eyre", + "vihaco-abi", "vihaco-bytecode", "vihaco-parser", "vihaco-parser-derive", diff --git a/Cargo.toml b/Cargo.toml index c83eeef5..4be016e2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,7 @@ members = [ "crates/vihaco-runtime-derive", "crates/vihaco-stdlib", "crates/vihaco-syntax", + "demos", ] [workspace.package] diff --git a/README.md b/README.md index b7f80cf1..ba92c34c 100644 --- a/README.md +++ b/README.md @@ -12,48 +12,37 @@ machine. vihaco is a framework for building small virtual machines. You define -- the **instruction set** — an enum, with `#[derive(Instruction)]`; -- the **components** that execute it — with `#[component]`; -- the **effects** they emit; and -- (optionally) **SST source syntax** — with `#[derive(Parse)]`, +- reusable **components** and their instruction products with `component!`; +- one `Execute` implementation per product, with typed messages and effects; +- **composite routes** with `composite!`; +- executable composite **surface syntax and program loading** with the parser + derives; and +- (optionally) standalone **SST source syntax** with the parser derives, -all as ordinary Rust, then compose them into a machine. A component is one -`execute(instruction, message) -> effects`: +all as ordinary Rust. A component step is +`execute(&instruction, message) -> StepResult`: ```rust use eyre::Result; -use vihaco::{Effects, Instruction, Message, component}; +use vihaco::{component, Effects, Execute, Execution, StepResult}; -// Bytecode-visible operations: each variant is an opcode, tuple fields its payload. -#[derive(Debug, Clone, Instruction)] -pub enum CounterInst { - Add(i64), - Print, +component! { + component Counter { value: i64, } + runtime { + instruction { Add(i64), Read, } + } } -// Runtime-supplied input, not encoded in the instruction stream. -#[derive(Debug, Clone, Message)] -pub struct Prefix(pub String); - -// A value the component emits for the runtime / observers to consume. -#[derive(Debug, Clone, PartialEq)] -pub struct Line(pub String); - -#[derive(Debug, Default)] -pub struct Counter { - value: i64, -} +// `component!` generates the component and these instruction structs. +// A containing `composite!` owns the machine-local instruction sum. -#[component(instruction = CounterInst, message = Prefix, effect = Line)] -impl Counter { - fn execute(&mut self, inst: CounterInst, msg: Prefix) -> Result> { - match inst { - CounterInst::Add(v) => { - self.value += v; - Ok(Effects::none()) - } - CounterInst::Print => Ok(Effects::one(Line(format!("{}{}", msg.0, self.value)))), - } +impl Execute for counter::Counter { + type Message = (); + type Effect = (); + type Fault = eyre::Report; + fn execute(&mut self, instruction: &counter::runtime::instruction::Add, _: ()) -> Result> { + self.value += instruction.0; + Ok(StepResult { effects: Effects::none(), execution: Execution::Complete }) } } ``` @@ -65,16 +54,16 @@ needs; there is no umbrella crate. | Crate | Role | |---|---| -| [`vihaco`](crates/vihaco) | The batteries-included facade: re-exports every crate below at stable paths (`Instruction` / `Message` / `Effects`, the `#[component]` / `#[observe]` / `#[composite]` macros, the module / syntax / runtime layers, the `Value` / `Type` model), so most projects depend only on this crate. | +| [`vihaco`](crates/vihaco) | The batteries-included facade: re-exports the instruction, message, effects, execution, component, and composite APIs, plus the module / syntax / runtime layers and `Value` / `Type` model. | | [`vihaco-abi`](crates/vihaco-abi) | The ISA vocabulary: the `Instruction` / `Effects` types, the `Value` / `Type` model, and the encoding + host-VM traits. | | [`vihaco-abi-derive`](crates/vihaco-abi-derive) | `#[derive(Instruction)]`, re-exported through `vihaco-abi`'s `derive` feature. | | [`vihaco-bytecode`](crates/vihaco-bytecode) | The binary / SST container format: headers, sections, and instruction (de)coding. | | [`vihaco-module`](crates/vihaco-module) | The loadable `Module` model, program loader, host-VM traits, and assembly-style `Display`. | -| [`vihaco-runtime`](crates/vihaco-runtime) | The component/machine runtime: `GeneratedComponent`, effect sinks, and observation machinery. | -| [`vihaco-runtime-derive`](crates/vihaco-runtime-derive) | `#[derive(Message)]`, `#[component]`, `#[composite]`, `#[observe]`, re-exported through `vihaco-runtime`'s `derive` feature. | +| [`vihaco-runtime`](crates/vihaco-runtime) | The component/machine runtime: `Execute`, `StepResult`, `Execution`, `Supply`, `Absorb`, `Observe`, `Handle`, and effect machinery. | +| [`vihaco-runtime-derive`](crates/vihaco-runtime-derive) | The `component!` and `composite!` declaration macros, re-exported through `vihaco` and `vihaco-runtime`'s `derive` feature. | | [`vihaco-stdlib`](crates/vihaco-stdlib) | Standard-library components and observers, including `StdoutObserver`. | | [`vihaco-syntax`](crates/vihaco-syntax) | Typed SST parsing and module construction (`Resolve`). | -| [`vihaco-cpu`](crates/vihaco-cpu) | A ready-made CPU/host component — a small stack machine (constants, arithmetic, branches, halt, …) with a `StepOutcome` control-flow effect. Use directly, or as a reference for writing your own. | +| [`vihaco-cpu`](crates/vihaco-cpu) | A ready-made CPU/host component — a small stack machine (constants, arithmetic, branches, halt, …). Use directly, or as a reference for writing your own. | | [`vihaco-parser`](crates/vihaco-parser) | The `Parse<'src>` and `SurfaceInstruction` traits plus lexical, primitive, and collection implementations shared by the parser derive. | | [`vihaco-parser-derive`](crates/vihaco-parser-derive) | `#[derive(Parse)]` — turns instruction, value, and type enums or structs into [chumsky](https://github.com/zesterer/chumsky) parsers via `#[syntax_class]` and `#[pattern]`. | @@ -109,8 +98,8 @@ No mise? A stable Rust 2024 toolchain is enough — `cargo test --workspace Guides and the API reference are published to GitHub Pages: ****. The guides walk through defining -instructions, pattern parser integration, messages, components, observers, and -composites. +instructions, pattern parser integration, typed module resolution, messages, +components, observers, composites, and composite-owned program loading. Every code block in the guides and on the site is compiled — and, where runnable, executed — in CI (via the `vihaco-doctests` crate), so the examples diff --git a/crates/vihaco-cpu/src/component.rs b/crates/vihaco-cpu/src/component.rs index 74fb4fc3..23aaab47 100644 --- a/crates/vihaco-cpu/src/component.rs +++ b/crates/vihaco-cpu/src/component.rs @@ -2,14 +2,17 @@ // SPDX-License-Identifier: MIT use eyre::Result; -use std::ops::{Add, BitAnd, BitOr, BitXor, Div, Mul, Rem, Shl, Shr, Sub}; +use std::ops::{ + Add as _, BitAnd as _, BitOr as _, BitXor as _, Div as _, Mul as _, Rem as _, Shl as _, + Shr as _, Sub as _, +}; use crate::StepOutcome; use crate::data::CPU; -use crate::instruction::RuntimeInstruction; -use vihaco::Effects; use vihaco::program::{Type, Value}; -use vihaco::{component, frame::Frame, traits::*}; +use vihaco::{Execute, NoEffect, NoMessage, StepResult, Supply, frame::Frame, traits::*}; + +pub use crate::instruction::cpu::runtime::instruction::*; impl Reset for CPU { fn reset(&mut self) { @@ -23,159 +26,211 @@ impl Reset for CPU { } } -impl CPU { - pub fn execute_instruction(&mut self, inst: RuntimeInstruction) -> eyre::Result { - self.clear_pending_pc(); - use RuntimeInstruction::*; - match inst { - Span(file, start, end) => self.op_span(file, start, end), - Label | FunctionStart | FunctionEnd => Ok(StepOutcome::Continue), - Breakpoint => Ok(StepOutcome::Breakpoint), - Branch(target) => self.op_branch(target), - ConditionalBranch(true_target, false_target) => { - self.op_conditional_branch(true_target, false_target) - } - Return(keep) => self.op_return(keep), - Call(arity, target) => self.op_call(arity, target), - IndirectCall => self.op_indirect_call(), - Halt => Ok(StepOutcome::Halt), - Print => Err(eyre::eyre!( - "Print must be handled via execute with CPUMessage::Print" - )), - Load(ty, addr) => self.op_load(ty, addr), - Store(ty, addr) => self.op_store(ty, addr), - Dup => self.op_dup(), - HeapAlloc(n_elements) => self.op_heap_alloc(n_elements), - GetItem => self.op_get_item(), - HeapDealloc => self.op_heap_dealloc(), - Const(v) => self.op_const(v), - Add(ty) => self.op_add(ty), - Sub(ty) => self.op_sub(ty), - Mul(ty) => self.op_mul(ty), - Div(ty) => self.op_div(ty), - Rem(ty) => self.op_rem(ty), - Neg(ty) => self.op_neg(ty), - Shl(ty) => self.op_shl(ty), - Shr(ty) => self.op_shr(ty), - Rol(ty) => self.op_rol(ty), - Ror(ty) => self.op_ror(ty), - BitAnd(ty) => self.op_bitand(ty), - BitOr(ty) => self.op_bitor(ty), - BitXor(ty) => self.op_bitxor(ty), - Not => self.op_not(), - And => self.op_and(), - Or => self.op_or(), - Xor => self.op_xor(), - Eq(ty) => self.op_eq(ty), - Ne(ty) => self.op_ne(ty), - Lt(ty) => self.op_lt(ty), - Gt(ty) => self.op_gt(ty), - Le(ty) => self.op_le(ty), - Ge(ty) => self.op_ge(ty), - } +pub mod message { + #[derive(Debug, Clone, PartialEq)] + pub struct FunctionInfo { + pub arity: u32, + pub start_address: u32, } + + #[derive(Debug, Clone, PartialEq)] + pub struct Print(pub String); } -#[derive(Debug, Clone, PartialEq, vihaco::Message)] -pub enum CPUMessage { - None, - FunctionInfo { arity: u32, start_address: u32 }, - Print(String), +impl vihaco::Message for message::FunctionInfo {} +impl vihaco::Message for message::Print {} + +#[derive(Debug, Clone, PartialEq)] +pub struct PrintEffect(pub String); + +impl Supply for CPU { + type Fault = eyre::Report; + + fn supply(&mut self) -> Result { + let start_address: u32 = self.stack_pop()?.try_into()?; + let arity: u32 = self.stack_pop()?.try_into()?; + Ok(message::FunctionInfo { + arity, + start_address, + }) + } } -#[component(instruction = RuntimeInstruction, message = CPUMessage, effect = StepOutcome)] -impl CPU { +impl Execute for CPU { + type Message = NoMessage; + type Effect = NoEffect; + type Fault = eyre::Report; + fn execute( &mut self, - inst: RuntimeInstruction, - msg: CPUMessage, - ) -> eyre::Result> { - use RuntimeInstruction::*; - match (inst, msg) { - (Print, CPUMessage::Print(text)) => { - self.stack_pop()?; - drop(text); - Ok(Effects::one(StepOutcome::Continue)) - } - (Print, _) => Err(eyre::eyre!("Print requires CPUMessage::Print")), - (_, CPUMessage::Print(_)) => Err(eyre::eyre!( - "CPUMessage::Print is only valid for Print instruction" - )), - ( - inst, - CPUMessage::FunctionInfo { - arity, - start_address, - }, - ) => { - self.stack_push(arity); - self.stack_push(start_address); - self.execute_instruction(inst).map(Effects::one) - } - (inst, CPUMessage::None) => self.execute_instruction(inst).map(Effects::one), - } + instruction: &Span, + _message: Self::Message, + ) -> eyre::Result> { + self.span = (instruction.0, instruction.1, instruction.2); + vihaco::complete!() } } -impl CPU { - pub fn op_span(&mut self, file: u32, start: u32, end: u32) -> eyre::Result { - self.span = (file, start, end); - Ok(StepOutcome::Continue) +impl Execute