forked from katzen-cafe/iowo
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:
parent
0ce869e859
commit
17878b3e87
7 changed files with 278 additions and 145 deletions
89
src/error/mod.rs
Normal file
89
src/error/mod.rs
Normal 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
117
src/evaluator.rs
Normal 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,
|
||||
}
|
62
src/main.rs
62
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<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();
|
||||
}
|
||||
|
|
|
@ -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>>();
|
||||
|
||||
|
|
|
@ -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(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -6,7 +6,6 @@ use logos::Span;
|
|||
use crate::lexer::Token;
|
||||
|
||||
pub mod check;
|
||||
pub mod error;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct PipelineElement {
|
||||
|
|
32
src/typed.rs
32
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<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))
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue