diff --git a/mers/src/libs/mod.rs b/mers/src/libs/mod.rs index 518799b..1ff7f9b 100755 --- a/mers/src/libs/mod.rs +++ b/mers/src/libs/mod.rs @@ -121,7 +121,10 @@ impl Lib { fn_signature.next(); } else { loop { - let mut t = match parse::parse_type_adv(&mut fn_signature, true) { + let mut t = match parse::implementation::parse_type_adv( + &mut fn_signature, + true, + ) { Ok(v) => v, Err(e) => panic!("{e}"), }; @@ -132,7 +135,8 @@ impl Lib { } } } - let mut fn_out = match parse::parse_type(&mut fn_signature) { + let mut fn_out = match parse::implementation::parse_type(&mut fn_signature) + { Ok(v) => v, Err(e) => panic!("{e}"), }; diff --git a/mers/src/libs/path.rs b/mers/src/libs/path.rs index 6dac339..fd22e83 100755 --- a/mers/src/libs/path.rs +++ b/mers/src/libs/path.rs @@ -1,16 +1,20 @@ use std::path::PathBuf; -pub fn path_from_string(path: &str, script_directory: &PathBuf) -> Option { +pub fn path_from_string(path: &str, script_path: &PathBuf) -> Option { let path = PathBuf::from(path); if path.is_absolute() { return Some(path); } - if let Some(p) = script_directory + if let Some(p) = script_path .canonicalize() - .unwrap_or_else(|_| script_directory.clone()) + .unwrap_or_else(|_| script_path.clone()) .parent() { + #[cfg(debug_assertions)] + eprintln!("path: parent path: {p:?}"); let p = p.join(&path); + #[cfg(debug_assertions)] + eprintln!("path: joined: {p:?}"); if p.exists() { return Some(p); } diff --git a/mers/src/parse/file.rs b/mers/src/parse/file.rs index 401f8b2..8d2f60f 100755 --- a/mers/src/parse/file.rs +++ b/mers/src/parse/file.rs @@ -108,12 +108,27 @@ impl File { } pub fn skip_whitespaces(&mut self) { loop { - match self.chars.get(self.pos.current_char_index) { - Some(ch) if ch.1.is_whitespace() => _ = self.next(), + match self.peek() { + Some(ch) if ch.is_whitespace() => _ = self.next(), _ => break, } } } + pub fn collect_to_whitespace(&mut self) -> String { + let mut o = String::new(); + loop { + if let Some(ch) = self.next() { + if ch.is_whitespace() { + self.set_pos(*self.get_ppos()); + break; + } + o.push(ch); + } else { + break; + } + } + o + } pub fn path(&self) -> &PathBuf { &self.path } diff --git a/mers/src/parse/parse.rs b/mers/src/parse/parse.rs index ea867e4..1b8bb70 100755 --- a/mers/src/parse/parse.rs +++ b/mers/src/parse/parse.rs @@ -3,6 +3,7 @@ use std::{process::Command, sync::Arc}; use crate::{ libs, script::{ + code_macro::MacroError, code_parsed::*, code_runnable::RScript, global_info::GSInfo, @@ -239,6 +240,7 @@ pub enum ParseErrors { CannotUseFixedIndexingWithThisType(VType), CannotWrapWithThisStatement(SStatementEnum), ErrorParsingFunctionArgs(Box), + MacroError(MacroError), } impl ParseErrors { fn fmtgs( @@ -285,41 +287,48 @@ impl ParseErrors { write!(f, "error parsing function args: ")?; parse_error.fmt_custom(f, file) } + Self::MacroError(e) => write!(f, "error in macro: {e}"), } } } -fn parse_block(file: &mut File) -> Result { - parse_block_advanced(file, None, true, false, false) -} -fn parse_block_advanced( - file: &mut File, - assume_single_statement: Option, - treat_closed_block_bracket_as_closing_delimeter: bool, - treat_eof_as_closing_delimeter: bool, - treat_closed_normal_bracket_as_closing_delimeter: bool, -) -> Result { - file.skip_whitespaces(); - // if true, parse exactly one statement. unless assume_single_statement.is_some(), this depends on whether the code block starts with { or not. - let single_statement = if let Some(v) = assume_single_statement { - v - } else { - if let Some('{') = file.get_char(file.get_pos().current_char_index) { - file.next(); - false - } else { - true - } - }; - let mut statements = vec![]; - // each iteration of this loop parses one statement - loop { +use implementation::*; + +pub mod implementation { + + use super::*; + + fn parse_block(file: &mut File) -> Result { + parse_block_advanced(file, None, true, false, false) + } + pub(crate) fn parse_block_advanced( + file: &mut File, + assume_single_statement: Option, + treat_closed_block_bracket_as_closing_delimeter: bool, + treat_eof_as_closing_delimeter: bool, + treat_closed_normal_bracket_as_closing_delimeter: bool, + ) -> Result { file.skip_whitespaces(); - let err_start_of_this_statement = *file.get_pos(); - match file.get_char(file.get_pos().current_char_index) { - Some(')') if treat_closed_normal_bracket_as_closing_delimeter => { - if single_statement { - return Err(ParseError { + // if true, parse exactly one statement. unless assume_single_statement.is_some(), this depends on whether the code block starts with { or not. + let single_statement = if let Some(v) = assume_single_statement { + v + } else { + if let Some('{') = file.get_char(file.get_pos().current_char_index) { + file.next(); + false + } else { + true + } + }; + let mut statements = vec![]; + // each iteration of this loop parses one statement + loop { + file.skip_whitespaces(); + let err_start_of_this_statement = *file.get_pos(); + match file.get_char(file.get_pos().current_char_index) { + Some(')') if treat_closed_normal_bracket_as_closing_delimeter => { + if single_statement { + return Err(ParseError { err: ParseErrors::FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement, location: err_start_of_this_statement, @@ -327,100 +336,93 @@ fn parse_block_advanced( context: vec![], info: None, }); - } else { - file.next(); - break; + } else { + file.next(); + break; + } } - } - Some('}') if treat_closed_block_bracket_as_closing_delimeter => { - if single_statement { - return Err(ParseError { + Some('}') if treat_closed_block_bracket_as_closing_delimeter => { + if single_statement { + return Err(ParseError { err: ParseErrors::FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement, location: err_start_of_this_statement, location_end: Some(*file.get_pos()), context: vec![], info: None, }); - } else { + } else { + file.next(); + break; + } + } + None if treat_eof_as_closing_delimeter => { + break; + } + None => { + return Err(ParseError { + err: ParseErrors::FoundEofInBlockBeforeStatementOrClosingCurlyBracket, + location: err_start_of_this_statement, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }) + } + _ => (), + } + statements.push(parse_statement(file)?); + match file.peek() { + // Some('}') if treat_closed_block_bracket_as_closing_delimeter => break, + Some(')') if treat_closed_normal_bracket_as_closing_delimeter => { file.next(); break; } + _ => (), } - None if treat_eof_as_closing_delimeter => { + if single_statement && !statements.is_empty() { break; } - None => { - return Err(ParseError { - err: ParseErrors::FoundEofInBlockBeforeStatementOrClosingCurlyBracket, - location: err_start_of_this_statement, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }) - } - _ => (), } - statements.push(parse_statement(file)?); + Ok(SBlock::new(statements)) + } + + /// convenience function. If you require a string, use this. If the first character is a " it will parse + /// until it finds a closing ". If the first character is anything else it will parse until it finds a character matching closing_char. + /// Escape sequences (like \n) will only be interpreted as such if "" is used. + pub(crate) fn parse_string_val(file: &mut File, closing_char: F) -> String + where + F: Fn(char) -> bool, + { match file.peek() { - // Some('}') if treat_closed_block_bracket_as_closing_delimeter => break, - Some(')') if treat_closed_normal_bracket_as_closing_delimeter => { + Some('"') => { file.next(); - break; + parse_string(file).unwrap_or(String::new()) + } + _ => { + let mut buf = String::new(); + loop { + if let Some(ch) = file.next() { + if closing_char(ch) { + file.set_pos(*file.get_ppos()); + break; + } + buf.push(ch); + } else { + break; + } + } + buf } - _ => (), - } - if single_statement && !statements.is_empty() { - break; } } - Ok(SBlock::new(statements)) -} - -fn parse_statement(file: &mut File) -> Result { - parse_statement_adv(file, false) -} -fn parse_statement_adv( - file: &mut File, - is_part_of_chain_already: bool, -) -> Result { - file.skip_whitespaces(); - let err_start_of_statement = *file.get_pos(); - let out = match file.peek() { - Some('{') => Some(SStatementEnum::Block(parse_block(file)?).to()), - Some('[') => { - file.next(); - let mut v = vec![]; - let mut list = false; - loop { - file.skip_whitespaces(); - if let Some(']') = file.peek() { - file.next(); - break; - } - if file[file.get_pos().current_char_index..].starts_with("...]") { - list = true; - file.next(); - file.next(); - file.next(); - file.next(); - break; - } - v.push(parse_statement(file)?); - } - Some(if list { - SStatementEnum::List(v).to() - } else { - SStatementEnum::Tuple(v).to() - }) - } - Some('"') => { - file.next(); - let mut buf = String::new(); - loop { - match file.next() { - Some('\\') => { - if let Some(ch) = file.next() { - buf.push(match ch { + /// assumes the starting " has already been consumed. + pub(crate) fn parse_string(file: &mut File) -> Result { + let mut buf = String::new(); + let err_start_of_statement = *file.get_pos(); + loop { + match file.next() { + Some('\\') => { + if let Some(ch) = file.next() { + buf.push(match ch { '\\' => '\\', 'n' => '\n', 't' => '\t', @@ -430,47 +432,89 @@ fn parse_statement_adv( ch }, }) - } - } - Some('"') => break, - Some(ch) => buf.push(ch), - None => { - return Err(ParseError { - err: ParseErrors::FoundEofInString, - location: err_start_of_statement, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }) } } + Some('"') => break, + Some(ch) => buf.push(ch), + None => { + return Err(ParseError { + err: ParseErrors::FoundEofInString, + location: err_start_of_statement, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }) + } } - Some(SStatementEnum::Value(VDataEnum::String(buf).to()).to()) } - _ => None, - }; - let mut out = if let Some(out) = out { - out - } else { - let mut start = String::new(); - loop { - fn is_delimeter(ch: char) -> bool { - matches!(ch, '}' | ']' | ')' | '.') - } - let nchar = match file.peek() { - Some(ch) if is_delimeter(ch) => Some(ch), - _ => file.next(), - }; - match nchar { - Some(':') => { - let file_pos_before_pot_type = *file.get_pos(); - let parsed_type = parse_type(file); + Ok(buf) + } + + pub(crate) fn parse_statement(file: &mut File) -> Result { + parse_statement_adv(file, false) + } + pub(crate) fn parse_statement_adv( + file: &mut File, + is_part_of_chain_already: bool, + ) -> Result { + file.skip_whitespaces(); + let err_start_of_statement = *file.get_pos(); + let out = match file.peek() { + Some('{') => Some(SStatementEnum::Block(parse_block(file)?).to()), + Some('[') => { + file.next(); + let mut v = vec![]; + let mut list = false; + loop { file.skip_whitespaces(); - if let Some('=') = file.next() { - let err_equals_sign = *file.get_pos(); - let start = start.trim(); - let derefs = start.chars().take_while(|c| *c == '*').count(); - match parse_statement(file) { + if let Some(']') = file.peek() { + file.next(); + break; + } + if file[file.get_pos().current_char_index..].starts_with("...]") { + list = true; + file.next(); + file.next(); + file.next(); + file.next(); + break; + } + v.push(parse_statement(file)?); + } + Some(if list { + SStatementEnum::List(v).to() + } else { + SStatementEnum::Tuple(v).to() + }) + } + Some('"') => { + file.next(); + Some(SStatementEnum::Value(VDataEnum::String(parse_string(file)?).to()).to()) + } + _ => None, + }; + let mut out = if let Some(out) = out { + out + } else { + let mut start = String::new(); + loop { + fn is_delimeter(ch: char) -> bool { + matches!(ch, '}' | ']' | ')' | '.') + } + let nchar = match file.peek() { + Some(ch) if is_delimeter(ch) => Some(ch), + _ => file.next(), + }; + match nchar { + Some(':') => { + let file_pos_before_pot_type = *file.get_pos(); + let parsed_type = parse_type(file); + file.skip_whitespaces(); + if let Some('=') = file.next() { + let err_equals_sign = *file.get_pos(); + let start = start.trim(); + let derefs = start.chars().take_while(|c| *c == '*').count(); + match parse_statement(file) { Ok(v) => break v .output_to(start[derefs..].to_owned(), derefs) .force_output_type(Some(match parsed_type { @@ -492,261 +536,322 @@ fn parse_statement_adv( return Err(e); } } + } + file.set_pos(file_pos_before_pot_type); + return Ok(SStatement::new(SStatementEnum::EnumVariant( + start, + parse_statement(file)?, + ))); } - file.set_pos(file_pos_before_pot_type); - return Ok(SStatement::new(SStatementEnum::EnumVariant( - start, - parse_statement(file)?, - ))); - } - Some(ch) if ch.is_whitespace() || is_delimeter(ch) => { - if start.trim().is_empty() { - return Err(ParseError { - err: ParseErrors::StatementCannotStartWith(ch), - location: *file.get_pos(), - location_end: None, - context: vec![], - info: None, - }); - } - file.skip_whitespaces(); - // var = statement - if let Some('=') = file.peek() { - file.next(); - let err_equals_sign = *file.get_pos(); - let start = start.trim(); - let derefs = start.chars().take_while(|c| *c == '*').count(); - match parse_statement(file) { - Ok(v) => break v.output_to(start[derefs..].to_owned(), derefs), - Err(mut e) => { - e.context.push(( - format!( + Some(ch) if ch.is_whitespace() || is_delimeter(ch) => { + if start.trim().is_empty() { + return Err(ParseError { + err: ParseErrors::StatementCannotStartWith(ch), + location: *file.get_pos(), + location_end: None, + context: vec![], + info: None, + }); + } + file.skip_whitespaces(); + // var = statement + if let Some('=') = file.peek() { + file.next(); + let err_equals_sign = *file.get_pos(); + let start = start.trim(); + let derefs = start.chars().take_while(|c| *c == '*').count(); + match parse_statement(file) { + Ok(v) => break v.output_to(start[derefs..].to_owned(), derefs), + Err(mut e) => { + e.context.push(( + format!( "statement was supposed to be assigned to variable {start}" ), - Some((err_start_of_statement, Some(err_equals_sign))), - )); - return Err(e); - } - }; - } - // parse normal statement - let start = start.trim(); - match start { - "fn" => { - file.skip_whitespaces(); - let mut fn_name = String::new(); - loop { - match file.next() { - Some('(') => break, - Some(ch) => fn_name.push(ch), - None => break, - } - } - let func = parse_function(file, Some(err_start_of_statement))?; - break SStatementEnum::FunctionDefinition( - Some(fn_name.trim().to_string()), - func, - ) - .to(); - } - "if" => { - // TODO: Else - let condition = parse_statement(file)?; - let then = parse_statement(file)?; - let mut then_else = None; - file.skip_whitespaces(); - let i = file.get_pos().current_char_index; - if file[i..].starts_with("else ") { - while let Some('e' | 'l' | 's') = file.next() {} - then_else = Some(parse_statement(file)?); - } - break SStatementEnum::If(condition, then, then_else).to(); - } - "for" => { - break SStatementEnum::For( - { - file.skip_whitespaces(); - let mut buf = String::new(); - loop { - if let Some(ch) = file.next() { - if ch.is_whitespace() { - break; - } - buf.push(ch); - } else { - break; - } - } - buf - }, - parse_statement(file)?, - parse_statement(file)?, - ) - .to() - } - "while" => { - eprintln!("Warn: 'while' is now 'loop'. At some point, this will just be an error instead of a warning."); - break SStatementEnum::Loop(parse_statement(file)?).to(); - } - "loop" => { - break SStatementEnum::Loop(parse_statement(file)?).to(); - } - "switch" | "switch!" => { - let force = start.ends_with("!"); - let mut switch_on_what = String::new(); - loop { - match file.next() { - None => break, - Some(ch) if ch.is_whitespace() => break, - Some(ch) => switch_on_what.push(ch), - } - } - file.skip_whitespaces(); - if let Some('{') = file.next() { - } else { - eprintln!("switch statements should be followed by {{ (because they must be closed by }}). This might lead to errors when parsing, although it isn't fatal."); - } - let mut cases = vec![]; - loop { - file.skip_whitespaces(); - if let Some('}') = file.peek() { - file.next(); - break; - } - cases.push((parse_type(file)?, parse_statement(file)?)); - } - break SStatementEnum::Switch(switch_on_what, cases, force).to(); - } - "match" => { - let mut match_what = String::new(); - loop { - match file.next() { - None => break, - Some(ch) if ch.is_whitespace() => break, - Some(ch) => match_what.push(ch), - } - } - file.skip_whitespaces(); - if let Some('{') = file.next() { - } else { - eprintln!("match statements should be followed by {{ (because they must be closed by }}). This might lead to errors when parsing, although it isn't fatal."); - } - let mut cases = vec![]; - loop { - file.skip_whitespaces(); - if let Some('}') = file.peek() { - file.next(); - break; - } - cases.push((parse_statement(file)?, parse_statement(file)?)); - } - break SStatementEnum::Match(match_what, cases).to(); - } - "true" => break SStatementEnum::Value(VDataEnum::Bool(true).to()).to(), - "false" => break SStatementEnum::Value(VDataEnum::Bool(false).to()).to(), - _ => { - // int, float, var - break { - if let Ok(v) = start.parse() { - if let Some('.') = nchar { - let pos = *file.get_pos(); - file.next(); - let mut pot_float = String::new(); - for ch in &mut *file { - if ch.is_whitespace() || is_delimeter(ch) { - file.set_pos(*file.get_ppos()); - break; - } - pot_float.push(ch); - } - if let Ok(v) = format!("{start}.{pot_float}").parse() { - SStatementEnum::Value(VDataEnum::Float(v).to()).to() - } else { - file.set_pos(pos); - SStatementEnum::Value(VDataEnum::Int(v).to()).to() - } - } else { - SStatementEnum::Value(VDataEnum::Int(v).to()).to() - } - // } else if let Ok(v) = start.parse() { - // SStatementEnum::Value(VDataEnum::Float(v).to()).to() - } else { - if start.starts_with('&') { - SStatementEnum::Variable(start[1..].to_string(), true).to() - } else { - SStatementEnum::Variable(start.to_string(), false).to() - } + Some((err_start_of_statement, Some(err_equals_sign))), + )); + return Err(e); } }; } - } - } - Some('(') => { - // parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise) - let name = start.trim(); - if name.is_empty() { - break SStatementEnum::FunctionDefinition( - None, - parse_function(file, Some(err_start_of_statement))?, - ) - .to(); - } else { - break SStatementEnum::FunctionCall( - name.to_string(), - match parse_block_advanced(file, Some(false), false, false, true) { - Ok(block) => block.statements, - Err(e) => { - // NOTE: Alternatively, just add an entry to the original error's context. - return Err(ParseError { - err: ParseErrors::ErrorParsingFunctionArgs(Box::new(e)), - location: err_start_of_statement, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }); + // parse normal statement + let start = start.trim(); + match start { + "fn" => { + file.skip_whitespaces(); + let mut fn_name = String::new(); + loop { + match file.next() { + Some('(') => break, + Some(ch) => fn_name.push(ch), + None => break, + } } - }, - ) - .to(); + let func = parse_function(file, Some(err_start_of_statement))?; + break SStatementEnum::FunctionDefinition( + Some(fn_name.trim().to_string()), + func, + ) + .to(); + } + "if" => { + // TODO: Else + let condition = parse_statement(file)?; + let then = parse_statement(file)?; + let mut then_else = None; + file.skip_whitespaces(); + let i = file.get_pos().current_char_index; + if file[i..].starts_with("else ") { + while let Some('e' | 'l' | 's') = file.next() {} + then_else = Some(parse_statement(file)?); + } + break SStatementEnum::If(condition, then, then_else).to(); + } + "for" => { + break SStatementEnum::For( + { + file.skip_whitespaces(); + let mut buf = String::new(); + loop { + if let Some(ch) = file.next() { + if ch.is_whitespace() { + break; + } + buf.push(ch); + } else { + break; + } + } + buf + }, + parse_statement(file)?, + parse_statement(file)?, + ) + .to() + } + "while" => { + eprintln!("Warn: 'while' is now 'loop'. At some point, this will just be an error instead of a warning."); + break SStatementEnum::Loop(parse_statement(file)?).to(); + } + "loop" => { + break SStatementEnum::Loop(parse_statement(file)?).to(); + } + "switch" | "switch!" => { + let force = start.ends_with("!"); + let mut switch_on_what = String::new(); + loop { + match file.next() { + None => break, + Some(ch) if ch.is_whitespace() => break, + Some(ch) => switch_on_what.push(ch), + } + } + file.skip_whitespaces(); + if let Some('{') = file.next() { + } else { + eprintln!("switch statements should be followed by {{ (because they must be closed by }}). This might lead to errors when parsing, although it isn't fatal."); + } + let mut cases = vec![]; + loop { + file.skip_whitespaces(); + if let Some('}') = file.peek() { + file.next(); + break; + } + cases.push((parse_type(file)?, parse_statement(file)?)); + } + break SStatementEnum::Switch(switch_on_what, cases, force).to(); + } + "match" => { + let mut match_what = String::new(); + loop { + match file.next() { + None => break, + Some(ch) if ch.is_whitespace() => break, + Some(ch) => match_what.push(ch), + } + } + file.skip_whitespaces(); + if let Some('{') = file.next() { + } else { + eprintln!("match statements should be followed by {{ (because they must be closed by }}). This might lead to errors when parsing, although it isn't fatal."); + } + let mut cases = vec![]; + loop { + file.skip_whitespaces(); + if let Some('}') = file.peek() { + file.next(); + break; + } + cases.push((parse_statement(file)?, parse_statement(file)?)); + } + break SStatementEnum::Match(match_what, cases).to(); + } + "true" => break SStatementEnum::Value(VDataEnum::Bool(true).to()).to(), + "false" => { + break SStatementEnum::Value(VDataEnum::Bool(false).to()).to() + } + _ => { + // int, float, var + break { + if let Ok(v) = start.parse() { + if let Some('.') = nchar { + let pos = *file.get_pos(); + file.next(); + let mut pot_float = String::new(); + for ch in &mut *file { + if ch.is_whitespace() || is_delimeter(ch) { + file.set_pos(*file.get_ppos()); + break; + } + pot_float.push(ch); + } + if let Ok(v) = format!("{start}.{pot_float}").parse() { + SStatementEnum::Value(VDataEnum::Float(v).to()).to() + } else { + file.set_pos(pos); + SStatementEnum::Value(VDataEnum::Int(v).to()).to() + } + } else { + SStatementEnum::Value(VDataEnum::Int(v).to()).to() + } + // } else if let Ok(v) = start.parse() { + // SStatementEnum::Value(VDataEnum::Float(v).to()).to() + } else { + if start.starts_with('&') { + SStatementEnum::Variable(start[1..].to_string(), true) + .to() + } else { + SStatementEnum::Variable(start.to_string(), false).to() + } + } + }; + } + } + } + Some('(') => { + // parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise) + let name = start.trim(); + match name { + "" => { + break SStatementEnum::FunctionDefinition( + None, + parse_function(file, Some(err_start_of_statement))?, + ) + .to(); + } + "!" => { + break SStatementEnum::Macro( + match crate::script::code_macro::parse_macro(file) { + Ok(v) => v, + Err(e) => { + return Err(ParseError { + err: ParseErrors::MacroError(e), + location: err_start_of_statement, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }); + } + }, + ) + .to() + } + _ => { + break SStatementEnum::FunctionCall( + name.to_string(), + match parse_block_advanced( + file, + Some(false), + false, + false, + true, + ) { + Ok(block) => block.statements, + Err(e) => { + // NOTE: Alternatively, just add an entry to the original error's context. + return Err(ParseError { + err: ParseErrors::ErrorParsingFunctionArgs( + Box::new(e), + ), + location: err_start_of_statement, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }); + } + }, + ) + .to(); + } + } + } + Some(ch) => start.push(ch), + None => { + return Err(ParseError { + err: ParseErrors::FoundEofInStatement, + location: err_start_of_statement, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }) } - } - Some(ch) => start.push(ch), - None => { - return Err(ParseError { - err: ParseErrors::FoundEofInStatement, - location: err_start_of_statement, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }) } } - } - }; - let err_end_of_original_statement = *file.get_pos(); - file.skip_whitespaces(); - if !file[file.get_pos().current_char_index..].starts_with("..") { - // dot chain syntax only works if there is only one dot - if let Some('.') = file.peek() { - // consume the dot (otherwise, a.b.c syntax will break in certain cases) - file.next(); - } - if !is_part_of_chain_already { - let mut chain_length = 0; - let mut err_end_of_prev = err_end_of_original_statement; - while let Some('.') = file.get_char(file.get_pos().current_char_index.saturating_sub(1)) - { - let err_start_of_wrapper = *file.get_pos(); - let wrapper = parse_statement_adv(file, true)?; - let err_end_of_wrapper = *file.get_pos(); - out = match *wrapper.statement { - SStatementEnum::FunctionCall(func, args) => { - let args = [out].into_iter().chain(args.into_iter()).collect(); - SStatementEnum::FunctionCall(func, args).to() - } - SStatementEnum::Value(vd) => match vd.data { - VDataEnum::Int(i) => SStatementEnum::IndexFixed(out, i as _).to(), - _ => { + }; + let err_end_of_original_statement = *file.get_pos(); + file.skip_whitespaces(); + if !file[file.get_pos().current_char_index..].starts_with("..") { + // dot chain syntax only works if there is only one dot + if let Some('.') = file.peek() { + // consume the dot (otherwise, a.b.c syntax will break in certain cases) + file.next(); + } + if !is_part_of_chain_already { + let mut chain_length = 0; + let mut err_end_of_prev = err_end_of_original_statement; + while let Some('.') = + file.get_char(file.get_pos().current_char_index.saturating_sub(1)) + { + let err_start_of_wrapper = *file.get_pos(); + let wrapper = parse_statement_adv(file, true)?; + let err_end_of_wrapper = *file.get_pos(); + out = match *wrapper.statement { + SStatementEnum::FunctionCall(func, args) => { + let args = [out].into_iter().chain(args.into_iter()).collect(); + SStatementEnum::FunctionCall(func, args).to() + } + SStatementEnum::Value(vd) => match vd.data { + VDataEnum::Int(i) => SStatementEnum::IndexFixed(out, i as _).to(), + _ => { + let mut context = vec![]; + if chain_length > 0 { + context.push(( + format!( + "this is the {} wrapping statement in a chain.", + match chain_length { + 1 => format!("second"), + 2 => format!("third"), + // NOTE: this technically breaks at 21, but I don't care. + len => format!("{}th", len + 1), + } + ), + None, + )); + } + context.push(( + format!("the statement that was supposed to be wrapped"), + Some((err_start_of_statement, Some(err_end_of_prev))), + )); + return Err(ParseError { + err: ParseErrors::CannotUseFixedIndexingWithThisType(vd.out()), + location: err_start_of_wrapper, + location_end: Some(err_end_of_wrapper), + context, + info: None, + }); + } + }, + other => { let mut context = vec![]; if chain_length > 0 { context.push(( @@ -767,300 +872,287 @@ fn parse_statement_adv( Some((err_start_of_statement, Some(err_end_of_prev))), )); return Err(ParseError { - err: ParseErrors::CannotUseFixedIndexingWithThisType(vd.out()), + err: ParseErrors::CannotWrapWithThisStatement(other), location: err_start_of_wrapper, location_end: Some(err_end_of_wrapper), context, info: None, }); } - }, - other => { - let mut context = vec![]; - if chain_length > 0 { - context.push(( - format!( - "this is the {} wrapping statement in a chain.", - match chain_length { - 1 => format!("second"), - 2 => format!("third"), - // NOTE: this technically breaks at 21, but I don't care. - len => format!("{}th", len + 1), - } - ), - None, - )); - } - context.push(( - format!("the statement that was supposed to be wrapped"), - Some((err_start_of_statement, Some(err_end_of_prev))), - )); - return Err(ParseError { - err: ParseErrors::CannotWrapWithThisStatement(other), - location: err_start_of_wrapper, - location_end: Some(err_end_of_wrapper), - context, - info: None, - }); - } - }; - err_end_of_prev = err_end_of_wrapper; - chain_length += 1; - } - } - } - Ok(out) -} - -/// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) " -fn parse_function( - file: &mut File, - err_fn_start: Option, -) -> Result { - file.skip_whitespaces(); - // find the arguments to the function - let mut args = Vec::new(); - if let Some(')') = file.peek() { - file.next(); - } else { - loop { - let mut arg_name = String::new(); - loop { - let err_fn_arg_name_start = *file.get_pos(); - match file.next() { - Some(ch) if ch.is_whitespace() => break, - Some(ch) => arg_name.push(ch), - None => { - return Err(ParseError { - err: ParseErrors::FoundEofInFunctionArgName, - location: err_fn_arg_name_start, - location_end: Some(*file.get_pos()), - context: vec![if let Some(err_fn_start) = err_fn_start { - ( - format!("the function"), - Some((err_fn_start, Some(*file.get_pos()))), - ) - } else { - (format!("not a real fn definition"), None) - }], - info: None, - }) - } + }; + err_end_of_prev = err_end_of_wrapper; + chain_length += 1; } } - let (t, brk) = parse_type_adv(file, true)?; - args.push((arg_name, t)); - if brk { - break; - } } + Ok(out) } - Ok(SFunction::new(args, parse_block(file)?)) -} -pub(crate) fn parse_type(file: &mut File) -> Result { - match parse_type_adv(file, false) { - Ok((v, _)) => Ok(v), - Err(e) => Err(e), - } -} -pub(crate) fn parse_type_adv( - file: &mut File, - in_fn_args: bool, -) -> Result<(VType, bool), ParseError> { - let mut types = vec![]; - let mut closed_fn_args = false; - loop { - let (st, closed_bracket) = parse_single_type_adv(file, in_fn_args)?; - types.push(st); - if closed_bracket { - closed_fn_args = true; - break; - } + /// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) " + fn parse_function( + file: &mut File, + err_fn_start: Option, + ) -> Result { file.skip_whitespaces(); - match file.peek() { - Some('/') => { - file.next(); - } - Some(')') => { - closed_fn_args = true; - file.next(); - break; - } - Some(_) => break, - - None => break, - } - } - Ok((VType { types }, closed_fn_args)) -} -fn parse_single_type(file: &mut File) -> Result { - match parse_single_type_adv(file, false) { - Ok((v, _)) => Ok(v), - Err(e) => Err(e), - } -} -fn parse_single_type_adv( - file: &mut File, - in_fn_args: bool, -) -> Result<(VSingleType, bool), ParseError> { - file.skip_whitespaces(); - let mut closed_bracket_in_fn_args = false; - let err_start_of_single_type = *file.get_pos(); - Ok(( - match file.next() { - Some('&') => { - let parse_output = parse_single_type_adv(file, in_fn_args)?; - if parse_output.1 { - closed_bracket_in_fn_args = true; - } - VSingleType::Reference(Box::new(parse_output.0)) - } - // Tuple or Array - Some('[') => { - let mut types = vec![]; - let mut list = false; + // find the arguments to the function + let mut args = Vec::new(); + if let Some(')') = file.peek() { + file.next(); + } else { + loop { + let mut arg_name = String::new(); loop { - file.skip_whitespaces(); - if file[file.get_pos().current_char_index..].starts_with("...]") { - list = true; - file.next(); - file.next(); - file.next(); - file.next(); - break; - } - match file.peek() { - Some(']') => { - file.next(); - break; - } - _ => (), - } - types.push(parse_type(file)?); - } - if in_fn_args { - file.skip_whitespaces(); - if let Some(')') = file.peek() { - closed_bracket_in_fn_args = true; - file.next(); - } - } - if list { - VSingleType::List(types.pop().unwrap()) - } else { - VSingleType::Tuple(types) - } - } - Some(ch) => 'parse_single_type: { - let mut name = ch.to_string(); - loop { - match file.peek() { - Some(']') => break, - Some('/') => break, - Some(')') if in_fn_args => { - file.next(); - closed_bracket_in_fn_args = true; - break; - } - Some(ch) if ch.is_whitespace() => break, - _ => (), - } + let err_fn_arg_name_start = *file.get_pos(); match file.next() { - Some('(') => { - break 'parse_single_type if name.as_str() == "fn" { - let mut fn_types = vec![]; - loop { - file.skip_whitespaces(); - match file.next() { - Some('(') => { - let mut args = vec![]; - loop { - let (t, fn_args_closed) = - parse_type_adv(file, true)?; - args.push(t); - if fn_args_closed { - break; - } - } - let out = if let Some(v) = args.pop() { - v - } else { - VSingleType::Tuple(vec![]).to() - }; - fn get_all_single_types( - types: &mut Vec, - ) -> Vec> - { - if types.is_empty() { - vec![] - } else if types.len() == 1 { - vec![types[0].types.clone()] - } else { - let last = types.pop().unwrap(); - let o = get_all_single_types(types); - let mut out = Vec::with_capacity( - o.len() * last.types.len(), - ); - for other in o { - for t in &last.types { - let mut vec = other.clone(); - vec.push(t.clone()); - out.push(vec); - } - } - types.push(last); - out - } - } - for t in get_all_single_types(&mut args) { - fn_types.push((t, out.clone())); - } - } - Some(')') => break, - Some(other) => { - eprintln!("Found char '{other}' in fn type when ')' or '(' was expected (will be treated as ')'). format is fn((input11 input12 output1) (input21 input22 output2))"); - break; - } - None => { - return Err(ParseError { - err: ParseErrors::FoundEofInType, - location: err_start_of_single_type, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }) - } - } - } - if in_fn_args { - if let Some(')') = file.peek() { - _ = file.next(); - closed_bracket_in_fn_args = true; - } - } - VSingleType::Function(fn_types) - } else if name.as_str() == "thread" { - let inner = parse_type_adv(file, true)?; - if !inner.1 { - eprintln!("Warn: Parsed type thread(inner_type), but might have missed the closing bracket!"); - } - VSingleType::Thread(inner.0) - } else { - VSingleType::EnumVariantS(name, { - let po = parse_type_adv(file, true)?; - if !po.1 { - // eprintln!("enum type should be closed by ')', but apparently wasn't?"); - assert_eq!(file.next(), Some(')')); - } - po.0 - }) - }; - } - Some(ch) => name.push(ch), + Some(ch) if ch.is_whitespace() => break, + Some(ch) => arg_name.push(ch), None => { return Err(ParseError { - err: ParseErrors::FoundEofInType, + err: ParseErrors::FoundEofInFunctionArgName, + location: err_fn_arg_name_start, + location_end: Some(*file.get_pos()), + context: vec![if let Some(err_fn_start) = err_fn_start { + ( + format!("the function"), + Some((err_fn_start, Some(*file.get_pos()))), + ) + } else { + (format!("not a real fn definition"), None) + }], + info: None, + }) + } + } + } + let (t, brk) = parse_type_adv(file, true)?; + args.push((arg_name, t)); + if brk { + break; + } + } + } + Ok(SFunction::new(args, parse_block(file)?)) + } + + pub(crate) fn parse_type(file: &mut File) -> Result { + match parse_type_adv(file, false) { + Ok((v, _)) => Ok(v), + Err(e) => Err(e), + } + } + pub(crate) fn parse_type_adv( + file: &mut File, + in_fn_args: bool, + ) -> Result<(VType, bool), ParseError> { + let mut types = vec![]; + let mut closed_fn_args = false; + loop { + let (st, closed_bracket) = parse_single_type_adv(file, in_fn_args)?; + types.push(st); + if closed_bracket { + closed_fn_args = true; + break; + } + file.skip_whitespaces(); + match file.peek() { + Some('/') => { + file.next(); + } + Some(')') => { + closed_fn_args = true; + file.next(); + break; + } + Some(_) => break, + + None => break, + } + } + Ok((VType { types }, closed_fn_args)) + } + fn parse_single_type(file: &mut File) -> Result { + match parse_single_type_adv(file, false) { + Ok((v, _)) => Ok(v), + Err(e) => Err(e), + } + } + fn parse_single_type_adv( + file: &mut File, + in_fn_args: bool, + ) -> Result<(VSingleType, bool), ParseError> { + file.skip_whitespaces(); + let mut closed_bracket_in_fn_args = false; + let err_start_of_single_type = *file.get_pos(); + Ok(( + match file.next() { + Some('&') => { + let parse_output = parse_single_type_adv(file, in_fn_args)?; + if parse_output.1 { + closed_bracket_in_fn_args = true; + } + VSingleType::Reference(Box::new(parse_output.0)) + } + // Tuple or Array + Some('[') => { + let mut types = vec![]; + let mut list = false; + loop { + file.skip_whitespaces(); + if file[file.get_pos().current_char_index..].starts_with("...]") { + list = true; + file.next(); + file.next(); + file.next(); + file.next(); + break; + } + match file.peek() { + Some(']') => { + file.next(); + break; + } + _ => (), + } + types.push(parse_type(file)?); + } + if in_fn_args { + file.skip_whitespaces(); + if let Some(')') = file.peek() { + closed_bracket_in_fn_args = true; + file.next(); + } + } + if list { + VSingleType::List(types.pop().unwrap()) + } else { + VSingleType::Tuple(types) + } + } + Some(ch) => 'parse_single_type: { + let mut name = ch.to_string(); + loop { + match file.peek() { + Some(']') => break, + Some('/') => break, + Some(')') if in_fn_args => { + file.next(); + closed_bracket_in_fn_args = true; + break; + } + Some(ch) if ch.is_whitespace() => break, + _ => (), + } + match file.next() { + Some('(') => { + break 'parse_single_type if name.as_str() == "fn" { + let mut fn_types = vec![]; + loop { + file.skip_whitespaces(); + match file.next() { + Some('(') => { + let mut args = vec![]; + loop { + let (t, fn_args_closed) = + parse_type_adv(file, true)?; + args.push(t); + if fn_args_closed { + break; + } + } + let out = if let Some(v) = args.pop() { + v + } else { + VSingleType::Tuple(vec![]).to() + }; + fn get_all_single_types( + types: &mut Vec, + ) -> Vec> + { + if types.is_empty() { + vec![] + } else if types.len() == 1 { + vec![types[0].types.clone()] + } else { + let last = types.pop().unwrap(); + let o = get_all_single_types(types); + let mut out = Vec::with_capacity( + o.len() * last.types.len(), + ); + for other in o { + for t in &last.types { + let mut vec = other.clone(); + vec.push(t.clone()); + out.push(vec); + } + } + types.push(last); + out + } + } + for t in get_all_single_types(&mut args) { + fn_types.push((t, out.clone())); + } + } + Some(')') => break, + Some(other) => { + eprintln!("Found char '{other}' in fn type when ')' or '(' was expected (will be treated as ')'). format is fn((input11 input12 output1) (input21 input22 output2))"); + break; + } + None => { + return Err(ParseError { + err: ParseErrors::FoundEofInType, + location: err_start_of_single_type, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }) + } + } + } + if in_fn_args { + if let Some(')') = file.peek() { + _ = file.next(); + closed_bracket_in_fn_args = true; + } + } + VSingleType::Function(fn_types) + } else if name.as_str() == "thread" { + let inner = parse_type_adv(file, true)?; + if !inner.1 { + eprintln!("Warn: Parsed type thread(inner_type), but might have missed the closing bracket!"); + } + VSingleType::Thread(inner.0) + } else { + VSingleType::EnumVariantS(name, { + let po = parse_type_adv(file, true)?; + if !po.1 { + // eprintln!("enum type should be closed by ')', but apparently wasn't?"); + assert_eq!(file.next(), Some(')')); + } + po.0 + }) + }; + } + Some(ch) => name.push(ch), + None => { + return Err(ParseError { + err: ParseErrors::FoundEofInType, + location: err_start_of_single_type, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }); + } + } + } + match name.trim().to_lowercase().as_str() { + "bool" => VSingleType::Bool, + "int" => VSingleType::Int, + "float" => VSingleType::Float, + "string" => VSingleType::String, + _ => { + return Err(ParseError { + err: ParseErrors::InvalidType(name.trim().to_string()), location: err_start_of_single_type, location_end: Some(*file.get_pos()), context: vec![], @@ -1069,32 +1161,17 @@ fn parse_single_type_adv( } } } - match name.trim().to_lowercase().as_str() { - "bool" => VSingleType::Bool, - "int" => VSingleType::Int, - "float" => VSingleType::Float, - "string" => VSingleType::String, - _ => { - return Err(ParseError { - err: ParseErrors::InvalidType(name.trim().to_string()), - location: err_start_of_single_type, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }); - } + None => { + return Err(ParseError { + err: ParseErrors::FoundEofInsteadOfType, + location: err_start_of_single_type, + location_end: Some(*file.get_pos()), + context: vec![], + info: None, + }) } - } - None => { - return Err(ParseError { - err: ParseErrors::FoundEofInsteadOfType, - location: err_start_of_single_type, - location_end: Some(*file.get_pos()), - context: vec![], - info: None, - }) - } - }, - closed_bracket_in_fn_args, - )) + }, + closed_bracket_in_fn_args, + )) + } } diff --git a/mers/src/script/code_parsed.rs b/mers/src/script/code_parsed.rs index 18bff7b..665d8b8 100644 --- a/mers/src/script/code_parsed.rs +++ b/mers/src/script/code_parsed.rs @@ -1,6 +1,6 @@ use std::fmt::{self, Display, Formatter, Pointer}; -use super::{global_info::GSInfo, val_data::VData, val_type::VType}; +use super::{code_macro::Macro, global_info::GSInfo, val_data::VData, val_type::VType}; pub enum SStatementEnum { Value(VData), @@ -17,6 +17,7 @@ pub enum SStatementEnum { Match(String, Vec<(SStatement, SStatement)>), IndexFixed(SStatement, usize), EnumVariant(String, SStatement), + Macro(Macro), } impl SStatementEnum { pub fn to(self) -> SStatement { @@ -172,6 +173,9 @@ impl SStatementEnum { write!(f, "{variant}: ")?; inner.fmtgs(f, info) } + Self::Macro(m) => { + write!(f, "!({m})") + } } } } diff --git a/mers/src/script/mod.rs b/mers/src/script/mod.rs index a0c5979..6dad008 100755 --- a/mers/src/script/mod.rs +++ b/mers/src/script/mod.rs @@ -1,4 +1,5 @@ pub mod builtins; +pub mod code_macro; pub mod code_parsed; pub mod code_runnable; pub mod global_info; diff --git a/mers/src/script/to_runnable.rs b/mers/src/script/to_runnable.rs index f951415..92fadab 100644 --- a/mers/src/script/to_runnable.rs +++ b/mers/src/script/to_runnable.rs @@ -16,6 +16,7 @@ use crate::{ use super::{ builtins::BuiltinFunction, + code_macro::Macro, code_parsed::{SBlock, SFunction, SStatement, SStatementEnum}, code_runnable::{RBlock, RFunction, RScript, RStatement, RStatementEnum}, }; @@ -351,7 +352,7 @@ fn statement( }); } } else { - return Err(ToRunnableError::UseOfUndefinedFunction(v.clone())); + return Err(ToRunnableError::UseOfUndefinedFunction(v.clone())); } } } @@ -565,6 +566,9 @@ fn statement( v } }, statement(s, ginfo, linfo)?), + SStatementEnum::Macro(m) => match m { + Macro::StaticMers(val) => RStatementEnum::Value(val.clone()), + }, } .to(); // if force_output_type is set, verify that the real output type actually fits in the forced one. diff --git a/mers/src/script/val_data.rs b/mers/src/script/val_data.rs index 6e19b30..a2cfc41 100755 --- a/mers/src/script/val_data.rs +++ b/mers/src/script/val_data.rs @@ -47,6 +47,9 @@ impl PartialEq for VDataEnum { } impl VData { + pub fn safe_to_share(&self) -> bool { + self.data.safe_to_share() + } pub fn out(&self) -> VType { VType { types: vec![self.out_single()], @@ -82,6 +85,15 @@ impl VDataEnum { // get() impl VDataEnum { + pub fn safe_to_share(&self) -> bool { + match self { + Self::Bool(_) | Self::Int(_) | Self::Float(_) | Self::String(_) | Self::Function(_) => { + true + } + Self::Tuple(v) | Self::List(_, v) => v.iter().all(|v| v.safe_to_share()), + Self::Thread(..) | Self::Reference(..) | Self::EnumVariant(..) => false, + } + } pub fn noenum(self) -> VData { match self { Self::EnumVariant(_, v) => *v,