mindustry logic execution, map- and schematic- parsing and rendering
Diffstat (limited to 'lemu/src/parser/mod.rs')
-rw-r--r--lemu/src/parser/mod.rs743
1 files changed, 743 insertions, 0 deletions
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(())
+}