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)

This commit is contained in:
Dummi26 2023-04-24 15:55:37 +02:00
parent 14d004d848
commit 9c1564b426
3 changed files with 313 additions and 200 deletions

View File

@ -10,8 +10,16 @@ pub mod script;
#[allow(unused)] #[allow(unused)]
fn main() { fn main() {
let args: Vec<_> = std::env::args().skip(1).collect(); 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 path = std::env::args().nth(1).unwrap();
let script = parse::parse::parse(&mut match args.len() { let mut file = match args.len() {
0 => { 0 => {
println!("Please provide some arguments, such as the path to a file or \"-e <code>\"."); println!("Please provide some arguments, such as the path to a file or \"-e <code>\".");
std::process::exit(100); std::process::exit(100);
@ -43,16 +51,16 @@ fn main() {
} else { } else {
if let Some(prev_char) = prev_char { if let Some(prev_char) = prev_char {
match prev_char { match prev_char {
'i' => { 'i' => match ch {
match ch { 't' => interactive_use_new_terminal = true,
't' => interactive_use_new_terminal = true, _ => eprintln!("Ignoring i+{ch}. (unknown adv char)"),
_ => eprintln!("Ignoring i+{ch}. (unknown adv char)"), },
}
}
_ => (), _ => (),
} }
} else { } else {
eprintln!("Ignoring advanced args because there was no previous argument."); eprintln!(
"Ignoring advanced args because there was no previous argument."
);
} }
advanced = false; advanced = false;
} }
@ -64,39 +72,58 @@ fn main() {
let (contents, path) = match interactive { let (contents, path) = match interactive {
1 => { 1 => {
// basic: open file and watch for fs changes // 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(); let temp_file = temp_file_edit.path();
eprintln!("Using temporary file at {temp_file:?}. Save the file to update the output here."); 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(_) = std::fs::write(&temp_file, []) {
if let Ok(mut watcher) = { if let Ok(mut watcher) = {
let temp_file = temp_file.to_path_buf(); let temp_file = temp_file.to_path_buf();
// the file watcher // the file watcher
notify::recommended_watcher(move |event: Result<notify::Event, notify::Error>| { notify::recommended_watcher(
if let Ok(event) = event { move |event: Result<notify::Event, notify::Error>| {
match &event.kind { if let Ok(event) = event {
notify::EventKind::Modify(notify::event::ModifyKind::Data(_)) => { match &event.kind {
println!(); notify::EventKind::Modify(
if let Ok(file_contents) = fs::read_to_string(&temp_file) { notify::event::ModifyKind::Data(_),
let mut file = parse::file::File::new(file_contents, temp_file.clone()); ) => {
match parse::parse::parse(&mut file) { println!();
Ok(func) => { if let Ok(file_contents) =
println!(" - - - - -"); fs::read_to_string(&temp_file)
let output = func.run(vec![]); {
println!(" - - - - -"); let mut file = parse::file::File::new(
println!("{}", output); file_contents,
}, temp_file.clone(),
Err(e) => println!("{}", e.with_file(&file)), );
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
if let Ok(_) = watcher.watch(&temp_file, notify::RecursiveMode::NonRecursive) { .watch(&temp_file, notify::RecursiveMode::NonRecursive)
{
if interactive_use_new_terminal { if interactive_use_new_terminal {
if let Ok(term) = std::env::var("TERM") { if let Ok(term) = std::env::var("TERM") {
let editor = edit::get_editor().unwrap(); let editor = edit::get_editor().unwrap();
@ -111,12 +138,15 @@ fn main() {
.unwrap(); .unwrap();
} }
} else { } else {
edit::edit_file(temp_file_edit.path()).unwrap(); edit::edit_file(temp_file_edit.path()).unwrap();
} }
temp_file_edit.close().unwrap(); temp_file_edit.close().unwrap();
std::process::exit(0); std::process::exit(0);
} else { } 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); std::process::exit(104);
} }
} else { } else {
@ -131,12 +161,8 @@ fn main() {
} }
_ => (String::new(), String::new()), _ => (String::new(), String::new()),
}; };
parse::file::File::new( parse::file::File::new(contents, path.into())
contents, } else if execute {
path.into()
)
}
else if execute {
parse::file::File::new( parse::file::File::new(
args.iter().skip(1).fold(String::new(), |mut s, v| { args.iter().skip(1).fold(String::new(), |mut s, v| {
if !s.is_empty() { if !s.is_empty() {
@ -155,12 +181,19 @@ fn main() {
parse::file::File::new(std::fs::read_to_string(&args[0]).unwrap(), path.into()) parse::file::File::new(std::fs::read_to_string(&args[0]).unwrap(), path.into())
} }
} }
}) };
.unwrap(); match parse::parse::parse(&mut file) {
println!(" - - - - -"); Ok(script) => {
let start = Instant::now(); println!(" - - - - -");
let out = script.run(std::env::args().skip(2).collect()); let start = Instant::now();
let elapsed = start.elapsed(); let out = script.run(std::env::args().skip(2).collect());
println!(" - - - - -"); let elapsed = start.elapsed();
println!("Output ({}s)\n{out}", elapsed.as_secs_f64()); println!(" - - - - -");
println!("Output ({}s)\n{out}", elapsed.as_secs_f64());
}
Err(e) => {
println!("Couldn't compile:\n{e}");
std::process::exit(99);
}
}
} }

View File

@ -240,6 +240,7 @@ impl std::fmt::Display for ParseError {
} }
#[derive(Debug)] #[derive(Debug)]
pub enum ParseErrors { pub enum ParseErrors {
StatementCannotStartWith(char),
FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement, FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement,
FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement, FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement,
FoundEofInBlockBeforeStatementOrClosingCurlyBracket, FoundEofInBlockBeforeStatementOrClosingCurlyBracket,
@ -256,6 +257,9 @@ pub enum ParseErrors {
impl std::fmt::Display for ParseErrors { impl std::fmt::Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self { match self {
Self::StatementCannotStartWith(ch) => {
write!(f, "statements cannot start with the {ch} character.",)
}
Self::FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement => write!( Self::FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement => write!(
f, f,
"closing round bracket in single-statement block before any statement" "closing round bracket in single-statement block before any statement"
@ -451,18 +455,13 @@ fn parse_statement_adv(
let mut start = String::new(); let mut start = String::new();
loop { loop {
fn is_delimeter(ch: char) -> bool { fn is_delimeter(ch: char) -> bool {
matches!(ch, '}' | ']' | ')' | '.') matches!(ch, '}' | ']' | ')' | '.' | '=')
} }
let nchar = match file.peek() { let nchar = match file.peek() {
Some(ch) if is_delimeter(ch) => Some(ch), Some(ch) if is_delimeter(ch) => Some(ch),
_ => file.next(), _ => file.next(),
}; };
match nchar { 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(':') => { Some(':') => {
return Ok(SStatement::new(SStatementEnum::EnumVariant( return Ok(SStatement::new(SStatementEnum::EnumVariant(
start, start,
@ -470,162 +469,215 @@ fn parse_statement_adv(
))); )));
} }
Some(ch) if ch.is_whitespace() || is_delimeter(ch) => { 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(); file.skip_whitespaces();
if let Some('=') = file.peek() { if let Some('=') = file.peek() {
continue; file.next();
} else { let err_equals_sign = *file.get_pos();
let start = start.trim(); let start = start.trim();
match start { let derefs = start.chars().take_while(|c| *c == '*').count();
"fn" => { break match parse_statement(file) {
file.skip_whitespaces(); Ok(v) => v,
let mut fn_name = String::new(); Err(mut e) => {
loop { e.context.push((
match file.next() { format!(
Some('(') => break, "statement was supposed to be assigned to variable {start}"
Some(ch) => fn_name.push(ch), ),
None => break, Some((err_start_of_statement, Some(err_equals_sign))),
} ));
} return Err(e);
let func = parse_function(file, Some(err_start_of_statement))?;
break SStatementEnum::FunctionDefinition(
Some(fn_name.trim().to_string()),
func,
)
.into();
} }
"if" => { }
// TODO: Else .output_to(start[derefs..].to_owned(), derefs)
let condition = parse_statement(file)?; .force_output_type(Some(match parsed_type {
let then = parse_statement(file)?; Ok(v) => v,
let mut then_else = None; Err(mut e) => {
file.skip_whitespaces(); e.context.push((
let i = file.get_pos().current_char_index; format!("interpreted this as an assignment to a variable with the format <var> <var_type> = <statement>"), Some((err_start_of_statement, Some(err_equals_sign)))
if file[i..].starts_with("else ") { ));
while let Some('e' | 'l' | 's') = file.next() {} return Err(e);
then_else = Some(parse_statement(file)?); }
}));
}
// 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" => { let func = parse_function(file, Some(err_start_of_statement))?;
break SStatementEnum::For( break SStatementEnum::FunctionDefinition(
{ Some(fn_name.trim().to_string()),
file.skip_whitespaces(); func,
let mut buf = String::new(); )
loop { .into();
if let Some(ch) = file.next() { }
if ch.is_whitespace() { "if" => {
break; // TODO: Else
} let condition = parse_statement(file)?;
buf.push(ch); let then = parse_statement(file)?;
} else { 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; break;
} }
} buf.push(ch);
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 { } 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() SStatementEnum::Value(VDataEnum::Int(v).to()).into()
} }
// } else if let Ok(v) = start.parse() {
// SStatementEnum::Value(VDataEnum::Float(v).to()).into()
} else { } else {
if start.starts_with('&') { SStatementEnum::Value(VDataEnum::Int(v).to()).into()
SStatementEnum::Variable(start[1..].to_string(), true)
.into()
} else {
SStatementEnum::Variable(start.to_string(), false)
.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()
}
}
};
} }
} }
} }

View File

@ -43,18 +43,25 @@ impl SFunction {
pub struct SStatement { pub struct SStatement {
pub output_to: Option<(String, usize)>, pub output_to: Option<(String, usize)>,
pub statement: Box<SStatementEnum>, pub statement: Box<SStatementEnum>,
pub force_output_type: Option<VType>,
} }
impl SStatement { impl SStatement {
pub fn new(statement: SStatementEnum) -> Self { pub fn new(statement: SStatementEnum) -> Self {
Self { Self {
output_to: None, output_to: None,
statement: Box::new(statement), statement: Box::new(statement),
force_output_type: None,
} }
} }
pub fn output_to(mut self, var: String, derefs: usize) -> Self { pub fn output_to(mut self, var: String, derefs: usize) -> Self {
self.output_to = Some((var, derefs)); self.output_to = Some((var, derefs));
self self
} }
// forces the statement's output to fit in a certain type.
pub fn force_output_type(mut self, force_output_type: Option<VType>) -> Self {
self.force_output_type = force_output_type;
self
}
} }
#[derive(Debug)] #[derive(Debug)]
@ -126,6 +133,7 @@ pub mod to_runnable {
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>), WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
WrongArgsForLibFunction(String, Vec<VType>), WrongArgsForLibFunction(String, Vec<VType>),
ForLoopContainerHasNoInnerTypes, ForLoopContainerHasNoInnerTypes,
StatementRequiresOutputTypeToBeAButItActuallyOutputsBWhichDoesNotFitInA(VType, VType, VType),
} }
impl Debug for ToRunnableError { impl Debug for ToRunnableError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
@ -173,6 +181,9 @@ pub mod to_runnable {
Self::ForLoopContainerHasNoInnerTypes => { Self::ForLoopContainerHasNoInnerTypes => {
write!(f, "For loop: container had no inner types, cannot iterate.") 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)?), }, statement(s, ginfo, linfo)?),
} }
.to(); .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((opt, derefs)) = &s.output_to {
if let Some((var_id, var_out)) = linfo.vars.get(opt) { if let Some((var_id, var_out)) = linfo.vars.get(opt) {
let out = statement.out(); let out = statement.out();
@ -681,8 +704,7 @@ pub mod to_runnable {
statement.output_to = Some((ginfo.vars, *derefs)); statement.output_to = Some((ginfo.vars, *derefs));
ginfo.vars += 1; ginfo.vars += 1;
} }
} } Ok(statement)
Ok(statement)
} }
} }
@ -763,6 +785,7 @@ impl RFunction {
pub struct RStatement { pub struct RStatement {
output_to: Option<(usize, usize)>, output_to: Option<(usize, usize)>,
statement: Box<RStatementEnum>, statement: Box<RStatementEnum>,
force_output_type: Option<VType>,
} }
impl RStatement { impl RStatement {
pub fn run(&self, vars: &Vec<Am<VData>>, libs: &Arc<Vec<libs::Lib>>) -> VData { pub fn run(&self, vars: &Vec<Am<VData>>, libs: &Arc<Vec<libs::Lib>>) -> VData {
@ -784,11 +807,15 @@ impl RStatement {
} }
} }
pub fn out(&self) -> VType { pub fn out(&self) -> VType {
// `a = b` evaluates to []
if self.output_to.is_some() { if self.output_to.is_some() {
return VType { return VType {
types: vec![VSingleType::Tuple(vec![])], types: vec![VSingleType::Tuple(vec![])],
}; };
} }
if let Some(t) = &self.force_output_type {
return t.clone();
}
self.statement.out() self.statement.out()
} }
} }
@ -1028,6 +1055,7 @@ impl RStatementEnum {
RStatement { RStatement {
output_to: None, output_to: None,
statement: Box::new(self), statement: Box::new(self),
force_output_type: None,
} }
} }
} }
@ -1245,7 +1273,7 @@ impl Display for VDataEnum {
} }
} }
match self { match self {
Self::List(..) => write!(f, "...")?, Self::List(..) => write!(f, " ...")?,
_ => (), _ => (),
} }
write!(f, "]")?; write!(f, "]")?;