1use vihaco::Instruction;
5use vihaco::program::{Type, Value};
6use vihaco_parser::{BareToken, Ident};
7
8#[derive(Debug, Clone, PartialEq, Instruction)]
15#[instruction(width = 16)]
16pub enum RuntimeInstruction {
17 Span(u32, u32, u32),
21
22 Label,
24
25 FunctionStart,
28 FunctionEnd,
30
31 Breakpoint,
34
35 Branch(u32),
38
39 ConditionalBranch(u32, u32),
41
42 Return(u32),
45
46 IndirectCall,
48
49 Call(u32, u32),
51
52 Halt,
54
55 Print,
58
59 Load(Type, u32),
62 Store(Type, u32),
64
65 Dup,
67
68 HeapAlloc(u32),
70
71 GetItem,
73
74 HeapDealloc,
77
78 Const(Value),
81
82 Add(Type),
84 Sub(Type),
85 Mul(Type),
86 Div(Type),
87 Rem(Type),
88 Neg(Type),
89
90 Shl(Type),
92 Shr(Type),
93 Rol(Type),
94 Ror(Type),
95 BitAnd(Type),
96 BitOr(Type),
97 BitXor(Type),
98
99 Not,
101 And,
102 Or,
103 Xor,
104
105 Eq(Type),
107 Ne(Type),
108 Lt(Type),
109 Gt(Type),
110 Le(Type),
111 Ge(Type),
112}
113
114#[derive(Debug, Clone, Copy, PartialEq, vihaco_parser_derive::Parse)]
115#[syntax_class(type)]
116pub enum SurfaceType {
117 #[pattern = "`undef`"]
118 Undefined,
119 #[pattern = "`str`"]
120 String,
121 #[pattern = "`bool`"]
122 Bool,
123 #[pattern = "`i64`"]
124 I64,
125 #[pattern = "`u32`"]
126 U32,
127 #[pattern = "`u64`"]
128 U64,
129 #[pattern = "`f64`"]
130 F64,
131 #[pattern = "`fn_ref`"]
132 FunctionRef,
133 #[pattern = "`heap_ref`"]
134 HeapRef,
135}
136
137#[derive(Debug, Clone, PartialEq, Eq, vihaco_parser_derive::Parse)]
138#[syntax_class(value)]
139pub enum SurfaceValue {
140 #[pattern = "$0"]
141 Quoted(vihaco_parser::QuotedString),
142 #[pattern = "$0"]
143 Bare(BareToken),
144}
145
146#[derive(Debug, Clone, PartialEq, vihaco_parser_derive::Parse)]
147#[syntax_class(instruction, head = "cpu")]
148pub enum SurfaceInstruction {
149 #[pattern = "'span $0 $1 $2"]
153 Span(u32, u32, u32),
154
155 #[pattern = "'label `@` $0"]
157 Label(Ident),
158
159 #[pattern = "'func_start"]
162 FunctionStart,
163 #[pattern = "'func_end"]
165 FunctionEnd,
166
167 Breakpoint,
170
171 #[pattern = "'br `@` $0"]
174 Branch(Ident),
175
176 #[pattern = "'cond_br `@` $0 `,` `@` $1"]
178 ConditionalBranch(Ident, Ident),
179
180 #[pattern = "'ret"]
183 Return,
184
185 #[pattern = "'call_indirect"]
187 IndirectCall,
188
189 Call(u32, Ident),
191
192 Halt,
194
195 Print,
198
199 Load(SurfaceType, u32),
202
203 Store(SurfaceType, u32),
205
206 Dup,
208
209 #[pattern = "'heap_alloc $0"]
211 HeapAlloc(u32),
212
213 #[pattern = "'get_item"]
215 GetItem,
216
217 #[pattern = "'heap_dealloc"]
220 HeapDealloc,
221
222 Const(SurfaceType, SurfaceValue),
225
226 Add(SurfaceType),
228 Sub(SurfaceType),
229 Mul(SurfaceType),
230 Div(SurfaceType),
231 Rem(SurfaceType),
232 Neg(SurfaceType),
233
234 Shl(SurfaceType),
236 Shr(SurfaceType),
237 Rol(SurfaceType),
238 Ror(SurfaceType),
239 #[pattern = "'bitand $0"]
240 BitAnd(SurfaceType),
241 #[pattern = "'bitor $0"]
242 BitOr(SurfaceType),
243 #[pattern = "'bitxor $0"]
244 BitXor(SurfaceType),
245
246 Not,
248 And,
249 Or,
250 Xor,
251
252 Eq(SurfaceType),
254 Ne(SurfaceType),
255 Lt(SurfaceType),
256 Gt(SurfaceType),
257 Le(SurfaceType),
258 Ge(SurfaceType),
259}
260
261impl<T: Into<Value>> From<T> for RuntimeInstruction {
262 fn from(value: T) -> Self {
263 RuntimeInstruction::Const(value.into())
264 }
265}
266
267impl vihaco::CanonicalInstructionSyntax for RuntimeInstruction {
268 fn variants() -> &'static [vihaco::CanonicalInstructionVariantSyntax] {
269 &[
270 vihaco::CanonicalInstructionVariantSyntax {
271 mnemonic: "cpu::const_i64",
272 operands: &[vihaco::OperandKind::I64],
273 },
274 vihaco::CanonicalInstructionVariantSyntax {
275 mnemonic: "cpu::const_f64",
276 operands: &[vihaco::OperandKind::F64],
277 },
278 vihaco::CanonicalInstructionVariantSyntax {
279 mnemonic: "cpu::const_bool",
280 operands: &[vihaco::OperandKind::Bool],
281 },
282 vihaco::CanonicalInstructionVariantSyntax {
283 mnemonic: "cpu::const_u64",
284 operands: &[vihaco::OperandKind::NonNegativeU64],
285 },
286 vihaco::CanonicalInstructionVariantSyntax {
287 mnemonic: "cpu::fn_ref",
288 operands: &[vihaco::OperandKind::Symbol],
289 },
290 vihaco::CanonicalInstructionVariantSyntax {
291 mnemonic: "cpu::call_direct",
292 operands: &[vihaco::OperandKind::Symbol],
293 },
294 ]
295 }
296}
297
298#[cfg(test)]
299#[allow(clippy::approx_constant)]
300mod parse_tests {
301 use super::{BareToken, SurfaceInstruction, SurfaceType, SurfaceValue};
302 use chumsky::Parser as _;
303 use vihaco_parser::Parse;
304
305 fn parse(input: &str) -> SurfaceInstruction {
306 SurfaceInstruction::parser()
307 .parse(input)
308 .into_result()
309 .unwrap_or_else(|e| panic!("parse({input:?}) failed: {e:?}"))
310 }
311
312 fn parse_type(input: &str) -> SurfaceType {
313 SurfaceType::parser()
314 .parse(input)
315 .into_result()
316 .unwrap_or_else(|e| panic!("parse_type({input:?}) failed: {e:?}"))
317 }
318
319 macro_rules! assert_parses {
320 ($input:literal, $pattern:pat $(if $guard:expr)?) => {
321 assert!(
322 matches!(parse($input), $pattern $(if $guard)?),
323 "input {:?} parsed to the wrong variant or operands",
324 $input
325 );
326 };
327 }
328
329 #[test]
330 fn parses_unit_variants() {
331 assert_parses!("cpu::halt", SurfaceInstruction::Halt);
332 assert_parses!("cpu::print", SurfaceInstruction::Print);
333 assert_parses!("cpu::dup", SurfaceInstruction::Dup);
334 assert_parses!("cpu::breakpoint", SurfaceInstruction::Breakpoint);
335 assert_parses!(
336 "cpu::label @loop",
337 SurfaceInstruction::Label(name) if name.as_str() == "loop"
338 );
339 assert_parses!("cpu::func_start", SurfaceInstruction::FunctionStart);
340 assert_parses!("cpu::func_end", SurfaceInstruction::FunctionEnd);
341 assert_parses!("cpu::get_item", SurfaceInstruction::GetItem);
342 assert_parses!("cpu::not", SurfaceInstruction::Not);
343 assert_parses!("cpu::and", SurfaceInstruction::And);
344 assert_parses!("cpu::or", SurfaceInstruction::Or);
345 assert_parses!("cpu::xor", SurfaceInstruction::Xor);
346 assert_parses!("cpu::call_indirect", SurfaceInstruction::IndirectCall);
347 assert_parses!("cpu::ret", SurfaceInstruction::Return);
348 }
349
350 #[test]
351 fn parses_surface_types() {
352 for (input, expected) in [
353 ("undef", SurfaceType::Undefined),
354 ("str", SurfaceType::String),
355 ("bool", SurfaceType::Bool),
356 ("i64", SurfaceType::I64),
357 ("u32", SurfaceType::U32),
358 ("u64", SurfaceType::U64),
359 ("f64", SurfaceType::F64),
360 ("fn_ref", SurfaceType::FunctionRef),
361 ("heap_ref", SurfaceType::HeapRef),
362 ] {
363 assert_eq!(parse_type(input), expected, "input {input:?}");
364 }
365 }
366
367 #[test]
368 fn parses_typed_operations() {
369 assert_parses!("cpu::add i64", SurfaceInstruction::Add(SurfaceType::I64));
370 assert_parses!("cpu::sub f64", SurfaceInstruction::Sub(SurfaceType::F64));
371 assert_parses!("cpu::mul u32", SurfaceInstruction::Mul(SurfaceType::U32));
372 assert_parses!("cpu::div u64", SurfaceInstruction::Div(SurfaceType::U64));
373 assert_parses!("cpu::rem i64", SurfaceInstruction::Rem(SurfaceType::I64));
374 assert_parses!("cpu::neg f64", SurfaceInstruction::Neg(SurfaceType::F64));
375 assert_parses!("cpu::lt i64", SurfaceInstruction::Lt(SurfaceType::I64));
376 assert_parses!("cpu::eq i64", SurfaceInstruction::Eq(SurfaceType::I64));
377 assert_parses!("cpu::ne u64", SurfaceInstruction::Ne(SurfaceType::U64));
378 assert_parses!("cpu::gt u32", SurfaceInstruction::Gt(SurfaceType::U32));
379 assert_parses!("cpu::le f64", SurfaceInstruction::Le(SurfaceType::F64));
380 assert_parses!("cpu::ge f64", SurfaceInstruction::Ge(SurfaceType::F64));
381 assert_parses!(
382 "cpu::bitand i64",
383 SurfaceInstruction::BitAnd(SurfaceType::I64)
384 );
385 assert_parses!(
386 "cpu::bitor u64",
387 SurfaceInstruction::BitOr(SurfaceType::U64)
388 );
389 assert_parses!(
390 "cpu::bitxor u32",
391 SurfaceInstruction::BitXor(SurfaceType::U32)
392 );
393 assert_parses!("cpu::shl u64", SurfaceInstruction::Shl(SurfaceType::U64));
394 assert_parses!("cpu::shr i64", SurfaceInstruction::Shr(SurfaceType::I64));
395 assert_parses!("cpu::rol u32", SurfaceInstruction::Rol(SurfaceType::U32));
396 assert_parses!("cpu::ror u64", SurfaceInstruction::Ror(SurfaceType::U64));
397 }
398
399 #[test]
400 fn parses_load_store() {
401 assert_parses!(
402 "cpu::load i64, 7",
403 SurfaceInstruction::Load(SurfaceType::I64, 7)
404 );
405 assert_parses!(
406 "cpu::store f64, 42",
407 SurfaceInstruction::Store(SurfaceType::F64, 42)
408 );
409 }
410
411 #[test]
412 fn parses_heap_alloc() {
413 assert_parses!("cpu::heap_alloc 5", SurfaceInstruction::HeapAlloc(5));
414 }
415
416 #[test]
417 fn parses_span() {
418 assert_parses!("cpu::span 0 1 2", SurfaceInstruction::Span(0, 1, 2));
419 }
420
421 #[test]
422 fn parses_const_numeric_flavors() {
423 assert_parses!(
424 "cpu::const i64, 42",
425 SurfaceInstruction::Const(SurfaceType::I64, value)
426 if value == SurfaceValue::Bare(BareToken("42".to_owned()))
427 );
428 assert_parses!(
429 "cpu::const u64, 7",
430 SurfaceInstruction::Const(SurfaceType::U64, value)
431 if value == SurfaceValue::Bare(BareToken("7".to_owned()))
432 );
433 assert_parses!(
434 "cpu::const u32, 3",
435 SurfaceInstruction::Const(SurfaceType::U32, value)
436 if value == SurfaceValue::Bare(BareToken("3".to_owned()))
437 );
438 assert_parses!(
439 "cpu::const f64, 3.14",
440 SurfaceInstruction::Const(SurfaceType::F64, value)
441 if value == SurfaceValue::Bare(BareToken("3.14".to_owned()))
442 );
443 assert_parses!(
444 "cpu::const bool, true",
445 SurfaceInstruction::Const(SurfaceType::Bool, value)
446 if value == SurfaceValue::Bare(BareToken("true".to_owned()))
447 );
448 }
449
450 #[test]
451 fn parses_const_quoted_string() {
452 assert_parses!(
453 "cpu::const str, \"hello world\"",
454 SurfaceInstruction::Const(SurfaceType::String, SurfaceValue::Quoted(value))
455 if value.as_str() == "hello world"
456 );
457 }
458
459 #[test]
460 fn parses_symbolic_control_flow() {
461 assert_parses!(
462 "cpu::br @body",
463 SurfaceInstruction::Branch(target) if target.as_str() == "body"
464 );
465 assert_parses!(
466 "cpu::cond_br @then, @else",
467 SurfaceInstruction::ConditionalBranch(then_target, else_target)
468 if then_target.as_str() == "then" && else_target.as_str() == "else"
469 );
470 assert_parses!(
471 "cpu::call 2, main",
472 SurfaceInstruction::Call(2, target) if target.as_str() == "main"
473 );
474 }
475
476 #[test]
477 fn rejects_malformed_quoted_value_instead_of_treating_it_as_bare() {
478 assert!(
479 SurfaceInstruction::parser()
480 .parse("cpu::const str, \"unterminated")
481 .has_errors()
482 );
483 }
484
485 #[test]
486 fn rejects_legacy_runtime_instruction_syntax() {
487 assert!(
488 SurfaceInstruction::parser()
489 .parse("const.i64 42")
490 .has_errors()
491 );
492 assert!(SurfaceInstruction::parser().parse("br @body").has_errors());
493 }
494}