mirror of
https://github.com/Dummi26/mers.git
synced 2025-03-10 14:13:52 +01:00
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:
parent
dfd83fa581
commit
b2a36416cf
@ -174,6 +174,14 @@ pub enum LaunchError {
|
||||
NoStdio,
|
||||
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 {
|
||||
fn line(&mut self) -> Result<String, io::Error>;
|
||||
|
@ -1,4 +1,4 @@
|
||||
use std::{fs, sync::Arc, time::Instant};
|
||||
use std::{fs, time::Instant};
|
||||
|
||||
use notify::Watcher as FsWatcher;
|
||||
|
||||
@ -75,27 +75,17 @@ fn main() {
|
||||
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());
|
||||
static_assertions::const_assert_eq!(parse::parse::PARSE_VERSION, 0);
|
||||
let mut ginfo = script::block::to_runnable::GInfo::default();
|
||||
let libs = parse::parse::parse_step_lib_paths(&mut file);
|
||||
match parse::parse::parse_step_interpret(&mut file) {
|
||||
match parse::parse::parse(&mut file) {
|
||||
Ok(func) => {
|
||||
let libs = parse::parse::parse_step_libs_load(libs, &mut ginfo);
|
||||
ginfo.libs = Arc::new(libs);
|
||||
match parse::parse::parse_step_compile(func, &mut ginfo) {
|
||||
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:?}"),
|
||||
},
|
||||
Err(e) => println!("{}", e.with_file(&file)),
|
||||
}
|
||||
} else {
|
||||
println!("can't read file at {:?}!", temp_file);
|
||||
|
@ -8,20 +8,24 @@ pub struct File {
|
||||
path: PathBuf,
|
||||
data: String,
|
||||
chars: Vec<(usize, char)>,
|
||||
// contains the byte indices of all newline characters
|
||||
newlines: Vec<usize>,
|
||||
pos: FilePosition,
|
||||
ppos: FilePosition,
|
||||
}
|
||||
#[derive(Clone, Copy)]
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct FilePosition {
|
||||
current_char_index: usize,
|
||||
current_line: usize,
|
||||
current_column: usize,
|
||||
pub current_char_index: usize,
|
||||
pub current_line: usize,
|
||||
pub current_column: usize,
|
||||
}
|
||||
impl Display for FilePosition {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"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') {
|
||||
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 {
|
||||
path,
|
||||
data,
|
||||
chars,
|
||||
pos: FilePosition {
|
||||
current_char_index: 0,
|
||||
current_line: 0,
|
||||
current_column: 0,
|
||||
},
|
||||
newlines,
|
||||
pos,
|
||||
ppos: pos,
|
||||
}
|
||||
}
|
||||
pub fn skip_whitespaces(&mut self) {
|
||||
@ -104,24 +115,53 @@ impl File {
|
||||
pub fn get_pos(&self) -> &FilePosition {
|
||||
&self.pos
|
||||
}
|
||||
pub fn get_ppos(&self) -> &FilePosition {
|
||||
&self.pos
|
||||
}
|
||||
pub fn set_pos(&mut self, pos: FilePosition) {
|
||||
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> {
|
||||
match self.chars.get(index) {
|
||||
Some(v) => Some(v.1),
|
||||
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 {
|
||||
let mut o = String::new();
|
||||
for ch in self {
|
||||
@ -144,6 +184,7 @@ impl File {
|
||||
impl Iterator for File {
|
||||
type Item = char;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.ppos = self.pos;
|
||||
let o = self.chars.get(self.pos.current_char_index);
|
||||
self.pos.current_char_index += 1;
|
||||
match o {
|
||||
|
@ -17,30 +17,68 @@ use super::file::File;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum ScriptError {
|
||||
CannotFindPathForLibrary(CannotFindPathForLibrary),
|
||||
ParseError(ParseError),
|
||||
UnableToLoadLibrary(UnableToLoadLibrary),
|
||||
ToRunnableError(ToRunnableError),
|
||||
}
|
||||
impl From<CannotFindPathForLibrary> for ScriptError {
|
||||
fn from(value: CannotFindPathForLibrary) -> Self {
|
||||
Self::CannotFindPathForLibrary(value)
|
||||
}
|
||||
}
|
||||
impl From<ParseError> for ScriptError {
|
||||
fn from(value: ParseError) -> Self {
|
||||
Self::ParseError(value)
|
||||
}
|
||||
}
|
||||
impl From<UnableToLoadLibrary> for ScriptError {
|
||||
fn from(value: UnableToLoadLibrary) -> Self {
|
||||
Self::UnableToLoadLibrary(value)
|
||||
}
|
||||
}
|
||||
impl From<ToRunnableError> for ScriptError {
|
||||
fn from(value: ToRunnableError) -> Self {
|
||||
Self::ToRunnableError(value)
|
||||
}
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub enum ParseError {}
|
||||
impl std::fmt::Display for ScriptError {
|
||||
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;
|
||||
|
||||
/// executes the 4 parse_steps in order: lib_paths => interpret => libs_load => compile
|
||||
pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
|
||||
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)?;
|
||||
ginfo.libs = Arc::new(parse_step_libs_load(libs, &mut ginfo));
|
||||
ginfo.libs = Arc::new(parse_step_libs_load(libs, &mut ginfo)?);
|
||||
|
||||
eprintln!();
|
||||
#[cfg(debug_assertions)]
|
||||
@ -56,7 +94,15 @@ pub fn parse(file: &mut File) -> Result<RScript, ScriptError> {
|
||||
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![];
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
@ -65,7 +111,7 @@ pub fn parse_step_lib_paths(file: &mut File) -> Vec<Command> {
|
||||
if line.starts_with("lib ") {
|
||||
let path_to_executable = match libs::path::path_from_string(&line[4..], file.path()) {
|
||||
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);
|
||||
if let Some(parent) = path_to_executable.parent() {
|
||||
@ -77,7 +123,7 @@ pub fn parse_step_lib_paths(file: &mut File) -> Vec<Command> {
|
||||
break;
|
||||
}
|
||||
}
|
||||
libs
|
||||
Ok(libs)
|
||||
}
|
||||
|
||||
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![];
|
||||
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) {
|
||||
Ok(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(
|
||||
@ -110,6 +167,125 @@ pub fn parse_step_compile(
|
||||
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> {
|
||||
parse_block_advanced(file, None, true, false, false)
|
||||
}
|
||||
@ -120,24 +296,33 @@ fn parse_block_advanced(
|
||||
treat_eof_as_closing_delimeter: bool,
|
||||
treat_closed_normal_bracket_as_closing_delimeter: bool,
|
||||
) -> Result<SBlock, ParseError> {
|
||||
let mut statements = vec![];
|
||||
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_char_index()) {
|
||||
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();
|
||||
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 => {
|
||||
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 {
|
||||
file.next();
|
||||
break;
|
||||
@ -145,7 +330,12 @@ fn parse_block_advanced(
|
||||
}
|
||||
Some('}') if treat_closed_block_bracket_as_closing_delimeter => {
|
||||
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 {
|
||||
file.next();
|
||||
break;
|
||||
@ -154,7 +344,14 @@ fn parse_block_advanced(
|
||||
None if treat_eof_as_closing_delimeter => {
|
||||
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)?);
|
||||
@ -181,7 +378,7 @@ fn parse_statement_adv(
|
||||
is_part_of_chain_already: bool,
|
||||
) -> Result<SStatement, ParseError> {
|
||||
file.skip_whitespaces();
|
||||
let mut start = String::new();
|
||||
let err_start_of_statement = *file.get_pos();
|
||||
let out = match file.peek() {
|
||||
Some('{') => Some(SStatementEnum::Block(parse_block(file)?).into()),
|
||||
Some('[') => {
|
||||
@ -194,7 +391,7 @@ fn parse_statement_adv(
|
||||
file.next();
|
||||
break;
|
||||
}
|
||||
if file[file.get_char_index()..].starts_with("...]") {
|
||||
if file[file.get_pos().current_char_index..].starts_with("...]") {
|
||||
list = true;
|
||||
file.next();
|
||||
file.next();
|
||||
@ -231,7 +428,14 @@ fn parse_statement_adv(
|
||||
}
|
||||
Some('"') => break,
|
||||
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())
|
||||
@ -241,6 +445,7 @@ fn parse_statement_adv(
|
||||
let mut out = if let Some(out) = out {
|
||||
out
|
||||
} else {
|
||||
let mut start = String::new();
|
||||
loop {
|
||||
match match file.peek() {
|
||||
Some(ch) if matches!(ch, '}' | ']' | ')' | '.') => Some(ch),
|
||||
@ -272,7 +477,7 @@ fn parse_statement_adv(
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
let func = parse_function(file)?;
|
||||
let func = parse_function(file, Some(err_start_of_statement))?;
|
||||
break SStatementEnum::FunctionDefinition(
|
||||
Some(fn_name.trim().to_string()),
|
||||
func,
|
||||
@ -285,7 +490,7 @@ fn parse_statement_adv(
|
||||
let then = parse_statement(file)?;
|
||||
let mut then_else = None;
|
||||
file.skip_whitespaces();
|
||||
let i = file.get_char_index();
|
||||
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)?);
|
||||
@ -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)
|
||||
let name = start.trim();
|
||||
if name.is_empty() {
|
||||
break SStatementEnum::FunctionDefinition(None, parse_function(file)?)
|
||||
break SStatementEnum::FunctionDefinition(
|
||||
None,
|
||||
parse_function(file, Some(err_start_of_statement))?,
|
||||
)
|
||||
.into();
|
||||
} else {
|
||||
break SStatementEnum::FunctionCall(
|
||||
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();
|
||||
}
|
||||
}
|
||||
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();
|
||||
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
|
||||
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)
|
||||
file.next();
|
||||
}
|
||||
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 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();
|
||||
@ -431,12 +663,64 @@ fn parse_statement_adv(
|
||||
}
|
||||
SStatementEnum::Value(vd) => match vd.data {
|
||||
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 => {
|
||||
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>"
|
||||
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
|
||||
let mut args = Vec::new();
|
||||
file.skip_whitespaces();
|
||||
loop {
|
||||
let mut arg_name = String::new();
|
||||
match file.next() {
|
||||
match file.peek() {
|
||||
Some(')') => break,
|
||||
Some(ch) => arg_name.push(ch),
|
||||
None => break,
|
||||
_ => (),
|
||||
}
|
||||
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 => 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)?;
|
||||
@ -519,6 +820,7 @@ fn parse_single_type_adv(
|
||||
) -> 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('&') => {
|
||||
@ -534,7 +836,7 @@ fn parse_single_type_adv(
|
||||
let mut list = false;
|
||||
loop {
|
||||
file.skip_whitespaces();
|
||||
if file[file.get_char_index()..].starts_with("...]") {
|
||||
if file[file.get_pos().current_char_index..].starts_with("...]") {
|
||||
list = true;
|
||||
file.next();
|
||||
file.next();
|
||||
@ -570,10 +872,15 @@ fn parse_single_type_adv(
|
||||
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(ch) if ch.is_whitespace() => break,
|
||||
Some('(') => {
|
||||
break 'parse_single_type if name.as_str() == "fn" {
|
||||
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),
|
||||
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() {
|
||||
@ -602,12 +912,23 @@ fn parse_single_type_adv(
|
||||
"float" => VSingleType::Float,
|
||||
"string" => VSingleType::String,
|
||||
_ => {
|
||||
eprintln!("in_fn_args: {in_fn_args}");
|
||||
todo!("Err: Invalid type: \"{}\"", name.trim())
|
||||
return Err(ParseError {
|
||||
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,
|
||||
))
|
||||
|
@ -121,6 +121,8 @@ pub mod to_runnable {
|
||||
CaseForceButTypeNotCovered(VType),
|
||||
MatchConditionInvalidReturn(VType),
|
||||
NotIndexableFixed(VType, usize),
|
||||
WrongInputsForBuiltinFunction(BuiltinFunction, String, Vec<VType>),
|
||||
WrongArgsForLibFunction(String, Vec<VType>),
|
||||
}
|
||||
impl Debug for ToRunnableError {
|
||||
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::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::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 {
|
||||
// TODO: type-checking for builtins
|
||||
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)
|
||||
} 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 {
|
||||
// LIBRARY FUNCTION?
|
||||
@ -391,7 +408,7 @@ pub mod to_runnable {
|
||||
} else {
|
||||
// TODO! better error here
|
||||
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 {
|
||||
ToRunnableError::FunctionWrongArgCount(v.to_string(), fn_in.len(), rargs.len())
|
||||
});
|
||||
|
@ -797,7 +797,7 @@ impl BuiltinFunction {
|
||||
if args.len() > 0 {
|
||||
if let VDataEnum::String(path) = args[0].run(vars, libs).data {
|
||||
if args.len() > 1 {
|
||||
todo!("fs_list advanced filters")
|
||||
eprintln!("NOT YET IMPLEMENTED (TODO!): fs_list advanced filters")
|
||||
}
|
||||
match std::fs::read_dir(path) {
|
||||
Ok(entries) => VDataEnum::List(
|
||||
|
Loading…
Reference in New Issue
Block a user