diff --git a/dummy.cl b/dummy.cl index 265d76a..351ff76 100644 --- a/dummy.cl +++ b/dummy.cl @@ -1,12 +1,10 @@ -// This is a Conlang file. Conlang is an expression-based language designed for maximum flexibility etc. etc. whatever +// This is a Conlang file. // This is a function. It can be called with the call operator. // The function called `main` is the program's entrypoint fn main() { - let x = 100; - // An if expression is like the ternary conditional operator in C - let y = if x < 50 { + let y = if 10 < 50 { "\u{1f988}" } else { "x" @@ -20,12 +18,16 @@ fn main() { // If `while` does not `break`, fall through to the `else` expression } else { false - } + }; // The same is true of `for` expressions! let w = for idx in 0..100 { - break idx + if idx > 2 * 2 { + break idx + } } else { 12345 - } - // TODO: decide how to do IO + }; + // A block evaluates to its last expression, + // or Empty if there is none + (y, z, w) // (🦈, false, 5) } diff --git a/grammar.ebnf b/grammar.ebnf index 4657784..942dc6d 100644 --- a/grammar.ebnf +++ b/grammar.ebnf @@ -11,16 +11,26 @@ Identifier = IDENTIFIER ; (* statement *) Stmt = Fn | Let | Expr ';' ; Let = "let" "mut"? Identifier (':' Type)? ('=' Expr)? ';' ; -Fn = "fn" Identifier Block ; (* TODO: params, return value*) +Fn = "fn" Identifier '(' Params? ')' Block ; +(* TODO: Type system *) +Params = (Param ',')* Param? ; +Param = Identifier (*':' Type *) ; (* # Expressions *) (* expression *) Expr = Assign ; Block = '{' Stmt* Expr? '}' ; -Group = '(' Expr? ')' ; Primary = Identifier | Literal | Block | Group | Branch ; +(* expression::call *) +Call = FnCall | Primary ; +FnCall = Primary ('(' Tuple? ')')? ; + +(* expression::tuple *) +Group = '(' Tuple? ')' ; +Tuple = Expr (',' Expr)* ; + (* expression::math *) Assign = Identifier (AssignOp Assign) | Compare ; Compare = Range (CompareOp Range )* ; @@ -30,7 +40,7 @@ Bitwise = Shift (BitwiseOp Shift )* ; Shift = Term (ShiftOp Term )* ; Term = Factor (TermOp Factor )* ; Factor = Unary (FactorOp Unary )* ; -Unary = (UnaryOp)* Primary ; +Unary = (UnaryOp)* Call ; (* expression::math::operator *) AssignOp = '=' | "+=" | "-=" | "*=" | "/=" | @@ -50,7 +60,7 @@ Branch = While | If | For | Break | Return | Continue ; If = "if" Expr Block (Else)? ; While = "while" Expr Block (Else)? ; For = "for" Identifier "in" Expr Block (Else)? ; -Else = "else" Block ; +Else = "else" Expr ; Break = "break" Expr ; Return = "return" Expr ; Continue = "continue" ; diff --git a/libconlang/src/ast.rs b/libconlang/src/ast.rs index c8dbaba..f848aa6 100644 --- a/libconlang/src/ast.rs +++ b/libconlang/src/ast.rs @@ -14,8 +14,11 @@ pub mod preamble { //! Common imports for working with the [ast](super) pub use super::{ expression::{ - self, control, + self, + call::*, + control, math::{self, operator}, + tuple::*, }, literal, statement::*, @@ -47,9 +50,10 @@ pub mod todo { //! when an item is in progress, remove it from todo. //! //! # General TODOs: - //! - [ ] Implement support for storing items in the AST + //! - [x] Implement support for storing items in the AST + //! - [ ] Keep track of the source location of each node //! - [ ] Implement paths - //! - [ ] Implement functions + //! - [x] Implement functions //! - [ ] Implement structs //! - [ ] Implement enums //! - [ ] Implement implementation @@ -61,11 +65,6 @@ pub mod todo { //! Path resolution will be vital to the implementation of structs, enums, impl blocks, //! traits, modules, etc. } - pub mod function { - //! Function support - //! - [ ] Add function declaration expression (returns a function) - //! - [ ] Add function call expression - } pub mod structure { //! Struct support @@ -148,7 +147,6 @@ pub mod statement { //! [`Stmt`]` := `[`Let`](Stmt::Let)` | `[`Expr`](Stmt::Expr) //! [`Let`](Stmt::Let)` := "let"` [`Identifier`] (`:` `Type`)? (`=` [`Expr`])? `;` //! [`Expr`](Stmt::Expr)` := `[`Expr`] `;` - use crate::token::Token; use super::{ expression::{Block, Expr}, @@ -164,6 +162,10 @@ pub mod statement { /// # Syntax /// [`Let`](Stmt::Let) := `"let"` [`Identifier`] (`:` `Type`)? (`=` [`Expr`])? `;` Let(Let), + /// Contains a function declaration + /// # Syntax + /// [`Fn`](Stmt::Fn) := `"fn"` [`Identifier`] `'('` [`Tuple`] `')'` [`Block`] + Fn(FnDecl), /// Contains an expression statement /// # Syntax /// [`Expr`](Stmt::Expr) := [`Expr`] `;` @@ -181,13 +183,18 @@ pub mod statement { pub init: Option, } + /// Contains a function declaration + /// # Syntax + /// [`FnDecl`] := `"fn"` [`Identifier`] `'('` [`Tuple`] `')'` #[derive(Clone, Debug)] - pub struct Fn { + pub struct FnDecl { pub name: Identifier, - pub args: (), // TODO: capture arguments - pub rety: Token, + pub args: Vec, pub body: Block, + // TODO: Store type information } + + // TODO: Create closure, transmute fndecl into a name and closure } pub mod expression { @@ -237,14 +244,14 @@ pub mod expression { /// [`Primary`]` := `[`IDENTIFIER`](Identifier)` /// | `[`Literal`](literal::Literal)` /// | `[`Block`]` - /// | `[`Group`]` + /// | `[`Group`](tuple::Group)` /// | `[`Branch`](control::Flow) #[derive(Clone, Debug)] pub enum Primary { Identifier(Identifier), Literal(literal::Literal), Block(Block), - Group(Group), + Group(tuple::Group), Branch(control::Flow), } @@ -257,13 +264,59 @@ pub mod expression { pub expr: Option>, } - /// Contains a Parenthesized Expression - /// # Syntax - /// [`Group`] := `'('` [`Expr`]? `')'` - #[derive(Clone, Debug)] - pub enum Group { - Expr(Box), - Empty, + pub mod call { + //! [Function](FnCall) and [Method](todo) Call Expressions + //! + //! # Syntax + //! [`Call`]` := `[`FnCall`]` | `[`Primary`] + //! + //! [`FnCall`]` := `[`Primary`]` (`[`Tuple`]`)*` + use super::{tuple::Tuple, Primary}; + #[derive(Clone, Debug)] + pub enum Call { + /// Contains a [Function Call Expression](FnCall) + FnCall(FnCall), + /// Contains only a [Primary] expression + Primary(Primary), + } + + /// Contains a Function Call Expression + #[derive(Clone, Debug)] + pub struct FnCall { + pub callee: Box, + pub args: Vec, + } + } + + pub mod tuple { + //! A [Tuple] expression contains an arbitrary number of sub-expressions + use super::Expr; + /// Contains a [Tuple], [`(Expr)`](Group::Single), + /// or [Empty](Group::Empty) + /// # Syntax + /// [`Group`]`:= '('(`[`Expr`]` | `[`Tuple`]`)?')'` + #[derive(Clone, Debug)] + pub enum Group { + /// Contains a variety of elements + /// # Syntax + /// [`Group::Tuple`]`:= '('`[`Tuple`]`')'` + Tuple(Tuple), + /// Contains a single element + /// # Syntax + /// [`Group::Single`]`:= '('`[`Expr`]`')'` + Single(Box), + /// Contains no elements + /// # Syntax + /// [`Group::Empty`]`:= '(' ')'` + Empty, + } + + /// Contains a heterogeneous collection of sub-expressions + /// # Syntax + #[derive(Clone, Debug)] + pub struct Tuple { + pub elements: Vec, + } } pub mod math { @@ -299,7 +352,7 @@ pub mod expression { //! [`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`] + //! [`Unary`][1]` := (`[`UnaryOp`][4]`)* `[`FnCall`][7] //! //! [1]: Operation::Unary //! [2]: Operation::Binary @@ -307,7 +360,7 @@ pub mod expression { //! [4]: operator::Unary //! [5]: operator::Binary //! [6]: operator::Assign - use super::*; + use super::{call::Call, *}; /// An Operation is a tree of [operands](Primary) and [operators](operator). #[derive(Clone, Debug)] @@ -319,10 +372,10 @@ pub mod expression { /// [`Operation`] ([`operator::Binary`] [`Operation`])* Binary(Binary), /// [`Unary`](Operation::Unary) := ([`operator::Unary`])* - /// [`Primary`](Operation::Primary) + /// [`Call`](Operation::Call) Unary(Unary), - /// [`Primary`](Operation::Primary) := [`expression::Primary`] - Primary(Primary), + /// [`Call`](Operation::Call) := [`expression::call::Call`] + Call(Call), } /// [`Assign`] := [`Identifier`] [`operator::Assign`] [`Operation`] | [`Operation`] @@ -340,7 +393,7 @@ pub mod expression { pub other: Vec<(operator::Binary, Operation)>, } - /// [`Unary`] := ([`operator::Unary`])* [`Primary`](Operation::Primary) + /// [`Unary`] := ([`operator::Unary`])* [`Call`](Operation::Call) #[derive(Clone, Debug)] pub struct Unary { pub operators: Vec, @@ -502,7 +555,7 @@ pub mod expression { //! [`While`]` := "while" `[`Expr`]` `[`Block`]` `[`Else`]`?` \ //! [`If`]` := "if" `[`Expr`]` `[`Block`]` `[`Else`]`?` \ //! [`For`]` := "for" `[`Identifier`]` "in" `[`Expr`]` `[`Block`]` `[`Else`]`?` \ - //! [`Else`]` := "else" `[`Block`] \ + //! [`Else`]` := "else" `[`Expr`] \ //! //! [`Break`]`  := "break" `[`Expr`] \ //! [`Return`]`  := "return" `[`Expr`] \ @@ -607,7 +660,7 @@ pub mod expression { /// Represents an [`else` block](control). /// - /// An [`else` block](Else) contains instructions to be executed if + /// An [`else` expression](Else) contains instructions to be executed if /// the corresponding body refused to produce a value. In the case of /// [`if` expressions](If), this happens if the condition fails. /// In the case of loop ([`while`](While), [`for`](For))expressions, @@ -617,10 +670,10 @@ pub mod expression { /// to something other than the Empty type, this block is mandatory. /// /// # Syntax - /// [`Else`] := `"else"` [`Block`] + /// [`Else`] := `"else"` [`Expr`] #[derive(Clone, Debug)] pub struct Else { - pub block: Block, + pub expr: Box, } /// Represents a [`continue` expression][control] @@ -650,10 +703,13 @@ pub mod expression { } 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) + //! A [`Visitor`] visits every kind of node in the [Abstract Syntax Tree](super) + //! + //! This trait is mostly here to ensure that every branch of the tree is accounted for. + //! + //! Default implementations are provided for use super::{ - expression::{control::*, math::*, Block, *}, + expression::{call::*, control::*, math::*, tuple::*, Block, *}, literal::*, statement::*, *, @@ -672,11 +728,14 @@ pub mod visitor { fn visit_statement(&mut self, stmt: &Stmt) -> R { match stmt { Stmt::Let(stmt) => self.visit_let(stmt), + Stmt::Fn(function) => self.visit_fn_decl(function), Stmt::Expr(expr) => self.visit_expr(expr), } } /// Visit a [Let statement](Let) fn visit_let(&mut self, stmt: &Let) -> R; + /// Visit a [Fn declaration](FnDecl) + fn visit_fn_decl(&mut self, function: &FnDecl) -> R; /// Visit an [Expression](Expr) fn visit_expr(&mut self, expr: &Expr) -> R { @@ -688,10 +747,22 @@ pub mod visitor { /// Visit a [Group] expression fn visit_group(&mut self, group: &Group) -> R { match group { - Group::Expr(expr) => self.visit_expr(expr), + Group::Tuple(tuple) => self.visit_tuple(tuple), + Group::Single(expr) => self.visit_expr(expr), Group::Empty => self.visit_empty(), } } + /// Visit a [Tuple] expression + fn visit_tuple(&mut self, tuple: &Tuple) -> R; + /// Visit a [Call] expression + fn visit_call(&mut self, call: &Call) -> R { + match call { + Call::FnCall(call) => self.visit_fn_call(call), + Call::Primary(primary) => self.visit_primary(primary), + } + } + /// Visit a [Function Call](FnCall) expression + fn visit_fn_call(&mut self, call: &FnCall) -> R; // Math expression /// Visit an [Operation] @@ -700,7 +771,7 @@ pub mod visitor { 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), + Operation::Call(call) => self.visit_call(call), } } /// Visit an [Assignment](Assign) operation diff --git a/libconlang/src/interpreter.rs b/libconlang/src/interpreter.rs index 0f4820e..559489e 100644 --- a/libconlang/src/interpreter.rs +++ b/libconlang/src/interpreter.rs @@ -1,18 +1,36 @@ //! Interprets an AST as a program -use self::scope::Environment; use crate::ast::preamble::*; -use error::{Error, IResult, Reason}; +use error::{Error, IResult}; +use scope::Environment; use temp_type_impl::ConValue; +/// Callable types can be called from within a Conlang program +pub trait Callable: std::fmt::Debug { + /// Calls this [Callable] in the provided [Interpreter], with [ConValue] args \ + /// The Callable is responsible for checking the argument count and validating types + fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult<()>; + /// Returns the common name of this identifier. + fn name(&self) -> &str; +} + +/// [BuiltIn]s are [Callable]s with bespoke definitions +pub trait BuiltIn: std::fmt::Debug + Callable {} + pub mod temp_type_impl { //! Temporary implementations of Conlang values - use super::error::{Error, IResult, Reason}; + //! + //! The most permanent fix is a temporary one. + use super::{ + error::{Error, IResult}, + function::Function, + BuiltIn, Callable, Interpreter, + }; use std::ops::*; /// A Conlang value /// - /// This is a hack to work around the fact that Conlang doesn't have a functioning type system - /// yet :( + /// This is a hack to work around the fact that Conlang doesn't + /// have a functioning type system yet :( #[derive(Clone, Debug, Default)] pub enum ConValue { /// The empty/unit `()` type @@ -26,28 +44,34 @@ pub mod temp_type_impl { Char(char), /// A string String(String), + /// A tuple + Tuple(Vec), /// An exclusive range RangeExc(i128, i128), /// An inclusive range RangeInc(i128, i128), + /// A callable thing + Function(Function), + /// A built-in function + BuiltIn(&'static dyn BuiltIn), } impl ConValue { /// Gets whether the current value is true or false pub fn truthy(&self) -> IResult { match self { ConValue::Bool(v) => Ok(*v), - _ => Err(Error::with_reason(Reason::TypeError))?, + _ => Err(Error::TypeError)?, } } pub fn range_exc(self, other: Self) -> IResult { let (Self::Int(a), Self::Int(b)) = (self, other) else { - Err(Error::with_reason(Reason::TypeError))? + Err(Error::TypeError)? }; Ok(Self::RangeExc(a, b.saturating_sub(1))) } pub fn range_inc(self, other: Self) -> IResult { let (Self::Int(a), Self::Int(b)) = (self, other) else { - Err(Error::with_reason(Reason::TypeError))? + Err(Error::TypeError)? }; Ok(Self::RangeInc(a, b)) } @@ -72,6 +96,23 @@ pub mod temp_type_impl { sub_assign: -; } } + + impl Callable for ConValue { + fn name(&self) -> &str { + match self { + ConValue::Function(func) => func.name(), + ConValue::BuiltIn(func) => func.name(), + _ => "", + } + } + fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + match self { + Self::Function(func) => func.call(interpreter, args), + Self::BuiltIn(func) => func.call(interpreter, args), + _ => Err(Error::NotCallable(self.clone())), + } + } + } /// Templates comparison functions for [ConValue] macro cmp ($($fn:ident: $empty:literal, $op:tt);*$(;)?) {$( /// TODO: Remove when functions are implemented: @@ -83,7 +124,7 @@ pub mod temp_type_impl { (Self::Bool(a), Self::Bool(b)) => Ok(Self::Bool(a $op b)), (Self::Char(a), Self::Char(b)) => Ok(Self::Bool(a $op b)), (Self::String(a), Self::String(b)) => Ok(Self::Bool(a $op b)), - _ => Err(Error::with_reason(Reason::TypeError)) + _ => Err(Error::TypeError) } } )*} @@ -105,12 +146,23 @@ pub mod temp_type_impl { char => ConValue::Char, &str => ConValue::String, String => ConValue::String, + Function => ConValue::Function, + Vec => ConValue::Tuple, } impl From<()> for ConValue { fn from(_: ()) -> Self { Self::Empty } } + impl From<&[ConValue]> for ConValue { + fn from(value: &[ConValue]) -> Self { + match value.len() { + 0 => Self::Empty, + 1 => value[0].clone(), + _ => Self::Tuple(value.into()), + } + } + } /// Implements binary [std::ops] traits for [ConValue] /// @@ -127,55 +179,55 @@ pub mod temp_type_impl { (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a + b), (ConValue::String(a), ConValue::String(b)) => ConValue::String(a + &b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] BitAnd: bitand = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a & b), (ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a & b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] BitOr: bitor = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a | b), (ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a | b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] BitXor: bitxor = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a ^ b), (ConValue::Bool(a), ConValue::Bool(b)) => ConValue::Bool(a ^ b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] Div: div = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a / b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] Mul: mul = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a * b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] Rem: rem = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a % b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] Shl: shl = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a << b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] Shr: shr = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a >> b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] Sub: sub = [ (ConValue::Empty, ConValue::Empty) => ConValue::Empty, (ConValue::Int(a), ConValue::Int(b)) => ConValue::Int(a - b), - _ => Err(Error::with_reason(Reason::TypeError))? + _ => Err(Error::TypeError)? ] } impl Neg for ConValue { @@ -184,7 +236,7 @@ pub mod temp_type_impl { Ok(match self { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(-v), - _ => Err(Error::with_reason(Reason::TypeError))?, + _ => Err(Error::TypeError)?, }) } } @@ -195,7 +247,7 @@ pub mod temp_type_impl { ConValue::Empty => ConValue::Empty, ConValue::Int(v) => ConValue::Int(!v), ConValue::Bool(v) => ConValue::Bool(!v), - _ => Err(Error::with_reason(Reason::TypeError))?, + _ => Err(Error::TypeError)?, }) } } @@ -205,17 +257,33 @@ pub mod temp_type_impl { ConValue::Empty => "Empty".fmt(f), ConValue::Int(v) => v.fmt(f), ConValue::Bool(v) => v.fmt(f), - ConValue::Char(v) => write!(f, "'{v}'"), - ConValue::String(v) => write!(f, "\"{v}\""), + ConValue::Char(v) => v.fmt(f), + ConValue::String(v) => v.fmt(f), ConValue::RangeExc(a, b) => write!(f, "{a}..{}", b + 1), ConValue::RangeInc(a, b) => write!(f, "{a}..={b}"), + ConValue::Tuple(tuple) => { + '('.fmt(f)?; + for (idx, element) in tuple.iter().enumerate() { + if idx > 0 { + ", ".fmt(f)? + } + element.fmt(f)? + } + ')'.fmt(f) + } + ConValue::Function(func) => { + write!(f, "fn {}", func.name()) + } + ConValue::BuiltIn(func) => { + write!(f, "internal fn {}", func.name()) + } } } } } /// A work-in-progress tree walk interpreter for Conlang -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Interpreter { scope: Box, stack: Vec, @@ -230,6 +298,13 @@ impl Interpreter { pub fn interpret(&mut self, start: &Start) -> IResult<()> { self.visit(start) } + /// Calls a function inside the interpreter's scope, + /// and returns the result + pub fn call(&mut self, name: &str, args: &[ConValue]) -> IResult { + let function = self.resolve(name)?; + function.call(self, args)?; + self.pop() + } /// Evaluates a single [Expression](expression::Expr) and returns the value stack. pub fn eval(&mut self, expr: &expression::Expr) -> IResult> { self.visit_expr(expr)?; @@ -239,24 +314,16 @@ impl Interpreter { self.stack.push(value.into()) } fn peek(&mut self) -> IResult<&ConValue> { - self.stack - .last() - .ok_or(Error::with_reason(Reason::StackUnderflow)) + self.stack.last().ok_or(Error::StackUnderflow) } fn pop(&mut self) -> IResult { - self.stack - .pop() - .ok_or(Error::with_reason(Reason::StackUnderflow)) + self.stack.pop().ok_or(Error::StackUnderflow) } 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()))) + fn resolve(&mut self, value: &str) -> IResult { + self.scope.get(value).cloned() } } @@ -271,6 +338,7 @@ impl Visitor> for Interpreter { fn visit_statement(&mut self, stmt: &Stmt) -> IResult<()> { match stmt { Stmt::Let(l) => self.visit_let(l), + Stmt::Fn(f) => self.visit_fn_decl(f), Stmt::Expr(e) => { self.visit_expr(e)?; self.pop().map(drop) @@ -279,7 +347,7 @@ impl Visitor> for Interpreter { } fn visit_let(&mut self, stmt: &Let) -> IResult<()> { - let Let { name, init, .. } = stmt; + let Let { name: Identifier(name), init, .. } = stmt; if let Some(init) = init { self.visit_expr(init)?; let init = self.pop()?; @@ -290,6 +358,12 @@ impl Visitor> for Interpreter { Ok(()) } + fn visit_fn_decl(&mut self, function: &FnDecl) -> IResult<()> { + // register the function in the current environment + self.scope.insert_fn(function); + Ok(()) + } + fn visit_block(&mut self, block: &expression::Block) -> IResult<()> { for stmt in &block.statements { self.visit_statement(stmt)?; @@ -302,14 +376,35 @@ impl Visitor> for Interpreter { } } + fn visit_tuple(&mut self, tuple: &Tuple) -> IResult<()> { + let mut out = vec![]; + for expr in &tuple.elements { + self.visit_expr(expr)?; + out.push(self.pop()?); + } + self.push(out); + Ok(()) + } + + fn visit_fn_call(&mut self, call: &FnCall) -> IResult<()> { + // evaluate the callee + self.visit_primary(&call.callee)?; + for args in &call.args { + self.visit_tuple(args)?; + let (ConValue::Tuple(args), callee) = self.pop_two()? else { + Err(Error::TypeError)? + }; + callee.call(self, &args)?; + } + 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())))? - }; + let resolved = self.scope.get_mut(&target.0)?; if let Assign::Assign = operator { use std::mem::discriminant as variant; // runtime typecheck @@ -318,15 +413,13 @@ impl Visitor> for Interpreter { *value = init; } None => *resolved = Some(init), - _ => Err(Error::with_reason(Reason::TypeError))?, + _ => Err(Error::TypeError)?, } self.push(ConValue::Empty); return Ok(()); } let Some(target) = resolved.as_mut() else { - Err(Error::with_reason(Reason::NotInitialized( - target.to_owned(), - )))? + Err(Error::NotInitialized(target.0.to_owned()))? }; match operator { Assign::AddAssign => target.add_assign(init)?, @@ -463,13 +556,13 @@ impl Visitor> for Interpreter { self.pop()?; continue; }; - match out.reason() { - Reason::Continue => continue, - Reason::Break(value) => { + match out { + Error::Continue => continue, + Error::Break(value) => { self.push(value); return Ok(()); } - r => Err(Error::with_reason(r))?, + r => Err(r)?, } } if let Some(r#else) = &expr.else_ { @@ -485,21 +578,21 @@ impl Visitor> for Interpreter { self.visit_expr(&expr.iter)?; let bounds = match self.pop()? { ConValue::RangeExc(a, b) | ConValue::RangeInc(a, b) => (a, b), - _ => Err(Error::with_reason(Reason::NotIterable))?, + _ => Err(Error::NotIterable)?, }; for loop_var in bounds.0..=bounds.1 { - self.scope.insert(&expr.var, Some(loop_var.into())); + self.scope.insert(&expr.var.0, Some(loop_var.into())); let Err(out) = self.visit_block(&expr.body) else { self.pop()?; continue; }; - match out.reason() { - Reason::Continue => continue, - Reason::Break(value) => { + match out { + Error::Continue => continue, + Error::Break(value) => { self.push(value); return Ok(()); } - r => Err(Error::with_reason(r))?, + r => Err(r)?, } } if let Some(r#else) = &expr.else_ { @@ -512,7 +605,7 @@ impl Visitor> for Interpreter { } fn visit_else(&mut self, else_: &control::Else) -> IResult<()> { - self.visit_block(&else_.block) + self.visit_expr(&else_.expr) } fn visit_continue(&mut self, _: &control::Continue) -> IResult<()> { @@ -534,7 +627,7 @@ impl Visitor> for Interpreter { } fn visit_identifier(&mut self, ident: &Identifier) -> IResult<()> { - let value = self.resolve(ident)?; + let value = self.resolve(&ident.0)?; self.push(value); Ok(()) } @@ -569,34 +662,139 @@ impl Visitor> for Interpreter { } } +impl Default for Interpreter { + fn default() -> Self { + Self { scope: Environment::new().into(), stack: Default::default() } + } +} + +pub mod function { + //! Represents a block of code which lives inside the Interpreter + use super::{Callable, ConValue, Error, FnDecl, IResult, Identifier, Interpreter}; + use crate::ast::visitor::Visitor; + /// Represents a block of code which persists inside the Interpreter + #[derive(Clone, Debug)] + pub struct Function { + /// Stores the contents of the function declaration + declaration: Box, + // /// Stores the enclosing scope of the function + // TODO: Capture variables + } + + impl Function { + pub fn new(declaration: &FnDecl) -> Self { + Self { declaration: declaration.clone().into() } + } + } + + impl Callable for Function { + fn name(&self) -> &str { + &self.declaration.name.0 + } + fn call(&self, interpreter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + // Check arg mapping + if args.len() != self.declaration.args.len() { + return Err(Error::ArgNumber { + want: self.declaration.args.len(), + got: args.len(), + }); + } + // TODO: Isolate cross-function scopes! + interpreter.scope.enter(); + for (Identifier(arg), value) in self.declaration.args.iter().zip(args) { + interpreter.scope.insert(arg, Some(value.clone())); + } + match interpreter.visit_block(&self.declaration.body) { + Err(Error::Return(value)) => interpreter.push(value), + Err(Error::Break(value)) => Err(Error::BadBreak(value))?, + Err(e) => Err(e)?, + Ok(_) => (), + } + interpreter.scope.exit()?; + Ok(()) + } + } +} + +pub mod builtin { + mod builtin_imports { + pub use crate::interpreter::{ + error::{Error, IResult}, + temp_type_impl::ConValue, + BuiltIn, Callable, Interpreter, + }; + } + use super::BuiltIn; + /// Builtins to load when a new interpreter is created + pub const DEFAULT_BUILTINS: &[&dyn BuiltIn] = &[&print::Print, &dbg::Dbg]; + + mod print { + //! Implements the unstable `print(...)` builtin + use super::builtin_imports::*; + /// Implements the `print(...)` builtin + #[derive(Clone, Debug)] + pub struct Print; + impl BuiltIn for Print {} + #[rustfmt::skip] + impl Callable for Print { + fn name(&self) -> &'static str { "print" } + fn call(&self, inter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + for arg in args { print!("{arg}") } + println!(); + inter.push(ConValue::Empty); + Ok(()) + } + } + } + mod dbg { + //! Implements the unstable `dbg(...)` builtin + use super::builtin_imports::*; + #[derive(Clone, Debug)] + pub struct Dbg; + impl BuiltIn for Dbg {} + #[rustfmt::skip] + impl Callable for Dbg { + fn name(&self) -> &str { "dbg" } + fn call(&self, inter: &mut Interpreter, args: &[ConValue]) -> IResult<()> { + println!("{args:?}"); + inter.push(args); + Ok(()) + } + } + } +} + pub mod scope { //! Lexical and non-lexical scoping for variables + use super::{ - error::{Error, IResult, Reason}, + builtin::DEFAULT_BUILTINS, + error::{Error, IResult}, + function::Function, temp_type_impl::ConValue, - Identifier, + FnDecl, }; 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>, + vars: HashMap>, } impl Environment { + pub fn new() -> Self { + let mut out = Self::default(); + for &builtin in DEFAULT_BUILTINS { + out.insert(builtin.name(), Some(ConValue::BuiltIn(builtin))) + } + out + } /// Enter a nested scope - pub fn enter(self: &mut Box) { + pub fn enter(&mut self) { let outer = std::mem::take(self); - self.outer = Some(outer); + self.outer = Some(outer.into()); } /// Exits the scope, destroying all local variables and /// returning the outer scope, if there is one @@ -605,71 +803,75 @@ pub mod scope { *self = *outer; Ok(()) } else { - Err(Error::with_reason(Reason::ScopeExit)) + Err(Error::ScopeExit) } } /// Resolves a variable mutably - pub fn get_mut(&mut self, id: &Identifier) -> Option<&mut Option> { + /// + /// Returns a mutable reference to the variable's record, if it exists + pub fn get_mut(&mut self, id: &str) -> IResult<&mut Option> { match self.vars.get_mut(id) { - Some(var) => Some(var), - None => self.outer.as_mut().and_then(|o| o.get_mut(id)), + Some(var) => Ok(var), + None => self + .outer + .as_mut() + .ok_or_else(|| Error::NotDefined(id.into()))? + .get_mut(id), } } /// Resolves a variable immutably - pub fn get(&self, id: &Identifier) -> Option<&Option> { + pub fn get(&self, id: &str) -> IResult<&ConValue> { match self.vars.get(id) { - Some(var) => Some(var), - None => self.outer.as_ref().and_then(|o| o.get(id)), + Some(var) => var.as_ref().ok_or_else(|| Error::NotInitialized(id.into())), + None => self + .outer + .as_ref() + .ok_or_else(|| Error::NotDefined(id.into()))? + .get(id), } } - pub fn insert(&mut self, id: &Identifier, value: Option) { - self.vars.insert(id.clone(), value); + pub fn insert(&mut self, id: &str, value: Option) { + self.vars.insert(id.to_string(), value); + } + /// A convenience function for registering a [FnDecl] as a [Function] + pub fn insert_fn(&mut self, decl: &FnDecl) { + self.vars + .insert(decl.name.0.clone(), Some(Function::new(decl).into())); } } } 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; /// Represents any error thrown by the [Interpreter](super::Interpreter) - #[derive(Clone, Debug)] - pub struct Error { - reason: Reason, - } impl Error { - /// Returns the [Reason] for this error - pub fn reason(self) -> Reason { - self.reason - } - /// Creates an error with a given [Reason] - pub(crate) fn with_reason(reason: Reason) -> Self { - Self { reason } - } - /// Creates a [Return](Reason::Return) error, with the given [value](ConValue) + /// Creates a [Return](Error::Return) error, with the given [value](ConValue) pub fn ret(value: ConValue) -> Self { - Self { reason: Reason::Return(value) } + Error::Return(value) } - /// Creates a [Break](Reason::Break) error, with the given [value](ConValue) + /// Creates a [Break](Error::Break) error, with the given [value](ConValue) pub fn brk(value: ConValue) -> Self { - Self { reason: Reason::Break(value) } + Error::Break(value) } - /// Creates a [Continue](Reason::Continue) error + /// Creates a [Continue](Error::Continue) error pub fn cnt() -> Self { - Self { reason: Reason::Continue } + Error::Continue } } /// The reason for the [Error] #[derive(Clone, Debug)] - pub enum Reason { + pub enum Error { /// Propagate a Return value Return(ConValue), /// Propagate a Break value Break(ConValue), + /// Break propagated across function bounds + BadBreak(ConValue), /// Continue to the next iteration of a loop Continue, /// Underflowed the stack @@ -682,32 +884,38 @@ pub mod error { /// In clause of For loop didn't yield a Range NotIterable, /// A name was not defined in scope before being used - NotDefined(Identifier), + NotDefined(String), /// A name was defined but not initialized - NotInitialized(Identifier), + NotInitialized(String), + /// A value was called, but is not callable + NotCallable(ConValue), + /// A function was called with the wrong number of arguments + ArgNumber { want: usize, got: usize }, } impl std::error::Error for Error {} impl std::fmt::Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.reason.fmt(f) - } - } - 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::Continue => "continue".fmt(f), - Reason::StackUnderflow => "Stack underflow".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) + Error::Return(value) => write!(f, "return {value}"), + Error::Break(value) => write!(f, "break {value}"), + Error::BadBreak(value) => write!(f, "rogue break: {value}"), + Error::Continue => "continue".fmt(f), + Error::StackUnderflow => "Stack underflow".fmt(f), + Error::ScopeExit => "Exited the last scope. This is a logic bug.".fmt(f), + Error::TypeError => "Incompatible types".fmt(f), + Error::NotIterable => "`in` clause of `for` loop did not yield an iterable".fmt(f), + Error::NotDefined(value) => { + write!(f, "{value} not bound. Did you mean `let {value};`?") } - Reason::NotInitialized(value) => { - write!(f, "{} bound, but not initialized", value.0) + Error::NotInitialized(value) => { + write!(f, "{value} bound, but not initialized") + } + Error::NotCallable(value) => { + write!(f, "{value} is not a function, and cannot be called") + } + Error::ArgNumber { want, got } => { + write!(f, "Expected {want} arguments, got {got}") } } } diff --git a/libconlang/src/parser.rs b/libconlang/src/parser.rs index a4d885f..09b0cfe 100644 --- a/libconlang/src/parser.rs +++ b/libconlang/src/parser.rs @@ -364,6 +364,7 @@ impl Parser { let token = self.peek()?; match token.ty() { Type::Keyword(Keyword::Let) => self.let_stmt().map(Stmt::Let), + Type::Keyword(Keyword::Fn) => self.fn_decl().map(Stmt::Fn), _ => { let out = Stmt::Expr(self.expr()?); self.consume_type(Type::Semi)?; @@ -385,9 +386,30 @@ impl Parser { self.consume_type(Type::Semi)?; Ok(out) } - // /// Parses a [Function] statement - // fn function_stmt(&mut self) -> PResult { - // } + /// Parses a [Function] statement + fn fn_decl(&mut self) -> PResult { + self.keyword(Keyword::Fn)?; + let name = self.identifier()?; + self.consume_type(Type::LParen)?; + let args = self.params()?; + self.consume_type(Type::RParen)?; + // Discard return type, for now + if self.consume_type(Type::Arrow).is_ok() { + self.expr()?; + } + Ok(FnDecl { name, args, body: self.block()? }) + } + + fn params(&mut self) -> PResult> { + let mut args = vec![]; + while let Ok(ident) = self.identifier() { + args.push(ident); + if self.consume_type(Type::Comma).is_err() { + break; + } + } + Ok(args) + } } /// Expressions impl Parser { @@ -416,22 +438,6 @@ impl Parser { } Ok(Block { statements, expr }) } - /// Parses a [group expression](expression::Group) - fn group(&mut self) -> PResult { - use expression::Group; - let t = self.consume_type(Type::LParen)?.peek()?; - match t.ty() { - Type::RParen => { - self.consume(); - Ok(Group::Empty) - } - _ => { - let out = self.expr().map(|expr| Group::Expr(expr.into())); - self.consume_type(Type::RParen)?; - out - } - } - } /// Parses a [primary expression](expression::Primary) fn primary(&mut self) -> PResult { use expression::Primary; @@ -450,6 +456,59 @@ impl Parser { } } } +/// [Call] expressions +impl Parser { + /// Parses a [call expression](Call) + fn call(&mut self) -> PResult { + let callee = self.primary()?; + let Ok(Type::LParen) = self.peek().map(Token::ty) else { + return Ok(Call::Primary(callee)); + }; + let mut args = vec![]; + while self.consume_type(Type::LParen).is_ok() { + match self.consume_type(Type::RParen) { + Ok(_) => args.push(Tuple { elements: vec![] }), + Err(_) => { + args.push(self.tuple()?); + self.consume_type(Type::RParen)?; + } + } + } + Ok(Call::FnCall(FnCall { callee: callee.into(), args })) + } +} + +/// Groups and Tuples +impl Parser { + /// Parses a [group expression](Group) + fn group(&mut self) -> PResult { + let t = self.consume_type(Type::LParen)?.peek()?; + match t.ty() { + Type::RParen => { + self.consume(); + Ok(Group::Empty) + } + _ => { + let mut out = self.tuple()?; + let out = if out.elements.len() == 1 { + Group::Single(out.elements.remove(0).into()) + } else { + Group::Tuple(out) + }; + self.consume_type(Type::RParen)?; + Ok(out) + } + } + } + /// Parses a [tuple expression](Tuple) + fn tuple(&mut self) -> PResult { + let mut elements = vec![self.expr()?]; + while self.consume_type(Type::Comma).is_ok() { + elements.push(self.expr()?); + } + Ok(Tuple { elements }) + } +} /// Helper macro for math parsing subexpressions with production /// ```ebnf @@ -481,12 +540,13 @@ macro binary ($($f:ident = $a:ident, $b:ident);*$(;)?) {$( /// # [Arithmetic and Logical Subexpressions](math) impl Parser { fn assign(&mut self) -> PResult { + use expression::Primary; 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 { + let Operation::Call(Call::Primary(Primary::Identifier(target))) = next else { return Ok(next); }; Ok(Operation::Assign(Assign { @@ -522,7 +582,7 @@ impl Parser { } /// Parses a [primary operation](math::Operation::Primary) expression fn primary_operation(&mut self) -> PResult { - Ok(math::Operation::Primary(self.primary()?)) + Ok(math::Operation::Call(self.call()?)) } } macro operator_impl ($($(#[$m:meta])* $f:ident : {$($type:pat => $op:ident),*$(,)?})*) { @@ -674,7 +734,7 @@ impl Parser { // it's fine for `else` to be missing entirely self.keyword(Keyword::Else) .ok() - .map(|p| Ok(control::Else { block: p.block()? })) + .map(|p| Ok(control::Else { expr: p.expr()?.into() })) .transpose() } /// Parses a [break](control::Break) expression diff --git a/libconlang/src/pretty_printer.rs b/libconlang/src/pretty_printer.rs index b434b4c..762b09e 100644 --- a/libconlang/src/pretty_printer.rs +++ b/libconlang/src/pretty_printer.rs @@ -78,6 +78,7 @@ impl Visitor> for Printer { fn visit_statement(&mut self, stmt: &Stmt) -> IOResult<()> { match stmt { Stmt::Let(stmt) => self.visit_let(stmt)?, + Stmt::Fn(function) => self.visit_fn_decl(function)?, Stmt::Expr(e) => { self.visit_expr(e)?; self.put(';').map(drop)? @@ -101,6 +102,21 @@ impl Visitor> for Printer { self.put(';').map(drop) } + fn visit_fn_decl(&mut self, function: &FnDecl) -> IOResult<()> { + let FnDecl { name, args, body } = function; + self.put("fn")?.space()?; + self.visit_identifier(name)?; + self.space()?.put('(')?; + for (idx, arg) in args.iter().enumerate() { + if idx > 0 { + self.put(',')?.space()?; + } + self.visit_identifier(arg)?; + } + self.put(')')?.space()?; + self.visit_block(body) + } + fn visit_assign(&mut self, assign: &math::Assign) -> IOResult<()> { let math::Assign { target, operator, init } = assign; self.visit_identifier(target)?; @@ -205,8 +221,8 @@ impl Visitor> for Printer { None => Ok(()), } } - fn visit_else(&mut self, expr: &control::Else) -> IOResult<()> { - self.space()?.put("else")?.space()?.visit_block(&expr.block) + fn visit_else(&mut self, else_: &control::Else) -> IOResult<()> { + self.space()?.put("else")?.space()?.visit_expr(&else_.expr) } fn visit_continue(&mut self, _: &control::Continue) -> IOResult<()> { self.put("continue").map(drop) @@ -254,14 +270,34 @@ impl Visitor> for Printer { self.dedent().newline()?.put('}').map(drop) } - fn visit_group(&mut self, expr: &expression::Group) -> IOResult<()> { - match expr { - expression::Group::Expr(expr) => { - self.put('(')?.space()?; - self.visit_expr(expr)?; - self.space()?.put(')').map(drop) + fn visit_tuple(&mut self, tuple: &Tuple) -> IOResult<()> { + for (idx, expr) in tuple.elements.iter().enumerate() { + if idx > 0 { + self.put(',')?.space()?; } - expression::Group::Empty => self.visit_empty(), + self.visit_expr(expr)?; } + Ok(()) + } + + fn visit_group(&mut self, expr: &Group) -> IOResult<()> { + self.put('(')?; + match expr { + Group::Tuple(tuple) => self.space()?.visit_tuple(tuple), + Group::Single(expr) => self.space()?.visit_expr(expr), + Group::Empty => self.visit_empty(), + }?; + self.space()?.put(')').map(drop) + } + + fn visit_fn_call(&mut self, call: &FnCall) -> IOResult<()> { + let FnCall { callee, args } = call; + self.visit_primary(callee)?; + for arg_list in args { + self.put('(')?; + self.visit_tuple(arg_list)?; + self.put(')')?; + } + Ok(()) } } diff --git a/readme.md b/readme.md index 878dbd7..1ec2461 100644 --- a/readme.md +++ b/readme.md @@ -11,15 +11,15 @@ Friday each month. - [x] Write AST for expression grammar - [x] Write parser for AST - [ ] Create tests for parser (and AST) -- [ ] Parse `dummy.cl` into a valid AST +- [x] Parse `dummy.cl` into a valid AST - [x] Pretty printer, for debugging -- [ ] Create minimal statement grammar - - [ ] Variable definition statements - - [ ] Function definition statements +- [x] Create minimal statement grammar + - [x] Variable definition statements + - [x] Function definition statements ## Short Goals: -- [ ] `for` loops and `while` loops can be used on the trailing side of an assignment -- [ ] Tree-walk interpreter for prototyping and debugging +- [x] `for` loops and `while` loops can be used on the trailing side of an assignment +- [x] Tree-walk interpreter for prototyping and debugging - [ ] Data structures and sum-type enums - [ ] Expression type-checker - [ ] Trait/Interface system