diff --git a/src/cli.rs b/src/cli.rs index 7d9f634..f3c5810 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -28,7 +28,7 @@ pub enum Error { pub enum Command { Parse { path: PathBuf }, - Evaluate { path: PathBuf }, + Run { path: PathBuf }, Repl, } @@ -43,9 +43,9 @@ impl Command { let path = args.free_from_str()?; Command::Parse { path } } - Some("evaluate") | Some("eval") => { + Some("run") => { let path = args.free_from_str()?; - Command::Evaluate { path } + Command::Run { path } } Some("repl") | None => Command::Repl, Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())), diff --git a/src/main.rs b/src/main.rs index c4ad52a..5b628be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,10 +1,13 @@ use std::fs::read_to_string; use std::io::{stdin, stdout, BufRead, Write}; +use std::path::Path; use anyhow::{anyhow, Context, Result}; use mul::cli::{Command, USAGE}; +use mul::phase::evaluate::Interpreter; use mul::phase::parse::{parse_interactive_entry, parse_program}; +use mul::syntax::Program; fn main() -> Result<()> { let Some(command) = Command::from_environment()? else { @@ -13,24 +16,33 @@ fn main() -> Result<()> { }; match command { Command::Parse { path } => { - let source = read_to_string(&path) - .with_context(|| format!("failed to open {}", path.display()))?; - let program = parse_program(source.as_str()) - .map_err(|e| anyhow!("{e}")) - .with_context(|| format!("failed to parse {}", path.display()))?; + let program = parse(path.as_path())?; println!("{program:#?}"); Ok(()) } - Command::Evaluate { path } => { - todo!() + Command::Run { path } => { + let program = parse(path.as_path())?; + let mut interpreter = Interpreter::new(); + let value = interpreter.run(&program)?; + println!("{value:#?}"); + Ok(()) } Command::Repl => repl(), } } +fn parse(path: &Path) -> Result { + let source = + read_to_string(path).with_context(|| format!("failed to open {}", path.display()))?; + parse_program(source.as_str()) + .map_err(|e| anyhow!("{e}")) + .with_context(|| format!("failed to parse {}", path.display())) +} + fn repl() -> Result<()> { let mut stdin = stdin().lock(); let mut stdout = stdout().lock(); + let mut interpreter = Interpreter::new(); println!("welcome to M U L"); loop { let mut line = String::new(); @@ -51,7 +63,8 @@ fn repl() -> Result<()> { continue; } }; - println!("{parsed:?}"); + let value = interpreter.evaluate_interactive_entry(&parsed)?; + println!("{value:?}"); } Ok(()) } diff --git a/src/phase/evaluate.rs b/src/phase/evaluate.rs index 8b13789..808d2d6 100644 --- a/src/phase/evaluate.rs +++ b/src/phase/evaluate.rs @@ -1 +1,133 @@ +use std::collections::HashMap; +use thiserror::Error; + +use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program}; + +pub type Dictionary = HashMap; + +#[derive(Debug, Clone)] +pub enum Value { + Closure(Dictionary, Vec, Expression), + Boolean(bool), + Integer(i64), +} + +#[derive(Debug, Error)] +pub enum Error { + #[error("The name '{0} was not bound to any value in this context")] + UnboundName(Name), + #[error("Tried to call a term which was not a function")] + NotAFunction, + #[error("Mismatched number of function arguments and parameters: expected {0}, got {1}")] + ArgumentsMismatch(usize, usize), + #[error("No function called main to run")] + MissingMainFunction, +} + +pub type Result = std::result::Result; + +#[derive(Debug, Clone)] +pub struct Interpreter { + environment: Dictionary, +} + +impl Interpreter { + pub fn new() -> Self { + Interpreter { + environment: Dictionary::new(), + } + } + + pub fn run(&mut self, program: &Program) -> Result { + for item in program { + self.interpret_item(item)?; + } + if let Some(main) = self.environment.get("main") { + // should main be an expression? a standalone closure type? + todo!() + } else { + Err(Error::MissingMainFunction) + } + } + + pub fn interpret_item(&mut self, item: &Item) -> Result { + match item { + Item::Fn { + name, + parameters, + body, + } => { + let closure = Value::Closure( + self.environment.clone(), + parameters.clone(), + Expression::Block(Box::new(body.clone())), + ); + self.environment.insert(name.clone(), closure.clone()); + Ok(closure) + } + } + } + + pub fn evaluate_interactive_entry(&mut self, entry: &InteractiveEntry) -> Result { + match entry { + InteractiveEntry::Item(item) => self.interpret_item(item), + InteractiveEntry::Binding(binding) => { + self.bind(binding.name.clone(), &binding.expression) + } + InteractiveEntry::Expression(term) => self.evaluate(term), + } + } + + pub fn evaluate(&mut self, term: &Expression) -> Result { + match term { + Expression::Variable(name) => self + .environment + .get(name) + .ok_or(Error::UnboundName(name.clone())) + .cloned(), + Expression::Block(b) => self.evaluate_block(b.as_ref()), + Expression::Lambda { parameters, result } => Ok(Value::Closure( + self.environment.clone(), + parameters.clone(), + *result.clone(), + )), + Expression::Call { callee, arguments } => match callee.as_ref() { + Expression::Lambda { parameters, result } => { + if parameters.len() != arguments.len() { + return Err(Error::ArgumentsMismatch(parameters.len(), arguments.len())); + } + let mut nested = self.clone(); + for (name, argument) in parameters.iter().zip(arguments) { + // we don't want arguments to refer to each other, so use the parent interpreter + nested.bind(name.clone(), argument)?; + } + nested.evaluate(result) + } + _ => Err(Error::NotAFunction), + }, + Expression::Boolean(b) => Ok(Value::Boolean(*b)), + Expression::Integer(i) => Ok(Value::Integer(*i)), + } + } + + fn evaluate_block(&mut self, block: &Block) -> Result { + let mut nested = self.clone(); + for binding in &block.bindings { + nested.bind(binding.name.clone(), &binding.expression)?; + } + nested.evaluate(&block.result) + } + + fn bind(&mut self, name: Name, term: &Expression) -> Result { + let value = self.evaluate(term)?; + self.environment.insert(name, value.clone()); + Ok(value) + } +} + +impl Default for Interpreter { + fn default() -> Self { + Self::new() + } +} diff --git a/src/phase/parse.rs b/src/phase/parse.rs index 533878e..540668c 100644 --- a/src/phase/parse.rs +++ b/src/phase/parse.rs @@ -6,10 +6,10 @@ lalrpop_mod!(parser, "/phase/parse/parser.rs"); pub type Result<'input, T> = std::result::Result, &'static str>>; -pub fn parse_program(input: &'_ str) -> Result<'_, Program> { +pub fn parse_program(input: &str) -> Result { parser::ProgramParser::new().parse(input) } -pub fn parse_interactive_entry(input: &'_ str) -> Result<'_, InteractiveEntry> { +pub fn parse_interactive_entry(input: &str) -> Result { parser::InteractiveEntryParser::new().parse(input) } diff --git a/src/phase/parse/parser.lalrpop b/src/phase/parse/parser.lalrpop index 4aff32d..cffb5b7 100644 --- a/src/phase/parse/parser.lalrpop +++ b/src/phase/parse/parser.lalrpop @@ -14,7 +14,7 @@ pub Program: Program = Item*; pub InteractiveEntry: InteractiveEntry = { Item => InteractiveEntry::Item(<>), - Binding => InteractiveEntry::Binding(<>), + ";"? => InteractiveEntry::Binding(<>), ";"? => InteractiveEntry::Expression(<>), } @@ -23,7 +23,7 @@ Item: Item = { } Binding: Binding = - "let" "=" ";" => Binding(<>); + "let" "=" => Binding { <> }; Expression: Expression = { Simple, @@ -43,8 +43,10 @@ Atom: Expression = { "(" ")", }; +BlockBinding = ";"; + Block: Block = - "{" "}" => Block { <> }; + "{" "}" => Block { <> }; // TODO: decide on identifier syntax Name: Name = r"[a-zA-Z_]+" => <>.to_string(); diff --git a/src/syntax.rs b/src/syntax.rs index 1e05b7c..20cd2cf 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -26,7 +26,10 @@ pub enum Item { /// Syntactic mapping from an identifier to an expression. #[derive(Debug, Clone)] -pub struct Binding(pub Name, pub Expression); +pub struct Binding { + pub name: Name, + pub expression: Expression, +} /// Some individually addressable syntax node, either atomic or compound. #[derive(Debug, Clone)]