diff --git a/src/desugar.rs b/src/desugar.rs index 4bbbc861..70e4efc1 100644 --- a/src/desugar.rs +++ b/src/desugar.rs @@ -11,7 +11,7 @@ use crate::{ hir::{self, decl, expr, pat, Pass}, smallvec, span::{SourceMap, Spanning}, - support::{pluralize, release, InvalidFallback, ManyErrExt, TrySoftly}, + support::{accumulate_errors::*, pluralize, release, InvalidFallback, ManyErrExt, TrySoftly}, SmallVec, }; @@ -87,10 +87,13 @@ impl<'a> Desugarer<'a> { // partial moves, I hate those), use local bindings let expression = match value.expression { Some(expression) => { - let mut expression = self.desugar_expression(expression); + let mut expression = + self.desugar_expression(expression).try_softly(&mut errors); { - let mut type_annotation = - once(self.desugar_expression(declaration_type_annotation.clone())); + let mut type_annotation = once( + self.desugar_expression(declaration_type_annotation.clone()) + .try_softly(&mut errors), + ); for parameter_group in value.parameters.iter().rev() { let parameter_type_annotation = @@ -104,8 +107,9 @@ impl<'a> Desugarer<'a> { amount: parameter_group.parameters.len(), }, )) - .try_softly(&mut errors), - }; + .many_err(), + } + .try_softly(&mut errors); for binder in parameter_group.parameters.iter().rev() { expression = expr! { @@ -217,6 +221,7 @@ impl<'a> Desugarer<'a> { let declarations = match module.declarations { Some(declarations) => declarations, + // @Bug @Task disallow external module declarations inside of non-file modules None => { use crate::{lexer::Lexer, parser::Parser, span}; @@ -466,7 +471,8 @@ impl<'a> Desugarer<'a> { (true, false) => Err(Diagnostic::error() .with_code(Code::E012) .with_message("declaration without a definition") - .with_span(declaration)) + .with_span(declaration) + .with_help("provide a definition for the declaration: `= VALUE`")) .many_err(), (false, true) => { // @Task make non-fatal, @Task improve message @@ -483,22 +489,30 @@ impl<'a> Desugarer<'a> { } /// Lower an expression from AST to HIR. - // @Task rewrite this returning Results<_> for future use (that work is a bit tedious) + // @Question should we provide methods on Desugarer which abstract over TrySoftly? pub fn desugar_expression( &mut self, expression: ast::Expression, - ) -> hir::Expression { + ) -> Results> { use ast::ExpressionKind::*; - match expression.kind { - PiTypeLiteral(pi) => expr! { - PiType[expression.span] { - parameter: pi.binder.clone(), - domain: self.desugar_expression(pi.parameter), - codomain: self.desugar_expression(pi.expression), - explicitness: pi.explicitness, + Ok(match expression.kind { + PiTypeLiteral(pi) => { + let (domain, codomain) = ( + self.desugar_expression(pi.parameter), + self.desugar_expression(pi.expression), + ) + .accumulate_err()?; + + expr! { + PiType[expression.span] { + parameter: pi.binder.clone(), + domain, + codomain, + explicitness: pi.explicitness, + } } - }, + } Application(application) => { // @Temporary if let Some(binder) = &application.binder { @@ -509,10 +523,16 @@ impl<'a> Desugarer<'a> { ) } + let (callee, argument) = ( + self.desugar_expression(application.callee), + self.desugar_expression(application.argument), + ) + .accumulate_err()?; + expr! { Application[expression.span] { - callee: self.desugar_expression(application.callee), - argument: self.desugar_expression(application.argument), + callee, + argument, explicitness: application.explicitness, } } @@ -529,18 +549,27 @@ impl<'a> Desugarer<'a> { } }, LambdaLiteral(lambda) => { - let mut expression = self.desugar_expression(lambda.body); + let mut errors = Diagnostics::default(); + + let mut expression = self.desugar_expression(lambda.body).try_softly(&mut errors); let mut type_annotation = lambda .body_type_annotation - .map(|type_annotation| self.desugar_expression(type_annotation)) + .map(|type_annotation| { + self.desugar_expression(type_annotation) + .try_softly(&mut errors) + }) .into_iter(); for parameter_group in lambda.parameters.iter().rev() { - let parameter = parameter_group - .type_annotation - .clone() - .map(|type_annotation| self.desugar_expression(type_annotation)); + let parameter = + parameter_group + .type_annotation + .clone() + .map(|type_annotation| { + self.desugar_expression(type_annotation) + .try_softly(&mut errors) + }); for binder in parameter_group.parameters.iter().rev() { expression = expr! { @@ -554,21 +583,31 @@ impl<'a> Desugarer<'a> { }; } } + + release!(errors); + expression } LetIn(let_in) => { - let mut expression = self.desugar_expression(let_in.expression); + let mut errors = Diagnostics::default(); + + let mut expression = self + .desugar_expression(let_in.expression) + .try_softly(&mut errors); let mut type_annotation = let_in .type_annotation - .map(|type_annotation| self.desugar_expression(type_annotation)) + .map(|type_annotation| { + self.desugar_expression(type_annotation) + .try_softly(&mut errors) + }) .into_iter(); for parameter_group in let_in.parameters.iter().rev() { - let parameter = parameter_group - .type_annotation - .clone() - .map(|expression| self.desugar_expression(expression)); + let parameter = parameter_group.type_annotation.clone().map(|expression| { + self.desugar_expression(expression).try_softly(&mut errors) + }); + for binder in parameter_group.parameters.iter().rev() { expression = expr! { Lambda[] { @@ -582,6 +621,12 @@ impl<'a> Desugarer<'a> { } } + let body = self + .desugar_expression(let_in.scope) + .try_softly(&mut errors); + + release!(errors); + expr! { Application[] { callee: expr! { @@ -595,7 +640,7 @@ impl<'a> Desugarer<'a> { parameter_type_annotation: type_annotation.next(), explicitness: Explicit, body_type_annotation: None, - body: self.desugar_expression(let_in.scope), + body, } }, argument: expression, @@ -606,24 +651,39 @@ impl<'a> Desugarer<'a> { // @Beacon @Task UseIn(_use_in) => todo!("use/in expressions not supported yet"), CaseAnalysis(analysis) => { + let mut errors = Diagnostics::default(); let mut cases = Vec::new(); for case_group in analysis.cases { cases.push(hir::Case { pattern: self.desugar_pattern(case_group.pattern), - body: self.desugar_expression(case_group.expression.clone()), + body: self + .desugar_expression(case_group.expression.clone()) + .try_softly(&mut errors), }); } + let subject = self + .desugar_expression(analysis.expression) + .try_softly(&mut errors); + + release!(errors); + expr! { CaseAnalysis[expression.span] { - subject: self.desugar_expression(analysis.expression), + subject, cases, } } } + DoBlock(_block) => { + return Err(Diagnostic::bug() + .with_message("do blocks not fully implemented yet") + .with_span(&expression.span)) + .many_err() + } Invalid => InvalidFallback::invalid(), - } + }) } /// Lower a pattern from AST to HIR. @@ -662,10 +722,12 @@ impl<'a> Desugarer<'a> { parameters: ast::Parameters, type_annotation: ast::Expression, ) -> Results> { - let mut expression = self.desugar_expression(type_annotation); - let mut errors = Diagnostics::default(); + let mut expression = self + .desugar_expression(type_annotation) + .try_softly(&mut errors); + for parameter_group in parameters.parameters.into_iter().rev() { let parameter_type_annotation = match parameter_group.type_annotation { Some(type_annotation) => self.desugar_expression(type_annotation), @@ -675,8 +737,9 @@ impl<'a> Desugarer<'a> { amount: parameter_group.parameters.len(), }, )) - .try_softly(&mut errors), - }; + .many_err(), + } + .try_softly(&mut errors); for binder in parameter_group.parameters.iter().rev() { expression = expr! { @@ -704,6 +767,10 @@ fn missing_mandatory_type_annotation( .with_code(Code::E015) .with_message(format!("missing mandatory type annotation on {}", target)) .with_span(spanning) + .with_help(format!( + "provide a type annotation for the {}: `: TYPE`", + target + )) } /// Used for error reporting. diff --git a/src/diagnostic.rs b/src/diagnostic.rs index 87b8678e..6044dd21 100644 --- a/src/diagnostic.rs +++ b/src/diagnostic.rs @@ -15,6 +15,8 @@ pub type Result = std::result::Result; // @Question bad name? pub type Results = Result; +// @Question should write a wrapper that does not allocate on the heap for one Diagnostic? +// (many_err is a common occurence idk) pub type Diagnostics = HashSet; // @Question is this indirection actually worth it? diff --git a/src/parser.rs b/src/parser.rs index e874916b..be8bdd58 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -14,12 +14,6 @@ // @Task allow underscores in places where binders are allowed // @Task parse temporary // Lazy-Expression ::= "lazy" Expression -// @Task parse do expression -// Do-Expression ::= "do" Indentation Statement* Expression Line-Break Dedentation -// Statement ::= Bind-Statement | Expression-Statement | Let-Statement -// Bind-Statement ::= (#Identifier | "_") Type-Annotation? "<-" Expression Line-Break -// (necessary? idths) Expression-Statement ::= Expression Line-Break -// Let-Statement ::= "let" #Identifier Parameters Type-Annotation? "=" Expression Line-Break pub mod ast; @@ -399,7 +393,7 @@ impl<'a> Parser<'a> { /// Finish parsing module declaration. /// - /// This is either a module declaration or a file system module declaration. + /// This is either a module declaration or an external module declaration. /// /// ## Grammar /// @@ -430,6 +424,7 @@ impl<'a> Parser<'a> { span.merging(&binder); if self.consumed(LineBreak) { + // it is an external module declaration return Ok(decl! { Module[span] { binder, @@ -621,7 +616,7 @@ impl<'a> Parser<'a> { binder: Some(binder), }); } else { - // @Note this is really really fragile=non-extensible! + // @Note @Bug this is really really fragile=non-extensible! bindings.push(self.parse_use_bindings(&[ OpeningRoundBracket.into(), ClosingRoundBracket.into(), @@ -713,6 +708,7 @@ impl<'a> Parser<'a> { /// ```text /// Expression ::= Let-In | Use-In | Lambda-Literal | Case-Analysis | Pi-Literal-Or-Lower /// ``` + // @Task parse sigma literals fn parse_expression(&mut self) -> Result { use TokenKind::*; let token = self.current(); @@ -734,6 +730,10 @@ impl<'a> Parser<'a> { self.advance(); self.finish_parse_case_analysis(token.span) } + Do => { + self.advance(); + self.finish_parse_do_block(token.span) + } _ => self.parse_pi_type_literal_or_lower(), } } @@ -1055,6 +1055,95 @@ impl<'a> Parser<'a> { }) } + /// Finish parsing a do block. + /// + /// The keyword `do` should have already been consumed. + /// + /// ## Grammar + /// + /// ```text + /// Do-Block ::= "do" Line-Break Indentation Statement+ Dedentation + /// Statement ::= Let-Statement | Use-Declaration | Bind-Statement | Expression-Statement + /// Let-Statement ::= "let" Value-Declaration + /// Bind-Statement ::= #Identifier Type-Annotation? "<-" Expression Line-Break + /// Expression-Statement ::= Expression Line-Break + /// ``` + /// + /// Bind statements are the worst right now. We need to look ahead for `:` (type annotation) + /// or `<-` to differenciate them from expressions. Maybe there is prefix-oriented syntax + /// we could switch to like `!x = …` or `set x = …`. The latter look-ahead is not much of an + /// issue, `:` is a bad *but only in case* of adding type annotation expressions (not that likely + /// as they clash with other syntactic elements like pi literals). + fn finish_parse_do_block(&mut self, span_of_do: Span) -> Result { + use TokenKind::*; + let mut span = span_of_do; + let mut statements = Vec::new(); + + self.consume(LineBreak)?; + self.consume(Indentation)?; + + while !self.current_is(Dedentation) { + let token = self.current(); + + statements.push(match token.kind { + Let => { + self.advance(); + let binder = self.consume_identifier()?; + let parameters = self.parse_parameters(&[ + Delimiter::TypeAnnotationPrefix, + Delimiter::DefinitionPrefix, + ])?; + let type_annotation = self.parse_optional_type_annotation()?; + self.consume(TokenKind::Equals)?; + let expression = self.parse_possibly_indented_terminated_expression()?; + Statement::Let(LetStatement { + binder, + parameters, + type_annotation, + expression, + }) + } + Use => { + self.advance(); + let bindings = self.parse_use_bindings(&[TokenKind::LineBreak.into()])?; + self.consume(LineBreak)?; + Statement::Use(ast::Use { bindings }) + } + _ => { + if self.current_is(Identifier) && self.succeeding_is(Colon) + || self.succeeding_is(ThinArrowLeft) + { + self.advance(); + let binder = ast::Identifier::from_token(token); + let type_annotation = self.parse_optional_type_annotation()?; + self.consume(ThinArrowLeft)?; + let expression = self.parse_possibly_indented_terminated_expression()?; + Statement::Bind(BindStatement { + binder, + type_annotation, + expression, + }) + } else { + // @Task improve error diagnostics for an unexpected token to not only mention an + // expression was expected but also statements were + let expression = self.parse_expression()?; + self.consume(LineBreak)?; + Statement::Expression(expression) + } + } + }); + } + + span.merging(&self.current()); + self.advance(); + + Ok(expr! { + DoBlock[span] { + statements, + } + }) + } + /// Parse parameters until one of the given delimiters is encountered. /// /// One needs to specify delimiters to allow for better error diagnostics. @@ -1305,7 +1394,7 @@ impl<'a> Parser<'a> { /// ## Grammar /// /// ```text - /// Terminated-Expression ::= Expression (Dedentation Line-Break)? + /// Terminated-Expression ::= Expression ( Result { let expression = self.parse_expression()?; diff --git a/src/parser/ast.rs b/src/parser/ast.rs index cf85430d..f1b7d1c6 100644 --- a/src/parser/ast.rs +++ b/src/parser/ast.rs @@ -70,8 +70,10 @@ pub enum DeclarationKind { Use(Box), } -/// The syntax node of a value declaration. -#[derive(Debug)] +/// The syntax node of a value declaration or a let statement. +/// +/// See [DeclarationKind::Value] and [Statement::Let]. +#[derive(Clone, Debug)] pub struct Value { pub binder: Identifier, pub parameters: Parameters, @@ -124,8 +126,10 @@ pub struct Group { pub declarations: Vec, } -/// The syntax node of a use declaration. -#[derive(Debug)] +/// The syntax node of a use declaration or statement. +/// +/// See [DeclarationKind::Use] and [Statement::Use]. +#[derive(Clone, Debug)] pub struct Use { pub bindings: UseBindings, } @@ -299,6 +303,7 @@ pub enum ExpressionKind { LetIn(Box), UseIn(Box), CaseAnalysis(Box), + DoBlock(Box), /// See documentation on [crate::hir::Expression::Invalid]. Invalid, } @@ -341,13 +346,16 @@ pub struct LambdaLiteral { pub body: Expression, } -/// The syntax-node of a let-in expression. +/// The syntax-node of a let/in expression. #[derive(Debug, Clone)] pub struct LetIn { pub binder: Identifier, pub parameters: Parameters, pub type_annotation: Option, // @Task improve upon naming + // @Note we could make this syntactically optional and then prove a beatiful error message + // in the desugarer (that's what we currently do for [DeclarationKind::Value] and plan to do + // for [LetStatement]) pub expression: Expression, pub scope: Expression, } @@ -365,6 +373,41 @@ pub struct CaseAnalysis { pub cases: Vec, } +#[derive(Debug, Clone)] +pub struct DoBlock { + pub statements: Vec, +} + +// @Note we probably gonna need to make this spanning in the future (for diagnostics) just like +// Expression, Pattern, Declaration. +#[derive(Debug, Clone)] +pub enum Statement { + // @Note we could make the definition syntactically optional and provide a good error message + // (missing definition) when desugaring + Let(LetStatement), + Use(Use), + // @Question should we rename this to Assign since we plan on not only desugaring + // to monads but also applicatives? + Bind(BindStatement), + Expression(Expression), +} + +// @Note has a lot of overlap with [StatementKind::Value] (and a bit with [ExpressionKind::LetIn]) +#[derive(Debug, Clone)] +pub struct LetStatement { + pub binder: Identifier, + pub parameters: Parameters, + pub type_annotation: Option, + pub expression: Expression, +} + +#[derive(Debug, Clone)] +pub struct BindStatement { + pub binder: Identifier, + pub type_annotation: Option, + pub expression: Expression, +} + impl InvalidFallback for Expression { fn invalid() -> Self { expr! {