diff --git a/CONFIGURING.md b/CONFIGURING.md index 34a59dc0..a868fafa 100644 --- a/CONFIGURING.md +++ b/CONFIGURING.md @@ -50,6 +50,7 @@ Raised by DreamChecker: * `control_condition_static` - Raised on a control condition such as `if`/`while` having a static condition such as `1` or `"string"` * `if_condition_determinate` - Raised on if condition being always true or always false * `loop_condition_determinate` - Raised on loop condition such as in `for` being always true or always false +* `improper_index` - Raised on accessing a non list with [] Raised by Lexer: diff --git a/crates/builtins-proc-macro/src/lib.rs b/crates/builtins-proc-macro/src/lib.rs index 9d64abcf..64be58a9 100644 --- a/crates/builtins-proc-macro/src/lib.rs +++ b/crates/builtins-proc-macro/src/lib.rs @@ -11,6 +11,7 @@ use syn::*; struct Header { attrs: Vec, path: Vec, + operator_overload_target: Option, } impl Header { @@ -24,7 +25,110 @@ impl Header { input.parse::()?; self.path.push(Ident::parse_any(input)?); } + if let Some(final_ident) = self.path.last() { + // If we find an operator{some token}() pattern we allow the some token part + if final_ident == "operator" { + self.parse_operator(input)?; + } + } + Ok(()) + } + fn parse_operator(&mut self, input: ParseStream) -> Result<()> { + let text_token: Option<&str> = if input.parse::().is_ok() { + if input.parse::().is_ok() { + Some("%%") + } else if input.parse::().is_ok() { + Some("%%=") + } else { + Some("%") + } + } else if input.parse::().is_ok() { + Some("&") + } else if input.parse::().is_ok() { + Some("&=") + } else if input.parse::().is_ok() { + if input.parse::().is_ok() { + Some("**") + } else { + Some("*") + } + } else if input.parse::().is_ok() { + Some("*=") + } else if input.parse::().is_ok() { + Some("/") + } else if input.parse::().is_ok() { + Some("/=") + } else if input.parse::().is_ok() { + if input.parse::().is_ok() { + Some("++") + } else { + Some("+") + } + } else if input.parse::().is_ok() { + Some("+=") + } else if input.parse::().is_ok() { + if input.parse::().is_ok() { + Some("--") + } else { + Some("-") + } + } else if input.parse::().is_ok() { + Some("-=") + } else if input.parse::().is_ok() { + Some("<") + } else if input.parse::().is_ok() { + Some("<<") + } else if input.parse::().is_ok() { + Some("<<=") + } else if input.parse::().is_ok() { + Some("<=") + } else if input.parse::=]>().is_ok() { + Some(">=") + } else if input.parse::>]>().is_ok() { + Some(">>") + } else if input.parse::>=]>().is_ok() { + Some(">>=") + } else if input.parse::().is_ok() { + Some("^") + } else if input.parse::().is_ok() { + Some("^=") + } else if input.parse::().is_ok() { + Some("|") + } else if input.parse::().is_ok() { + Some("|=") + } else if input.parse::().is_ok() { + if input.parse::().is_ok() { + Some("~=") + } else { + Some("~") + } + } else if input.parse::().is_ok() { + Some("~") + } else if input.peek(Token![:]) && input.peek2(Token![=]) { + input.parse::()?; + input.parse::()?; + Some(":=") + } else if self.brackets_next(input).is_ok() { + if input.parse::().is_ok() { + Some("[]=") + } else { + Some("[]") + } + } else { + // Todo: Implement operator""() support. Unsure how to expect an empty string + None + }; + if let Some(text) = text_token { + self.operator_overload_target = Some(text.to_string()); + } + Ok(()) + } + + fn brackets_next(&mut self, input: ParseStream) -> Result<()> { + // Sorry + let _bracket_dummy; + bracketed!(_bracket_dummy in input); Ok(()) } } @@ -143,7 +247,11 @@ pub fn builtins_table(input: TokenStream) -> TokenStream { let mut output = Vec::new(); for entry in builtins { let span = entry.header.path.first().unwrap().span(); - let lit_strs: Vec<_> = entry.header.path.into_iter().map(|x| LitStr::new(&x.to_string(), x.span())).collect(); + let mut lit_strs: Vec<_> = entry.header.path.into_iter().map(|x| LitStr::new(&x.to_string(), x.span())).collect(); + if let Some(operator) = entry.header.operator_overload_target { + let last_entry = lit_strs.pop().unwrap(); + lit_strs.push(LitStr::new((last_entry.value() + operator.as_str()).as_str(), last_entry.span())); + } let path = quote! { &[ #(#lit_strs),* ] }; diff --git a/crates/dreamchecker/src/lib.rs b/crates/dreamchecker/src/lib.rs index 62ea2322..5988f070 100644 --- a/crates/dreamchecker/src/lib.rs +++ b/crates/dreamchecker/src/lib.rs @@ -2105,6 +2105,14 @@ impl<'o, 's> AnalyzeProc<'o, 's> { } res }, + StaticType::Type(typeref) => { + if typeref.get_proc("operator[]").is_none() { + error(location, format!("invalid list access on {}", typeref.path)) + .with_errortype("improper_index") + .register(self.context); + } + lhs.clone() + }, _ => lhs.clone() // carry through fix_hint } }, diff --git a/crates/dreammaker/src/builtins.rs b/crates/dreammaker/src/builtins.rs index 5985237e..1506d2d5 100644 --- a/crates/dreammaker/src/builtins.rs +++ b/crates/dreammaker/src/builtins.rs @@ -626,6 +626,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) { list/var/const/parent_type; list/var/tag; list/var/const/list/vars; + list/proc/operator[](); list/proc/Add(Item1, Item2/*,...*/); list/proc/Copy(Start=1, End=0); list/proc/Cut(Start=1, End=0); @@ -642,6 +643,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) { alist/var/const/type; alist/var/const/parent_type; alist/var/tag; + alist/proc/operator[](); alist/proc/Add(Item1, Item2/*,...*/); alist/proc/Copy(Start=1, End=0); alist/proc/Cut(Start=1, End=0); @@ -1110,6 +1112,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) { savefile/var/list/dir; savefile/var/eof; savefile/var/name; + savefile/proc/operator[](); savefile/proc/ExportText(/* path=cd, file */); savefile/proc/Flush(); savefile/proc/ImportText(/* path=cd, file */); @@ -1282,6 +1285,7 @@ pub fn register_builtins(tree: &mut ObjectTreeBuilder) { vector/var/y; vector/var/z; + vector/proc/operator[](); vector/proc/Cross(B); vector/proc/Dot(B); vector/proc/Interpolate(B, t);