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), /// We have a limit of [`u32::MAX`] variables. #[error("too many variables")] TooManyVariables(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"))); } Self::TooManyVariables(s) => { msg!("{error}: {bold_red}too many variables{reset}. ") .label((s, cmt!("we only have 65536 variable slots"))) .note(cmt!("consider not using variables")); } }; e } }