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
This commit is contained in:
Schrottkatze 2023-11-19 16:52:38 +01:00
parent 0ce869e859
commit 17878b3e87
7 changed files with 278 additions and 145 deletions

89
src/error/mod.rs Normal file
View file

@ -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<Span>,
}
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<Span>) -> 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<usize> {
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<Span>, file_id: usize, msg: &str) -> Diagnostic<usize> {
Diagnostic::error().with_message(msg).with_labels(
spans
.into_iter()
.map(|span| Label::primary(file_id, span))
.collect(),
)
}

117
src/evaluator.rs Normal file
View file

@ -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<usize, Vec<Errors>>,
}
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<!, codespan_reporting::files::Error> {
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<PipelineElement>),
BareTyped(usize, Vec<PipelineElement>),
Failed,
}

View file

@ -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<usize>;
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();
}

View file

@ -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<SyntaxError>> {
pub fn check(syntax: &[PipelineElement]) -> Result<(), Vec<Errors>> {
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<logos::Span>> {
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<logos::
None
}
})
.skip(1)
.take(syntax.len() - 1)
.filter_map(|err| err.map(Clone::clone))
.collect::<Vec<logos::Span>>();

View file

@ -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<logos::Span>),
/// `MissingStreamer` means, that the pipeline starts with a Pipe (`|`), so it has no streamer as input in front of it.
MissingStreamer(Vec<logos::Span>),
/// `MissingSink` means, that the pipeline ends with a Pipe (`|`), meaning that the output can't go anywhere
MissingSink(Vec<logos::Span>),
/// This indicates a missing filter somewhere in the pipeline, meaning that there's 2 pipes after one another
MissingFilter(Vec<logos::Span>),
/// A literal cannot be a sink, TODO
LiteralAsSink(Vec<logos::Span>),
/// A literal can't be a filter either, TODO
LiteralAsFilter(Vec<logos::Span>),
/// A literal acting as streamer cannot take arguments, TODO
LiteralWithArgs(Vec<logos::Span>),
}
// TODO: much better and more complex errors, with suggestions for fixes
impl SyntaxError {
pub fn to_diagnostic(&self, file_id: usize) -> Diagnostic<usize> {
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(),
),
}
}
}

View file

@ -6,7 +6,6 @@ use logos::Span;
use crate::lexer::Token;
pub mod check;
pub mod error;
#[derive(Debug, Clone, PartialEq)]
pub struct PipelineElement {

View file

@ -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<PipelineElement>) -> Vec<Expr> {
pub fn into_typed_repr(
ns: &GlobalNamespace,
syntax: Vec<PipelineElement>,
) -> Result<Vec<Expr>, 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<PipelineElement>) -> 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<PipelineElement>) -> 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<PipelineElement>) -> Ve
}
}
res
if errs.is_empty() {
Ok(res)
} else {
Err(Errors::new(crate::error::ErrorKind::CommandNotFound, errs))
}
}