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)]
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 <code>\".");
std::process::exit(100);
@ -43,16 +51,16 @@ fn main() {
} else {
if let Some(prev_char) = prev_char {
match prev_char {
'i' => {
match ch {
'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<notify::Event, notify::Error>| {
notify::recommended_watcher(
move |event: Result<notify::Event, notify::Error>| {
if let Ok(event) = event {
match &event.kind {
notify::EventKind::Modify(notify::event::ModifyKind::Data(_)) => {
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());
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)),
}
Err(e) => println!(
"{}",
e.with_file(&file)
),
}
} else {
println!("can't read file at {:?}!", temp_file);
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();
@ -116,7 +143,10 @@ fn main() {
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();
};
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);
}
}
}

View File

@ -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,10 +469,70 @@ 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();
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)
.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 <var> <var_type> = <statement>"), 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" => {
@ -582,12 +641,8 @@ fn parse_statement_adv(
}
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()
}
"true" => break SStatementEnum::Value(VDataEnum::Bool(true).to()).into(),
"false" => break SStatementEnum::Value(VDataEnum::Bool(false).to()).into(),
_ => {
// int, float, var
break {
@ -604,8 +659,7 @@ fn parse_statement_adv(
pot_float.push(ch);
}
if let Ok(v) = format!("{start}.{pot_float}").parse() {
SStatementEnum::Value(VDataEnum::Float(v).to())
.into()
SStatementEnum::Value(VDataEnum::Float(v).to()).into()
} else {
file.set_pos(pos);
SStatementEnum::Value(VDataEnum::Int(v).to()).into()
@ -620,15 +674,13 @@ fn parse_statement_adv(
SStatementEnum::Variable(start[1..].to_string(), true)
.into()
} else {
SStatementEnum::Variable(start.to_string(), false)
.into()
SStatementEnum::Variable(start.to_string(), false).into()
}
}
};
}
}
}
}
Some('(') => {
// parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise)
let name = start.trim();

View File

@ -43,18 +43,25 @@ impl SFunction {
pub struct SStatement {
pub output_to: Option<(String, usize)>,
pub statement: Box<SStatementEnum>,
pub force_output_type: Option<VType>,
}
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<VType>) -> Self {
self.force_output_type = force_output_type;
self
}
}
#[derive(Debug)]
@ -126,6 +133,7 @@ pub mod to_runnable {
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
WrongArgsForLibFunction(String, Vec<VType>),
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<RStatementEnum>,
force_output_type: Option<VType>,
}
impl RStatement {
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 {
// `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, "]")?;