From 9c1564b426b79fd35e0bd312310fc41a7c16afff Mon Sep 17 00:00:00 2001 From: Dummi26 Date: Mon, 24 Apr 2023 15:55:37 +0200 Subject: [PATCH] added `var_name var_type = statement` syntax, which forces the statements return type to fit in var_type. This is useful if your initial value doesn't cover all types (like [ ...], which is a list with an inner type that cannot exist because there are 0 valid types, making the list completely useless. but `arr [int/string ...] = []` expands that inner type to int/string, making the list usable) --- mers/src/main.rs | 129 +++++++++------ mers/src/parse/parse.rs | 350 ++++++++++++++++++++++----------------- mers/src/script/block.rs | 34 +++- 3 files changed, 313 insertions(+), 200 deletions(-) diff --git a/mers/src/main.rs b/mers/src/main.rs index 5ebe5ca..4e78718 100755 --- a/mers/src/main.rs +++ b/mers/src/main.rs @@ -10,8 +10,16 @@ pub mod script; #[allow(unused)] fn main() { let args: Vec<_> = std::env::args().skip(1).collect(); + #[cfg(debug_assertions)] + let args = if args.len() == 0 { + let mut args = args; + args.push("../script.mers".to_owned()); + args + } else { + args + }; let path = std::env::args().nth(1).unwrap(); - let script = parse::parse::parse(&mut match args.len() { + let mut file = match args.len() { 0 => { println!("Please provide some arguments, such as the path to a file or \"-e \"."); std::process::exit(100); @@ -43,16 +51,16 @@ fn main() { } else { if let Some(prev_char) = prev_char { match prev_char { - 'i' => { - match ch { - 't' => interactive_use_new_terminal = true, - _ => eprintln!("Ignoring i+{ch}. (unknown adv char)"), - } - } + 'i' => match ch { + 't' => interactive_use_new_terminal = true, + _ => eprintln!("Ignoring i+{ch}. (unknown adv char)"), + }, _ => (), } } else { - eprintln!("Ignoring advanced args because there was no previous argument."); + eprintln!( + "Ignoring advanced args because there was no previous argument." + ); } advanced = false; } @@ -64,39 +72,58 @@ fn main() { let (contents, path) = match interactive { 1 => { // basic: open file and watch for fs changes - let temp_file_edit = edit::Builder::new().suffix(".mers").tempfile().unwrap(); + let temp_file_edit = + edit::Builder::new().suffix(".mers").tempfile().unwrap(); let temp_file = temp_file_edit.path(); eprintln!("Using temporary file at {temp_file:?}. Save the file to update the output here."); if let Ok(_) = std::fs::write(&temp_file, []) { if let Ok(mut watcher) = { let temp_file = temp_file.to_path_buf(); // the file watcher - notify::recommended_watcher(move |event: Result| { - if let Ok(event) = event { - match &event.kind { - notify::EventKind::Modify(notify::event::ModifyKind::Data(_)) => { - println!(); - if let Ok(file_contents) = fs::read_to_string(&temp_file) { - let mut file = parse::file::File::new(file_contents, temp_file.clone()); - match parse::parse::parse(&mut file) { - Ok(func) => { - println!(" - - - - -"); - let output = func.run(vec![]); - println!(" - - - - -"); - println!("{}", output); - }, - Err(e) => println!("{}", e.with_file(&file)), + notify::recommended_watcher( + move |event: Result| { + if let Ok(event) = event { + match &event.kind { + notify::EventKind::Modify( + notify::event::ModifyKind::Data(_), + ) => { + println!(); + if let Ok(file_contents) = + fs::read_to_string(&temp_file) + { + let mut file = parse::file::File::new( + file_contents, + temp_file.clone(), + ); + match parse::parse::parse(&mut file) { + Ok(func) => { + println!(" - - - - -"); + let output = func.run(vec![]); + println!(" - - - - -"); + println!("{}", output); + } + Err(e) => println!( + "{}", + e.with_file(&file) + ), + } + } else { + println!( + "can't read file at {:?}!", + temp_file + ); + std::process::exit(105); + } } - } else { - println!("can't read file at {:?}!", temp_file); - std::process::exit(105); + _ => (), } } - _ => (), - } - } - })} { - if let Ok(_) = watcher.watch(&temp_file, notify::RecursiveMode::NonRecursive) { + }, + ) + } { + if let Ok(_) = watcher + .watch(&temp_file, notify::RecursiveMode::NonRecursive) + { if interactive_use_new_terminal { if let Ok(term) = std::env::var("TERM") { let editor = edit::get_editor().unwrap(); @@ -111,12 +138,15 @@ fn main() { .unwrap(); } } else { - edit::edit_file(temp_file_edit.path()).unwrap(); + edit::edit_file(temp_file_edit.path()).unwrap(); } temp_file_edit.close().unwrap(); std::process::exit(0); } else { - println!("Cannot watch the file at \"{:?}\" for hot-reload.", temp_file); + println!( + "Cannot watch the file at \"{:?}\" for hot-reload.", + temp_file + ); std::process::exit(104); } } else { @@ -131,12 +161,8 @@ fn main() { } _ => (String::new(), String::new()), }; - parse::file::File::new( - contents, - path.into() - ) - } - else if execute { + parse::file::File::new(contents, path.into()) + } else if execute { parse::file::File::new( args.iter().skip(1).fold(String::new(), |mut s, v| { if !s.is_empty() { @@ -155,12 +181,19 @@ fn main() { parse::file::File::new(std::fs::read_to_string(&args[0]).unwrap(), path.into()) } } - }) - .unwrap(); - println!(" - - - - -"); - let start = Instant::now(); - let out = script.run(std::env::args().skip(2).collect()); - let elapsed = start.elapsed(); - println!(" - - - - -"); - println!("Output ({}s)\n{out}", elapsed.as_secs_f64()); + }; + match parse::parse::parse(&mut file) { + Ok(script) => { + println!(" - - - - -"); + let start = Instant::now(); + let out = script.run(std::env::args().skip(2).collect()); + let elapsed = start.elapsed(); + println!(" - - - - -"); + println!("Output ({}s)\n{out}", elapsed.as_secs_f64()); + } + Err(e) => { + println!("Couldn't compile:\n{e}"); + std::process::exit(99); + } + } } diff --git a/mers/src/parse/parse.rs b/mers/src/parse/parse.rs index 48948db..0d6a671 100755 --- a/mers/src/parse/parse.rs +++ b/mers/src/parse/parse.rs @@ -240,6 +240,7 @@ impl std::fmt::Display for ParseError { } #[derive(Debug)] pub enum ParseErrors { + StatementCannotStartWith(char), FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement, FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement, FoundEofInBlockBeforeStatementOrClosingCurlyBracket, @@ -256,6 +257,9 @@ pub enum ParseErrors { impl std::fmt::Display for ParseErrors { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { + Self::StatementCannotStartWith(ch) => { + write!(f, "statements cannot start with the {ch} character.",) + } Self::FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement => write!( f, "closing round bracket in single-statement block before any statement" @@ -451,18 +455,13 @@ fn parse_statement_adv( let mut start = String::new(); loop { fn is_delimeter(ch: char) -> bool { - matches!(ch, '}' | ']' | ')' | '.') + matches!(ch, '}' | ']' | ')' | '.' | '=') } let nchar = match file.peek() { Some(ch) if is_delimeter(ch) => Some(ch), _ => file.next(), }; match nchar { - Some('=') => { - let start = start.trim(); - let derefs = start.chars().take_while(|c| *c == '*').count(); - break parse_statement(file)?.output_to(start[derefs..].to_owned(), derefs); - } Some(':') => { return Ok(SStatement::new(SStatementEnum::EnumVariant( start, @@ -470,162 +469,215 @@ fn parse_statement_adv( ))); } 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![], + }); + } + 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(); + break match parse_statement(file) { + Ok(v) => v, + 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); + } + } + .output_to(start[derefs..].to_owned(), derefs); + } + // var type = statement + let file_pos_before_pot_type = *file.get_pos(); + let parsed_type = parse_type(file); file.skip_whitespaces(); if let Some('=') = file.peek() { - continue; - } else { + file.next(); + let err_equals_sign = *file.get_pos(); 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, - ) - .into(); + let derefs = start.chars().take_while(|c| *c == '*').count(); + break match parse_statement(file) { + Ok(v) => v, + 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); } - "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)?); + } + .output_to(start[derefs..].to_owned(), derefs) + .force_output_type(Some(match parsed_type { + Ok(v) => v, + Err(mut e) => { + e.context.push(( + format!("interpreted this as an assignment to a variable with the format = "), Some((err_start_of_statement, Some(err_equals_sign))) + )); + return Err(e); + } + })); + } + // nevermind, not var type = statement + file.set_pos(file_pos_before_pot_type); + // 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, } - break SStatementEnum::If(condition, then, then_else).into(); } - "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 { + let func = parse_function(file, Some(err_start_of_statement))?; + break SStatementEnum::FunctionDefinition( + Some(fn_name.trim().to_string()), + func, + ) + .into(); + } + "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).into(); + } + "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 - }, - parse_statement(file)?, - parse_statement(file)?, - ) - .into() - } - "while" => { - break SStatementEnum::While(parse_statement(file)?).into(); - } - "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).into(); - } - "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).into(); - } - "true" => { - break SStatementEnum::Value(VDataEnum::Bool(true).to()).into() - } - "false" => { - break SStatementEnum::Value(VDataEnum::Bool(false).to()).into() - } - _ => { - // 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()) - .into() - } else { - file.set_pos(pos); - SStatementEnum::Value(VDataEnum::Int(v).to()).into() - } + buf.push(ch); } else { + break; + } + } + buf + }, + parse_statement(file)?, + parse_statement(file)?, + ) + .into() + } + "while" => { + break SStatementEnum::While(parse_statement(file)?).into(); + } + "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).into(); + } + "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).into(); + } + "true" => break SStatementEnum::Value(VDataEnum::Bool(true).to()).into(), + "false" => break SStatementEnum::Value(VDataEnum::Bool(false).to()).into(), + _ => { + // 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()).into() + } else { + file.set_pos(pos); SStatementEnum::Value(VDataEnum::Int(v).to()).into() } - // } else if let Ok(v) = start.parse() { - // SStatementEnum::Value(VDataEnum::Float(v).to()).into() } else { - if start.starts_with('&') { - SStatementEnum::Variable(start[1..].to_string(), true) - .into() - } else { - SStatementEnum::Variable(start.to_string(), false) - .into() - } + SStatementEnum::Value(VDataEnum::Int(v).to()).into() } - }; - } + // } else if let Ok(v) = start.parse() { + // SStatementEnum::Value(VDataEnum::Float(v).to()).into() + } else { + if start.starts_with('&') { + SStatementEnum::Variable(start[1..].to_string(), true) + .into() + } else { + SStatementEnum::Variable(start.to_string(), false).into() + } + } + }; } } } diff --git a/mers/src/script/block.rs b/mers/src/script/block.rs index f3afc49..0b2c4eb 100755 --- a/mers/src/script/block.rs +++ b/mers/src/script/block.rs @@ -43,18 +43,25 @@ impl SFunction { pub struct SStatement { pub output_to: Option<(String, usize)>, pub statement: Box, + pub force_output_type: Option, } impl SStatement { pub fn new(statement: SStatementEnum) -> Self { Self { output_to: None, statement: Box::new(statement), + force_output_type: None, } } pub fn output_to(mut self, var: String, derefs: usize) -> Self { self.output_to = Some((var, derefs)); self } + // forces the statement's output to fit in a certain type. + pub fn force_output_type(mut self, force_output_type: Option) -> Self { + self.force_output_type = force_output_type; + self + } } #[derive(Debug)] @@ -126,6 +133,7 @@ pub mod to_runnable { WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec), WrongArgsForLibFunction(String, Vec), ForLoopContainerHasNoInnerTypes, + StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(VType, VType, VType), } impl Debug for ToRunnableError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { @@ -173,6 +181,9 @@ pub mod to_runnable { Self::ForLoopContainerHasNoInnerTypes => { write!(f, "For loop: container had no inner types, cannot iterate.") } + Self::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(required, real, problematic) => write!(f, + "the statement requires its output type to be {required}, but its real output type is {real}, which doesn not fit in the required type because of the problematic types {problematic}." + ), } } } @@ -640,6 +651,18 @@ pub mod to_runnable { }, statement(s, ginfo, linfo)?), } .to(); + // if force_output_type is set, verify that the real output type actually fits in the forced one. + if let Some(force_opt) = &s.force_output_type { + let real_output_type = statement.out(); + let problematic_types = real_output_type.fits_in(force_opt); + eprintln!("Real: {real_output_type}"); + eprintln!("Prob: {problematic_types:?}"); + if problematic_types.is_empty() { + statement.force_output_type = Some(force_opt.clone()); + } else { + return Err(ToRunnableError::StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(force_opt.clone(), real_output_type, VType { types: problematic_types })) + } + } if let Some((opt, derefs)) = &s.output_to { if let Some((var_id, var_out)) = linfo.vars.get(opt) { let out = statement.out(); @@ -681,8 +704,7 @@ pub mod to_runnable { statement.output_to = Some((ginfo.vars, *derefs)); ginfo.vars += 1; } - } - Ok(statement) + } Ok(statement) } } @@ -763,6 +785,7 @@ impl RFunction { pub struct RStatement { output_to: Option<(usize, usize)>, statement: Box, + force_output_type: Option, } impl RStatement { pub fn run(&self, vars: &Vec>, libs: &Arc>) -> VData { @@ -784,11 +807,15 @@ impl RStatement { } } pub fn out(&self) -> VType { + // `a = b` evaluates to [] if self.output_to.is_some() { return VType { types: vec![VSingleType::Tuple(vec![])], }; } + if let Some(t) = &self.force_output_type { + return t.clone(); + } self.statement.out() } } @@ -1028,6 +1055,7 @@ impl RStatementEnum { RStatement { output_to: None, statement: Box::new(self), + force_output_type: None, } } } @@ -1245,7 +1273,7 @@ impl Display for VDataEnum { } } match self { - Self::List(..) => write!(f, "...")?, + Self::List(..) => write!(f, " ...")?, _ => (), } write!(f, "]")?;