diff --git a/src/cli.rs b/src/cli.rs new file mode 100644 index 0000000..8761b84 --- /dev/null +++ b/src/cli.rs @@ -0,0 +1,45 @@ +use std::path::PathBuf; + +use pico_args::Arguments; + +#[derive(Debug)] +pub enum Error { + ArgsError(pico_args::Error), + InvalidCommand(String), + PrintUsage, +} + +impl From for Error { + fn from(value: pico_args::Error) -> Self { + Error::ArgsError(value) + } +} + +pub enum Command { + Parse { source: PathBuf }, + Run { source: PathBuf }, + Repl, +} + +impl Command { + pub fn from_environment() -> Result { + let mut args = Arguments::from_env(); + if args.contains(["-h", "--help"]) { + return Err(Error::PrintUsage); + } + let cmd = match args.subcommand()?.as_deref() { + Some("parse") => { + let source = args.free_from_str()?; + Command::Parse { source } + } + Some("run") => { + let source = args.free_from_str()?; + Command::Run { source } + } + Some("repl") => Command::Repl, + Some(cmd) => return Err(Error::InvalidCommand(cmd.to_string())), + None => return Err(Error::PrintUsage), + }; + Ok(cmd) + } +} diff --git a/src/interpreter.rs b/src/interpreter.rs deleted file mode 100644 index 9691e62..0000000 --- a/src/interpreter.rs +++ /dev/null @@ -1,150 +0,0 @@ -use std::collections::HashMap; - -use crate::syntax::{Expression, Name, Program, Statement}; - -pub type Environment = HashMap; - -#[derive(Debug, Clone)] -pub enum Value { - Boolean(bool), - Integer(i64), - Closure(Vec, Expression, Environment), -} - -pub struct Interpreter { - environment: Environment, -} - -#[derive(Debug)] -pub enum InterpreterError { - VariableNotFound(Name), - IncorrectArgumentCount(usize, usize), - NonFunctionCall(Value), - EmptyProgram, - ExecutionError(String), -} - -impl From for InterpreterError { - fn from(s: String) -> Self { - InterpreterError::ExecutionError(s) - } -} - -impl std::fmt::Display for InterpreterError { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - match self { - InterpreterError::VariableNotFound(name) => write!(f, "Variable '{}' not found", name), - InterpreterError::IncorrectArgumentCount(expected, actual) => { - write!(f, "Expected {} arguments, but got {}", expected, actual) - } - InterpreterError::NonFunctionCall(value) => { - write!(f, "Function call on non-function value: {:?}", value) - } - InterpreterError::EmptyProgram => write!(f, "Program is empty"), - InterpreterError::ExecutionError(s) => write!(f, "Execution error: {}", s), - } - } -} - -type Result = std::result::Result; - -impl Interpreter { - pub fn new() -> Self { - Self { - environment: Environment::new(), - } - } - - pub fn interpret_statement(&mut self, statement: &Statement) -> Result { - match statement { - Statement::Binding(name, expression) => { - let value = self.evaluate(expression)?; - self.environment.insert(name.clone(), value.clone()); - Ok(value) - } - Statement::Expression(expression) => self.evaluate(expression), - } - } - - pub fn interpret(&mut self, program: &Program) -> Result { - let mut result = None; - - for statement in program { - result = Some(self.interpret_statement(statement)?); - } - - match result { - Some(value) => Ok(value), - None => Err(InterpreterError::EmptyProgram), - } - } - - fn evaluate(&mut self, expression: &Expression) -> Result { - match expression { - Expression::Variable(name) => self - .environment - .get(name) - .cloned() - .ok_or_else(|| InterpreterError::VariableNotFound(name.clone())), - - Expression::Boolean(value) => Ok(Value::Boolean(*value)), - - Expression::Integer(value) => Ok(Value::Integer(*value)), - - Expression::Lambda(params, body) => Ok(Value::Closure( - params.clone(), - *body.clone(), - self.environment.clone(), - )), - - Expression::Call(function, args) => { - let function_value = self.evaluate(function)?; - - match function_value { - Value::Closure(params, body, mut environment) => { - if params.len() != args.len() { - return Err(InterpreterError::IncorrectArgumentCount( - params.len(), - args.len(), - )); - } - - let mut arg_values = Vec::new(); - for arg in args { - arg_values.push(self.evaluate(arg)?); - } - - // Bind argument values to parameter names in a new environment - for (param, arg_value) in params.iter().zip(arg_values.into_iter()) { - environment.insert(param.clone(), arg_value); - } - - // Evaluate the body expression in the new environment - let mut interpreter = Interpreter { environment }; - interpreter.evaluate(&body) - } - _ => Err(InterpreterError::NonFunctionCall(function_value)), - } - } - - Expression::Block(statements, expression) => { - let new_environment = self.environment.clone(); - - for statement in statements { - self.interpret_statement(statement)?; - } - - let mut interpreter = Interpreter { - environment: new_environment, - }; - interpreter.evaluate(expression) - } - } - } -} - -impl Default for Interpreter { - fn default() -> Self { - Self::new() - } -} diff --git a/src/lib.rs b/src/lib.rs index bf5927c..b90dd89 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,3 @@ -use lalrpop_util::lalrpop_mod; - -pub mod interpreter; -lalrpop_mod!(pub parser); +pub mod cli; +pub mod phase; pub mod syntax; diff --git a/src/main.rs b/src/main.rs index 4b6dd04..608ffa3 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,46 @@ -use std::env::args; use std::fs::read_to_string; use std::io::{stdin, stdout, BufRead, Write}; use std::path::Path; use std::process::exit; -use mul::interpreter::Interpreter; -use mul::parser::{ProgramParser, StatementParser}; +use mul::cli::{Command, Error}; +use mul::phase::parse::{parse_program, parse_repl_line}; + +fn main() -> Result<(), Error> { + let command = Command::from_environment()?; + match command { + Command::Parse { source } => parse(source.as_path()), + Command::Run { source } => run(source.as_path()), + Command::Repl => repl(), + } + Ok(()) +} + +fn parse(file: &Path) { + let content = read_to_string(file).expect("Failed to read file"); + let program = parse_program(content.as_str()).unwrap_or_else(|e| { + eprintln!("{e}"); + exit(1); + }); + println!("{program:#?}"); +} + +fn run(file: &Path) { + let content = read_to_string(file).expect("Failed to read file"); + let program = parse_program(content.as_str()).unwrap_or_else(|e| { + eprintln!("{e}"); + exit(1); + }); + // let v = interpreter.interpret(&program).unwrap_or_else(|e| { + // eprintln!("{e}"); + // exit(1); + // }); + // println!("{v:?}") +} fn repl() { let mut stdin = stdin().lock(); let mut stdout = stdout().lock(); - let parser = StatementParser::new(); - let mut intepreter = Interpreter::new(); loop { let mut line = String::new(); print!("> "); @@ -26,7 +55,7 @@ fn repl() { if !line.trim_end().ends_with(';') { line.push(';'); } - let parsed = match parser.parse(&line) { + let parsed = match parse_repl_line(&line) { Ok(parsed) => parsed, Err(lalrpop_util::ParseError::UnrecognizedEOF { .. }) => break, Err(e) => { @@ -34,35 +63,13 @@ fn repl() { continue; } }; - match intepreter.interpret_statement(&parsed) { - Ok(value) => println!("{value:?}"), - Err(e) => { - eprintln!("{e}"); - continue; - } - }; - } -} - -fn build(file: &Path) { - let content = read_to_string(file).expect("Failed to read file"); - let parser = ProgramParser::new(); - let mut interpreter = Interpreter::new(); - let program = parser.parse(content.as_str()).unwrap_or_else(|e| { - eprintln!("{e}"); - exit(1); - }); - let v = interpreter.interpret(&program).unwrap_or_else(|e| { - eprintln!("{e}"); - exit(1); - }); - println!("{v:?}") -} - -fn main() { - if let Some(file) = args().nth(1) { - build(file.as_ref()) - } else { - repl() + println!("{parsed:?}"); + // match intepreter.interpret_statement(&parsed) { + // Ok(value) => println!("{value:?}"), + // Err(e) => { + // eprintln!("{e}"); + // continue; + // } + // }; } } diff --git a/src/parser.lalrpop b/src/parser.lalrpop deleted file mode 100644 index 5fdfd15..0000000 --- a/src/parser.lalrpop +++ /dev/null @@ -1,52 +0,0 @@ -use crate::syntax::{Expression, Name, Program, Statement}; - -grammar; - -pub Program: Program = Statement*; - -pub Statement: Statement = { - "let" "=" ";" => Statement::Binding(<>), - ";"? => Statement::Expression(<>), - ";" => Statement::Expression(<>), -}; - -pub Expression = { - Simple, - Block, -}; - -Simple: Expression = { - "|" > "|" => Expression::Lambda(parameters, Box::new(body)), - Atom, -}; - -Atom: Expression = { - Name => Expression::Variable(<>), - "(" > ")" => Expression::Call(Box::new(callee), arguments), - Integer => Expression::Integer(<>), - Boolean => Expression::Boolean(<>), - "(" ")", -}; - -Block: Expression = - "{" "}" => Expression::Block(statements, Box::new(result)); - -// TODO: decide on identifier syntax -Name: Name = r"[a-zA-Z_]+" => <>.to_string(); - -Integer: i64 = r"[0-9]+" => <>.parse::().unwrap(); - -Boolean: bool = { - "true" => true, - "false" => false, -} - -Comma: Vec = { - ",")*> => match e { - None => v, - Some(e) => { - v.push(e); - v - } - } -}; diff --git a/src/phase.rs b/src/phase.rs new file mode 100644 index 0000000..ea86848 --- /dev/null +++ b/src/phase.rs @@ -0,0 +1 @@ +pub mod parse; diff --git a/src/phase/parse.rs b/src/phase/parse.rs new file mode 100644 index 0000000..d288f7a --- /dev/null +++ b/src/phase/parse.rs @@ -0,0 +1,15 @@ +use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError}; + +use crate::syntax::{self, Line}; + +lalrpop_mod!(parser, "/phase/parse/parser.rs"); + +pub type Result<'input, T> = std::result::Result, &'static str>>; + +pub fn parse_program(input: &str) -> Result { + parser::ProgramParser::new().parse(input) +} + +pub fn parse_repl_line(input: &str) -> Result { + parser::LineParser::new().parse(input) +} diff --git a/src/phase/parse/parser.lalrpop b/src/phase/parse/parser.lalrpop new file mode 100644 index 0000000..e1bfe11 --- /dev/null +++ b/src/phase/parse/parser.lalrpop @@ -0,0 +1,69 @@ +use crate::syntax::{ + Binding, + Block, + Expression, + Item, + Line, + Name, + Program, +}; + +grammar; + +pub Program: Program = Item*; + +pub Line: Line = { + Item => Line::Item(<>), + Binding => Line::Binding(<>), + ";"? => Line::Expression(<>), +} + +Item: Item = { + "fn" "(" > ")" ";"? => Item::Fn { <> }, +} + +Binding: Binding = + "let" "=" ";" => Binding(<>); + +Expression: Expression = { + Simple, + Boxed => Expression::Block(<>), +}; + +Simple: Expression = { + "|" > "|" > => Expression::Lambda { <> }, + Atom, +}; + +Atom: Expression = { + Name => Expression::Variable(<>), + > "(" > ")" => Expression::Call { <> }, + Integer => Expression::Integer(<>), + Boolean => Expression::Boolean(<>), + "(" ")", +}; + +Block: Block = + "{" "}" => Block { <> }; + +// TODO: decide on identifier syntax +Name: Name = r"[a-zA-Z_]+" => <>.to_string(); + +Integer: i64 = r"[0-9]+" => <>.parse::().unwrap(); + +Boolean: bool = { + "true" => true, + "false" => false, +} + +Boxed: Box = T => Box::new(<>); + +Comma: Vec = { + ",")*> => match e { + None => v, + Some(e) => { + v.push(e); + v + } + } +}; diff --git a/src/syntax.rs b/src/syntax.rs index 9f91f8b..28068ea 100644 --- a/src/syntax.rs +++ b/src/syntax.rs @@ -1,19 +1,44 @@ pub type Name = String; -pub type Program = Vec; +pub type Program = Vec; #[derive(Debug, Clone)] -pub enum Statement { - Binding(Name, Expression), +pub enum Line { + Item(Item), + Binding(Binding), Expression(Expression), } +#[derive(Debug, Clone)] +pub enum Item { + Fn { + name: Name, + parameters: Vec, + body: Block, + }, +} + +#[derive(Debug, Clone)] +pub struct Binding(pub Name, pub Expression); + #[derive(Debug, Clone)] pub enum Expression { Variable(Name), - Block(Vec, Box), - Lambda(Vec, Box), - Call(Box, Vec), + Block(Box), + Lambda { + parameters: Vec, + result: Box, + }, + Call { + callee: Box, + arguments: Vec, + }, Boolean(bool), Integer(i64), } + +#[derive(Debug, Clone)] +pub struct Block { + pub bindings: Vec, + pub result: Expression, +}