From 17878b3e879ec378c49fe5b6fbb47ba1110ce102 Mon Sep 17 00:00:00 2001 From: Schrottkatze Date: Sun, 19 Nov 2023 16:52:38 +0100 Subject: [PATCH] rewrite error system and add evaluator architecture the evaluator architecture is also only a temporary solution and very flawed but i have no other ideas --- src/error/mod.rs | 89 +++++++++++++++++++++++++++++++++ src/evaluator.rs | 117 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 62 +++++------------------ src/syntax/check.rs | 43 +++++++++++----- src/syntax/error.rs | 79 ------------------------------ src/syntax/mod.rs | 1 - src/typed.rs | 32 ++++++++++-- 7 files changed, 278 insertions(+), 145 deletions(-) create mode 100644 src/error/mod.rs create mode 100644 src/evaluator.rs delete mode 100644 src/syntax/error.rs diff --git a/src/error/mod.rs b/src/error/mod.rs new file mode 100644 index 0000000..62afd5d --- /dev/null +++ b/src/error/mod.rs @@ -0,0 +1,89 @@ +use std::cell::RefCell; + +use codespan_reporting::{ + diagnostic::{Diagnostic, Label}, + files::SimpleFiles, +}; + +use crate::Span; + +pub struct Errors { + pub kind: ErrorKind, + pub locs: Vec, +} + +pub enum ErrorKind { + InvalidToken, + SyntaxError(SyntaxErrorKind), + CommandNotFound, +} + +pub enum SyntaxErrorKind { + /// `MissingStreamer` means, that the pipeline starts with a Pipe (`|`), so it has no streamer as input in front of it. + MissingStreamer, + /// `MissingSink` means, that the pipeline ends with a Pipe (`|`), meaning that the output can't go anywhere + MissingSink, + /// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another + MissingFilter, + /// A literal cannot be a sink + LiteralAsSink, + /// A literal can't be a filter either + LiteralAsFilter, + /// A literal acting as streamer cannot take arguments + LiteralWithArgs, +} + +impl Errors { + pub fn new(kind: ErrorKind, locs: Vec) -> Self { + Self { kind, locs } + } + pub fn new_single(kind: ErrorKind, loc: Span) -> Self { + Self { + kind, + locs: vec![loc], + } + } + pub fn into_diag( + &self, + file_id: usize, + file_db: &SimpleFiles<&str, String>, + ) -> Diagnostic { + let Errors { kind, locs } = self; + + match kind { + ErrorKind::InvalidToken => simple_diag(locs.to_vec(), file_id, "invalid tokens"), + ErrorKind::SyntaxError(syntax_error) => match syntax_error { + SyntaxErrorKind::MissingStreamer => simple_diag( + locs.to_vec(), + file_id, + "pipeline is missing an input provider", + ), + SyntaxErrorKind::MissingSink => { + simple_diag(locs.to_vec(), file_id, "pipeline is missing a sink") + } + SyntaxErrorKind::MissingFilter => { + simple_diag(locs.to_vec(), file_id, "missing filters in pipeline") + } + SyntaxErrorKind::LiteralAsSink => { + simple_diag(locs.to_vec(), file_id, "pipelines can't end in a literal") + } + SyntaxErrorKind::LiteralAsFilter => { + simple_diag(locs.to_vec(), file_id, "literals can't filter data") + } + SyntaxErrorKind::LiteralWithArgs => { + simple_diag(locs.to_vec(), file_id, "literals can't take arguments") + } + }, + ErrorKind::CommandNotFound => simple_diag(locs.to_vec(), file_id, "command not found"), + } + } +} + +fn simple_diag(spans: Vec, file_id: usize, msg: &str) -> Diagnostic { + Diagnostic::error().with_message(msg).with_labels( + spans + .into_iter() + .map(|span| Label::primary(file_id, span)) + .collect(), + ) +} diff --git a/src/evaluator.rs b/src/evaluator.rs new file mode 100644 index 0000000..de55473 --- /dev/null +++ b/src/evaluator.rs @@ -0,0 +1,117 @@ +use std::{collections::HashMap, process::exit}; + +use clap::error::Result; +use codespan_reporting::{ + files::SimpleFiles, + term::{ + self, + termcolor::{ColorChoice, StandardStream}, + }, +}; + +use crate::{ + builtins::initialise_globals, + error::{ErrorKind, Errors}, + syntax::{ + check::{self, check}, + parse_syntax, PipelineElement, + }, + typed::into_typed_repr, +}; + +// this is also bad +// need a better architecture for this + +pub struct Evaluator<'a> { + curr_phase: EvalPhase, + files: SimpleFiles<&'a str, String>, + errors: HashMap>, +} + +impl<'a> Evaluator<'a> { + pub fn init() -> Self { + Self { + curr_phase: EvalPhase::Lex, + files: SimpleFiles::new(), + errors: HashMap::new(), + } + } + + pub fn run(&mut self, input: String, name: Option<&'a str>) { + let fid = self.files.add(name.unwrap_or("input"), input.clone()); + + let syntax = parse_syntax(&input); + + match syntax { + Ok(syntax) => self.curr_phase = EvalPhase::Check(fid, syntax), + Err(errs) => { + self.errors.insert( + fid, + vec![Errors { + kind: ErrorKind::InvalidToken, + locs: errs, + }], + ); + self.curr_phase = EvalPhase::Failed + } + }; + } + + pub fn next(mut self) { + match self.curr_phase { + EvalPhase::Lex => { + todo!() + } + EvalPhase::Check(file_id, syntax) => { + let r = check(&syntax); + + if let Err(errs) = r { + self.errors.insert(file_id, errs); + self.curr_phase = EvalPhase::Failed; + } else { + self.curr_phase = EvalPhase::BareTyped(file_id, syntax.clone()) + } + } + EvalPhase::BareTyped(file_id, syntax) => { + let ns = initialise_globals(); + let r = into_typed_repr(&ns, syntax); + + if let Err(errs) = r { + self.errors.insert(file_id, vec![errs]); + self.curr_phase = EvalPhase::Failed; + } else { + todo!() + } + } + EvalPhase::Failed => self.error_out().unwrap(), + } + self.next() + } + + pub fn error_out(self) -> Result { + let Evaluator { + curr_phase, + files, + errors, + } = self; + + let writer = StandardStream::stderr(ColorChoice::Always); + let config = term::Config::default(); + + for (file_id, errors) in errors.iter() { + let writer = &mut writer.lock(); + for error in errors { + term::emit(writer, &config, &files, &error.into_diag(*file_id, &files))?; + } + } + + exit(1) + } +} + +enum EvalPhase { + Lex, + Check(usize, Vec), + BareTyped(usize, Vec), + Failed, +} diff --git a/src/main.rs b/src/main.rs index d87a338..b7e6a77 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,64 +1,28 @@ +#![feature(never_type)] + +use std::ops::Range; + use args::Args; use clap::Parser; -use codespan_reporting::{ - files::SimpleFiles, - term::{ - self, - termcolor::{ColorChoice, StandardStream}, - }, -}; -use syntax::{check::check, error::SyntaxError, parse_syntax}; - -use crate::{builtins::initialise_globals, typed::into_typed_repr}; +use evaluator::Evaluator; mod args; mod builtins; +mod error; +mod evaluator; mod lexer; mod namespace; mod syntax; mod typed; +// basically logos::Span but in this repo +type Span = Range; + fn main() { let args = Args::parse(); - let syntax = parse_syntax(&args.text); + let mut evaluator = Evaluator::init(); - if args.debug_tokens { - println!("Tokens: {syntax:#?}"); - } - - let mut files = SimpleFiles::new(); - - let input_id = files.add("input", args.text); - - let writer = StandardStream::stderr(ColorChoice::Always); - let config = term::Config::default(); - - if let Err(errs) = syntax { - let writer = &mut writer.lock(); - - term::emit( - writer, - &config, - &files, - &SyntaxError::InvalidToken(errs).to_diagnostic(input_id), - ) - .unwrap(); - } else { - let check_res = check(&syntax.clone().unwrap()); - - if let Err(errs) = check_res { - let writer = &mut writer.lock(); - - let diags = errs.into_iter().map(|err| err.to_diagnostic(input_id)); - - for diag in diags { - term::emit(writer, &config, &files, &diag).unwrap(); - } - } else { - let ns = initialise_globals(); - - println!("typed: {:#?}", into_typed_repr(&ns, syntax.unwrap())); - } - } + evaluator.run(args.text, None); + evaluator.next(); } diff --git a/src/syntax/check.rs b/src/syntax/check.rs index ec4dc22..7f68c7d 100644 --- a/src/syntax/check.rs +++ b/src/syntax/check.rs @@ -1,35 +1,56 @@ #[cfg(test)] mod test; -use crate::syntax::CommandPart; +use crate::{ + error::{ErrorKind, Errors, SyntaxErrorKind}, + syntax::CommandPart, +}; -use super::{error::SyntaxError, CommandPartKind, PipelineElement, PipelineElementKind}; +use super::{CommandPartKind, PipelineElement, PipelineElementKind}; -pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec> { +pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec> { let mut errs = Vec::new(); if let Err(e_span) = check_missing_streamer(syntax) { - errs.push(SyntaxError::MissingStreamer(vec![e_span])); + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::MissingStreamer), + e_span, + )); } if let Err(err_locs) = check_missing_filters(syntax) { - errs.push(SyntaxError::MissingFilter(err_locs)); + errs.push(Errors::new( + ErrorKind::SyntaxError(SyntaxErrorKind::MissingFilter), + err_locs, + )); } if let Err(e_span) = check_missing_sink(syntax) { - errs.push(SyntaxError::MissingSink(vec![e_span])); + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::MissingSink), + e_span, + )); } if let Err(e_span) = check_literal_as_sink(syntax) { - errs.push(SyntaxError::LiteralAsSink(vec![e_span])); + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsSink), + e_span, + )); } if let Err(err_locs) = check_literal_as_filter(syntax) { - errs.push(SyntaxError::LiteralAsFilter(err_locs)); + errs.push(Errors::new( + ErrorKind::SyntaxError(SyntaxErrorKind::LiteralAsFilter), + err_locs, + )); } if let Err(e_span) = check_literal_with_args(syntax) { - errs.push(SyntaxError::LiteralWithArgs(vec![e_span])); + errs.push(Errors::new_single( + ErrorKind::SyntaxError(SyntaxErrorKind::LiteralWithArgs), + e_span, + )); } if errs.is_empty() { @@ -127,6 +148,8 @@ fn check_literal_as_sink(syntax: &[PipelineElement]) -> Result<(), logos::Span> fn check_literal_as_filter(syntax: &[PipelineElement]) -> Result<(), Vec> { let errs = syntax .iter() + .take(syntax.len() - 1) + .skip(1) .filter(|element| { !matches!( element, @@ -155,8 +178,6 @@ fn check_literal_as_filter(syntax: &[PipelineElement]) -> Result<(), Vec>(); diff --git a/src/syntax/error.rs b/src/syntax/error.rs deleted file mode 100644 index 7d5ee56..0000000 --- a/src/syntax/error.rs +++ /dev/null @@ -1,79 +0,0 @@ -use codespan_reporting::diagnostic::{Diagnostic, Label}; - -/// The enum representing a syntax error, used for error reporting -#[derive(Debug, Clone)] -pub enum SyntaxError { - /// This variant indicates a token that the Lexer didn't recognize - InvalidToken(Vec), - /// `MissingStreamer` means, that the pipeline starts with a Pipe (`|`), so it has no streamer as input in front of it. - MissingStreamer(Vec), - /// `MissingSink` means, that the pipeline ends with a Pipe (`|`), meaning that the output can't go anywhere - MissingSink(Vec), - /// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another - MissingFilter(Vec), - /// A literal cannot be a sink, TODO - LiteralAsSink(Vec), - /// A literal can't be a filter either, TODO - LiteralAsFilter(Vec), - /// A literal acting as streamer cannot take arguments, TODO - LiteralWithArgs(Vec), -} - -// TODO: much better and more complex errors, with suggestions for fixes -impl SyntaxError { - pub fn to_diagnostic(&self, file_id: usize) -> Diagnostic { - match self { - Self::InvalidToken(errs) => Diagnostic::error() - .with_message("failed to parse invalid tokens") - .with_labels( - errs.iter() - .map(|span| { - Label::primary(file_id, span.clone()).with_message("invalid token") - }) - .collect(), - ), - Self::MissingStreamer(locs) => Diagnostic::error() - .with_message("pipelines must always start with a provider") - .with_labels( - locs.iter() - .map(|span| Label::primary(file_id, span.clone())) - .collect(), - ), - Self::MissingFilter(locs) => Diagnostic::error() - .with_message("missing filters in pipeline") - .with_labels( - locs.iter() - .map(|span| Label::primary(file_id, span.clone())) - .collect(), - ), - Self::MissingSink(locs) => Diagnostic::error() - .with_message("pipelines need to end in a sink") - .with_labels( - locs.iter() - .map(|span| Label::primary(file_id, span.clone())) - .collect(), - ), - Self::LiteralAsSink(locs) => Diagnostic::error() - .with_message("literals cannot be sinks") - .with_labels( - locs.iter() - .map(|span| Label::primary(file_id, span.clone())) - .collect(), - ), - Self::LiteralAsFilter(locs) => Diagnostic::error() - .with_message("literals cannot be filters") - .with_labels( - locs.iter() - .map(|span| Label::primary(file_id, span.clone())) - .collect(), - ), - Self::LiteralWithArgs(locs) => Diagnostic::error() - .with_message("literals cannot take arguments") - .with_labels( - locs.iter() - .map(|span| Label::primary(file_id, span.clone())) - .collect(), - ), - } - } -} diff --git a/src/syntax/mod.rs b/src/syntax/mod.rs index 890de00..a43aaac 100644 --- a/src/syntax/mod.rs +++ b/src/syntax/mod.rs @@ -6,7 +6,6 @@ use logos::Span; use crate::lexer::Token; pub mod check; -pub mod error; #[derive(Debug, Clone, PartialEq)] pub struct PipelineElement { diff --git a/src/typed.rs b/src/typed.rs index 26823e9..3779d24 100644 --- a/src/typed.rs +++ b/src/typed.rs @@ -2,14 +2,16 @@ use core::panic; use crate::{ builtins::{TYPE_FLOAT, TYPE_INTEGER, TYPE_STRING}, + error::Errors, namespace::{command::Command, r#type::Type, GlobalNamespace}, syntax::{CommandPart, CommandPartKind, PipelineElement, PipelineElementKind}, + Span, }; #[derive(Debug)] pub struct Expr<'a> { kind: ExprKind<'a>, - span: logos::Span, + span: Span, } #[derive(Debug)] @@ -38,8 +40,12 @@ impl LiteralKind { } } -pub fn into_typed_repr(ns: &GlobalNamespace, syntax: Vec) -> Vec { +pub fn into_typed_repr( + ns: &GlobalNamespace, + syntax: Vec, +) -> Result, Errors> { let mut res = Vec::new(); + let mut errs = Vec::new(); for item in syntax { let PipelineElement { kind, span } = item; @@ -51,7 +57,13 @@ pub fn into_typed_repr(ns: &GlobalNamespace, syntax: Vec) -> Ve res.push(Expr { kind: match kind { CommandPartKind::Word(val) => ExprKind::Command { - command: ns.get_command_by_name(val).unwrap(), + command: { + let Some(c) = ns.get_command_by_name(val) else { + errs.push(span); + continue; + }; + c + }, args: Vec::new(), }, CommandPartKind::Integer(val) => { @@ -77,7 +89,13 @@ pub fn into_typed_repr(ns: &GlobalNamespace, syntax: Vec) -> Ve res.push(Expr { kind: ExprKind::Command { - command: ns.get_command_by_name(name).unwrap(), + command: { + let Some(c) = ns.get_command_by_name(name) else { + errs.push(span.clone()); + continue; + }; + c + }, args: c .iter() .skip(1) @@ -104,5 +122,9 @@ pub fn into_typed_repr(ns: &GlobalNamespace, syntax: Vec) -> Ve } } - res + if errs.is_empty() { + Ok(res) + } else { + Err(Errors::new(crate::error::ErrorKind::CommandNotFound, errs)) + } }