Add evaluation
parent
8af9855a27
commit
a1e5aef811
|
@ -28,7 +28,7 @@ pub enum Error {
|
||||||
|
|
||||||
pub enum Command {
|
pub enum Command {
|
||||||
Parse { path: PathBuf },
|
Parse { path: PathBuf },
|
||||||
Evaluate { path: PathBuf },
|
Run { path: PathBuf },
|
||||||
Repl,
|
Repl,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,9 +43,9 @@ impl Command {
|
||||||
let path = args.free_from_str()?;
|
let path = args.free_from_str()?;
|
||||||
Command::Parse { path }
|
Command::Parse { path }
|
||||||
}
|
}
|
||||||
Some("evaluate") | Some("eval") => {
|
Some("run") => {
|
||||||
let path = args.free_from_str()?;
|
let path = args.free_from_str()?;
|
||||||
Command::Evaluate { path }
|
Command::Run { path }
|
||||||
}
|
}
|
||||||
Some("repl") | None => Command::Repl,
|
Some("repl") | None => Command::Repl,
|
||||||
Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())),
|
Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())),
|
||||||
|
|
29
src/main.rs
29
src/main.rs
|
@ -1,10 +1,13 @@
|
||||||
use std::fs::read_to_string;
|
use std::fs::read_to_string;
|
||||||
use std::io::{stdin, stdout, BufRead, Write};
|
use std::io::{stdin, stdout, BufRead, Write};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
|
||||||
use mul::cli::{Command, USAGE};
|
use mul::cli::{Command, USAGE};
|
||||||
|
use mul::phase::evaluate::Interpreter;
|
||||||
use mul::phase::parse::{parse_interactive_entry, parse_program};
|
use mul::phase::parse::{parse_interactive_entry, parse_program};
|
||||||
|
use mul::syntax::Program;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let Some(command) = Command::from_environment()? else {
|
let Some(command) = Command::from_environment()? else {
|
||||||
|
@ -13,24 +16,33 @@ fn main() -> Result<()> {
|
||||||
};
|
};
|
||||||
match command {
|
match command {
|
||||||
Command::Parse { path } => {
|
Command::Parse { path } => {
|
||||||
let source = read_to_string(&path)
|
let program = parse(path.as_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()))?;
|
|
||||||
println!("{program:#?}");
|
println!("{program:#?}");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
Command::Evaluate { path } => {
|
Command::Run { path } => {
|
||||||
todo!()
|
let program = parse(path.as_path())?;
|
||||||
|
let mut interpreter = Interpreter::new();
|
||||||
|
let value = interpreter.run(&program)?;
|
||||||
|
println!("{value:#?}");
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
Command::Repl => repl(),
|
Command::Repl => repl(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse(path: &Path) -> Result<Program> {
|
||||||
|
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<()> {
|
fn repl() -> Result<()> {
|
||||||
let mut stdin = stdin().lock();
|
let mut stdin = stdin().lock();
|
||||||
let mut stdout = stdout().lock();
|
let mut stdout = stdout().lock();
|
||||||
|
let mut interpreter = Interpreter::new();
|
||||||
println!("welcome to M U L");
|
println!("welcome to M U L");
|
||||||
loop {
|
loop {
|
||||||
let mut line = String::new();
|
let mut line = String::new();
|
||||||
|
@ -51,7 +63,8 @@ fn repl() -> Result<()> {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
println!("{parsed:?}");
|
let value = interpreter.evaluate_interactive_entry(&parsed)?;
|
||||||
|
println!("{value:?}");
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +1,133 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
|
||||||
|
|
||||||
|
pub type Dictionary<T> = HashMap<Name, T>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum Value {
|
||||||
|
Closure(Dictionary<Value>, Vec<Name>, 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<T> = std::result::Result<T, Error>;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Interpreter {
|
||||||
|
environment: Dictionary<Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Interpreter {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Interpreter {
|
||||||
|
environment: Dictionary::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(&mut self, program: &Program) -> Result<Value> {
|
||||||
|
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<Value> {
|
||||||
|
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<Value> {
|
||||||
|
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<Value> {
|
||||||
|
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<Value> {
|
||||||
|
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<Value> {
|
||||||
|
let value = self.evaluate(term)?;
|
||||||
|
self.environment.insert(name, value.clone());
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Interpreter {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -6,10 +6,10 @@ lalrpop_mod!(parser, "/phase/parse/parser.rs");
|
||||||
|
|
||||||
pub type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
|
pub type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
|
||||||
|
|
||||||
pub fn parse_program(input: &'_ str) -> Result<'_, Program> {
|
pub fn parse_program(input: &str) -> Result<Program> {
|
||||||
parser::ProgramParser::new().parse(input)
|
parser::ProgramParser::new().parse(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn parse_interactive_entry(input: &'_ str) -> Result<'_, InteractiveEntry> {
|
pub fn parse_interactive_entry(input: &str) -> Result<InteractiveEntry> {
|
||||||
parser::InteractiveEntryParser::new().parse(input)
|
parser::InteractiveEntryParser::new().parse(input)
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,7 +14,7 @@ pub Program: Program = Item*;
|
||||||
|
|
||||||
pub InteractiveEntry: InteractiveEntry = {
|
pub InteractiveEntry: InteractiveEntry = {
|
||||||
Item => InteractiveEntry::Item(<>),
|
Item => InteractiveEntry::Item(<>),
|
||||||
Binding => InteractiveEntry::Binding(<>),
|
<Binding> ";"? => InteractiveEntry::Binding(<>),
|
||||||
<Expression> ";"? => InteractiveEntry::Expression(<>),
|
<Expression> ";"? => InteractiveEntry::Expression(<>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ Item: Item = {
|
||||||
}
|
}
|
||||||
|
|
||||||
Binding: Binding =
|
Binding: Binding =
|
||||||
"let" <Name> "=" <Expression> ";" => Binding(<>);
|
"let" <name:Name> "=" <expression:Expression> => Binding { <> };
|
||||||
|
|
||||||
Expression: Expression = {
|
Expression: Expression = {
|
||||||
Simple,
|
Simple,
|
||||||
|
@ -43,8 +43,10 @@ Atom: Expression = {
|
||||||
"(" <Expression> ")",
|
"(" <Expression> ")",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
BlockBinding = <Binding> ";";
|
||||||
|
|
||||||
Block: Block =
|
Block: Block =
|
||||||
"{" <bindings:Binding*> <result:Expression> "}" => Block { <> };
|
"{" <bindings:BlockBinding*> <result:Expression> "}" => Block { <> };
|
||||||
|
|
||||||
// TODO: decide on identifier syntax
|
// TODO: decide on identifier syntax
|
||||||
Name: Name = r"[a-zA-Z_]+" => <>.to_string();
|
Name: Name = r"[a-zA-Z_]+" => <>.to_string();
|
||||||
|
|
|
@ -26,7 +26,10 @@ pub enum Item {
|
||||||
|
|
||||||
/// Syntactic mapping from an identifier to an expression.
|
/// Syntactic mapping from an identifier to an expression.
|
||||||
#[derive(Debug, Clone)]
|
#[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.
|
/// Some individually addressable syntax node, either atomic or compound.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
|
|
Loading…
Reference in New Issue