implemented better errors (not finished). this also prevents crashes (because there were todo!()s and panic!()s as temporary errors before) when an error is found while in interactive mode.

This commit is contained in:
Dummi26 2023-04-14 20:47:13 +02:00
parent dfd83fa581
commit b2a36416cf
6 changed files with 473 additions and 96 deletions

View File

@ -174,6 +174,14 @@ pub enum LaunchError {
NoStdio, NoStdio,
CouldNotSpawnProcess(io::Error), CouldNotSpawnProcess(io::Error),
} }
impl std::fmt::Display for LaunchError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoStdio => write!(f, "couldn't get stdio (stdin/stdout) from child process."),
Self::CouldNotSpawnProcess(e) => write!(f, "couldn't spawn child process: {e}."),
}
}
}
pub trait DirectReader { pub trait DirectReader {
fn line(&mut self) -> Result<String, io::Error>; fn line(&mut self) -> Result<String, io::Error>;

View File

@ -1,4 +1,4 @@
use std::{fs, sync::Arc, time::Instant}; use std::{fs, time::Instant};
use notify::Watcher as FsWatcher; use notify::Watcher as FsWatcher;
@ -75,32 +75,22 @@ fn main() {
if let Ok(event) = event { if let Ok(event) = event {
match &event.kind { 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) { if let Ok(file_contents) = fs::read_to_string(&temp_file) {
let mut file = parse::file::File::new(file_contents, temp_file.clone()); let mut file = parse::file::File::new(file_contents, temp_file.clone());
static_assertions::const_assert_eq!(parse::parse::PARSE_VERSION, 0); match parse::parse::parse(&mut file) {
let mut ginfo = script::block::to_runnable::GInfo::default(); Ok(func) => {
let libs = parse::parse::parse_step_lib_paths(&mut file); println!(" - - - - -");
match parse::parse::parse_step_interpret(&mut file) { let output = func.run(vec![]);
Ok(func) => { println!(" - - - - -");
let libs = parse::parse::parse_step_libs_load(libs, &mut ginfo); println!("{}", output);
ginfo.libs = Arc::new(libs); },
match parse::parse::parse_step_compile(func, &mut ginfo) { Err(e) => println!("{}", e.with_file(&file)),
Ok(func) => {
println!();
println!(" - - - - -");
let output = func.run(vec![]);
println!(" - - - - -");
println!("{}", output);
}
Err(e) => eprintln!("Couldn't compile:\n{e:?}"),
}
}
Err(e) =>eprintln!("Couldn't interpret:\n{e:?}"),
}
} 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);
}
} }
_ => (), _ => (),
} }

View File

@ -8,20 +8,24 @@ pub struct File {
path: PathBuf, path: PathBuf,
data: String, data: String,
chars: Vec<(usize, char)>, chars: Vec<(usize, char)>,
// contains the byte indices of all newline characters
newlines: Vec<usize>,
pos: FilePosition, pos: FilePosition,
ppos: FilePosition,
} }
#[derive(Clone, Copy)] #[derive(Clone, Copy, Debug)]
pub struct FilePosition { pub struct FilePosition {
current_char_index: usize, pub current_char_index: usize,
current_line: usize, pub current_line: usize,
current_column: usize, pub current_column: usize,
} }
impl Display for FilePosition { impl Display for FilePosition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"line {}, col. {}", "line {}, col. {}",
self.current_line, self.current_column self.current_line + 1,
self.current_column + 1
) )
} }
} }
@ -78,16 +82,23 @@ impl File {
if !data.ends_with('\n') { if !data.ends_with('\n') {
data.push('\n'); data.push('\n');
} }
let chars = data.char_indices().collect(); let chars: Vec<_> = data.char_indices().collect();
let newlines: Vec<_> = chars
.iter()
.filter_map(|v| if v.1 == '\n' { Some(v.0) } else { None })
.collect();
let pos = FilePosition {
current_char_index: 0,
current_line: 0,
current_column: 0,
};
Self { Self {
path, path,
data, data,
chars, chars,
pos: FilePosition { newlines,
current_char_index: 0, pos,
current_line: 0, ppos: pos,
current_column: 0,
},
} }
} }
pub fn skip_whitespaces(&mut self) { pub fn skip_whitespaces(&mut self) {
@ -104,24 +115,53 @@ impl File {
pub fn get_pos(&self) -> &FilePosition { pub fn get_pos(&self) -> &FilePosition {
&self.pos &self.pos
} }
pub fn get_ppos(&self) -> &FilePosition {
&self.pos
}
pub fn set_pos(&mut self, pos: FilePosition) { pub fn set_pos(&mut self, pos: FilePosition) {
self.pos = pos; self.pos = pos;
} }
pub fn get_line(&self) -> usize {
self.pos.current_line
}
pub fn get_column(&self) -> usize {
self.pos.current_column
}
pub fn get_char_index(&self) -> usize {
self.pos.current_char_index
}
pub fn get_char(&self, index: usize) -> Option<char> { pub fn get_char(&self, index: usize) -> Option<char> {
match self.chars.get(index) { match self.chars.get(index) {
Some(v) => Some(v.1), Some(v) => Some(v.1),
None => None, None => None,
} }
} }
pub fn get_line(&self, line_nr: usize) -> Option<&str> {
if self.newlines.len() > line_nr {
Some(if line_nr == 0 {
&self.data[0..self.newlines[0]]
} else {
&self.data[self.newlines[line_nr - 1] + 1..self.newlines[line_nr]]
})
} else if self.newlines.len() == line_nr {
Some(if line_nr == 0 {
self.data.as_str()
} else {
&self.data[self.newlines[line_nr - 1] + 1..]
})
} else {
None
}
}
// returns the lines. both from and to are inclusive.
pub fn get_lines(&self, from: usize, to: usize) -> Option<&str> {
let start_index = if from == 0 {
0
} else if from <= self.newlines.len() {
self.newlines[from - 1] + 1
} else {
return None;
};
let end_index = if to == self.newlines.len() {
self.data.len()
} else if to < self.newlines.len() {
self.newlines[to]
} else {
return None;
};
Some(&self.data[start_index..end_index])
}
pub fn next_line(&mut self) -> String { pub fn next_line(&mut self) -> String {
let mut o = String::new(); let mut o = String::new();
for ch in self { for ch in self {
@ -144,6 +184,7 @@ impl File {
impl Iterator for File { impl Iterator for File {
type Item = char; type Item = char;
fn next(&mut self) -> Option<Self::Item> { fn next(&mut self) -> Option<Self::Item> {
self.ppos = self.pos;
let o = self.chars.get(self.pos.current_char_index); let o = self.chars.get(self.pos.current_char_index);
self.pos.current_char_index += 1; self.pos.current_char_index += 1;
match o { match o {

View File

@ -17,30 +17,68 @@ use super::file::File;
#[derive(Debug)] #[derive(Debug)]
pub enum ScriptError { pub enum ScriptError {
CannotFindPathForLibrary(CannotFindPathForLibrary),
ParseError(ParseError), ParseError(ParseError),
UnableToLoadLibrary(UnableToLoadLibrary),
ToRunnableError(ToRunnableError), ToRunnableError(ToRunnableError),
} }
impl From<CannotFindPathForLibrary> for ScriptError {
fn from(value: CannotFindPathForLibrary) -> Self {
Self::CannotFindPathForLibrary(value)
}
}
impl From<ParseError> for ScriptError { impl From<ParseError> for ScriptError {
fn from(value: ParseError) -> Self { fn from(value: ParseError) -> Self {
Self::ParseError(value) Self::ParseError(value)
} }
} }
impl From<UnableToLoadLibrary> for ScriptError {
fn from(value: UnableToLoadLibrary) -> Self {
Self::UnableToLoadLibrary(value)
}
}
impl From<ToRunnableError> for ScriptError { impl From<ToRunnableError> for ScriptError {
fn from(value: ToRunnableError) -> Self { fn from(value: ToRunnableError) -> Self {
Self::ToRunnableError(value) Self::ToRunnableError(value)
} }
} }
#[derive(Debug)] impl std::fmt::Display for ScriptError {
pub enum ParseError {} fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::CannotFindPathForLibrary(e) => write!(f, "{e}"),
Self::ParseError(e) => write!(f, "failed while parsing: {e}"),
Self::UnableToLoadLibrary(e) => write!(f, "{e}"),
Self::ToRunnableError(e) => write!(f, "failed to compile: {e}"),
}
}
}
pub struct ScriptErrorWithFile<'a>(&'a ScriptError, &'a File);
impl<'a> ScriptError {
pub fn with_file(&'a self, file: &'a File) -> ScriptErrorWithFile {
ScriptErrorWithFile(self, file)
}
}
impl<'a> std::fmt::Display for ScriptErrorWithFile<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
ScriptError::CannotFindPathForLibrary(e) => write!(f, "{e}"),
ScriptError::ParseError(e) => {
write!(f, "failed while parsing: {}", e.with_file(self.1))
}
ScriptError::UnableToLoadLibrary(e) => write!(f, "{e}"),
ScriptError::ToRunnableError(e) => write!(f, "failed to compile: {e}"),
}
}
}
pub const PARSE_VERSION: u64 = 0; pub const PARSE_VERSION: u64 = 0;
/// executes the 4 parse_steps in order: lib_paths => interpret => libs_load => compile /// executes the 4 parse_steps in order: lib_paths => interpret => libs_load => compile
pub fn parse(file: &mut File) -> Result<RScript, ScriptError> { pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
let mut ginfo = GInfo::default(); let mut ginfo = GInfo::default();
let libs = parse_step_lib_paths(file); let libs = parse_step_lib_paths(file)?;
let func = parse_step_interpret(file)?; let func = parse_step_interpret(file)?;
ginfo.libs = Arc::new(parse_step_libs_load(libs, &mut ginfo)); ginfo.libs = Arc::new(parse_step_libs_load(libs, &mut ginfo)?);
eprintln!(); eprintln!();
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
@ -56,7 +94,15 @@ pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
Ok(run) Ok(run)
} }
pub fn parse_step_lib_paths(file: &mut File) -> Vec<Command> { #[derive(Debug)]
pub struct CannotFindPathForLibrary(String);
impl std::error::Error for CannotFindPathForLibrary {}
impl std::fmt::Display for CannotFindPathForLibrary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Couldn't find a path for the library with the path '{}'. Maybe set the MERS_LIB_DIR env variable?", self.0)
}
}
pub fn parse_step_lib_paths(file: &mut File) -> Result<Vec<Command>, CannotFindPathForLibrary> {
let mut libs = vec![]; let mut libs = vec![];
loop { loop {
file.skip_whitespaces(); file.skip_whitespaces();
@ -65,7 +111,7 @@ pub fn parse_step_lib_paths(file: &mut File) -> Vec<Command> {
if line.starts_with("lib ") { if line.starts_with("lib ") {
let path_to_executable = match libs::path::path_from_string(&line[4..], file.path()) { let path_to_executable = match libs::path::path_from_string(&line[4..], file.path()) {
Some(v) => v, Some(v) => v,
None => panic!("Couldn't find a path for the library with the path '{}'. Maybe set the MERS_LIB_DIR env variable?", &line[4..]), None => return Err(CannotFindPathForLibrary(line[4..].to_string())),
}; };
let mut cmd = Command::new(&path_to_executable); let mut cmd = Command::new(&path_to_executable);
if let Some(parent) = path_to_executable.parent() { if let Some(parent) = path_to_executable.parent() {
@ -77,7 +123,7 @@ pub fn parse_step_lib_paths(file: &mut File) -> Vec<Command> {
break; break;
} }
} }
libs Ok(libs)
} }
pub fn parse_step_interpret(file: &mut File) -> Result<SFunction, ParseError> { pub fn parse_step_interpret(file: &mut File) -> Result<SFunction, ParseError> {
@ -90,17 +136,28 @@ pub fn parse_step_interpret(file: &mut File) -> Result<SFunction, ParseError> {
)) ))
} }
pub fn parse_step_libs_load(lib_cmds: Vec<Command>, ginfo: &mut GInfo) -> Vec<libs::Lib> { #[derive(Debug)]
pub struct UnableToLoadLibrary(String, crate::libs::LaunchError);
impl std::fmt::Display for UnableToLoadLibrary {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Unable to load library {}: {}", self.0, self.1)
}
}
pub fn parse_step_libs_load(
lib_cmds: Vec<Command>,
ginfo: &mut GInfo,
) -> Result<Vec<libs::Lib>, UnableToLoadLibrary> {
let mut libs = vec![]; let mut libs = vec![];
for cmd in lib_cmds { for cmd in lib_cmds {
let cmd_path = cmd.get_program().to_string_lossy().into_owned();
match libs::Lib::launch(cmd, &mut ginfo.enum_variants) { match libs::Lib::launch(cmd, &mut ginfo.enum_variants) {
Ok(lib) => { Ok(lib) => {
libs.push(lib); libs.push(lib);
} }
Err(e) => eprintln!("!! Unable to load library: {e:?} !!",), Err(e) => return Err(UnableToLoadLibrary(cmd_path, e)),
} }
} }
libs Ok(libs)
} }
pub fn parse_step_compile( pub fn parse_step_compile(
@ -110,6 +167,125 @@ pub fn parse_step_compile(
to_runnable::to_runnable(main_func, ginfo) to_runnable::to_runnable(main_func, ginfo)
} }
pub struct ParseErrorWithFile<'a>(&'a ParseError, &'a File);
impl<'a> ParseError {
pub fn with_file(&'a self, file: &'a File) -> ParseErrorWithFile {
ParseErrorWithFile(self, file)
}
}
impl<'a> std::fmt::Display for ParseErrorWithFile<'a> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt_custom(f, Some(self.1))
}
}
#[derive(Debug)]
pub struct ParseError {
err: ParseErrors,
// the location of the error
location: super::file::FilePosition,
location_end: Option<super::file::FilePosition>,
context: Vec<(
String,
Option<(super::file::FilePosition, Option<super::file::FilePosition>)>,
)>,
}
impl ParseError {
pub fn fmt_custom(
&self,
f: &mut std::fmt::Formatter<'_>,
file: Option<&super::file::File>,
) -> std::fmt::Result {
writeln!(f, "{}", self.err)?;
if let Some(location_end) = self.location_end {
writeln!(f, " from {} to {}", self.location, location_end)?;
if let Some(file) = file {
if self.location.current_line == location_end.current_line {
write!(
f,
" {}\n {}{} here",
file.get_line(self.location.current_line).unwrap(),
" ".repeat(self.location.current_column),
"^".repeat(
location_end
.current_column
.saturating_sub(self.location.current_column)
.saturating_add(1)
)
)?;
}
}
} else {
writeln!(f, " at {}", self.location)?;
}
for ctx in self.context.iter() {
writeln!(f, " {}", ctx.0)?;
if let Some(pos) = &ctx.1 {
if let Some(end) = &pos.1 {
writeln!(f, " from {} to {}", pos.0, end)?;
} else {
writeln!(f, " at {}", pos.0)?;
}
}
}
Ok(())
}
}
impl std::fmt::Display for ParseError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.fmt_custom(f, None)
}
}
#[derive(Debug)]
pub enum ParseErrors {
FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement,
FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement,
FoundEofInBlockBeforeStatementOrClosingCurlyBracket,
FoundEofInString,
FoundEofInStatement,
FoundEofInFunctionArgName,
FoundEofInType,
FoundEofInsteadOfType,
InvalidType(String),
CannotUseFixedIndexingWithThisType(VType),
CannotWrapWithThisStatement(SStatementEnum),
ErrorParsingFunctionArgs(Box<ParseError>),
}
impl std::fmt::Display for ParseErrors {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement => write!(
f,
"closing round bracket in single-statement block before any statement"
),
Self::FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement => write!(
f,
"closing curly bracket in single-statement block before any statement."
),
Self::FoundEofInBlockBeforeStatementOrClosingCurlyBracket => write!(
f,
"found EOF in block before any statement or closing curly bracket was found."
),
Self::FoundEofInString => write!(f, "found EOF in string literal."),
Self::FoundEofInStatement => write!(f, "found EOF in statement."),
Self::FoundEofInFunctionArgName => {
write!(f, "found EOF in the name of the function's argument.")
}
Self::FoundEofInType => write!(f, "found EOF in type."),
Self::FoundEofInsteadOfType => write!(f, "expected type, found EOF instead."),
Self::InvalidType(name) => write!(f, "\"{name}\" is not a type."),
Self::CannotUseFixedIndexingWithThisType(t) => {
write!(f, "cannot use fixed-indexing with type {t}.")
}
Self::CannotWrapWithThisStatement(s) => {
write!(f, "cannot wrap with this kind of statement: {s}.")
}
Self::ErrorParsingFunctionArgs(parse_error) => {
write!(f, "error parsing function args: {}", parse_error.err)
}
}
}
}
fn parse_block(file: &mut File) -> Result<SBlock, ParseError> { fn parse_block(file: &mut File) -> Result<SBlock, ParseError> {
parse_block_advanced(file, None, true, false, false) parse_block_advanced(file, None, true, false, false)
} }
@ -120,24 +296,33 @@ fn parse_block_advanced(
treat_eof_as_closing_delimeter: bool, treat_eof_as_closing_delimeter: bool,
treat_closed_normal_bracket_as_closing_delimeter: bool, treat_closed_normal_bracket_as_closing_delimeter: bool,
) -> Result<SBlock, ParseError> { ) -> Result<SBlock, ParseError> {
let mut statements = vec![];
file.skip_whitespaces(); 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 { let single_statement = if let Some(v) = assume_single_statement {
v v
} else { } else {
if let Some('{') = file.get_char(file.get_char_index()) { if let Some('{') = file.get_char(file.get_pos().current_char_index) {
file.next(); file.next();
false false
} else { } else {
true true
} }
}; };
let mut statements = vec![];
// each iteration of this loop parses one statement
loop { loop {
file.skip_whitespaces(); file.skip_whitespaces();
match file.get_char(file.get_char_index()) { 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 => { Some(')') if treat_closed_normal_bracket_as_closing_delimeter => {
if single_statement { if single_statement {
todo!("Err: closing function-arguments-delimeter in single-statement block before any statement (???fn with single-statement???)") return Err(ParseError {
err:
ParseErrors::FoundClosingRoundBracketInSingleStatementBlockBeforeAnyStatement,
location: err_start_of_this_statement,
location_end: Some(*file.get_pos()),
context: vec![],
});
} else { } else {
file.next(); file.next();
break; break;
@ -145,7 +330,12 @@ fn parse_block_advanced(
} }
Some('}') if treat_closed_block_bracket_as_closing_delimeter => { Some('}') if treat_closed_block_bracket_as_closing_delimeter => {
if single_statement { if single_statement {
todo!("Err: closing block-delimeter in single-statement block before any statement") return Err(ParseError {
err: ParseErrors::FoundClosingCurlyBracketInSingleStatementBlockBeforeAnyStatement,
location: err_start_of_this_statement,
location_end: Some(*file.get_pos()),
context: vec![]
});
} else { } else {
file.next(); file.next();
break; break;
@ -154,7 +344,14 @@ fn parse_block_advanced(
None if treat_eof_as_closing_delimeter => { None if treat_eof_as_closing_delimeter => {
break; break;
} }
None => todo!("eof in block before statement"), None => {
return Err(ParseError {
err: ParseErrors::FoundEofInBlockBeforeStatementOrClosingCurlyBracket,
location: err_start_of_this_statement,
location_end: Some(*file.get_pos()),
context: vec![],
})
}
_ => (), _ => (),
} }
statements.push(parse_statement(file)?); statements.push(parse_statement(file)?);
@ -181,7 +378,7 @@ fn parse_statement_adv(
is_part_of_chain_already: bool, is_part_of_chain_already: bool,
) -> Result<SStatement, ParseError> { ) -> Result<SStatement, ParseError> {
file.skip_whitespaces(); file.skip_whitespaces();
let mut start = String::new(); let err_start_of_statement = *file.get_pos();
let out = match file.peek() { let out = match file.peek() {
Some('{') => Some(SStatementEnum::Block(parse_block(file)?).into()), Some('{') => Some(SStatementEnum::Block(parse_block(file)?).into()),
Some('[') => { Some('[') => {
@ -194,7 +391,7 @@ fn parse_statement_adv(
file.next(); file.next();
break; break;
} }
if file[file.get_char_index()..].starts_with("...]") { if file[file.get_pos().current_char_index..].starts_with("...]") {
list = true; list = true;
file.next(); file.next();
file.next(); file.next();
@ -231,7 +428,14 @@ fn parse_statement_adv(
} }
Some('"') => break, Some('"') => break,
Some(ch) => buf.push(ch), Some(ch) => buf.push(ch),
None => todo!("Err: EOF in string"), None => {
return Err(ParseError {
err: ParseErrors::FoundEofInString,
location: err_start_of_statement,
location_end: Some(*file.get_pos()),
context: vec![],
})
}
} }
} }
Some(SStatementEnum::Value(VDataEnum::String(buf).to()).into()) Some(SStatementEnum::Value(VDataEnum::String(buf).to()).into())
@ -241,6 +445,7 @@ fn parse_statement_adv(
let mut out = if let Some(out) = out { let mut out = if let Some(out) = out {
out out
} else { } else {
let mut start = String::new();
loop { loop {
match match file.peek() { match match file.peek() {
Some(ch) if matches!(ch, '}' | ']' | ')' | '.') => Some(ch), Some(ch) if matches!(ch, '}' | ']' | ')' | '.') => Some(ch),
@ -272,7 +477,7 @@ fn parse_statement_adv(
None => break, None => break,
} }
} }
let func = parse_function(file)?; let func = parse_function(file, Some(err_start_of_statement))?;
break SStatementEnum::FunctionDefinition( break SStatementEnum::FunctionDefinition(
Some(fn_name.trim().to_string()), Some(fn_name.trim().to_string()),
func, func,
@ -285,7 +490,7 @@ fn parse_statement_adv(
let then = parse_statement(file)?; let then = parse_statement(file)?;
let mut then_else = None; let mut then_else = None;
file.skip_whitespaces(); file.skip_whitespaces();
let i = file.get_char_index(); let i = file.get_pos().current_char_index;
if file[i..].starts_with("else ") { if file[i..].starts_with("else ") {
while let Some('e' | 'l' | 's') = file.next() {} while let Some('e' | 'l' | 's') = file.next() {}
then_else = Some(parse_statement(file)?); then_else = Some(parse_statement(file)?);
@ -399,31 +604,58 @@ fn parse_statement_adv(
// parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise) // parse_block_advanced: only treat ) as closing delimeter, don't use single-statement (missing {, so would be assumed otherwise)
let name = start.trim(); let name = start.trim();
if name.is_empty() { if name.is_empty() {
break SStatementEnum::FunctionDefinition(None, parse_function(file)?) break SStatementEnum::FunctionDefinition(
.into(); None,
parse_function(file, Some(err_start_of_statement))?,
)
.into();
} else { } else {
break SStatementEnum::FunctionCall( break SStatementEnum::FunctionCall(
name.to_string(), name.to_string(),
parse_block_advanced(file, Some(false), false, false, true)?.statements, 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![],
});
}
},
) )
.into(); .into();
} }
} }
Some(ch) => start.push(ch), Some(ch) => start.push(ch),
None => todo!("EOF in statement"), None => {
return Err(ParseError {
err: ParseErrors::FoundEofInStatement,
location: err_start_of_statement,
location_end: Some(*file.get_pos()),
context: vec![],
})
}
} }
} }
}; };
let err_end_of_original_statement = *file.get_pos();
file.skip_whitespaces(); file.skip_whitespaces();
if !file[file.get_char_index()..].starts_with("..") { if !file[file.get_pos().current_char_index..].starts_with("..") {
// dot chain syntax only works if there is only one dot // dot chain syntax only works if there is only one dot
if let Some('.') = file.get_char(file.get_char_index()) { if let Some('.') = file.get_char(file.get_pos().current_char_index) {
// consume the dot (otherwise, a.b.c syntax will break in certain cases) // consume the dot (otherwise, a.b.c syntax will break in certain cases)
file.next(); file.next();
} }
if !is_part_of_chain_already { if !is_part_of_chain_already {
while let Some('.') = file.get_char(file.get_char_index().saturating_sub(1)) { 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 wrapper = parse_statement_adv(file, true)?;
let err_end_of_wrapper = *file.get_pos();
out = match *wrapper.statement { out = match *wrapper.statement {
SStatementEnum::FunctionCall(func, args) => { SStatementEnum::FunctionCall(func, args) => {
let args = [out].into_iter().chain(args.into_iter()).collect(); let args = [out].into_iter().chain(args.into_iter()).collect();
@ -431,12 +663,64 @@ fn parse_statement_adv(
} }
SStatementEnum::Value(vd) => match vd.data { SStatementEnum::Value(vd) => match vd.data {
VDataEnum::Int(i) => SStatementEnum::IndexFixed(out, i as _).into(), VDataEnum::Int(i) => SStatementEnum::IndexFixed(out, i as _).into(),
_ => todo!("fixed-indexing not available with this type."), _ => {
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,
});
}
}, },
other => { other => {
todo!("Wrapping in this type isn't implemented (yet?). Type: {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,
});
} }
} };
err_end_of_prev = err_end_of_wrapper;
chain_length += 1;
} }
} }
} }
@ -444,22 +728,39 @@ fn parse_statement_adv(
} }
/// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) <statement>" /// Assumes the function name and opening bracket have already been parsed. File should continue like "name type name type ...) <statement>"
fn parse_function(file: &mut File) -> Result<SFunction, ParseError> { fn parse_function(
file: &mut File,
err_fn_start: Option<super::file::FilePosition>,
) -> Result<SFunction, ParseError> {
file.skip_whitespaces();
// find the arguments to the function // find the arguments to the function
let mut args = Vec::new(); let mut args = Vec::new();
file.skip_whitespaces();
loop { loop {
let mut arg_name = String::new(); match file.peek() {
match file.next() {
Some(')') => break, Some(')') => break,
Some(ch) => arg_name.push(ch), _ => (),
None => break,
} }
let mut arg_name = String::new();
loop { loop {
let err_fn_arg_name_start = *file.get_pos();
match file.next() { match file.next() {
Some(ch) if ch.is_whitespace() => break, Some(ch) if ch.is_whitespace() => break,
Some(ch) => arg_name.push(ch), Some(ch) => arg_name.push(ch),
None => todo!("Err: EOF in function"), 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)
}],
})
}
} }
} }
let (t, brk) = parse_type_adv(file, true)?; let (t, brk) = parse_type_adv(file, true)?;
@ -519,6 +820,7 @@ fn parse_single_type_adv(
) -> Result<(VSingleType, bool), ParseError> { ) -> Result<(VSingleType, bool), ParseError> {
file.skip_whitespaces(); file.skip_whitespaces();
let mut closed_bracket_in_fn_args = false; let mut closed_bracket_in_fn_args = false;
let err_start_of_single_type = *file.get_pos();
Ok(( Ok((
match file.next() { match file.next() {
Some('&') => { Some('&') => {
@ -534,7 +836,7 @@ fn parse_single_type_adv(
let mut list = false; let mut list = false;
loop { loop {
file.skip_whitespaces(); file.skip_whitespaces();
if file[file.get_char_index()..].starts_with("...]") { if file[file.get_pos().current_char_index..].starts_with("...]") {
list = true; list = true;
file.next(); file.next();
file.next(); file.next();
@ -570,10 +872,15 @@ fn parse_single_type_adv(
match file.peek() { match file.peek() {
Some(']') => break, Some(']') => break,
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() { match file.next() {
Some(ch) if ch.is_whitespace() => break,
Some('(') => { Some('(') => {
break 'parse_single_type if name.as_str() == "fn" { break 'parse_single_type if name.as_str() == "fn" {
todo!("fn types"); todo!("fn types");
@ -588,12 +895,15 @@ fn parse_single_type_adv(
}) })
}; };
} }
Some(')') if in_fn_args => {
closed_bracket_in_fn_args = true;
break;
}
Some(ch) => name.push(ch), Some(ch) => name.push(ch),
None => todo!("Err: EOF in type"), None => {
return Err(ParseError {
err: ParseErrors::FoundEofInType,
location: err_start_of_single_type,
location_end: Some(*file.get_pos()),
context: vec![],
});
}
} }
} }
match name.trim().to_lowercase().as_str() { match name.trim().to_lowercase().as_str() {
@ -602,12 +912,23 @@ fn parse_single_type_adv(
"float" => VSingleType::Float, "float" => VSingleType::Float,
"string" => VSingleType::String, "string" => VSingleType::String,
_ => { _ => {
eprintln!("in_fn_args: {in_fn_args}"); return Err(ParseError {
todo!("Err: Invalid type: \"{}\"", name.trim()) err: ParseErrors::InvalidType(name.trim().to_string()),
location: err_start_of_single_type,
location_end: Some(*file.get_pos()),
context: vec![],
});
} }
} }
} }
None => todo!("Err: EOF in type (1)"), None => {
return Err(ParseError {
err: ParseErrors::FoundEofInsteadOfType,
location: err_start_of_single_type,
location_end: Some(*file.get_pos()),
context: vec![],
})
}
}, },
closed_bracket_in_fn_args, closed_bracket_in_fn_args,
)) ))

View File

@ -121,6 +121,8 @@ pub mod to_runnable {
CaseForceButTypeNotCovered(VType), CaseForceButTypeNotCovered(VType),
MatchConditionInvalidReturn(VType), MatchConditionInvalidReturn(VType),
NotIndexableFixed(VType, usize), NotIndexableFixed(VType, usize),
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
WrongArgsForLibFunction(String, Vec<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 {
@ -147,6 +149,20 @@ pub mod to_runnable {
Self::CaseForceButTypeNotCovered(v) => write!(f, "Switch! statement, but not all types covered. Types to cover: {v}"), Self::CaseForceButTypeNotCovered(v) => write!(f, "Switch! statement, but not all types covered. Types to cover: {v}"),
Self::MatchConditionInvalidReturn(v) => write!(f, "match statement condition returned {v}, which is not necessarily a tuple of size 0 to 1."), Self::MatchConditionInvalidReturn(v) => write!(f, "match statement condition returned {v}, which is not necessarily a tuple of size 0 to 1."),
Self::NotIndexableFixed(t, i) => write!(f, "Cannot use fixed-index {i} on type {t}."), Self::NotIndexableFixed(t, i) => write!(f, "Cannot use fixed-index {i} on type {t}."),
Self::WrongInputsForBuiltinFunction(_builtin, builtin_name, args) => {
write!(f, "Wrong arguments for builtin function {}:", builtin_name)?;
for arg in args {
write!(f, " {arg}")?;
}
write!(f, ".")
}
Self::WrongArgsForLibFunction(name, args) => {
write!(f, "Wrong arguments for library function {}:", name)?;
for arg in args {
write!(f, " {arg}")?;
}
write!(f, ".")
}
} }
} }
} }
@ -377,10 +393,11 @@ pub mod to_runnable {
} else { } else {
// TODO: type-checking for builtins // TODO: type-checking for builtins
if let Some(builtin) = BuiltinFunction::get(v) { if let Some(builtin) = BuiltinFunction::get(v) {
if builtin.can_take(&rargs.iter().map(|v| v.out()).collect()) { let arg_types = rargs.iter().map(|v| v.out()).collect();
if builtin.can_take(&arg_types) {
RStatementEnum::BuiltinFunction(builtin, rargs) RStatementEnum::BuiltinFunction(builtin, rargs)
} else { } else {
todo!("ERR: Builtin function \"{v}\" with wrong args - this isn't a proper error yet, sorry."); return Err(ToRunnableError::WrongInputsForBuiltinFunction(builtin, v.to_string(), arg_types));
} }
} else { } else {
// LIBRARY FUNCTION? // LIBRARY FUNCTION?
@ -391,7 +408,7 @@ pub mod to_runnable {
} else { } else {
// TODO! better error here // TODO! better error here
return Err(if fn_in.len() == rargs.len() { return Err(if fn_in.len() == rargs.len() {
todo!("Err: Wrong args for LibFunction \"{v}\"."); ToRunnableError::WrongArgsForLibFunction(v.to_string(), rargs.iter().map(|v| v.out()).collect())
} else { } else {
ToRunnableError::FunctionWrongArgCount(v.to_string(), fn_in.len(), rargs.len()) ToRunnableError::FunctionWrongArgCount(v.to_string(), fn_in.len(), rargs.len())
}); });

View File

@ -797,7 +797,7 @@ impl BuiltinFunction {
if args.len() > 0 { if args.len() > 0 {
if let VDataEnum::String(path) = args[0].run(vars, libs).data { if let VDataEnum::String(path) = args[0].run(vars, libs).data {
if args.len() > 1 { if args.len() > 1 {
todo!("fs_list advanced filters") eprintln!("NOT YET IMPLEMENTED (TODO!): fs_list advanced filters")
} }
match std::fs::read_dir(path) { match std::fs::read_dir(path) {
Ok(entries) => VDataEnum::List( Ok(entries) => VDataEnum::List(