diff --git a/grammar.ebnf b/grammar.ebnf index f985ca1..4657784 100644 --- a/grammar.ebnf +++ b/grammar.ebnf @@ -16,13 +16,13 @@ Fn = "fn" Identifier Block ; (* TODO: params, return value*) (* # Expressions *) (* expression *) Expr = Assign ; -Block = '{' Expr '}' ; +Block = '{' Stmt* Expr? '}' ; Group = '(' Expr? ')' ; -Primary = Item | Identifier | Literal +Primary = Identifier | Literal | Block | Group | Branch ; (* expression::math *) -Assign = Compare (AssignOp Compare)* ; +Assign = Identifier (AssignOp Assign) | Compare ; Compare = Range (CompareOp Range )* ; Range = Logic (RangeOp Logic )* ; Logic = Bitwise (LogicOp Bitwise)* ; diff --git a/libconlang/src/ast.rs b/libconlang/src/ast.rs index 21c2580..c8dbaba 100644 --- a/libconlang/src/ast.rs +++ b/libconlang/src/ast.rs @@ -18,8 +18,8 @@ pub mod preamble { math::{self, operator}, }, literal, - statement::Stmt, - visitor::{Visitor, Walk}, + statement::*, + visitor::Visitor, Identifier, Program, Start, }; } @@ -39,7 +39,7 @@ pub struct Program(pub Vec); /// An Identifier stores the name of an item /// # Syntax /// [`Identifier`]` := `[`IDENTIFIER`](crate::token::token_type::Type::Identifier) -#[derive(Clone, Debug, Hash)] +#[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct Identifier(pub String); pub mod todo { @@ -148,7 +148,12 @@ pub mod statement { //! [`Stmt`]` := `[`Let`](Stmt::Let)` | `[`Expr`](Stmt::Expr) //! [`Let`](Stmt::Let)` := "let"` [`Identifier`] (`:` `Type`)? (`=` [`Expr`])? `;` //! [`Expr`](Stmt::Expr)` := `[`Expr`] `;` - use super::{expression::Expr, Identifier}; + use crate::token::Token; + + use super::{ + expression::{Block, Expr}, + Identifier, + }; /// Contains a statement /// # Syntax @@ -158,17 +163,31 @@ pub mod statement { /// Contains a variable declaration /// # Syntax /// [`Let`](Stmt::Let) := `"let"` [`Identifier`] (`:` `Type`)? (`=` [`Expr`])? `;` - Let { - name: Identifier, - mutable: bool, - ty: Option, - init: Option, - }, + Let(Let), /// Contains an expression statement /// # Syntax /// [`Expr`](Stmt::Expr) := [`Expr`] `;` Expr(Expr), } + + /// Contains a variable declaration + /// # Syntax + /// [`Let`] := `let` [`Identifier`] (`:`) `Type`)? (`=` [`Expr`])? `;` + #[derive(Clone, Debug)] + pub struct Let { + pub name: Identifier, + pub mutable: bool, + pub ty: Option, + pub init: Option, + } + + #[derive(Clone, Debug)] + pub struct Fn { + pub name: Identifier, + pub args: (), // TODO: capture arguments + pub rety: Token, + pub body: Block, + } } pub mod expression { @@ -203,14 +222,14 @@ pub mod expression { //! `[`Group`]` | `[`control::Flow`] //! //! See [control] and [math] for their respective production rules. - use super::*; + use super::{statement::Stmt, *}; /// Contains an expression /// /// # Syntax /// [`Expr`]` := `[`math::Operation`] #[derive(Clone, Debug)] - pub struct Expr (pub math::Operation); + pub struct Expr(pub math::Operation); /// A [Primary] Expression is the expression with the highest precedence (i.e. the deepest /// derivation) @@ -234,7 +253,8 @@ pub mod expression { /// [`Block`] := `'{'` [`Expr`] `'}'` #[derive(Clone, Debug)] pub struct Block { - pub expr: Box, + pub statements: Vec, + pub expr: Option>, } /// Contains a Parenthesized Expression @@ -252,64 +272,83 @@ pub mod expression { //! ## Precedence Order //! Operator associativity is always left-to-right among members of the same group //! - //! | # | Name | Operators | Associativity - //! |---|----------:|:----------------------------------------|--------------- - // | | TODO: Try | `?` | - //! | 1 | Unary | [`*` `&` `-` `!`][3] | Right - //! | 2 | Factor | [`*` `/` `%`][4] | Left to Right - //! | 3 | Term | [`+` `-`][4] | Left to Right - //! | 4 | Shift | [`<<` `>>`][4] | Left to Right - //! | 5 | Bitwise | [`&` |][4] | Left to Right - //! | 6 | Logic | [`&&` || `^^`][4]| Left to Right - //! | 7 | Compare | [`<` `<=` `==` `!=` `>=` `>`][4] | Left to Right - #![doc = concat!( //| | - r" | 8 | Assign | [`*=`, `/=`, `%=`, `+=`, `-=`, ", //| - /* | | |*/ r"`&=`, |=, ", //| - /* | | |*/ r"`^=`, `<<=`, `>>=`][4]", r"| Left to Right")] + //! | # | Name | Operators | Associativity + //! |---|-----------:|:----------------------------------------|--------------- + // | | TODO: Try | `?` | + //! | 1 | [Unary][1]| [`*` `&` `-` `!`][4] | Right + //! | 2 | [Factor][2]| [`*` `/` `%`][5] | Left to Right + //! | 3 | [Term][2]| [`+` `-`][5] | Left to Right + //! | 4 | [Shift][2]| [`<<` `>>`][5] | Left to Right + //! | 5 |[Bitwise][2]| [`&` |][4] | Left to Right + //! | 6 | [Logic][2]| [`&&` || `^^`][5]| Left to Right + //! | 7 |[Compare][2]| [`<` `<=` `==` `!=` `>=` `>`][5] | Left to Right + #![doc = concat!( //| | + r" | 8 | [Assign][3]| [`*=`, `/=`, `%=`, `+=`, `-=`, ", //| + /* | | |*/ r"`&=`, |=, ", //| + /* | | |*/ r"`^=`, `<<=`, `>>=`][6]", r"| Left to Right")] //! //! //! //! ## Syntax //! All precedence levels other than [Unary][1] fold into [Binary][2] //! - //! [`Assign`][2]` := `[`Compare`][2]` (`[`AssignOp`][4]` `[`Compare`][2]`)*` \ - //! [`Compare`][2]` := `[`Logic`][2]` (`[`CompareOp`][4]` `[`Logic`][2]` )*` \ - //! [`Logic`][2]` := `[`Bitwise`][2]` (`[`LogicOp`][4]` `[`Bitwise`][2]`)*` \ - //! [`Bitwise`][2]` := `[`Shift`][2]` (`[`BitwiseOp`][4]` `[`Shift`][2]` )*` \ - //! [`Shift`][2]` := `[`Term`][2]` (`[`ShiftOp`][4]` `[`Term`][2]` )*` \ - //! [`Term`][2]` := `[`Factor`][2]` (`[`TermOp`][4]` `[`Factor`][2]` )*` \ - //! [`Factor`][2]` := `[`Unary`][1]` (`[`FactorOp`][4]` `[`Unary`][1]` )*` \ - //! [`Unary`][1]` := (`[`UnaryOp`][3]`)* `[`Primary`] + //! [`Assign`][3]` := `[`Compare`][2]` (`[`AssignOp`][6]` `[`Compare`][2]`)*` \ + //! [`Compare`][2]` := `[`Logic`][2]` (`[`CompareOp`][5]` `[`Logic`][2]` )*` \ + //! [`Logic`][2]` := `[`Bitwise`][2]` (`[`LogicOp`][5]` `[`Bitwise`][2]`)*` \ + //! [`Bitwise`][2]` := `[`Shift`][2]` (`[`BitwiseOp`][5]` `[`Shift`][2]` )*` \ + //! [`Shift`][2]` := `[`Term`][2]` (`[`ShiftOp`][5]` `[`Term`][2]` )*` \ + //! [`Term`][2]` := `[`Factor`][2]` (`[`TermOp`][5]` `[`Factor`][2]` )*` \ + //! [`Factor`][2]` := `[`Unary`][1]` (`[`FactorOp`][5]` `[`Unary`][1]` )*` \ + //! [`Unary`][1]` := (`[`UnaryOp`][4]`)* `[`Primary`] //! //! [1]: Operation::Unary //! [2]: Operation::Binary - //! [3]: operator::Unary - //! [4]: operator::Binary + //! [3]: Operation::Assign + //! [4]: operator::Unary + //! [5]: operator::Binary + //! [6]: operator::Assign use super::*; /// An Operation is a tree of [operands](Primary) and [operators](operator). #[derive(Clone, Debug)] pub enum Operation { + /// [`Assign`](Operation::Assign) := + /// [`Identifier`] [`operator::Assign`] [`Operation`] | [`Operation`] + Assign(Assign), /// [`Binary`](Operation::Binary) := /// [`Operation`] ([`operator::Binary`] [`Operation`])* - Binary { - first: Box, - other: Vec<(operator::Binary, Self)>, - }, - /// [`Unary`](Operation::Unary) := ([`operator::Unary`])* [`Primary`] - Unary { - operators: Vec, - operand: Primary, - }, + Binary(Binary), + /// [`Unary`](Operation::Unary) := ([`operator::Unary`])* + /// [`Primary`](Operation::Primary) + Unary(Unary), + /// [`Primary`](Operation::Primary) := [`expression::Primary`] + Primary(Primary), } - impl Operation { - pub fn binary(first: Self, other: Vec<(operator::Binary, Self)>) -> Self { - Self::Binary { first: Box::new(first), other } - } + + /// [`Assign`] := [`Identifier`] [`operator::Assign`] [`Operation`] | [`Operation`] + #[derive(Clone, Debug)] + pub struct Assign { + pub target: Identifier, + pub operator: operator::Assign, + pub init: Box, + } + + /// [`Binary`] := [`Operation`] ([`operator::Binary`] [`Operation`])* + #[derive(Clone, Debug)] + pub struct Binary { + pub first: Box, + pub other: Vec<(operator::Binary, Operation)>, + } + + /// [`Unary`] := ([`operator::Unary`])* [`Primary`](Operation::Primary) + #[derive(Clone, Debug)] + pub struct Unary { + pub operators: Vec, + pub operand: Box, } pub mod operator { - //! # [Unary] and [Binary] operators + //! # [Unary], [Binary], and [Assign] operators //! //! An Operator represents the action taken during an [operation](super::Operation) @@ -351,13 +390,6 @@ pub mod expression { /// ## Comparison operators /// [`<`](Binary::Less), [`<=`](Binary::LessEq), [`==`](Binary::Equal), /// [`!=`](Binary::NotEq), [`>=`](Binary::GreaterEq), [`>`](Binary::Greater), - /// ## Assignment operators - /// [`=`](Binary::Assign), [`+=`](Binary::AddAssign), [`-=`](Binary::SubAssign), - /// [`*=`](Binary::MulAssign), [`/=`](Binary::DivAssign), [`%=`](Binary::RemAssign), - /// [`&=`](Binary::BitAndAssign), [`|=`](Binary::BitOrAssign), - /// [`^=`](Binary::BitXorAssign) [`<<=`](Binary::ShlAssign), - /// [`>>=`](Binary::ShrAssign) - #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum Binary { /// `*`: Multiplication @@ -402,6 +434,16 @@ pub mod expression { GreaterEq, /// `>`: Greater-than Comparison Greater, + } + + /// # Assignment operators + /// [`=`](Assign::Assign), [`+=`](Assign::AddAssign), [`-=`](Assign::SubAssign), + /// [`*=`](Assign::MulAssign), [`/=`](Assign::DivAssign), [`%=`](Assign::RemAssign), + /// [`&=`](Assign::BitAndAssign), [`|=`](Assign::BitOrAssign), + /// [`^=`](Assign::BitXorAssign) [`<<=`](Assign::ShlAssign), + /// [`>>=`](Assign::ShrAssign) + #[derive(Clone, Copy, Debug, PartialEq, Eq)] + pub enum Assign { /// `=`: Assignment Assign, /// `+=`: Additive In-place Assignment @@ -611,182 +653,11 @@ pub mod visitor { //! A [`Visitor`] visits every kind of node in the [Abstract Syntax Tree](super). Nodes, //! conversely are [`Walkers`](Walk) for Visitors which return a [`Result<(), E>`](Result) use super::{ - expression::{ - control::*, - math::{operator::*, *}, - Block, *, - }, + expression::{control::*, math::*, Block, *}, literal::*, - statement::Stmt, + statement::*, *, }; - /// A [Walker](Walk) is a node in the AST, and calls [`Visitor::visit_*()`](Visitor) on all its - /// children - pub trait Walk + ?Sized, R> { - /// Traverses the children of this node in order, calling the appropriate [Visitor] function - fn walk(&self, visitor: &mut T) -> R; - } - mod walker { - use crate::ast::statement::Stmt; - - use super::*; - macro leaf($($T:ty),*$(,)?) {$( - impl> + ?Sized, E> Walk> for $T { - #[doc = concat!("A(n) [`", stringify!($T), "`] is a leaf node.")] - /// Calling this will do nothing. - fn walk(&self, _visitor: &mut T) -> Result<(), E> { Ok(()) } - } - )*} - leaf!(Binary, bool, char, Continue, Float, Identifier, str, u128, Unary); - impl> + ?Sized, E> Walk> for While { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_expr(&self.cond)?; - visitor.visit_block(&self.body)?; - match &self.else_ { - Some(expr) => visitor.visit_else(expr), - None => Ok(()), - } - } - } - impl> + ?Sized, E> Walk> for If { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_expr(&self.cond)?; - visitor.visit_block(&self.body)?; - match &self.else_ { - Some(expr) => visitor.visit_else(expr), - None => Ok(()), - } - } - } - impl> + ?Sized, E> Walk> for For { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_identifier(&self.var)?; - visitor.visit_expr(&self.iter)?; - visitor.visit_block(&self.body)?; - match &self.else_ { - Some(expr) => visitor.visit_else(expr), - None => Ok(()), - } - } - } - impl> + ?Sized, E> Walk> for Else { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_block(&self.block) - } - } - impl> + ?Sized, E> Walk> for Return { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_expr(&self.expr) - } - } - impl> + ?Sized, E> Walk> for Break { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_expr(&self.expr) - } - } - impl> + ?Sized, E> Walk> for Start { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_program(&self.0) - } - } - impl> + ?Sized, E> Walk> for Expr { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_operation(&self.0) - } - } - impl> + ?Sized, E> Walk> for Group { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - match self { - Group::Expr(expr) => visitor.visit_expr(expr), - Group::Empty => visitor.visit_empty(), - } - } - } - impl> + ?Sized, E> Walk> for Block { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - visitor.visit_expr(&self.expr) - } - } - impl> + ?Sized, E> Walk> for Operation { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - match self { - Operation::Binary { first, other } => { - visitor.visit_operation(first)?; - for (op, other) in other { - visitor.visit_binary_op(op)?; - visitor.visit_operation(other)?; - } - Ok(()) - } - Operation::Unary { operators, operand } => { - for op in operators { - visitor.visit_unary_op(op)?; - } - visitor.visit_primary(operand) - } - } - } - } - impl> + ?Sized, E> Walk> for Primary { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - match self { - Primary::Identifier(i) => visitor.visit_identifier(i), - Primary::Literal(l) => visitor.visit_literal(l), - Primary::Block(b) => visitor.visit_block(b), - Primary::Group(g) => visitor.visit_group(g), - Primary::Branch(b) => visitor.visit_branch_expr(b), - } - } - } - impl> + ?Sized, E> Walk> for Literal { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - match self { - Literal::String(s) => visitor.visit_string_literal(s), - Literal::Char(c) => visitor.visit_char_literal(c), - Literal::Bool(b) => visitor.visit_bool_literal(b), - Literal::Float(f) => visitor.visit_float_literal(f), - Literal::Int(i) => visitor.visit_int_literal(i), - } - } - } - impl> + ?Sized, E> Walk> for Flow { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - match self { - Flow::While(w) => visitor.visit_while(w), - Flow::If(i) => visitor.visit_if(i), - Flow::For(f) => visitor.visit_for(f), - Flow::Continue(c) => visitor.visit_continue(c), - Flow::Return(r) => visitor.visit_return(r), - Flow::Break(b) => visitor.visit_break(b), - } - } - } - impl> + ?Sized, E> Walk> for Stmt { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - match self { - Stmt::Let { name, mutable: _, ty, init } => { - visitor.visit_identifier(name)?; - if let Some(ty) = ty { - visitor.visit_identifier(ty)?; - } - if let Some(init) = init { - visitor.visit_expr(init)?; - } - Ok(()) - } - Stmt::Expr(e) => visitor.visit_expr(e), - } - } - } - impl> + ?Sized, E> Walk> for Program { - fn walk(&self, visitor: &mut T) -> Result<(), E> { - for stmt in &self.0 { - visitor.visit_statement(stmt)?; - } - Ok(()) - } - } - } /// A Visitor traverses every kind of node in the [Abstract Syntax Tree](super) pub trait Visitor { @@ -798,7 +669,14 @@ pub mod visitor { fn visit_program(&mut self, prog: &Program) -> R; /// Visit a [Statement](Stmt) - fn visit_statement(&mut self, stmt: &Stmt) -> R; + fn visit_statement(&mut self, stmt: &Stmt) -> R { + match stmt { + Stmt::Let(stmt) => self.visit_let(stmt), + Stmt::Expr(expr) => self.visit_expr(expr), + } + } + /// Visit a [Let statement](Let) + fn visit_let(&mut self, stmt: &Let) -> R; /// Visit an [Expression](Expr) fn visit_expr(&mut self, expr: &Expr) -> R { @@ -806,9 +684,7 @@ pub mod visitor { } // Block expression /// Visit a [Block] expression - fn visit_block(&mut self, expr: &Block) -> R { - self.visit_expr(&expr.expr) - } + fn visit_block(&mut self, block: &Block) -> R; /// Visit a [Group] expression fn visit_group(&mut self, group: &Group) -> R { match group { @@ -819,9 +695,23 @@ pub mod visitor { // Math expression /// Visit an [Operation] - fn visit_operation(&mut self, expr: &Operation) -> R; - /// Visit a [Binary](Operation::Binary) [operator](operator::Binary) + fn visit_operation(&mut self, operation: &Operation) -> R { + match operation { + Operation::Assign(assign) => self.visit_assign(assign), + Operation::Binary(binary) => self.visit_binary(binary), + Operation::Unary(unary) => self.visit_unary(unary), + Operation::Primary(primary) => self.visit_primary(primary), + } + } + /// Visit an [Assignment](Assign) operation + fn visit_assign(&mut self, assign: &Assign) -> R; + /// Visit a [Binary] Operation + fn visit_binary(&mut self, binary: &Binary) -> R; + /// Visit a [Unary] Operation + fn visit_unary(&mut self, unary: &Unary) -> R; // Math operators + fn visit_assign_op(&mut self, op: &operator::Assign) -> R; + /// Visit a [Binary](Operation::Binary) [operator](operator::Binary) fn visit_binary_op(&mut self, op: &operator::Binary) -> R; /// Visit a [Unary](Operation::Unary) [operator](operator::Unary) fn visit_unary_op(&mut self, op: &operator::Unary) -> R; @@ -829,8 +719,8 @@ pub mod visitor { /// Visit a [Primary] expression /// /// [`Primary`]` := `[`Identifier`]` | `[`Literal`]` | `[`Block`]` | `[`Flow`] - fn visit_primary(&mut self, expr: &Primary) -> R { - match expr { + fn visit_primary(&mut self, primary: &Primary) -> R { + match primary { Primary::Identifier(v) => self.visit_identifier(v), Primary::Literal(v) => self.visit_literal(v), Primary::Block(v) => self.visit_block(v), diff --git a/libconlang/src/interpreter.rs b/libconlang/src/interpreter.rs index 40d4f8b..0f4820e 100644 --- a/libconlang/src/interpreter.rs +++ b/libconlang/src/interpreter.rs @@ -1,5 +1,6 @@ //! Interprets an AST as a program +use self::scope::Environment; use crate::ast::preamble::*; use error::{Error, IResult, Reason}; use temp_type_impl::ConValue; @@ -12,9 +13,10 @@ pub mod temp_type_impl { /// /// This is a hack to work around the fact that Conlang doesn't have a functioning type system /// yet :( - #[derive(Clone, Debug)] + #[derive(Clone, Debug, Default)] pub enum ConValue { /// The empty/unit `()` type + #[default] Empty, /// An integer Int(i128), @@ -57,6 +59,18 @@ pub mod temp_type_impl { gt_eq: true, >=; gt: false, >; } + assign! { + add_assign: +; + bitand_assign: &; + bitor_assign: |; + bitxor_assign: ^; + div_assign: /; + mul_assign: *; + rem_assign: %; + shl_assign: <<; + shr_assign: >>; + sub_assign: -; + } } /// Templates comparison functions for [ConValue] macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$( @@ -73,6 +87,12 @@ pub mod temp_type_impl { } } )*} + macro assign($( $fn: ident: $op: tt );*$(;)?) {$( + pub fn $fn(&mut self, other: Self) -> IResult<()> { + *self = (std::mem::take(self) $op other)?; + Ok(()) + } + )*} /// Implements [From] for an enum with 1-tuple variants macro from ($($T:ty => $v:expr),*$(,)?) { $(impl From<$T> for ConValue { @@ -197,6 +217,7 @@ pub mod temp_type_impl { /// A work-in-progress tree walk interpreter for Conlang #[derive(Clone, Debug, Default)] pub struct Interpreter { + scope: Box, stack: Vec, } @@ -230,6 +251,13 @@ impl Interpreter { fn pop_two(&mut self) -> IResult<(ConValue, ConValue)> { Ok((self.pop()?, self.pop()?)) } + fn resolve(&mut self, value: &Identifier) -> IResult { + self.scope + .get(value) + .cloned() + .ok_or_else(|| Error::with_reason(Reason::NotDefined(value.to_owned())))? + .ok_or_else(|| Error::with_reason(Reason::NotInitialized(value.to_owned()))) + } } impl Visitor> for Interpreter { @@ -242,10 +270,7 @@ impl Visitor> for Interpreter { fn visit_statement(&mut self, stmt: &Stmt) -> IResult<()> { match stmt { - Stmt::Let { name, mutable, ty, init } => todo!( - "let{} {name:?}: {ty:?} = {init:?}", - if *mutable { " mut" } else { "" } - ), + Stmt::Let(l) => self.visit_let(l), Stmt::Expr(e) => { self.visit_expr(e)?; self.pop().map(drop) @@ -253,57 +278,124 @@ impl Visitor> for Interpreter { } } - fn visit_operation(&mut self, expr: &math::Operation) -> IResult<()> { - use math::Operation; - // TODO: the indentation depth here is driving me insane. - // maybe refactor the ast to break binary and unary - // operations into their own nodes, and use - // Operation to unify them? - match expr { - Operation::Binary { first, other } => { - self.visit_operation(first)?; - for (op, other) in other { - match op { - operator::Binary::LogAnd => { - if self.peek()?.truthy()? { - self.pop()?; - self.visit_operation(other)?; - } - } - operator::Binary::LogOr => { - if !self.peek()?.truthy()? { - self.pop()?; - self.visit_operation(other)?; - } - } - operator::Binary::LogXor => { - let first = self.pop()?.truthy()?; - self.visit_operation(other)?; - let second = self.pop()?.truthy()?; - self.push(first ^ second); - } - _ => { - self.visit_operation(other)?; - self.visit_binary_op(op)?; - } + fn visit_let(&mut self, stmt: &Let) -> IResult<()> { + let Let { name, init, .. } = stmt; + if let Some(init) = init { + self.visit_expr(init)?; + let init = self.pop()?; + self.scope.insert(name, Some(init)); + } else { + self.scope.insert(name, None); + } + Ok(()) + } + + fn visit_block(&mut self, block: &expression::Block) -> IResult<()> { + for stmt in &block.statements { + self.visit_statement(stmt)?; + } + if let Some(expr) = block.expr.as_ref() { + self.visit_expr(expr) + } else { + self.push(ConValue::Empty); + Ok(()) + } + } + + fn visit_assign(&mut self, assign: &math::Assign) -> IResult<()> { + use operator::Assign; + let math::Assign { target, operator, init } = assign; + self.visit_operation(init)?; + let init = self.pop()?; + let Some(resolved) = self.scope.get_mut(target) else { + Err(Error::with_reason(Reason::NotDefined(target.to_owned())))? + }; + if let Assign::Assign = operator { + use std::mem::discriminant as variant; + // runtime typecheck + match resolved.as_mut() { + Some(value) if variant(value) == variant(&init) => { + *value = init; + } + None => *resolved = Some(init), + _ => Err(Error::with_reason(Reason::TypeError))?, + } + self.push(ConValue::Empty); + return Ok(()); + } + let Some(target) = resolved.as_mut() else { + Err(Error::with_reason(Reason::NotInitialized( + target.to_owned(), + )))? + }; + match operator { + Assign::AddAssign => target.add_assign(init)?, + Assign::SubAssign => target.sub_assign(init)?, + Assign::MulAssign => target.mul_assign(init)?, + Assign::DivAssign => target.div_assign(init)?, + Assign::RemAssign => target.rem_assign(init)?, + Assign::BitAndAssign => target.bitand_assign(init)?, + Assign::BitOrAssign => target.bitor_assign(init)?, + Assign::BitXorAssign => target.bitxor_assign(init)?, + Assign::ShlAssign => target.shl_assign(init)?, + Assign::ShrAssign => target.shr_assign(init)?, + _ => (), + } + self.push(ConValue::Empty); + Ok(()) + } + + fn visit_binary(&mut self, bin: &math::Binary) -> IResult<()> { + use math::Binary; + let Binary { first, other } = bin; + + self.visit_operation(first)?; + for (op, other) in other { + match op { + operator::Binary::LogAnd => { + if self.peek()?.truthy()? { + self.pop()?; + self.visit_operation(other)?; } } - Ok(()) - } - Operation::Unary { operators, operand } => { - self.visit_primary(operand)?; - for op in operators.iter().rev() { - self.visit_unary_op(op)?; + operator::Binary::LogOr => { + if !self.peek()?.truthy()? { + self.pop()?; + self.visit_operation(other)?; + } + } + operator::Binary::LogXor => { + let first = self.pop()?.truthy()?; + self.visit_operation(other)?; + let second = self.pop()?.truthy()?; + self.push(first ^ second); + } + _ => { + self.visit_operation(other)?; + self.visit_binary_op(op)?; } - Ok(()) } } + Ok(()) + } + + fn visit_unary(&mut self, unary: &math::Unary) -> IResult<()> { + let math::Unary { operand, operators } = unary; + self.visit_operation(operand)?; + for op in operators.iter().rev() { + self.visit_unary_op(op)?; + } + Ok(()) + } + + fn visit_assign_op(&mut self, _: &operator::Assign) -> IResult<()> { + unimplemented!("visit_assign_op is implemented in visit_operation") } fn visit_binary_op(&mut self, op: &operator::Binary) -> IResult<()> { use operator::Binary; let (second, first) = self.pop_two()?; - self.push(match op { + let out = match op { Binary::Mul => first * second, Binary::Div => first / second, Binary::Rem => first % second, @@ -325,18 +417,8 @@ impl Visitor> for Interpreter { Binary::NotEq => first.neq(&second), Binary::GreaterEq => first.gt_eq(&second), Binary::Greater => first.gt(&second), - Binary::Assign => todo!("Assignment"), - Binary::AddAssign => todo!("Assignment"), - Binary::SubAssign => todo!("Assignment"), - Binary::MulAssign => todo!("Assignment"), - Binary::DivAssign => todo!("Assignment"), - Binary::RemAssign => todo!("Assignment"), - Binary::BitAndAssign => todo!("Assignment"), - Binary::BitOrAssign => todo!("Assignment"), - Binary::BitXorAssign => todo!("Assignment"), - Binary::ShlAssign => todo!("Assignment"), - Binary::ShrAssign => todo!("Assignment"), - }?); + }?; + self.push(out); Ok(()) } @@ -364,12 +446,13 @@ impl Visitor> for Interpreter { self.visit_block(&expr.body)?; } else if let Some(block) = &expr.else_ { self.visit_else(block)?; + } else { + self.push(ConValue::Empty) } Ok(()) } fn visit_while(&mut self, expr: &control::While) -> IResult<()> { - let mut broke = false; while { self.visit_expr(&expr.cond)?; self.pop()?.truthy()? @@ -384,26 +467,28 @@ impl Visitor> for Interpreter { Reason::Continue => continue, Reason::Break(value) => { self.push(value); - broke = true; - break; + return Ok(()); } r => Err(Error::with_reason(r))?, } } - if let (Some(r#else), false) = (&expr.else_, broke) { + if let Some(r#else) = &expr.else_ { self.visit_else(r#else)?; + } else { + self.push(ConValue::Empty); } Ok(()) } fn visit_for(&mut self, expr: &control::For) -> IResult<()> { + self.scope.enter(); self.visit_expr(&expr.iter)?; - let mut broke = false; let bounds = match self.pop()? { ConValue::RangeExc(a, b) | ConValue::RangeInc(a, b) => (a, b), _ => Err(Error::with_reason(Reason::NotIterable))?, }; - for _ in bounds.0..=bounds.1 { + for loop_var in bounds.0..=bounds.1 { + self.scope.insert(&expr.var, Some(loop_var.into())); let Err(out) = self.visit_block(&expr.body) else { self.pop()?; continue; @@ -412,15 +497,17 @@ impl Visitor> for Interpreter { Reason::Continue => continue, Reason::Break(value) => { self.push(value); - broke = true; - break; + return Ok(()); } r => Err(Error::with_reason(r))?, } } - if let (Some(r#else), false) = (&expr.else_, broke) { + if let Some(r#else) = &expr.else_ { self.visit_else(r#else)?; + } else { + self.push(ConValue::Empty) } + self.scope.exit()?; Ok(()) } @@ -447,7 +534,9 @@ impl Visitor> for Interpreter { } fn visit_identifier(&mut self, ident: &Identifier) -> IResult<()> { - todo!("Identifier lookup and scoping rules: {ident:?}") + let value = self.resolve(ident)?; + self.push(value); + Ok(()) } fn visit_string_literal(&mut self, string: &str) -> IResult<()> { @@ -480,8 +569,69 @@ impl Visitor> for Interpreter { } } +pub mod scope { + //! Lexical and non-lexical scoping for variables + use super::{ + error::{Error, IResult, Reason}, + temp_type_impl::ConValue, + Identifier, + }; + use std::collections::HashMap; + + #[derive(Clone, Debug, Default)] + pub enum Variable { + #[default] + Uninit, + Init(ConValue), + } + + /// Implements a nested lexical scope + #[derive(Clone, Debug, Default)] + pub struct Environment { + outer: Option>, + vars: HashMap>, + } + + impl Environment { + /// Enter a nested scope + pub fn enter(self: &mut Box) { + let outer = std::mem::take(self); + self.outer = Some(outer); + } + /// Exits the scope, destroying all local variables and + /// returning the outer scope, if there is one + pub fn exit(&mut self) -> IResult<()> { + if let Some(outer) = std::mem::take(&mut self.outer) { + *self = *outer; + Ok(()) + } else { + Err(Error::with_reason(Reason::ScopeExit)) + } + } + /// Resolves a variable mutably + pub fn get_mut(&mut self, id: &Identifier) -> Option<&mut Option> { + match self.vars.get_mut(id) { + Some(var) => Some(var), + None => self.outer.as_mut().and_then(|o| o.get_mut(id)), + } + } + /// Resolves a variable immutably + pub fn get(&self, id: &Identifier) -> Option<&Option> { + match self.vars.get(id) { + Some(var) => Some(var), + None => self.outer.as_ref().and_then(|o| o.get(id)), + } + } + pub fn insert(&mut self, id: &Identifier, value: Option) { + self.vars.insert(id.clone(), value); + } + } +} + pub mod error { //! The [Error] type represents any error thrown by the [Interpreter](super::Interpreter) + use crate::ast::Identifier; + use super::temp_type_impl::ConValue; pub type IResult = Result; @@ -524,11 +674,17 @@ pub mod error { Continue, /// Underflowed the stack StackUnderflow, + /// Exited the last scope + ScopeExit, /// Type incompatibility // TODO: store the type information in this error TypeError, /// In clause of For loop didn't yield a Range NotIterable, + /// A name was not defined in scope before being used + NotDefined(Identifier), + /// A name was defined but not initialized + NotInitialized(Identifier), } impl std::error::Error for Error {} @@ -540,12 +696,19 @@ pub mod error { impl std::fmt::Display for Reason { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { - Reason::Return(value) => write!(f, "return {value:?}"), - Reason::Break(value) => write!(f, "break {value:?}"), + Reason::Return(value) => write!(f, "return {value}"), + Reason::Break(value) => write!(f, "break {value}"), Reason::Continue => "continue".fmt(f), Reason::StackUnderflow => "Stack underflow".fmt(f), - Reason::TypeError => "Type error".fmt(f), + Reason::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f), + Reason::TypeError => "Incompatible types".fmt(f), Reason::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f), + Reason::NotDefined(value) => { + write!(f, "{} not bound. Did you mean `let {};`?", value.0, value.0) + } + Reason::NotInitialized(value) => { + write!(f, "{} bound, but not initialized", value.0) + } } } } diff --git a/libconlang/src/parser.rs b/libconlang/src/parser.rs index 123f49b..a4d885f 100644 --- a/libconlang/src/parser.rs +++ b/libconlang/src/parser.rs @@ -8,7 +8,7 @@ pub mod error { use std::fmt::Display; /// The reason for the [Error] - #[derive(Clone, Debug, Default, PartialEq, Eq)] + #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)] pub enum Reason { Expected(Type), Unexpected(Type), @@ -101,7 +101,11 @@ pub mod error { self.start.as_ref() } /// Gets the [Reason] for this error - pub fn reason(self, reason: Reason) -> Self { + pub fn reason(&self) -> Reason { + self.reason + } + /// Modifies the [Reason] of this error + pub fn with_reason(self, reason: Reason) -> Self { Self { reason, ..self } } error_impl! { @@ -131,6 +135,7 @@ pub mod error { pub struct Parser { tokens: Vec, panic_stack: Vec, + pub errors: Vec, cursor: usize, } impl<'t> From> for Parser { @@ -153,7 +158,7 @@ impl Parser { /// /// [1]: Token pub fn new(tokens: Vec) -> Self { - Self { tokens, panic_stack: vec![], cursor: 0 } + Self { tokens, panic_stack: vec![], errors: vec![], cursor: 0 } } /// Parses the [start of an AST](Start) pub fn parse(&mut self) -> PResult { @@ -184,6 +189,10 @@ impl Parser { self.consume_comments(); self } +} +/// Panicking +#[allow(dead_code)] +impl Parser { /// Records the current position on the panic stack fn mark(&mut self) -> &mut Self { self.panic_stack.push(self.cursor); @@ -204,11 +213,22 @@ impl Parser { fn advance_until(&mut self, t: Type) -> PResult<&mut Self> { while self.matches(t).is_err() { self.check_eof() - .map_err(|e| e.reason(Expected(t)))? + .map_err(|e| e.with_reason(Expected(t)))? .consume(); } Ok(self) } + /// Marks the current position, and unwinds the panic stack if `f` fails. + fn attempt(&mut self, f: F) -> PResult + where F: FnOnce(&mut Self) -> PResult { + self.mark(); + let out = f(self); + match out { + Ok(_) => self.unmark(), + Err(_) => self.unwind()?, + }; + out + } } /// Helpers impl Parser { @@ -241,22 +261,6 @@ impl Parser { self.matches(t)?; Ok(self.consume()) } - /// Parses anything wrapped in `lhs` and `rhs` delimiters. - fn delimited(&mut self, lhs: Type, mid: F, rhs: Type) -> PResult - where F: Fn(&mut Self) -> PResult { - self.consume_type(lhs)?.mark(); - let out = match mid(self) { - Ok(out) => out, - Err(e) => { - eprintln!("{e}"); - // Jump back in time and try to re-parse from the next brace - self.unwind()?.advance_until(lhs)?.mark(); - return self.delimited(lhs, mid, rhs); - } - }; - self.consume_type(rhs)?.unmark(); - Ok(out) - } #[doc(hidden)] fn todo_error(&mut self, l: u32, c: u32, s: &str) -> Error { eprintln!("TODO: {s}:{l}:{c}"); @@ -359,15 +363,7 @@ impl Parser { fn stmt(&mut self) -> PResult { let token = self.peek()?; match token.ty() { - Type::Keyword(Keyword::Let) => Ok(Stmt::Let { - mutable: self.consume().keyword(Keyword::Mut).is_ok(), - name: self.identifier()?, - ty: self - .consume_type(Type::Colon) - .and_then(Self::identifier) - .ok(), - init: self.consume_type(Type::Eq).and_then(Self::expr).ok(), - }), + Type::Keyword(Keyword::Let) => self.let_stmt().map(Stmt::Let), _ => { let out = Stmt::Expr(self.expr()?); self.consume_type(Type::Semi)?; @@ -375,6 +371,23 @@ impl Parser { } } } + /// Parses a [Let] statement + fn let_stmt(&mut self) -> PResult { + let out = Let { + mutable: self.consume().keyword(Keyword::Mut).is_ok(), + name: self.identifier()?, + ty: self + .consume_type(Type::Colon) + .and_then(Self::identifier) + .ok(), + init: self.consume_type(Type::Eq).and_then(Self::expr).ok(), + }; + self.consume_type(Type::Semi)?; + Ok(out) + } + // /// Parses a [Function] statement + // fn function_stmt(&mut self) -> PResult { + // } } /// Expressions impl Parser { @@ -385,8 +398,23 @@ impl Parser { } /// Parses a [block expression](expression::Block) fn block(&mut self) -> PResult { - self.delimited(Type::LCurly, |p| p.expr(), Type::RCurly) - .map(|e| expression::Block { expr: Box::new(e) }) + use expression::{Block, Expr}; + let mut statements = vec![]; + let mut expr: Option> = None; + self.consume_type(Type::LCurly)?; + // tHeRe Is No PlAcE iN yOuR gRaMmAr WhErE bOtH aN eXpReSsIoN aNd A sTaTeMeNt ArE eXpEcTeD + while self.consume_type(Type::RCurly).is_err() { + match self.expr() { + Ok(e) if self.consume_type(Type::Semi).is_ok() => statements.push(Stmt::Expr(e)), + Ok(e) => { + expr = Some(Box::new(e)); + self.consume_type(Type::RCurly)?; + break; + } + Err(_) => statements.push(self.stmt()?), + } + } + Ok(Block { statements, expr }) } /// Parses a [group expression](expression::Group) fn group(&mut self) -> PResult { @@ -440,20 +468,35 @@ impl Parser { macro binary ($($f:ident = $a:ident, $b:ident);*$(;)?) {$( #[doc = concat!("Parses a(n) [", stringify!($f), " operation](math::Operation::Binary) expression")] fn $f (&mut self) -> PResult { - let (first, mut others) = (self.$a()?, vec![]); + use math::{Operation, Binary}; + let (first, mut other) = (self.$a()?, vec![]); while let Ok(op) = self.$b() { - others.push((op, self.$a()?)); + other.push((op, self.$a()?)); } - Ok(if others.is_empty() { first } else { - math::Operation::binary(first, others) + Ok(if other.is_empty() { first } else { + Operation::Binary(Binary { first: first.into(), other }) }) } )*} /// # [Arithmetic and Logical Subexpressions](math) impl Parser { + fn assign(&mut self) -> PResult { + use math::{Assign, Operation}; + let next = self.compare()?; + let Ok(operator) = self.assign_op() else { + return Ok(next); + }; + let Operation::Primary(expression::Primary::Identifier(target)) = next else { + return Ok(next); + }; + Ok(Operation::Assign(Assign { + target, + operator, + init: self.assign()?.into(), + })) + } binary! { // name operands operators - assign = compare, assign_op; compare = range, compare_op; range = logic, range_op; logic = bitwise, logic_op; @@ -464,11 +507,22 @@ impl Parser { } /// Parses a [unary operation](math::Operation::Unary) expression fn unary(&mut self) -> PResult { + use math::{Operation, Unary}; let mut operators = vec![]; while let Ok(op) = self.unary_op() { operators.push(op) } - Ok(math::Operation::Unary { operators, operand: self.primary()? }) + if operators.is_empty() { + return self.primary_operation(); + } + Ok(Operation::Unary(Unary { + operators, + operand: self.primary_operation()?.into(), + })) + } + /// Parses a [primary operation](math::Operation::Primary) expression + fn primary_operation(&mut self) -> PResult { + Ok(math::Operation::Primary(self.primary()?)) } } macro operator_impl ($($(#[$m:meta])* $f:ident : {$($type:pat => $op:ident),*$(,)?})*) { @@ -528,20 +582,27 @@ impl Parser { Type::GtEq => GreaterEq, Type::Gt => Greater, } - /// Parses an [assign operator](operator) - assign_op: { - Type::Eq => Assign, - Type::PlusEq => AddAssign, - Type::MinusEq => SubAssign, - Type::StarEq => MulAssign, - Type::SlashEq => DivAssign, - Type::RemEq => RemAssign, - Type::AmpEq => BitAndAssign, - Type::BarEq => BitOrAssign, - Type::XorEq => BitXorAssign, - Type::LtLtEq => ShlAssign, - Type::GtGtEq => ShrAssign, - } + } + /// Parses an [assign operator](operator::Assign) + fn assign_op(&mut self) -> PResult { + use operator::Assign; + let token = self.peek()?; + let out = Ok(match token.ty() { + Type::Eq => Assign::Assign, + Type::PlusEq => Assign::AddAssign, + Type::MinusEq => Assign::SubAssign, + Type::StarEq => Assign::MulAssign, + Type::SlashEq => Assign::DivAssign, + Type::RemEq => Assign::RemAssign, + Type::AmpEq => Assign::BitAndAssign, + Type::BarEq => Assign::BitOrAssign, + Type::XorEq => Assign::BitXorAssign, + Type::LtLtEq => Assign::ShlAssign, + Type::GtGtEq => Assign::ShrAssign, + _ => Err(Error::not_operator().token(token.clone()))?, + }); + self.consume(); + out } /// Parses a [unary operator](operator::Unary) fn unary_op(&mut self) -> PResult { @@ -578,7 +639,7 @@ impl Parser { Type::Keyword(Continue) => self.parse_continue().map(Flow::Continue), e => Err(Error::unexpected(e).token(token.clone()))?, } - .map_err(|e| e.reason(IncompleteBranch)) + .map_err(|e| e.with_reason(IncompleteBranch)) } /// Parses an [if](control::If) expression fn parse_if(&mut self) -> PResult { diff --git a/libconlang/src/pretty_printer.rs b/libconlang/src/pretty_printer.rs index f420bf3..b434b4c 100644 --- a/libconlang/src/pretty_printer.rs +++ b/libconlang/src/pretty_printer.rs @@ -13,10 +13,10 @@ pub trait PrettyPrintable { } impl PrettyPrintable for Start { fn print(&self) { - let _ = self.walk(&mut Printer::default()); + let _ = Printer::default().visit(self); } fn write(&self, into: impl Write) -> IOResult<()> { - self.walk(&mut Printer::from(into)) + Printer::from(into).visit(self) } } @@ -70,47 +70,75 @@ macro visit_operator($self:ident.$op:expr) { impl Visitor> for Printer { fn visit_program(&mut self, prog: &Program) -> IOResult<()> { // delegate to the walker - prog.walk(self) + for stmt in &prog.0 { + self.visit_statement(stmt)?; + } + Ok(()) } fn visit_statement(&mut self, stmt: &Stmt) -> IOResult<()> { match stmt { - Stmt::Let { name, mutable, ty, init } => { - self.put("let")?.space()?; - if *mutable { - self.put("mut")?.space()?; - } - self.visit_identifier(name)?; - if let Some(ty) = ty { - self.put(':')?.space()?.visit_identifier(ty)?; - } - if let Some(init) = init { - self.space()?.put('=')?.space()?.visit_expr(init)?; - } - }, + Stmt::Let(stmt) => self.visit_let(stmt)?, Stmt::Expr(e) => { self.visit_expr(e)?; - }, + self.put(';').map(drop)? + } } - self.put(';')?.newline().map(drop) + self.newline().map(drop) } - fn visit_operation(&mut self, expr: &math::Operation) -> IOResult<()> { - use math::Operation; - match expr { - Operation::Binary { first, other } => { - self.put('(')?.visit_operation(first)?; - for (op, other) in other { - self.visit_binary_op(op)?; - self.visit_operation(other)?; - } - self.put(')').map(drop) - } - Operation::Unary { operators, operand } => { - for op in operators { - self.visit_unary_op(op)?; - } - self.visit_primary(operand) - } + fn visit_let(&mut self, stmt: &Let) -> IOResult<()> { + let Let { name, mutable, ty, init } = stmt; + self.put("let")?.space()?; + if *mutable { + self.put("mut")?.space()?; } + self.visit_identifier(name)?; + if let Some(ty) = ty { + self.put(':')?.space()?.visit_identifier(ty)?; + } + if let Some(init) = init { + self.space()?.put('=')?.space()?.visit_expr(init)?; + } + self.put(';').map(drop) + } + + fn visit_assign(&mut self, assign: &math::Assign) -> IOResult<()> { + let math::Assign { target, operator, init } = assign; + self.visit_identifier(target)?; + self.visit_assign_op(operator)?; + self.visit_operation(init) + } + fn visit_binary(&mut self, binary: &math::Binary) -> IOResult<()> { + let math::Binary { first, other } = binary; + self.put('(')?.visit_operation(first)?; + for (op, other) in other { + self.visit_binary_op(op)?; + self.visit_operation(other)?; + } + self.put(')').map(drop) + } + fn visit_unary(&mut self, unary: &math::Unary) -> IOResult<()> { + let math::Unary { operators, operand } = unary; + for op in operators { + self.visit_unary_op(op)?; + } + self.visit_operation(operand) + } + + fn visit_assign_op(&mut self, op: &operator::Assign) -> IOResult<()> { + use operator::Assign; + visit_operator!(self.match op { + Assign::Assign => "=", + Assign::AddAssign => "+=", + Assign::SubAssign => "-=", + Assign::MulAssign => "*=", + Assign::DivAssign => "/=", + Assign::RemAssign => "%=", + Assign::BitAndAssign => "&=", + Assign::BitOrAssign => "|=", + Assign::BitXorAssign => "^=", + Assign::ShlAssign => "<<=", + Assign::ShrAssign => ">>=", + }) } fn visit_binary_op(&mut self, op: &operator::Binary) -> IOResult<()> { use operator::Binary; @@ -136,17 +164,6 @@ impl Visitor> for Printer { Binary::NotEq => "!=", Binary::GreaterEq => ">=", Binary::Greater => ">", - Binary::Assign => "=", - Binary::AddAssign => "+=", - Binary::SubAssign => "-=", - Binary::MulAssign => "*=", - Binary::DivAssign => "/=", - Binary::RemAssign => "%=", - Binary::BitAndAssign => "&=", - Binary::BitOrAssign => "|=", - Binary::BitXorAssign => "^=", - Binary::ShlAssign => "<<=", - Binary::ShrAssign => ">>=", }) } fn visit_unary_op(&mut self, op: &operator::Unary) -> IOResult<()> { @@ -223,18 +240,28 @@ impl Visitor> for Printer { self.put(int).map(drop) } fn visit_empty(&mut self) -> IOResult<()> { - self.put("").map(drop) + self.put("()").map(drop) } - fn visit_block(&mut self, expr: &expression::Block) -> IOResult<()> { + fn visit_block(&mut self, block: &expression::Block) -> IOResult<()> { self.put('{')?.indent().newline()?; - expr.walk(self)?; + for stmt in &block.statements { + self.visit_statement(stmt)?; + } + for expr in &block.expr { + self.visit_expr(expr)?; + } self.dedent().newline()?.put('}').map(drop) } fn visit_group(&mut self, expr: &expression::Group) -> IOResult<()> { - self.put('(')?.space()?; - expr.walk(self)?; - self.space()?.put(')').map(drop) + match expr { + expression::Group::Expr(expr) => { + self.put('(')?.space()?; + self.visit_expr(expr)?; + self.space()?.put(')').map(drop) + } + expression::Group::Empty => self.visit_empty(), + } } }