mindustry logic execution, map- and schematic- parsing and rendering
(shabby) errors for all
bendn 2023-09-19
parent 862bbb5 · commit beea58f
-rw-r--r--lemu/Cargo.toml2
-rw-r--r--lemu/src/celliterate.mlog2
-rw-r--r--lemu/src/executor/builder.rs20
-rw-r--r--lemu/src/executor/mod.rs10
-rw-r--r--lemu/src/instructions/mod.rs15
-rw-r--r--lemu/src/lexer.rs13
-rw-r--r--lemu/src/lib.rs2
-rw-r--r--lemu/src/parser.rs699
-rw-r--r--lemu/src/parser/error.rs382
-rw-r--r--lemu/src/parser/mod.rs743
10 files changed, 1149 insertions, 739 deletions
diff --git a/lemu/Cargo.toml b/lemu/Cargo.toml
index 5d4bc34..b877728 100644
--- a/lemu/Cargo.toml
+++ b/lemu/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "lemu"
-version = "0.2.8"
+version = "0.2.9"
edition = "2021"
description = "M-LOG runner"
authors = ["bend-n <[email protected]>"]
diff --git a/lemu/src/celliterate.mlog b/lemu/src/celliterate.mlog
index 3eee00a..d0a53ec 100644
--- a/lemu/src/celliterate.mlog
+++ b/lemu/src/celliterate.mlog
@@ -1,3 +1,3 @@
read result cell1 0
op add result result 1
-write result cell1 0 \ No newline at end of file
+write result cell1 0
diff --git a/lemu/src/executor/builder.rs b/lemu/src/executor/builder.rs
index f14f4c8..9df29ab 100644
--- a/lemu/src/executor/builder.rs
+++ b/lemu/src/executor/builder.rs
@@ -7,6 +7,7 @@ use super::{
};
use crate::{
instructions::{DrawInstr, Instr},
+ lexer::Token,
memory::LRegistry,
};
@@ -50,8 +51,8 @@ impl<'s, W: Wr> ExecutorBuilderInternal<'s, W> {
self.program.push(UPInstr::UnfinishedJump);
}
- pub(crate) fn code(&mut self, s: String) {
- self.program.push(UPInstr::Code(s));
+ pub(crate) fn code(&mut self, v: Box<[Token<'s>]>) {
+ self.program.push(UPInstr::Code(v));
}
pub(crate) fn bank(&mut self, n: usize) -> Memory {
@@ -141,15 +142,12 @@ impl<'s, W: Wr> ExecutorBuilderInternal<'s, W> {
program: Pin::new(
program
.into_iter()
- .map(|v| {
- match v {
- UPInstr::Instr(i) => PInstr::Instr(i),
- UPInstr::Draw(i) => PInstr::Draw(i),
- UPInstr::NoOp => PInstr::NoOp,
- UPInstr::UnfinishedJump => panic!("all jumps should have finished"),
- // todo
- UPInstr::Code(c) => PInstr::Code(c),
- }
+ .map(|v| match v {
+ UPInstr::Instr(i) => PInstr::Instr(i),
+ UPInstr::Draw(i) => PInstr::Draw(i),
+ UPInstr::NoOp => PInstr::NoOp,
+ UPInstr::UnfinishedJump => panic!("all jumps should have finished"),
+ UPInstr::Code(c) => PInstr::Code(c),
})
.collect::<Box<[PInstr]>>(),
),
diff --git a/lemu/src/executor/mod.rs b/lemu/src/executor/mod.rs
index 41d6696..d1df0af 100644
--- a/lemu/src/executor/mod.rs
+++ b/lemu/src/executor/mod.rs
@@ -1,6 +1,8 @@
mod builder;
+
use super::{
instructions::{DrawInstr, DrawInstruction, Flow, Instr, LInstruction},
+ lexer::Token,
memory::{LAddress, LRegistry, LVar},
};
pub use builder::ExecutorBuilderInternal;
@@ -13,7 +15,7 @@ pub struct Display(pub usize);
// negative means bank, positive means cell
pub struct Memory(pub(crate) i8);
impl Memory {
- pub(crate) fn fits(self, i: usize) -> bool {
+ pub(crate) const fn fits(self, i: usize) -> bool {
if self.0 < 0 {
i < BANK_SIZE
} else {
@@ -38,7 +40,7 @@ pub struct Instruction(usize);
impl Instruction {
/// # Safety
/// verify n is valid.
- pub unsafe fn new(n: usize) -> Self {
+ pub const unsafe fn new(n: usize) -> Self {
Self(n)
}
@@ -57,7 +59,7 @@ impl std::fmt::Debug for Instruction {
pub enum PInstr<'s> {
Instr(Instr<'s>),
Draw(DrawInstr<'s>),
- Code(String),
+ Code(Box<[Token<'s>]>),
NoOp,
}
@@ -104,7 +106,7 @@ pub enum UPInstr<'s> {
Instr(Instr<'s>),
Draw(DrawInstr<'s>),
UnfinishedJump,
- Code(String),
+ Code(Box<[Token<'s>]>),
NoOp,
}
diff --git a/lemu/src/instructions/mod.rs b/lemu/src/instructions/mod.rs
index 36fc84d..70888a6 100644
--- a/lemu/src/instructions/mod.rs
+++ b/lemu/src/instructions/mod.rs
@@ -30,21 +30,6 @@ use super::{
memory::{LAddress, LVar},
};
-// pub const INSTRS: &[&str] = &[
-// "getlink",
-// "read",
-// "write",
-// "set",
-// "op",
-// "end",
-// "drawflush",
-// "draw",
-// "print",
-// "packcolor",
-// "jump",
-// "stop",
-// ];
-
pub const OPS: &[&str] = &[
"equal",
"notEqual",
diff --git a/lemu/src/lexer.rs b/lemu/src/lexer.rs
index 63d5eda..01a75b4 100644
--- a/lemu/src/lexer.rs
+++ b/lemu/src/lexer.rs
@@ -8,19 +8,19 @@ macro_rules! instrs {
pub enum Token<'strings> {
#[token("\n")]
Newline,
- #[regex("#[^\n]+")]
+ #[regex("#[^\n]+", priority = 8)]
Comment(&'strings str),
#[regex(r"[0-9]+(\.[0-9]+)?", |lex| lex.slice().parse().ok())]
#[regex(r"(true)|(false)", |lex| lex.slice().parse::<bool>().ok().map(f64::from), priority = 10)]
#[regex(r#"0[xX][0-9a-fA-F]+"#, |lex| u64::from_str_radix(&lex.slice()[2..], 16).map(|v| v as f64).ok())]
#[regex(r#"0[bB][01]+"#, |lex| u64::from_str_radix(&lex.slice()[2..], 2).map(|v| v as f64).ok())]
#[regex(r#""[0-9]+(\.[0-9]+)?""#, callback = |lex| lex.slice()[1..lex.slice().len()-1].parse().ok(), priority = 13)]
- Num(f64),
+ Num(f64), // TODO have bool and integer tokens, and parser converts
#[regex(r#""([^\\"\n])*""#, callback = |lex| Cow::from(&lex.slice()[1..lex.slice().len()-1]), priority = 12)]
- #[regex(r#"@[^ "\n]*"#, |lex| Cow::from(&lex.slice()[1..]))]
+ #[regex(r#"@[^ "\n]*"#, |lex| Cow::from(lex.slice()))]
#[regex(r#""[^"]*""#, callback = |lex| Cow::from(lex.slice()[1..lex.slice().len()-1].replace(r"\n", "\n")), priority = 8)]
String(Cow<'strings, str>),
- #[regex("[^0-9 \t\n]+", priority = 7)]
+ #[regex("[^@0-9 \t\n][^ \t\n]*", priority = 7)]
Ident(&'strings str),
$(#[token($z, priority = 8)] $v,)+
@@ -41,7 +41,7 @@ macro_rules! instrs {
}
instrs! {
- "getlink" => GetLink,
+ "null" => Null,
"read" => Read,
"write" => Write,
"set" => Set,
@@ -50,7 +50,6 @@ instrs! {
"drawflush" => DrawFlush,
"draw" => Draw,
"print" => Print,
- "packcolor" => PackColor,
"jump" => Jump,
"stop" => Stop,
"@counter" => Counter,
@@ -118,7 +117,7 @@ impl<'s> Lexer<'s> {
}
#[allow(dead_code)]
-pub fn print_stream<'s>(mut stream: Lexer) {
+pub fn print_stream(mut stream: Lexer) {
print!("[");
let Some(tok) = stream.next() else {
println!("]");
diff --git a/lemu/src/lib.rs b/lemu/src/lib.rs
index 8a46264..bfa2d29 100644
--- a/lemu/src/lib.rs
+++ b/lemu/src/lib.rs
@@ -1,5 +1,5 @@
//! crate for [MLOG](https://mindustrygame.github.io/wiki/logic/0-introduction/#what-is-mindustry-logic) emulation.
-#![feature(let_chains)]
+#![feature(let_chains, trace_macros)]
#![allow(clippy::redundant_closure_call)]
#![warn(
clippy::multiple_unsafe_ops_per_block,
diff --git a/lemu/src/parser.rs b/lemu/src/parser.rs
deleted file mode 100644
index 7c9cf40..0000000
--- a/lemu/src/parser.rs
+++ /dev/null
@@ -1,699 +0,0 @@
-use std::io::Write as Wr;
-
-use logos::Span;
-
-use super::{
- executor::{ExecutorBuilderInternal, Instruction, UPInstr},
- instructions::{
- draw::{
- Clear, Flush, Line, RectBordered, RectFilled, SetColorConst, SetColorDyn, SetStroke,
- Triangle,
- },
- io::{Print, Read, Write},
- AlwaysJump, ConditionOp, DynJump, End, Instr, Jump, MathOp1, MathOp2, Op1, Op2, Set, Stop,
- },
- lexer::{Lexer, Token},
- memory::{LAddress, LVar},
-};
-
-/// Errors returned when parsing fails.
-#[derive(thiserror::Error, Debug)]
-pub enum Error<'s> {
- /// Occurs from eg `set x`. (needs a value to set to)
- #[error("unexpected end of stream")]
- UnexpectedEof,
- /// Occurs from eg `op add\n...` (needs a variable)
- #[error("expected variable, got {0:?}")]
- ExpectedVar(Token<'s>, Span),
- /// Occurs from eg `draw 4` (needs a ident of the type of drawing)
- #[error("expected identifier, got {0:?}")]
- ExpectedIdent(Token<'s>, Span),
- /// Occurs from eg `jump house` (assuming house isnt a label).
- #[error("expected jump target, got {0:?}")]
- ExpectedJump(Token<'s>, Span),
- /// Occurs from eg `op add "three" "four"`
- #[error("expected number, got {0:?}")]
- ExpectedNum(Token<'s>, Span),
- /// Occurs from eg `op 4` (4 is not add/mul/...)
- #[error("expected operator, got {0:?}")]
- ExpectedOp(Token<'s>, Span),
- /// Occurs from eg `write cell1 5.5` (5.5 is not int)
- #[error("expected integer, got {0:?}")]
- ExpectedInt(Token<'s>, Span),
- /// Occurs from eg `4.0 add 5.0`
- #[error("expected instruction, got {0:?}")]
- ExpectedInstr(Token<'s>, Span),
- /// Occurs from eg
- /// ```text
- /// lable:
- /// jump label always
- /// ```
- /// (typo: lable not label)
- #[error("unable to find label {0}")]
- LabelNotFound(&'s str, Span),
- /// Occurs from eg `jump 4910294029 always`
- #[error("unable to jump to instruction {0:?}")]
- InvalidJump(Instruction, Span),
- /// Occurs from eg `read bank9223372036854775807 5` (only `126` banks can exist)
- #[error("cannot get cell>{0}")]
- MemoryTooFar(usize, Span),
- /// Occurs from eg `read bank1 512`
- #[error("index {0} out of bounds ({1} max)")]
- IndexOutOfBounds(usize, usize, Span),
- /// Occurs from `read register1`
- #[error("unknown memory type {0:?}, expected (cell)|(bank)")]
- InvalidMemoryType(&'s str, Span),
- /// Occurs from `drawflush bank1`
- #[error("unknown display type {0:?}, expected 'display'")]
- InvalidDisplayType(&'s str, Span),
- /// Occurs from `draw house` (or `draw image`, a valid but unsupported instruction here)
- #[error("unknown image operation {0:?}")]
- UnsupportedImageOp(&'s str, Span),
- #[error("couldnt get display #{0:?}.")]
- /// Occurs from eg `display 50`.
- ///
- /// call `display` 50 more times to have more display options:
- /// ```rust,ignore
- /// executor
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display()
- /// .display();
- /// ```
- NoDisplay(usize, Span),
-}
-
-macro_rules! tokstr {
- ($tok:expr) => {
- match $tok {
- Token::Ident(i) => Some(i),
- Token::GetLink => Some("getlink"),
- Token::Read => Some("read"),
- Token::Write => Some("write"),
- Token::Set => Some("set"),
- Token::Op => Some("op"),
- Token::End => Some("end"),
- Token::Draw => Some("draw"),
- Token::DrawFlush => Some("drawflush"),
- Token::Print => Some("print"),
- Token::PackColor => Some("packcolor"),
- Token::Jump => Some("jump"),
- Token::Stop => Some("stop"),
- Token::Counter => Some("@counter"),
- Token::Equal => Some("equal"),
- Token::NotEqual => Some("notEqual"),
- Token::LessThan => Some("lessThan"),
- Token::LessThanEq => Some("lessThanEq"),
- Token::GreaterThan => Some("greaterThan"),
- Token::GreaterThanEq => Some("greaterThanEq"),
- Token::StrictEqual => Some("strictEqual"),
- Token::Always => Some("always"),
- Token::Add => Some("add"),
- Token::Sub => Some("sub"),
- Token::Mul => Some("mul"),
- Token::Div => Some("div"),
- Token::IDiv => Some("idiv"),
- Token::Mod => Some("mod"),
- Token::Pow => Some("pow"),
- Token::And => Some("land"),
- Token::Not => Some("not"),
- Token::ShiftLeft => Some("shl"),
- Token::ShiftRight => Some("shr"),
- Token::BitOr => Some("or"),
- Token::BitAnd => Some("and"),
- Token::ExclusiveOr => Some("xor"),
- Token::Max => Some("max"),
- Token::Min => Some("min"),
- Token::Angle => Some("angle"),
- Token::AngleDiff => Some("angleDiff"),
- Token::Len => Some("len"),
- Token::Noise => Some("noise"),
- Token::Abs => Some("abs"),
- Token::Log => Some("log"),
- Token::Log10 => Some("log10"),
- Token::Floor => Some("floor"),
- Token::Ceil => Some("ceil"),
- Token::Sqrt => Some("sqrt"),
- Token::Rand => Some("rand"),
- Token::Sin => Some("sin"),
- Token::Cos => Some("cos"),
- Token::Tan => Some("tan"),
- Token::ASin => Some("asin"),
- Token::ACos => Some("acos"),
- Token::ATan => Some("atan"),
- _ => None,
- }
- };
-}
-
-impl Error<'_> {
- /// Produces a [`Error`](lerr::Error) from this error.
- #[cfg(feature = "diagnose")]
- pub fn diagnose<'s>(&self, source: &'s str) -> lerr::Error<'s> {
- use comat::{cformat as cmt, cformat_args};
- use lerr::Error;
-
- let error = cformat_args!("{bold_red}error{reset}");
- let note = cformat_args!("{bold_blue}note{reset}");
- let help = cformat_args!("{bold_green}help{reset}");
- let mut e = Error::new(source);
- macro_rules! msg {
- ($ms:literal $(, $args:expr)* $(,)?) => {
- e.message(cmt!($ms $(, $args)*))
- };
- }
- match self {
- Self::UnexpectedEof => {
- msg!("{error}: wasnt able to finish read, got newline").label((
- source.len()..source.len(),
- cmt!("there was supposed to be another token here"),
- ));
- }
- Self::ExpectedVar(_, s) => {
- msg!("{error}: expected variable")
- .label((s, cmt!("this was supposed to be a {blue}variable{reset} ({magenta}identifier{reset}, {magenta}number{reset}, or {magenta}string{reset})")));
- }
- Self::ExpectedIdent(_, s) => {
- msg!("{error}: expected identifier").label((
- s,
- cmt!("this was supposed to be a {bold_blue}identifier{reset} (eg. {magenta}name{reset})"),
- ));
- }
- Self::ExpectedJump(t, s) => {
- msg!("{error}: expected jump target")
- .label((s, cmt!("this was supposed to be a jump target")))
- .note(
- cmt!("{note}: a jump target is a {bold_blue}label{reset} ({magenta}ident{reset}, or {magenta}integer{reset})"),
- );
- if let Token::Num(n) = t {
- e.note(cmt!(
- "{help}: remove the fractional part: {bold_green}{n:.0}{reset}"
- ));
- }
- }
- Self::ExpectedNum(_, s) => {
- msg!("{error}: expected number")
- .label((s, cmt!("this was supposed to be a {bold_blue}number{reset} (eg. {magenta}3.14159{reset})")));
- }
- Self::ExpectedOp(t, s) => {
- msg!("{error}: expected operator")
- .label((s, cmt!("this was supposed to be a {bold_blue}operator{reset} (eg. {magenta}equals{reset})")));
- if let Some(i) = tokstr!(*t) && let Some((mat,score)) = rust_fuzzy_search::fuzzy_search_best_n(i, crate::instructions::OPS, 1).first() && *score > 0.5 {
- e.note(cmt!("{help}: maybe you meant {bold_green}{mat}{reset}"));
- }
- }
- Self::ExpectedInt(t, s) => {
- msg!("{error}: expected integer")
- .label((s, cmt!("this was supposed to be a {bold_blue}integer{reset} (eg. {magenta}4{reset})")));
- if let Token::Num(n) = t {
- e.note(cmt!(
- "{help}: remove the mantissa: {bold_green}{n:.0}{reset}"
- ));
- }
- }
- Self::ExpectedInstr(_, s) => {
- msg!("{error}: expected instruction")
- .label((s, cmt!("this was supposed to be a {bold_blue}instruction{reset} (eg. {magenta}print{reset})")));
- // it occurs to me that this wont ever be a string, as idents are turned into `Code`
- // if let Some(i) = tokstr!(t.clone()) && let Some((mat,score)) = rust_fuzzy_search::fuzzy_search_best_n(i, crate::instructions::INSTRS, 1).get(0) && *score > 0.5 {
- // e.note(format!("{help}: maybe you meant {mat}"));
- // }
- }
- Self::LabelNotFound(_, s) => {
- msg!("{error}: label not found").label((
- s,
- cmt!("this was supposed to be a (existing) {bold_blue}label{reset}"),
- )).note(cmt!("{help}: define a label with {yellow}`label_name:`{reset}, then you can {yellow}`jump label_name`{reset}."));
- }
- Self::InvalidJump(target, s) => {
- msg!("{error}: invalid jump")
- .label((
- s,
- cmt!(
- "line#{bold_red}{}{reset} is not in the program",
- target.get()
- ),
- ))
- .note(cmt!(
- "{help}: there are {bold_blue}{}{reset} available lines",
- source.lines().count()
- ));
- }
- Self::MemoryTooFar(b, s) => {
- msg!("{error}: invalid memory cell/bank")
- .label((s, cmt!("cant get cell/bank#{bold_red}{b}{reset}")))
- .note(cmt!(
- "{note}: only {blue}126{reset} cells/banks are allowed"
- ));
- }
- Self::InvalidMemoryType(t, s) => {
- msg!("{error}: invalid memory type {bold_red}{}{reset}", t)
- .label((s, "here"))
- .note(cmt!("{note}: only banks/cells are allowed"));
- }
- Self::InvalidDisplayType(disp, s) => {
- msg!("{error}: invalid display type {bold_red}{}{reset}", disp)
- .label((s, "here"))
- .note(cmt!("{help}: change this to {bold_green}'display'{reset}"));
- }
- Self::UnsupportedImageOp(op, s) => {
- msg!("{error}: invalid image op {}", op).label((
- s,
- "must be one of {clear, color, col, stroke, line, rect, lineRect, triangle}",
- ));
- if let Some((mat,score)) = rust_fuzzy_search::fuzzy_search_best_n(op, crate::instructions::draw::INSTRS, 1).first() && *score > 0.5 {
- e.note(cmt!("{help}: you may have meant {bold_green}{mat}{reset}"));
- }
- }
- Self::NoDisplay(disp, s) => {
- msg!("{error}: no display allocated")
- .label((s, cmt!("display#{bold_red}{disp}{reset} has not been created")))
- .note(cmt!("{note}: it is impossible for me to dynamically allocate displays, as {blue}'display1'{reset} could be large or small"));
- }
- Self::IndexOutOfBounds(index, size, s) => {
- msg!("{error}: {bold_red}index{reset} {} out of bounds", index)
- .label((s, cmt!("memory has only {magenta}{size}{reset} elements")));
- }
- };
- e
- }
-}
-
-#[derive(Debug)]
-enum UJump<'v> {
- Sometimes {
- a: LAddress<'v>,
- b: LAddress<'v>,
- op: ConditionOp,
- },
- Always,
-}
-
-pub fn parse<'source, W: Wr>(
- mut tokens: Lexer<'source>,
- executor: &mut ExecutorBuilderInternal<'source, W>,
-) -> Result<(), Error<'source>> {
- let mut mem = Vec::new(); // maps &str to usize
- // maps "start" to 0
- let mut labels = Vec::new();
- let mut unfinished_jumps = Vec::new();
- macro_rules! tok {
- () => {
- tokens.next().ok_or(Error::UnexpectedEof)
- };
- }
- macro_rules! err {
- ($e:ident($($stuff:expr),+)) => {
- Error::$e($($stuff,)+ tokens.span())
- }
- }
- macro_rules! yeet {
- ($e:ident($($stuff:expr),+)) => {
- return Err(Error::$e($($stuff,)+ tokens.span()))
- };
- }
- #[rustfmt::skip]
- macro_rules! nextline {
- () => {
- while let Some(tok) = tokens.next() && tok != Token::Newline { }
- };
- }
- macro_rules! take_int {
- ($tok:expr) => {
- match $tok {
- Token::Num(n) if n.fract() == 0.0 && n >= 0.0 => Ok(n as usize),
- t => Err(err!(ExpectedInt(t))),
- }
- };
- }
- macro_rules! take_memory {
- () => {{
- let container = take_ident!(tok!()?)?;
- let cell_n = take_int!(tok!()?)?;
- if cell_n > 126 || cell_n == 0 {
- yeet!(MemoryTooFar(cell_n));
- }
- match container {
- "bank" => executor.bank(cell_n),
- "cell" => executor.cell(cell_n),
- _ => yeet!(InvalidMemoryType(container)),
- }
- }};
- }
- macro_rules! addr {
- ($n:expr) => {{
- let n = $n;
- match mem
- .iter()
- .enumerate()
- .find(|(_, &v)| v == n)
- .map(|(i, _)| i)
- {
- // SAFETY: we tell it the size is mem.len(); i comes from mem, this is fine
- Some(i) => unsafe { LAddress::addr(i) },
- None => {
- mem.push(n);
- // SAFETY: see above
- unsafe { LAddress::addr(mem.len() - 1) }
- }
- }
- }};
- }
- macro_rules! take_ident {
- ($tok:expr) => {{
- let tok = $tok;
- tokstr!(tok).ok_or(err!(ExpectedIdent(tok)))
- }};
- }
- macro_rules! take_var {
- ($tok:expr) => {{
- let tok = $tok;
- if let Some(i) = tokstr!(tok) {
- Ok(addr!(i))
- } else {
- match tok {
- Token::Num(n) => Ok(LAddress::cnst(n)),
- Token::String(s) => Ok(LAddress::cnst(s)),
- t => Err(err!(ExpectedVar(t))),
- }
- }
- }};
- }
- macro_rules! take_numvar {
- ($tok:expr) => {{
- let tok = $tok;
- if let Some(i) = tokstr!(tok) {
- Ok(addr!(i))
- } else {
- match tok {
- Token::Num(n) => Ok(LAddress::cnst(n)),
- t => Err(err!(ExpectedNum(t))),
- }
- }
- }};
- }
- while let Some(token) = tokens.next() {
- match token {
- // # omg
- Token::Comment(_) => {
- executor.noop();
- }
- // label:
- Token::Ident(v) if v.ends_with(':') => {
- labels.push((&v[..v.len() - 1], executor.next()));
- }
- // print "5"
- Token::Print => {
- let val = take_var!(tok!()?)?;
- executor.add(Print { val });
- }
- // set x 4
- Token::Set => {
- let from = tok!()?;
- if from == Token::Counter {
- let to = take_numvar!(tok!()?)?;
- executor.add(DynJump { to, proglen: 0 });
- } else {
- let from = addr!(take_ident!(from)?);
- let to = take_var!(tok!()?)?;
- executor.add(Set { from, to });
- }
- }
- // stop
- Token::Stop => {
- executor.add(Stop {});
- }
- // jump start equal a b
- Token::Jump => {
- let tok = tok!()?;
- // label jump
- if let Some(i) = tokstr!(tok) {
- let span = tokens.span();
- let op = tok!()?;
- if op == Token::Always {
- executor.jmp();
- unfinished_jumps.push((UJump::Always, (i, span), executor.last()));
- } else {
- let op = op.try_into().map_err(|op| err!(ExpectedOp(op)))?;
- let a = take_var!(tok!()?)?;
- let b = take_var!(tok!()?)?;
- executor.jmp();
- unfinished_jumps.push((
- UJump::Sometimes { a, b, op },
- (i, span),
- executor.last(),
- ));
- }
- } else if let Ok(n) = take_int!(tok.clone()) {
- // SAFETY: we check at the end of the block that it is valid
- let to = unsafe { Instruction::new(n) };
- let op = tok!()?;
- if op == Token::Always {
- executor.add(AlwaysJump { to });
- } else {
- let op = op.try_into().map_err(|op| err!(ExpectedOp(op)))?;
- let a = take_var!(tok!()?)?;
- let b = take_var!(tok!()?)?;
- executor.add(Jump::new(op, to, a, b));
- }
- } else {
- yeet!(ExpectedJump(tok));
- };
- }
- // op add c 1 2
- Token::Op => {
- let op = tok!()?;
- if let Ok(op) = MathOp1::try_from(op.clone()) {
- // assigning to a var is useless but legal
- let out = take_numvar!(tok!()?)?;
- let x = take_numvar!(tok!()?)?;
- executor.add(Op1::new(op, x, out));
- } else if let Ok(op) = MathOp2::try_from(op.clone()) {
- let out = take_numvar!(tok!()?)?;
- let a = take_numvar!(tok!()?)?;
- let b = take_numvar!(tok!()?)?;
- executor.add(Op2::new(op, a, b, out));
- } else {
- yeet!(ExpectedOp(op));
- }
- }
- // write 5.0 bank1 4 (aka bank1[4] = 5.0)
- Token::Write => {
- let set = take_numvar!(tok!()?)?;
- let container = take_memory!();
- let index = take_numvar!(tok!()?)?;
- match index {
- LAddress::Const(LVar::Num(v)) => {
- if !container.fits(v.round() as usize) {
- yeet!(IndexOutOfBounds(v.round() as usize, container.size()));
- }
- }
- _ => {}
- }
-
- executor.add(Write {
- index,
- set,
- container,
- });
- }
- // read result cell1 4 (aka result = cell1[4])
- Token::Read => {
- let output = take_var!(tok!()?)?;
- let container = take_memory!();
- let index = take_numvar!(tok!()?)?;
- match index {
- LAddress::Const(LVar::Num(v)) => {
- if !container.fits(v.round() as usize) {
- yeet!(IndexOutOfBounds(v.round() as usize, container.size()));
- }
- }
- _ => {}
- }
- executor.add(Read {
- index,
- output,
- container,
- });
- }
- Token::Draw => {
- let dty = tok!()?;
- let Token::Ident(instr) = dty else {
- yeet!(ExpectedIdent(dty));
- };
- #[rustfmt::skip]
- macro_rules! four { ($a:expr) => { ($a, $a, $a, $a) }; }
- #[rustfmt::skip]
- macro_rules! six { ($a:expr) => { ($a, $a, $a, $a, $a, $a) }; }
- match instr {
- "clear" => {
- let (r, g, b, a) = four! { take_numvar!(tok!()?)? };
- executor.draw(Clear { r, g, b, a });
- }
- "color" => {
- let (r, g, b, a) = four! { take_numvar!(tok!()?)? };
- executor.draw(SetColorDyn { r, g, b, a });
- }
- "col" => {
- let col = take_int!(tok!()?)?;
- let r = (col & 0xff00_0000 >> 24) as u8;
- let g = (col & 0x00ff_0000 >> 16) as u8;
- let b = (col & 0x0000_ff00 >> 8) as u8;
- let a = (col & 0x0000_00ff) as u8;
- executor.draw(SetColorConst { r, g, b, a });
- }
- "stroke" => {
- let size = take_numvar!(tok!()?)?;
- executor.draw(SetStroke { size });
- }
- "line" => {
- let (x, y, x2, y2) = four! { take_numvar!(tok!()?)? };
- executor.draw(Line {
- point_a: (x, y),
- point_b: (x2, y2),
- });
- }
- "rect" => {
- let (x, y, width, height) = four! { take_numvar!(tok!()?)? };
- executor.draw(RectFilled {
- position: (x, y),
- width,
- height,
- });
- }
- "lineRect" => {
- let (x, y, width, height) = four! { take_numvar!(tok!()?)? };
- executor.draw(RectBordered {
- position: (x, y),
- width,
- height,
- });
- }
- "triangle" => {
- let (x, y, x2, y2, x3, y3) = six! { take_numvar!(tok!()?)? };
- executor.draw(Triangle {
- points: ((x, y), (x2, y2), (x3, y3)),
- });
- }
- // poly is TODO, image is WONTFIX
- i => yeet!(UnsupportedImageOp(i)),
- }
- }
- Token::DrawFlush => {
- let t = tok!();
- if let Ok(t) = t && t != Token::Newline {
- let screen = take_ident!(t)?;
- if screen != "display" {
- yeet!(InvalidDisplayType(screen));
- }
- let display = executor
- .display(take_int!(tok!()?)?)
- .map_err(|n| err!(NoDisplay(n)))?;
- executor.add(Flush { display });
- } else {
- executor.add(Flush::default())
- }
- }
- // end
- Token::End => {
- executor.add(End {});
- }
- // starting newline, simply skip. continue, so as not to to trigger the nextline!()
- Token::Newline => continue,
- // unknown instruction
- Token::Ident(i) => {
- let mut c = String::from(i);
- while let Some(tok) = tokens.next() && tok != Token::Newline {
- use std::fmt::Write;
- write!(c, " {tok}").expect("didnt know writing to a string could fail");
- }
- executor.code(c);
- // we take the newline here
- continue;
- }
- t => yeet!(ExpectedInstr(t)),
- }
- nextline!();
- }
-
- for (j, (l, s), i) in unfinished_jumps {
- let to = labels
- .iter()
- .find(|(v, _)| v == &l)
- .ok_or_else(|| Error::LabelNotFound(l, s))?
- .1;
- executor.program[i.get()] = UPInstr::Instr(match j {
- UJump::Always => Instr::from(AlwaysJump { to }),
- UJump::Sometimes { a, b, op } => Instr::from(Jump::new(op, to, a, b)),
- });
- }
-
- // check jump validity
- for i in &executor.program {
- if let UPInstr::Instr(Instr::Jump(Jump { to, .. }) | Instr::AlwaysJump(AlwaysJump { to })) =
- i
- {
- if !executor.valid(*to) {
- yeet!(InvalidJump(*to));
- }
- }
- }
-
- // set dynjumps
- let len = executor.program.len();
- for i in &mut executor.program {
- if let UPInstr::Instr(Instr::DynJump(DynJump { proglen, .. })) = i {
- *proglen = len;
- }
- }
-
- executor.mem(mem.len());
-
- Ok(())
-}
diff --git a/lemu/src/parser/error.rs b/lemu/src/parser/error.rs
new file mode 100644
index 0000000..b9ffb66
--- /dev/null
+++ b/lemu/src/parser/error.rs
@@ -0,0 +1,382 @@
+use super::tokstr;
+use crate::executor::Instruction;
+use crate::lexer::Token;
+use logos::Span;
+
+/// Errors returned when parsing fails.
+#[derive(thiserror::Error, Debug)]
+pub enum Error<'s> {
+ /// Occurs from eg `set x`. (needs a value to set to)
+ #[error("unexpected end of stream")]
+ UnexpectedEof,
+ /// Occurs from eg `op add\n...` (needs a variable)
+ #[error("expected variable, got {0:?}")]
+ ExpectedVar(Token<'s>, Span),
+ /// Occurs from eg `draw 4` (needs a ident of the type of drawing)
+ #[error("expected identifier, got {0:?}")]
+ ExpectedIdent(Token<'s>, Span),
+ /// Occurs from eg `jump house` (assuming house isnt a label).
+ #[error("expected jump target, got {0:?}")]
+ ExpectedJump(Token<'s>, Span),
+ /// Occurs from eg `op add "three" "four"`
+ #[error("expected number, got {0:?}")]
+ ExpectedNum(Token<'s>, Span),
+ /// Occurs from eg `op 4` (4 is not add/mul/...)
+ #[error("expected operator, got {0:?}")]
+ ExpectedOp(Token<'s>, Span),
+ /// Occurs from eg `write cell1 5.5` (5.5 is not int)
+ #[error("expected integer, got {0:?}")]
+ ExpectedInt(Token<'s>, Span),
+ /// Occurs from eg `control shootp building 4`
+ #[error("expected string, got {0:?}")]
+ ExpectedString(Token<'s>, Span),
+ /// Occurs from `status not_a_bool`
+ #[error("expected bool, got {0:?}")]
+ ExpectedBool(Token<'s>, Span),
+ /// Occurs from eg `4.0 add 5.0`
+ #[error("expected instruction, got {0:?}")]
+ ExpectedInstr(Token<'s>, Span),
+ /// Occurs from eg
+ /// ```text
+ /// lable:
+ /// jump label always
+ /// ```
+ /// (typo: lable not label)
+ #[error("unable to find label {0}")]
+ LabelNotFound(&'s str, Span),
+ /// Occurs from eg `jump 4910294029 always`
+ #[error("unable to jump to instruction {0:?}")]
+ InvalidJump(Instruction, Span),
+ /// Occurs from eg `read bank9223372036854775807 5` (only `126` banks can exist)
+ #[error("cannot get cell>{0}")]
+ MemoryTooFar(usize, Span),
+ /// Occurs from eg `read bank1 512`
+ #[error("index {0} out of bounds ({1} max)")]
+ IndexOutOfBounds(usize, usize, Span),
+ /// Occurs from `read register1`
+ #[error("unknown memory type {0:?}, expected (cell)|(bank)")]
+ InvalidMemoryType(&'s str, Span),
+ /// Occurs from `drawflush bank1`
+ #[error("unknown display type {0}, expected 'display'")]
+ InvalidDisplayType(&'s str, Span),
+ /// Occurs from `draw house` (or `draw image`, a valid but unsupported instruction here)
+ #[error("unknown image operation {0}")]
+ UnsupportedImageOp(&'s str, Span),
+ /// Occurs from `control what`
+ #[error("unknown control operation {0}")]
+ UnknownControlOp(&'s str, Span),
+ /// Occurs from `ucontrol kill`
+ #[error("unknown ucontrol operation {0}")]
+ UnknownUnitControlOp(&'s str, Span),
+ /// Occurs from `ulocate five`
+ #[error("unknown ulocate operation {0}")]
+ UnknownUnitLocateOp(&'s str, Span),
+ /// Occurs from `getblock core`
+ #[error("unknown getblock operation {0}")]
+ UnknownGetBlockOp(&'s str, Span),
+ /// Occurs from `setblock unit`
+ #[error("unknown setblock operation {0}")]
+ UnknownSetBlockOp(&'s str, Span),
+ /// Occurs from `setrule hello`
+ #[error("unknown rule {0}")]
+ UnknownRule(&'s str, Span),
+ /// Occurs from `cutscene begin`
+ #[error("unknown cutscene {0}")]
+ UnknownCutscene(&'s str, Span),
+ /// Occurs from `fetch hostages`
+ #[error("unknown fetch operation {0}")]
+ UnknownFetchOp(&'s str, Span),
+ #[error("couldnt get display #{0:?}.")]
+ /// Occurs from eg `display 50`.
+ ///
+ /// call `display` 50 more times to have more display options:
+ /// ```rust,ignore
+ /// executor
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display()
+ /// .display();
+ /// ```
+ NoDisplay(usize, Span),
+}
+
+impl Error<'_> {
+ /// Produces a [`Error`](lerr::Error) from this error.
+ #[cfg(feature = "diagnose")]
+ pub fn diagnose<'s>(&self, source: &'s str) -> lerr::Error<'s> {
+ use comat::{cformat as cmt, cformat_args};
+ use lerr::Error;
+
+ let error = cformat_args!("{bold_red}error{reset}");
+ let note = cformat_args!("{bold_blue}note{reset}");
+ let help = cformat_args!("{bold_green}help{reset}");
+ let mut e = Error::new(source);
+ macro_rules! msg {
+ ($ms:literal $(, $args:expr)* $(,)?) => {
+ e.message(cmt!($ms $(, $args)*))
+ };
+ }
+ macro_rules! op {
+ ($op:ident, $ops:expr) => {{
+ let mut out = String::from("{");
+ let mut ops = $ops.iter();
+ use std::fmt::Write;
+ write!(out, "{}", ops.next().unwrap()).unwrap();
+ for op in ops {
+ write!(out, ", {}", op).unwrap();
+ }
+ out.write_char('}').unwrap();
+ if let Some((mat,score)) = rust_fuzzy_search::fuzzy_search_best_n($op, $ops, 1).first() && *score > 0.5 {
+ e.note(cmt!("{help}: you may have meant {bold_green}{mat}{reset}"));
+ }
+ out
+ }}
+ }
+ match self {
+ Self::UnexpectedEof => {
+ msg!("{error}: wasnt able to finish read, got newline").label((
+ source.len()..source.len(),
+ cmt!("there was supposed to be another token here"),
+ ));
+ }
+ Self::ExpectedVar(t, s) => {
+ msg!("{error}: expected variable, got {:?}", t)
+ .label((s, cmt!("this was supposed to be a {blue}variable{reset} ({magenta}identifier{reset}, {magenta}number{reset}, or {magenta}string{reset})")));
+ }
+ Self::ExpectedIdent(_, s) => {
+ msg!("{error}: expected identifier").label((
+ s,
+ cmt!("this was supposed to be a {bold_blue}identifier{reset} (eg. {magenta}name{reset})"),
+ ));
+ }
+ Self::ExpectedJump(t, s) => {
+ msg!("{error}: expected jump target")
+ .label((s, cmt!("this was supposed to be a jump target")))
+ .note(
+ cmt!("{note}: a jump target is a {bold_blue}label{reset} ({magenta}ident{reset}, or {magenta}integer{reset})"),
+ );
+ if let Token::Num(n) = t {
+ e.note(cmt!(
+ "{help}: remove the fractional part: {bold_green}{n:.0}{reset}"
+ ));
+ }
+ }
+ Self::ExpectedNum(_, s) => {
+ msg!("{error}: expected number")
+ .label((s, cmt!("this was supposed to be a {bold_blue}number{reset} (eg. {magenta}3.14159{reset})")));
+ }
+ Self::ExpectedString(_, s) => {
+ msg!("{error}: expected string").label((s, cmt!(r#"this was supposed to be a {bold_blue}string{reset} (eg. {magenta}"a cool string"{reset})"#)));
+ }
+ Self::ExpectedBool(_, s) => {
+ msg!("{error}: expected bool").label((s, cmt!("this was supposed to be a {bold_blue}boolean{reset} (eg. {magenta}true{reset})")));
+ }
+ Self::ExpectedOp(t, s) => {
+ msg!("{error}: expected operator")
+ .label((s, cmt!("this was supposed to be a {bold_blue}operator{reset} (eg. {magenta}equal{reset})")));
+ if let Some(i) = tokstr!(*t) && let Some((mat,score)) = rust_fuzzy_search::fuzzy_search_best_n(i, crate::instructions::OPS, 1).first() && *score > 0.5 {
+ e.note(cmt!("{help}: maybe you meant {bold_green}{mat}{reset}"));
+ }
+ }
+ Self::ExpectedInt(t, s) => {
+ msg!("{error}: expected integer")
+ .label((s, cmt!("this was supposed to be a {bold_blue}integer{reset} (eg. {magenta}4{reset})")));
+ if let Token::Num(n) = t {
+ e.note(cmt!(
+ "{help}: remove the mantissa: {bold_green}{n:.0}{reset}"
+ ));
+ }
+ }
+ Self::ExpectedInstr(t, s) => {
+ msg!("{error}: expected instruction")
+ .label((s, cmt!("this was supposed to be a {bold_blue}instruction{reset} (eg. {magenta}print{reset})")));
+ if let Some(i) = tokstr!(*t) && let Some((mat,score)) = rust_fuzzy_search::fuzzy_search_best_n(i, &[
+ "getlink", "read", "write", "set", "op", "end", "drawflush", "draw", "print", "packcolor", "jump", "stop", "printflush", "control", "radar", "sensor", "wait", "lookup", "packcolor", "ubind", "ucontrol", "uradar", "ulocate", "getblock", "setblock", "spawn", "status", "spawnwave", "setrule", "cutscene", "explosion", "setrate", "fetch", "getflag", "setflag", "setprop", "effect"
+ ], 1).first() && *score > 0.5 {
+ e.note(cmt!("{help}: maybe you meant {mat}"));
+ }
+ }
+ Self::LabelNotFound(_, s) => {
+ msg!("{error}: label not found").label((
+ s,
+ cmt!("this was supposed to be a (existing) {bold_blue}label{reset}"),
+ )).note(cmt!("{help}: define a label with {yellow}`label_name:`{reset}, then you can {yellow}`jump label_name`{reset}."));
+ }
+ Self::InvalidJump(target, s) => {
+ msg!("{error}: invalid jump")
+ .label((
+ s,
+ cmt!(
+ "line#{bold_red}{}{reset} is not in the program",
+ target.get()
+ ),
+ ))
+ .note(cmt!(
+ "{help}: there are {bold_blue}{}{reset} available lines",
+ source.lines().count()
+ ));
+ }
+ Self::MemoryTooFar(b, s) => {
+ msg!("{error}: invalid memory cell/bank")
+ .label((s, cmt!("cant get cell/bank#{bold_red}{b}{reset}")))
+ .note(cmt!(
+ "{note}: only {blue}126{reset} cells/banks are allowed"
+ ));
+ }
+ Self::InvalidMemoryType(t, s) => {
+ msg!("{error}: invalid memory type {bold_red}{}{reset}", t)
+ .label((s, "here"))
+ .note(cmt!("{note}: only banks/cells are allowed"));
+ }
+ Self::InvalidDisplayType(disp, s) => {
+ msg!("{error}: invalid display type {bold_red}{}{reset}", disp)
+ .label((s, "here"))
+ .note(cmt!("{help}: change this to {bold_green}'display'{reset}"));
+ }
+ Self::UnknownControlOp(op, s) => {
+ let available = op!(op, &["enabled", "shoot", "shootp", "config", "color"]);
+ msg!("{error}: invalid control op {}", op)
+ .label((s, cmt!("must be one of {available}",)));
+ }
+ Self::UnknownUnitControlOp(op, s) => {
+ let available = op!(
+ op,
+ &[
+ "idle", "stop", "move", "approach", "boost", "pathfind", "target",
+ "targetp", "itemDrop", "itemTake", "payDrop", "payEnter", "mine", "flag",
+ "build", "getBlock", "within"
+ ]
+ );
+ msg!("{error}: unknown unit control op {}", op)
+ .label((s, cmt!("must be one of {available}",)));
+ }
+ Self::UnknownSetBlockOp(op, s) => {
+ let available = op!(op, &["floor", "ore", "block"]);
+ msg!("{error}: unknown set block op {}", op)
+ .label((s, cmt!("must be one of {available}")));
+ }
+ Self::UnknownUnitLocateOp(op, s) => {
+ let a = op!(op, &["ore", "spawn", "damaged", "building"]);
+ msg!("{error}: unkown unit locate op {}", op)
+ .label((s, cmt!("must be one of {a}",)));
+ }
+ Self::UnknownGetBlockOp(op, s) => {
+ let a = op!(op, &["floor", "ore", "block", "building"]);
+ msg!("{error}: unknown getblock op {}", op).label((s, cmt!("must be one of {a}",)));
+ }
+ Self::UnsupportedImageOp(op, s) => {
+ let a = op!(op, crate::instructions::draw::INSTRS);
+ msg!("{error}: invalid image op {}", op).label((s, cmt!("must be one of {a}")));
+ }
+ Self::UnknownRule(rule, s) => {
+ let a = op!(
+ rule,
+ &[
+ "currentWaveTime",
+ "waveTimer",
+ "waves",
+ "wave",
+ "waveSpacing",
+ "waveSending",
+ "attackMode",
+ "enemyCoreBuildRadius",
+ "dropZoneRadius",
+ "unitCap",
+ "wave",
+ "lighting",
+ "ambientLight",
+ "solarMultiplier",
+ "mapArea",
+ "buildSpeed",
+ "unitHealth",
+ "unitBuildSpeed",
+ "unitCost",
+ "unitDamage",
+ "blockHealth",
+ "blockDamage",
+ "rtsMinWeight",
+ "rtsMinSquad",
+ ]
+ );
+ msg!("{error}: invalid rule {}", rule).label((s, cmt!("must be one of {a}")));
+ }
+ Self::UnknownCutscene(op, s) => {
+ let a = op!(op, &["pan", "zoom", "stop"]);
+ msg!("{error}: invalid cutscene type {}", op)
+ .label((s, cmt!("must be one of {a}")));
+ }
+ Self::UnknownFetchOp(op, s) => {
+ let a = op!(
+ op,
+ &[
+ "buildCount",
+ "coreCount",
+ "playerCount",
+ "unitCount",
+ "build",
+ "core",
+ "player",
+ "unit"
+ ]
+ );
+ msg!("{error}: invalid op {}", op).label((s, cmt!("must be one of {a}")));
+ }
+ Self::NoDisplay(disp, s) => {
+ msg!("{error}: no display allocated")
+ .label((s, cmt!("display#{bold_red}{disp}{reset} has not been created")))
+ .note(cmt!("{note}: it is impossible for me to dynamically allocate displays, as {blue}'display1'{reset} could be large or small"));
+ }
+ Self::IndexOutOfBounds(index, size, s) => {
+ msg!("{error}: {bold_red}index{reset} {} out of bounds", index)
+ .label((s, cmt!("memory has only {magenta}{size}{reset} elements")));
+ }
+ };
+ e
+ }
+}
diff --git a/lemu/src/parser/mod.rs b/lemu/src/parser/mod.rs
new file mode 100644
index 0000000..a6a7aae
--- /dev/null
+++ b/lemu/src/parser/mod.rs
@@ -0,0 +1,743 @@
+use std::io::Write as Wr;
+
+mod error;
+pub use error::Error;
+
+use super::{
+ executor::{ExecutorBuilderInternal, Instruction, UPInstr},
+ instructions::{
+ draw::{
+ Clear, Flush, Line, RectBordered, RectFilled, SetColorConst, SetColorDyn, SetStroke,
+ Triangle,
+ },
+ io::{Print, Read, Write},
+ AlwaysJump, ConditionOp, DynJump, End, Instr, Jump, MathOp1, MathOp2, Op1, Op2, Set, Stop,
+ },
+ lexer::{Lexer, Token},
+ memory::{LAddress, LVar},
+};
+
+macro_rules! tokstr {
+ ($tok:expr) => {
+ match $tok {
+ Token::Ident(i) => Some(i),
+ Token::Null => Some("null"),
+ Token::Read => Some("read"),
+ Token::Write => Some("write"),
+ Token::Set => Some("set"),
+ Token::Op => Some("op"),
+ Token::End => Some("end"),
+ Token::Draw => Some("draw"),
+ Token::DrawFlush => Some("drawflush"),
+ Token::Print => Some("print"),
+ Token::Jump => Some("jump"),
+ Token::Stop => Some("stop"),
+ Token::Counter => Some("@counter"),
+ Token::Equal => Some("equal"),
+ Token::NotEqual => Some("notEqual"),
+ Token::LessThan => Some("lessThan"),
+ Token::LessThanEq => Some("lessThanEq"),
+ Token::GreaterThan => Some("greaterThan"),
+ Token::GreaterThanEq => Some("greaterThanEq"),
+ Token::StrictEqual => Some("strictEqual"),
+ Token::Always => Some("always"),
+ Token::Add => Some("add"),
+ Token::Sub => Some("sub"),
+ Token::Mul => Some("mul"),
+ Token::Div => Some("div"),
+ Token::IDiv => Some("idiv"),
+ Token::Mod => Some("mod"),
+ Token::Pow => Some("pow"),
+ Token::And => Some("land"),
+ Token::Not => Some("not"),
+ Token::ShiftLeft => Some("shl"),
+ Token::ShiftRight => Some("shr"),
+ Token::BitOr => Some("or"),
+ Token::BitAnd => Some("and"),
+ Token::ExclusiveOr => Some("xor"),
+ Token::Max => Some("max"),
+ Token::Min => Some("min"),
+ Token::Angle => Some("angle"),
+ Token::AngleDiff => Some("angleDiff"),
+ Token::Len => Some("len"),
+ Token::Noise => Some("noise"),
+ Token::Abs => Some("abs"),
+ Token::Log => Some("log"),
+ Token::Log10 => Some("log10"),
+ Token::Floor => Some("floor"),
+ Token::Ceil => Some("ceil"),
+ Token::Sqrt => Some("sqrt"),
+ Token::Rand => Some("rand"),
+ Token::Sin => Some("sin"),
+ Token::Cos => Some("cos"),
+ Token::Tan => Some("tan"),
+ Token::ASin => Some("asin"),
+ Token::ACos => Some("acos"),
+ Token::ATan => Some("atan"),
+ _ => None,
+ }
+ };
+}
+use tokstr;
+
+#[derive(Debug)]
+enum UJump<'v> {
+ Sometimes {
+ a: LAddress<'v>,
+ b: LAddress<'v>,
+ op: ConditionOp,
+ },
+ Always,
+}
+
+pub fn parse<'source, W: Wr>(
+ mut tokens: Lexer<'source>,
+ executor: &mut ExecutorBuilderInternal<'source, W>,
+) -> Result<(), Error<'source>> {
+ let mut mem = Vec::new(); // maps &str to usize
+ // maps "start" to 0
+ let mut labels = Vec::new();
+ let mut unfinished_jumps = Vec::new();
+ macro_rules! tok {
+ () => {
+ tokens.next().ok_or(Error::UnexpectedEof)
+ };
+ }
+ macro_rules! err {
+ ($e:ident($($stuff:expr),+)) => {
+ Error::$e($($stuff,)+ tokens.span())
+ }
+ }
+ macro_rules! yeet {
+ ($e:ident($($stuff:expr),+)) => {
+ return Err(Error::$e($($stuff,)+ tokens.span()))
+ };
+ }
+ #[rustfmt::skip]
+ macro_rules! nextline {
+ () => {
+ while let Some(tok) = tokens.next() && tok != Token::Newline { }
+ };
+ }
+ macro_rules! take_int {
+ ($tok:expr) => {
+ match $tok {
+ Token::Num(n) if n.fract() == 0.0 && n >= 0.0 => Ok(n as usize),
+ t => Err(err!(ExpectedInt(t))),
+ }
+ };
+ }
+ macro_rules! take_memory {
+ () => {{
+ let t = tok!()?;
+ let container = take_ident!(t.clone())?;
+ let mut out = String::new();
+ for ch in container.bytes() {
+ if matches!(ch, b'0'..=b'9') {
+ out.push(ch as char);
+ }
+ }
+ let container = &container[..container.len() - out.len()];
+ let n_span = tokens.span().start + container.len()..tokens.span().end;
+ let cell_n = out
+ .parse::<usize>()
+ .map_err(|_| Error::ExpectedInt(t, n_span.clone()))?;
+ if cell_n > 126 || cell_n == 0 {
+ return Err(Error::MemoryTooFar(cell_n, n_span));
+ }
+ match container {
+ "bank" => executor.bank(cell_n),
+ "cell" => executor.cell(cell_n),
+ _ => {
+ return Err(Error::InvalidMemoryType(
+ container,
+ tokens.span().start..tokens.span().end - out.len(),
+ ))
+ }
+ }
+ }};
+ }
+ macro_rules! addr {
+ ($n:expr) => {{
+ let n = $n;
+ match mem
+ .iter()
+ .enumerate()
+ .find(|(_, &v)| v == n)
+ .map(|(i, _)| i)
+ {
+ // SAFETY: we tell it the size is mem.len(); i comes from mem, this is fine
+ Some(i) => unsafe { LAddress::addr(i) },
+ None => {
+ mem.push(n);
+ // SAFETY: see above
+ unsafe { LAddress::addr(mem.len() - 1) }
+ }
+ }
+ }};
+ }
+ macro_rules! take_ident {
+ ($tok:expr) => {{
+ let tok = $tok;
+ tokstr!(tok).ok_or(err!(ExpectedIdent(tok)))
+ }};
+ }
+ macro_rules! take_var {
+ ($tok:expr) => {{
+ let tok = $tok;
+ if let Some(i) = tokstr!(tok) {
+ Ok(addr!(i))
+ } else {
+ match tok {
+ Token::Num(n) => Ok(LAddress::cnst(n)),
+ Token::String(s) => Ok(LAddress::cnst(s)),
+ t => Err(err!(ExpectedVar(t))),
+ }
+ }
+ }};
+ }
+ macro_rules! take_numvar {
+ ($tok:expr) => {{
+ let tok = $tok;
+ if let Some(i) = tokstr!(tok) {
+ Ok(addr!(i))
+ } else {
+ match tok {
+ Token::Num(n) => Ok(LAddress::cnst(n)),
+ t => Err(err!(ExpectedNum(t))),
+ }
+ }
+ }};
+ }
+ while let Some(token) = tokens.next() {
+ match token {
+ // # omg
+ Token::Comment(_) => {
+ executor.noop();
+ }
+ // label:
+ Token::Ident(v) if v.ends_with(':') => {
+ labels.push((&v[..v.len() - 1], executor.next()));
+ }
+ // print "5"
+ Token::Print => {
+ let val = take_var!(tok!()?)?;
+ executor.add(Print { val });
+ }
+ // set x 4
+ Token::Set => {
+ let from = tok!()?;
+ if from == Token::Counter {
+ let to = take_numvar!(tok!()?)?;
+ executor.add(DynJump { to, proglen: 0 });
+ } else {
+ let from = addr!(take_ident!(from)?);
+ let to = take_var!(tok!()?)?;
+ executor.add(Set { from, to });
+ }
+ }
+ // stop
+ Token::Stop => {
+ executor.add(Stop {});
+ }
+ // jump start equal a b
+ Token::Jump => {
+ let tok = tok!()?;
+ // label jump
+ if let Some(i) = tokstr!(tok) {
+ let span = tokens.span();
+ let op = tok!()?;
+ if op == Token::Always {
+ executor.jmp();
+ unfinished_jumps.push((UJump::Always, (i, span), executor.last()));
+ } else {
+ let op = op.try_into().map_err(|op| err!(ExpectedOp(op)))?;
+ let a = take_var!(tok!()?)?;
+ let b = take_var!(tok!()?)?;
+ executor.jmp();
+ unfinished_jumps.push((
+ UJump::Sometimes { a, b, op },
+ (i, span),
+ executor.last(),
+ ));
+ }
+ } else if let Ok(n) = take_int!(tok.clone()) {
+ // SAFETY: we check at the end of the block that it is valid
+ let to = unsafe { Instruction::new(n) };
+ let op = tok!()?;
+ if op == Token::Always {
+ executor.add(AlwaysJump { to });
+ } else {
+ let op = op.try_into().map_err(|op| err!(ExpectedOp(op)))?;
+ let a = take_var!(tok!()?)?;
+ let b = take_var!(tok!()?)?;
+ executor.add(Jump::new(op, to, a, b));
+ }
+ } else {
+ yeet!(ExpectedJump(tok));
+ };
+ }
+ // op add c 1 2
+ Token::Op => {
+ let op = tok!()?;
+ if let Ok(op) = MathOp1::try_from(op.clone()) {
+ // assigning to a var is useless but legal
+ let out = take_numvar!(tok!()?)?;
+ let x = take_numvar!(tok!()?)?;
+ executor.add(Op1::new(op, x, out));
+ } else if let Ok(op) = MathOp2::try_from(op.clone()) {
+ let out = take_numvar!(tok!()?)?;
+ let a = take_numvar!(tok!()?)?;
+ let b = take_numvar!(tok!()?)?;
+ executor.add(Op2::new(op, a, b, out));
+ } else {
+ yeet!(ExpectedOp(op));
+ }
+ }
+ // write 5.0 bank1 4 (aka bank1[4] = 5.0)
+ Token::Write => {
+ let set = take_numvar!(tok!()?)?;
+ let container = take_memory!();
+ let index = take_numvar!(tok!()?)?;
+ if let LAddress::Const(LVar::Num(v)) = index && !container.fits(v.round() as usize) {
+ yeet!(IndexOutOfBounds(v.round() as usize, container.size()));
+ }
+ executor.add(Write {
+ index,
+ set,
+ container,
+ });
+ }
+ // read result cell1 4 (aka result = cell1[4])
+ Token::Read => {
+ let output = take_var!(tok!()?)?;
+ let container = take_memory!();
+ let index = take_numvar!(tok!()?)?;
+ if let LAddress::Const(LVar::Num(v)) = index && !container.fits(v.round() as usize) {
+ yeet!(IndexOutOfBounds(v.round() as usize, container.size()));
+ }
+ executor.add(Read {
+ index,
+ output,
+ container,
+ });
+ }
+ Token::Draw => {
+ let dty = tok!()?;
+ let Token::Ident(instr) = dty else {
+ yeet!(ExpectedIdent(dty));
+ };
+ #[rustfmt::skip]
+ macro_rules! four { ($a:expr) => { ($a, $a, $a, $a) }; }
+ #[rustfmt::skip]
+ macro_rules! six { ($a:expr) => { ($a, $a, $a, $a, $a, $a) }; }
+ match instr {
+ "clear" => {
+ let (r, g, b, a) = four! { take_numvar!(tok!()?)? };
+ executor.draw(Clear { r, g, b, a });
+ }
+ "color" => {
+ let (r, g, b, a) = four! { take_numvar!(tok!()?)? };
+ executor.draw(SetColorDyn { r, g, b, a });
+ }
+ "col" => {
+ let col = take_int!(tok!()?)?;
+ let r = (col & 0xff00_0000 >> 24) as u8;
+ let g = (col & 0x00ff_0000 >> 16) as u8;
+ let b = (col & 0x0000_ff00 >> 8) as u8;
+ let a = (col & 0x0000_00ff) as u8;
+ executor.draw(SetColorConst { r, g, b, a });
+ }
+ "stroke" => {
+ let size = take_numvar!(tok!()?)?;
+ executor.draw(SetStroke { size });
+ }
+ "line" => {
+ let (x, y, x2, y2) = four! { take_numvar!(tok!()?)? };
+ executor.draw(Line {
+ point_a: (x, y),
+ point_b: (x2, y2),
+ });
+ }
+ "rect" => {
+ let (x, y, width, height) = four! { take_numvar!(tok!()?)? };
+ executor.draw(RectFilled {
+ position: (x, y),
+ width,
+ height,
+ });
+ }
+ "lineRect" => {
+ let (x, y, width, height) = four! { take_numvar!(tok!()?)? };
+ executor.draw(RectBordered {
+ position: (x, y),
+ width,
+ height,
+ });
+ }
+ "triangle" => {
+ let (x, y, x2, y2, x3, y3) = six! { take_numvar!(tok!()?)? };
+ executor.draw(Triangle {
+ points: ((x, y), (x2, y2), (x3, y3)),
+ });
+ }
+ // poly is TODO, image is WONTFIX
+ i => yeet!(UnsupportedImageOp(i)),
+ }
+ }
+ Token::DrawFlush => {
+ let t = tok!();
+ if let Ok(t) = t && t != Token::Newline {
+ let screen = take_ident!(t)?;
+ if screen != "display" {
+ yeet!(InvalidDisplayType(screen));
+ }
+ let display = executor
+ .display(take_int!(tok!()?)?)
+ .map_err(|n| err!(NoDisplay(n)))?;
+ executor.add(Flush { display });
+ } else {
+ executor.add(Flush::default())
+ }
+ }
+ // end
+ Token::End => {
+ executor.add(End {});
+ }
+ // starting newline, simply skip. continue, so as not to to trigger the nextline!()
+ Token::Newline => continue,
+ // unknown instruction
+ Token::Ident(i) => {
+ macro_rules! all {
+ ($is:expr) => {
+ |b| {
+ for var in &mut *b {
+ *var = $is;
+ }
+ Ok(())
+ }
+ };
+ }
+ macro_rules! take {
+ (@ $b:ident $n:ident skip) => {
+ $b[$n]=tok!()?;
+
+ };
+ (@ $b:ident $n:ident skip, $($rest:tt)*) => {
+ $b[$n] = tok!()?;$n+=1; take!(@ $b $n $($rest)*);
+ };
+ (@ $b:ident $n:ident $v: expr) => {
+ $b[$n] = $v;
+ };
+ (@ $b:ident $n:ident $v: expr, $($rest:tt)*) => {
+ $b[$n] = $v;
+ $n += 1;
+ take!(@ $b $n $($rest)*);
+ };
+ [$($this:tt)*] => {
+ |b| {
+ let mut n = 0;
+ take!(@ b n $($this)*);
+ Ok(())
+ }
+ };
+ }
+ macro_rules! num {
+ () => {{
+ let tok = tok!()?;
+ if let Some(i) = tokstr!(tok) {
+ Token::Ident(i)
+ } else {
+ match tok {
+ Token::Num(n) => Token::Num(n),
+ t => yeet!(ExpectedNum(t)),
+ }
+ }
+ }};
+ }
+ macro_rules! bool {
+ () => {{
+ let tok = tok!()?;
+ if let Some(i) = tokstr!(tok) {
+ Token::Ident(i)
+ } else {
+ match tok {
+ Token::Num(n) => Token::Num(n),
+ t => yeet!(ExpectedBool(t)),
+ }
+ }
+ }};
+ }
+ macro_rules! ident {
+ () => {
+ take_ident!(tok!()?).map(|v| Token::Ident(v))?
+ };
+ }
+ #[rustfmt::skip]
+ macro_rules! build { () => { ident!() }}
+ macro_rules! str {
+ () => {
+ match tok!()? {
+ Token::String(s) => Token::String(s),
+ t => yeet!(ExpectedString(t)),
+ }
+ };
+ }
+ macro_rules! var {
+ () => {{
+ let tok = tok!()?;
+ if let Some(i) = tokstr!(tok) {
+ Token::Ident(i)
+ } else {
+ match tok {
+ Token::Num(n) => Token::Num(n),
+ Token::String(s) => Token::String(s),
+ t => yeet!(ExpectedVar(t)),
+ }
+ }
+ }};
+ }
+ macro_rules! instr {
+ (($argc:literal) => $block:expr) => {{
+ let mut v: Box<[_; $argc + 1]> =
+ Box::new(std::array::from_fn(|_| Token::Newline));
+ v[0] = Token::Ident(i);
+ const fn castor<
+ 'source,
+ F: FnMut(&mut [Token<'source>; $argc]) -> Result<(), Error<'source>>,
+ >(
+ f: F,
+ ) -> F {
+ f
+ }
+ castor($block)((&mut v[1..]).try_into().unwrap())?;
+ executor.code(v as Box<[Token<'source>]>);
+ nextline!();
+ continue;
+ }};
+ }
+ macro_rules! minstr {
+ ($($sub:ident($argc:literal) => $block:expr)+ => $err:expr $(,)?) => {{
+ let t = tok!()?;
+ let idnt = take_ident!(t.clone())?;
+ $(if idnt == stringify!($sub) {
+ let mut v: Box<[_; $argc + 2]> = Box::new(std::array::from_fn(|_| Token::Newline));
+ v[0]=Token::Ident(i);
+ v[1]=Token::Ident(stringify!($sub));
+ const fn castor<'source, F: FnMut(&mut [Token<'source>; $argc]) -> Result<(), Error<'source>>>(f: F) -> F { f }
+ castor($block)((&mut v[2..]).try_into().unwrap())?;
+ executor.code(v);
+ nextline!();
+ continue;
+ })+
+ return Err($err(idnt, t));
+ }};
+ }
+ match i {
+ "printflush" => instr! {
+ (1) => |b| {
+ let t = tok!()?;
+ if let Some(t) = tokstr!(t) {
+ b[0] = Token::Ident(t);
+ } else if t == Token::Null {
+ b[0] = Token::Null;
+ } else {
+ b[0] = Token::Ident("message1");
+ }
+ Ok(())
+ }
+ },
+ "getlink" => instr! { (2) => take![var!(), num!()] },
+ "control" => minstr! {
+ enabled(2) => take![build!(), bool!()]
+ shoot(4) => take![build!(), num!(), num!(), bool!()]
+ shootp(3) => take![build!(), str!(), bool!()]
+ config(2) => take![build!(), tok!()?]
+ color(4) => take![build!(), num!(), num!(), num!()]
+ => |t, _| { err!(UnknownControlOp(t)) },
+ },
+ "radar" => {
+ instr! { (7) => take![ident!(), ident!(), ident!(), ident!(), build!(), num!(), var!()] }
+ }
+ "sensor" => instr! { (3) => take![var!(), tok!()?, tok!()?] },
+ "wait" => instr! { (1) => all!(num!()) },
+ "lookup" => instr! { (3) => take![ident!(), var!(), num!()] },
+ "packcolor" => instr! { (4) => all!(num!()) },
+ "ubind" => instr! { (1) => |b| {
+ let t = tok!()?;
+ if matches!(t, Token::String(_) | Token::Null) {
+ b[0] = t;
+ } else {
+ yeet!(ExpectedString(t));
+ };
+ Ok(())
+ } },
+ "ucontrol" => minstr! {
+ idle(0) => |_| Ok(())
+ stop(0) => |_| Ok(())
+ move(2) => all!(num!())
+ approach(3) => all!(num!())
+ boost(1) => all!(bool!())
+ pathfind(2) => all!(num!())
+ target(3) => take![num!(), num!(), bool!()]
+ targetp(2) => take![str!(), bool!()]
+ itemDrop(2) => take![build!(), num!()]
+ itemTake(3) => take![build!(), str!(), num!()]
+ payDrop(0) => |_| Ok(())
+ payTake(1) => all!(bool!())
+ payEnter(0) => |_| Ok(())
+ mine(2) => all!(num!())
+ flag(1) => all!(num!())
+ build(5) => take![num!(), num!(), build!(), num!(), tok!()?]
+ getBlock(4) => take![num!(), num!(), build!(), build!()]
+ within(4) => take![num!(), num!(), num!(), var!()]
+ => |t, _| { err!(UnknownUnitControlOp(t)) }
+ },
+ "uradar" => {
+ instr! { (7) => take![ident!(), ident!(), ident!(), ident!(), build!(), num!(), var!()] }
+ }
+ "ulocate" => {
+ minstr! {
+ building(7) => take![build!(), bool!(), skip, num!(), num!(), bool!(), build!()]
+ spawn(6) => take![skip, skip, skip, num!(), num!(), bool!()]
+ damaged(7) => take![skip, skip, skip, num!(), num!(), var!(), var!()]
+ ore(6) => take![skip, skip, tok!()?, num!(), num!(), bool!()]
+ => |t, _| { err!(UnknownUnitLocateOp(t)) }
+ }
+ }
+ "getblock" => minstr! {
+ floor(3) => take![tok!()?, num!(), num!()]
+ ore(3) => take![tok!()?, num!(), num!()]
+ block(3) => take![tok!()?, num!(), num!()]
+ building(3) => take![tok!()?, num!(), num!()]
+ => |t, _| { err!(UnknownGetBlockOp(t)) }
+ },
+ "setblock" => minstr! {
+ floor(3) => take![num!(), num!(), str!()]
+ ore(3) => take![num!(), num!(), str!()]
+ block(3) => take![num!(), num!(), str!(), str!(), num!()]
+ => |t, _| { err!(UnknownSetBlockOp(t)) }
+ },
+ "spawn" => {
+ instr! { (6) => take![str!(), num!(), num!(), num!(), str!(), var!()] }
+ }
+ "status" => minstr! {
+ true(3) => take![ident!(), var!(), num!()]
+ false(2) => take![ident!(), var!()]
+ => |_, t| { err!(ExpectedBool(t)) }
+ },
+ "spawnwave" => instr! { (3) => take![num!(), num!(), bool!()] },
+ "setrule" => {
+ #[rustfmt::skip]
+ macro_rules! rule { () => { take![num!(), str!()] }}
+ minstr! {
+ currentWaveTime(1) => all!(num!())
+ waveTimer(1) => all!(num!())
+ waves(1) => all!(num!())
+ wave(1) => all!(num!())
+ waveSpacing(1) => all!(num!())
+ waveSending(1) => all!(num!())
+ attackMode(1) => all!(num!())
+ enemyCoreBuildRadius(1) => all!(num!())
+ dropZoneRadius(1) => all!(num!())
+ unitCap(1) => all!(num!())
+ wave(1) => all!(num!())
+ mapArea(4) => |b| {
+ tok!()?;
+ for var in &mut *b {
+ *var = num!();
+ }
+ Ok(())
+ }
+ lighting(1) => all!(num!())
+ ambientLight(1) => all!(num!())
+ solarMultiplier(1) => all!(num!())
+ buildSpeed(2) => rule!{}
+ unitHealth(2) => rule!{}
+ unitBuildSpeed(2) => rule!{}
+ unitCost(2) => rule!{}
+ unitDamage(2) => rule!{}
+ blockHealth(2) => rule!{}
+ blockDamage(2) => rule!{}
+ rtsMinWeight(2) => rule!{}
+ rtsMinSquad(2) => rule!{}
+ => |t, _| { err!(UnknownRule(t)) }
+ }
+ }
+ "cutscene" => minstr! {
+ pan(3) => all!(num!())
+ zoom(1) => all!(num!())
+ stop(0) => |_| Ok(())
+ => |t, _| { err!(UnknownCutscene(t)) }
+ },
+ "explosion" => {
+ instr! { (8) => take![str!(), num!(), num!(), num!(), bool!(), bool!(), bool!()] }
+ }
+ "setrate" => instr! { (1) => all!(num!()) },
+ "fetch" => minstr! {
+ buildCount(4) => take![var!(), str!(), num!(), str!()] // useless 0
+ unitCount(4) => take![var!(), str!()]
+ playerCount(4) => take![var!(), str!()]
+ coreCount(4) => take![var!(), str!()]
+ unit(3) => take![var!(), str!(), num!()]
+ player(3) => take![var!(), str!(), num!()]
+ core(3) => take![var!(), str!(), num!()]
+ build(3) => take![var!(), str!(), num!()]
+ => |t, _| { err!(UnknownFetchOp(t)) }
+ },
+ "getflag" => instr! { (2) => take![bool!(), str!()] },
+ "setflag" => instr! { (2) => take![str!(), bool!()] },
+ "setprop" => instr! { (3) => take![str!(), var!(), tok!()?] },
+ "effect" => {
+ let mut v = Vec::with_capacity(6);
+ v.push(Token::Ident("effect"));
+ while let Some(tok) = tokens.next() && tok != Token::Newline {
+ v.push(tok);
+ }
+ executor.code(v.into_boxed_slice());
+ // we take the newline here
+ continue;
+ }
+ t => yeet!(ExpectedInstr(Token::Ident(t))),
+ }
+ }
+ t => yeet!(ExpectedInstr(t)),
+ }
+ nextline!();
+ }
+
+ for (j, (l, s), i) in unfinished_jumps {
+ let to = labels
+ .iter()
+ .find(|(v, _)| v == &l)
+ .ok_or_else(|| Error::LabelNotFound(l, s))?
+ .1;
+ executor.program[i.get()] = UPInstr::Instr(match j {
+ UJump::Always => Instr::from(AlwaysJump { to }),
+ UJump::Sometimes { a, b, op } => Instr::from(Jump::new(op, to, a, b)),
+ });
+ }
+
+ // check jump validity
+ for i in &executor.program {
+ if let UPInstr::Instr(Instr::Jump(Jump { to, .. }) | Instr::AlwaysJump(AlwaysJump { to })) =
+ i
+ {
+ if !executor.valid(*to) {
+ yeet!(InvalidJump(*to));
+ }
+ }
+ }
+
+ // set dynjumps
+ let len = executor.program.len();
+ for i in &mut executor.program {
+ if let UPInstr::Instr(Instr::DynJump(DynJump { proglen, .. })) = i {
+ *proglen = len;
+ }
+ }
+
+ executor.mem(mem.len());
+
+ Ok(())
+}