mindustry logic execution, map- and schematic- parsing and rendering
| -rw-r--r-- | lemu/Cargo.toml | 2 | ||||
| -rw-r--r-- | lemu/src/celliterate.mlog | 2 | ||||
| -rw-r--r-- | lemu/src/executor/builder.rs | 20 | ||||
| -rw-r--r-- | lemu/src/executor/mod.rs | 10 | ||||
| -rw-r--r-- | lemu/src/instructions/mod.rs | 15 | ||||
| -rw-r--r-- | lemu/src/lexer.rs | 13 | ||||
| -rw-r--r-- | lemu/src/lib.rs | 2 | ||||
| -rw-r--r-- | lemu/src/parser.rs | 699 | ||||
| -rw-r--r-- | lemu/src/parser/error.rs | 382 | ||||
| -rw-r--r-- | lemu/src/parser/mod.rs | 743 |
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(()) +} |