Skip to main content

vihaco_cpu/
instruction.rs

1// SPDX-FileCopyrightText: 2026 The vihaco Authors
2// SPDX-License-Identifier: MIT
3
4use vihaco_parser::BareToken;
5
6#[derive(Debug, Clone, Copy, PartialEq, vihaco_parser_derive::Parse)]
7#[syntax_class(type)]
8pub enum SurfaceType {
9    #[pattern = "`undef`"]
10    Undefined,
11    #[pattern = "`str`"]
12    String,
13    #[pattern = "`bool`"]
14    Bool,
15    #[pattern = "`i64`"]
16    I64,
17    #[pattern = "`i32`"]
18    I32,
19    #[pattern = "`u32`"]
20    U32,
21    #[pattern = "`u64`"]
22    U64,
23    #[pattern = "`f64`"]
24    F64,
25    #[pattern = "`f32`"]
26    F32,
27    #[pattern = "`fn_ref`"]
28    FunctionRef,
29    #[pattern = "`heap_ref`"]
30    HeapRef,
31}
32
33#[derive(Debug, Clone, PartialEq, Eq, vihaco_parser_derive::Parse)]
34#[syntax_class(value)]
35pub enum SurfaceValue {
36    #[pattern = "$0"]
37    Quoted(vihaco_parser::QuotedString),
38    #[pattern = "$0"]
39    Bare(BareToken),
40}
41
42#[cfg(test)]
43#[allow(clippy::approx_constant)]
44mod parse_tests {
45    use super::SurfaceType;
46    use crate::SurfaceInstruction;
47    use chumsky::Parser as _;
48    use vihaco_parser::Parse;
49
50    fn parse(input: &str) -> SurfaceInstruction {
51        SurfaceInstruction::parser()
52            .parse(input)
53            .into_result()
54            .unwrap_or_else(|e| panic!("parse({input:?}) failed: {e:?}"))
55    }
56
57    fn parse_type(input: &str) -> SurfaceType {
58        SurfaceType::parser()
59            .parse(input)
60            .into_result()
61            .unwrap_or_else(|e| panic!("parse_type({input:?}) failed: {e:?}"))
62    }
63
64    macro_rules! assert_parses {
65        ($input:literal, $pattern:pat $(if $guard:expr)?) => {
66            assert!(
67                matches!(parse($input), $pattern $(if $guard)?),
68                "input {:?} parsed to the wrong variant or operands",
69                $input
70            );
71        };
72    }
73
74    #[test]
75    fn parses_unit_variants() {
76        assert_parses!("cpu.halt", SurfaceInstruction::Halt);
77        assert_parses!("cpu.print", SurfaceInstruction::Print);
78        assert_parses!("cpu.dup", SurfaceInstruction::Dup);
79        assert_parses!("cpu.breakpoint", SurfaceInstruction::Breakpoint);
80        assert_parses!(
81            "cpu.label @loop",
82            SurfaceInstruction::Label(name) if name.as_str() == "loop"
83        );
84        assert_parses!("cpu.func_start", SurfaceInstruction::FunctionStart);
85        assert_parses!("cpu.func_end", SurfaceInstruction::FunctionEnd);
86        assert_parses!("cpu.get_item", SurfaceInstruction::GetItem);
87        assert_parses!("cpu.not", SurfaceInstruction::Not);
88        assert_parses!("cpu.and", SurfaceInstruction::And);
89        assert_parses!("cpu.or", SurfaceInstruction::Or);
90        assert_parses!("cpu.xor", SurfaceInstruction::Xor);
91        assert_parses!("cpu.call_indirect", SurfaceInstruction::IndirectCall);
92        assert_parses!("cpu.ret 0", SurfaceInstruction::Return(0));
93    }
94
95    #[test]
96    fn parses_explicit_typed_variants() {
97        assert_parses!("cpu.add_i32", SurfaceInstruction::AddI32);
98        assert_parses!("cpu.add_f32", SurfaceInstruction::AddF32);
99        assert_parses!("cpu.load_i64 7", SurfaceInstruction::LoadI64(7));
100        assert_parses!("cpu.const_i64 42", SurfaceInstruction::ConstI64(_));
101    }
102
103    #[test]
104    fn parses_surface_types() {
105        for (input, expected) in [
106            ("undef", SurfaceType::Undefined),
107            ("str", SurfaceType::String),
108            ("bool", SurfaceType::Bool),
109            ("i64", SurfaceType::I64),
110            ("u32", SurfaceType::U32),
111            ("u64", SurfaceType::U64),
112            ("f64", SurfaceType::F64),
113            ("fn_ref", SurfaceType::FunctionRef),
114            ("heap_ref", SurfaceType::HeapRef),
115        ] {
116            assert_eq!(parse_type(input), expected, "input {input:?}");
117        }
118    }
119
120    #[cfg(any())]
121    #[test]
122    fn parses_typed_operations() {
123        assert_parses!("cpu.add i64", SurfaceInstruction::Add(SurfaceType::I64));
124        assert_parses!("cpu.sub f64", SurfaceInstruction::Sub(SurfaceType::F64));
125        assert_parses!("cpu.mul u32", SurfaceInstruction::Mul(SurfaceType::U32));
126        assert_parses!("cpu.div u64", SurfaceInstruction::Div(SurfaceType::U64));
127        assert_parses!("cpu.rem i64", SurfaceInstruction::Rem(SurfaceType::I64));
128        assert_parses!("cpu.neg f64", SurfaceInstruction::Neg(SurfaceType::F64));
129        assert_parses!("cpu.lt i64", SurfaceInstruction::Lt(SurfaceType::I64));
130        assert_parses!("cpu.eq i64", SurfaceInstruction::Eq(SurfaceType::I64));
131        assert_parses!("cpu.ne u64", SurfaceInstruction::Ne(SurfaceType::U64));
132        assert_parses!("cpu.gt u32", SurfaceInstruction::Gt(SurfaceType::U32));
133        assert_parses!("cpu.le f64", SurfaceInstruction::Le(SurfaceType::F64));
134        assert_parses!("cpu.ge f64", SurfaceInstruction::Ge(SurfaceType::F64));
135        assert_parses!(
136            "cpu.bitand i64",
137            SurfaceInstruction::BitAnd(SurfaceType::I64)
138        );
139        assert_parses!("cpu.bitor u64", SurfaceInstruction::BitOr(SurfaceType::U64));
140        assert_parses!(
141            "cpu.bitxor u32",
142            SurfaceInstruction::BitXor(SurfaceType::U32)
143        );
144        assert_parses!("cpu.shl u64", SurfaceInstruction::Shl(SurfaceType::U64));
145        assert_parses!("cpu.shr i64", SurfaceInstruction::Shr(SurfaceType::I64));
146        assert_parses!("cpu.rol u32", SurfaceInstruction::Rol(SurfaceType::U32));
147        assert_parses!("cpu.ror u64", SurfaceInstruction::Ror(SurfaceType::U64));
148    }
149
150    #[cfg(any())]
151    #[test]
152    fn parses_load_store() {
153        assert_parses!(
154            "cpu.load i64, 7",
155            SurfaceInstruction::Load(SurfaceType::I64, 7)
156        );
157        assert_parses!(
158            "cpu.store f64, 42",
159            SurfaceInstruction::Store(SurfaceType::F64, 42)
160        );
161    }
162
163    #[test]
164    fn parses_heap_alloc() {
165        assert_parses!("cpu.heap_alloc 5", SurfaceInstruction::HeapAlloc(5));
166    }
167
168    #[test]
169    fn parses_span() {
170        assert_parses!("cpu.span 0 1 2", SurfaceInstruction::Span(0, 1, 2));
171    }
172
173    #[cfg(any())]
174    #[test]
175    fn parses_const_numeric_flavors() {
176        assert_parses!(
177            "cpu.const i64, 42",
178            SurfaceInstruction::Const(SurfaceType::I64, value)
179                if value == SurfaceValue::Bare(BareToken("42".to_owned()))
180        );
181        assert_parses!(
182            "cpu.const u64, 7",
183            SurfaceInstruction::Const(SurfaceType::U64, value)
184                if value == SurfaceValue::Bare(BareToken("7".to_owned()))
185        );
186        assert_parses!(
187            "cpu.const u32, 3",
188            SurfaceInstruction::Const(SurfaceType::U32, value)
189                if value == SurfaceValue::Bare(BareToken("3".to_owned()))
190        );
191        assert_parses!(
192            "cpu.const f64, 3.14",
193            SurfaceInstruction::Const(SurfaceType::F64, value)
194                if value == SurfaceValue::Bare(BareToken("3.14".to_owned()))
195        );
196        assert_parses!(
197            "cpu.const bool, true",
198            SurfaceInstruction::Const(SurfaceType::Bool, value)
199                if value == SurfaceValue::Bare(BareToken("true".to_owned()))
200        );
201    }
202
203    #[cfg(any())]
204    #[test]
205    fn parses_const_quoted_string() {
206        assert_parses!(
207            "cpu.const str, \"hello world\"",
208            SurfaceInstruction::Const(SurfaceType::String, SurfaceValue::Quoted(value))
209                if value.as_str() == "hello world"
210        );
211    }
212
213    #[test]
214    fn parses_symbolic_control_flow() {
215        assert_parses!(
216            "cpu.br @body",
217            SurfaceInstruction::Branch(target) if target.as_str() == "body"
218        );
219        assert_parses!(
220            "cpu.cond_br @then, @else",
221            SurfaceInstruction::ConditionalBranch(then_target, else_target)
222                if then_target.as_str() == "then" && else_target.as_str() == "else"
223        );
224        assert_parses!(
225            "cpu.call 2, main",
226            SurfaceInstruction::Call(2, target) if target.as_str() == "main"
227        );
228    }
229
230    #[test]
231    fn rejects_malformed_quoted_value_instead_of_treating_it_as_bare() {
232        assert!(
233            SurfaceInstruction::parser()
234                .parse("cpu.const str, \"unterminated")
235                .has_errors()
236        );
237    }
238}