Add more documenation, minor tweaks

main
mat ess 2023-07-24 23:46:12 -04:00
parent bcee1cf160
commit bce8a006b0
4 changed files with 51 additions and 30 deletions

View File

@ -1,9 +1,10 @@
use std::fmt::Write; //! Language builtins, wired into the implementation.
use thiserror::Error; use std::fmt::Write;
use crate::evaluate::{Dictionary, Value}; use crate::evaluate::{Dictionary, Value};
/// Builtin functions.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Builtin { pub enum Builtin {
Print, Print,
@ -16,18 +17,10 @@ pub enum Builtin {
LessThan, LessThan,
} }
#[derive(Debug, Error)] fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> i64 {
pub enum Error {
#[error("Tried to {0} a non-integer: {1}")]
NonInteger(&'static str, Value),
}
type Result<T> = std::result::Result<T, Error>;
fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> Result<i64> {
move |value| match value { move |value| match value {
Value::Integer(i) => Ok(i), Value::Integer(i) => i,
_ => Err(Error::NonInteger(op_name, value)), _ => panic!("got non-integer during {op_name}"),
} }
} }
@ -37,8 +30,8 @@ macro_rules! int_op {
$arguments $arguments
.into_iter() .into_iter()
.map(unwrap_int_value($op_name)) .map(unwrap_int_value($op_name))
.reduce(|acc, v| Ok(acc? $op v?)) .reduce(|acc, v| acc $op v)
.unwrap()?, .unwrap(),
) )
}; };
} }
@ -47,13 +40,14 @@ macro_rules! int_cmp {
($arguments:expr, $op:tt) => {{ ($arguments:expr, $op:tt) => {{
let mut arguments = $arguments; let mut arguments = $arguments;
let unwrap = unwrap_int_value("compare"); let unwrap = unwrap_int_value("compare");
let y = unwrap(arguments.pop().unwrap())?; let y = unwrap(arguments.pop().unwrap());
let x = unwrap(arguments.pop().unwrap())?; let x = unwrap(arguments.pop().unwrap());
Value::Boolean(x $op y) Value::Boolean(x $op y)
}}; }};
} }
impl Builtin { impl Builtin {
/// A mapping from runtime names to builtin sentinels.
pub fn dictionary() -> Dictionary<Value> { pub fn dictionary() -> Dictionary<Value> {
[ [
("_print", Builtin::Print), ("_print", Builtin::Print),
@ -69,6 +63,7 @@ impl Builtin {
.into() .into()
} }
/// Minimum # of required arguments for a builtin.
pub fn min_parameters(&self) -> usize { pub fn min_parameters(&self) -> usize {
match self { match self {
Builtin::Print => 0, Builtin::Print => 0,
@ -81,6 +76,8 @@ impl Builtin {
| Builtin::LessThan => 2, | Builtin::LessThan => 2,
} }
} }
/// Maximum # of parameters allowed, if any.
pub fn max_parameters(&self) -> Option<usize> { pub fn max_parameters(&self) -> Option<usize> {
match self { match self {
Builtin::Print Builtin::Print
@ -93,8 +90,9 @@ impl Builtin {
} }
} }
pub fn call(&self, arguments: Vec<Value>) -> Result<Value> { /// Execute the given builtin on the provided arguments.
let result = match self { pub fn call(&self, arguments: Vec<Value>) -> Value {
match self {
Builtin::Print => { Builtin::Print => {
let mut output = String::new(); let mut output = String::new();
let mut arguments = arguments.into_iter().peekable(); let mut arguments = arguments.into_iter().peekable();
@ -126,7 +124,6 @@ impl Builtin {
} }
Builtin::GreaterThan => int_cmp!(arguments, >), Builtin::GreaterThan => int_cmp!(arguments, >),
Builtin::LessThan => int_cmp!(arguments, <), Builtin::LessThan => int_cmp!(arguments, <),
}; }
Ok(result)
} }
} }

View File

@ -1,8 +1,12 @@
use std::{ffi::OsString, path::PathBuf}; //! Command line interface for the language binary.
use std::ffi::OsString;
use std::path::PathBuf;
use pico_args::Arguments; use pico_args::Arguments;
use thiserror::Error; use thiserror::Error;
/// Usage help text.
pub const USAGE: &str = " pub const USAGE: &str = "
USAGE: USAGE:
mul [-h|--help] (parse <FILE> | evaluate <FILE> | repl) mul [-h|--help] (parse <FILE> | evaluate <FILE> | repl)
@ -16,6 +20,7 @@ COMMANDS:
repl Start the Read-Evaluate-Print-Loop repl Start the Read-Evaluate-Print-Loop
"; ";
/// Errors that may occur during command line parsing.
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("Problem while parsing arguments, {0}")] #[error("Problem while parsing arguments, {0}")]
@ -26,6 +31,7 @@ pub enum Error {
UnrecognizedArguments(Vec<OsString>), UnrecognizedArguments(Vec<OsString>),
} }
/// Possible top level commands.
pub enum Command { pub enum Command {
Parse { path: PathBuf }, Parse { path: PathBuf },
Run { path: PathBuf }, Run { path: PathBuf },
@ -33,8 +39,13 @@ pub enum Command {
} }
impl Command { impl Command {
/// Parse the command line from the current environment.
pub fn from_environment() -> Result<Option<Self>, Error> { pub fn from_environment() -> Result<Option<Self>, Error> {
let mut args = Arguments::from_env(); Self::from_arguments(Arguments::from_env())
}
/// Parse a command from raw arguments.
pub fn from_arguments(mut args: Arguments) -> Result<Option<Self>, Error> {
if args.contains(["-h", "--help"]) { if args.contains(["-h", "--help"]) {
return Ok(None); return Ok(None);
} }

View File

@ -1,12 +1,17 @@
use std::{collections::HashMap, fmt::Display}; //! Tree-walk interpreter for syntax.
use std::collections::HashMap;
use std::fmt::Display;
use thiserror::Error; use thiserror::Error;
use crate::builtins::{self, Builtin}; use crate::builtins::Builtin;
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program}; use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
/// Mapping of names to some T.
pub type Dictionary<T> = HashMap<Name, T>; pub type Dictionary<T> = HashMap<Name, T>;
/// Runtime values.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Value { pub enum Value {
Builtin(Builtin), Builtin(Builtin),
@ -32,6 +37,7 @@ impl Display for Value {
} }
} }
/// Errors that may occur during evaluation.
#[derive(Debug, Error)] #[derive(Debug, Error)]
pub enum Error { pub enum Error {
#[error("The name '{0} was not bound to any value in this context")] #[error("The name '{0} was not bound to any value in this context")]
@ -42,28 +48,30 @@ pub enum Error {
ArgumentsMismatch(usize, usize), ArgumentsMismatch(usize, usize),
#[error("No function called main to run")] #[error("No function called main to run")]
MissingMainFunction, MissingMainFunction,
#[error("Problem with builtin call: {0}")]
BuiltinError(#[from] builtins::Error),
} }
type Result<T> = std::result::Result<T, Error>; type Result<T> = std::result::Result<T, Error>;
/// AST walking interpreter.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub struct Interpreter { pub struct Interpreter {
environment: Dictionary<Value>, environment: Dictionary<Value>,
} }
impl Interpreter { impl Interpreter {
/// Create a fresh interpreter populated with builtins.
pub fn new() -> Self { pub fn new() -> Self {
Interpreter { Interpreter {
environment: Builtin::dictionary(), environment: Builtin::dictionary(),
} }
} }
/// Create a nested interpreter given an existing environment.
fn nested(environment: Dictionary<Value>) -> Self { fn nested(environment: Dictionary<Value>) -> Self {
Interpreter { environment } Interpreter { environment }
} }
/// Run a program. Expects a main function to exist.
pub fn run(&mut self, program: &Program) -> Result<Value> { pub fn run(&mut self, program: &Program) -> Result<Value> {
for item in program { for item in program {
self.interpret_item(item)?; self.interpret_item(item)?;
@ -75,7 +83,7 @@ impl Interpreter {
} }
} }
pub fn interpret_item(&mut self, item: &Item) -> Result<Value> { fn interpret_item(&mut self, item: &Item) -> Result<Value> {
match item { match item {
Item::Fn { Item::Fn {
name, name,
@ -93,6 +101,7 @@ impl Interpreter {
} }
} }
/// Evaluate a code fragment from an interactive context.
pub fn evaluate_interactive_entry(&mut self, entry: &InteractiveEntry) -> Result<Value> { pub fn evaluate_interactive_entry(&mut self, entry: &InteractiveEntry) -> Result<Value> {
match entry { match entry {
InteractiveEntry::Item(item) => self.interpret_item(item), InteractiveEntry::Item(item) => self.interpret_item(item),
@ -103,7 +112,7 @@ impl Interpreter {
} }
} }
pub fn evaluate(&mut self, term: &Expression) -> Result<Value> { fn evaluate(&mut self, term: &Expression) -> Result<Value> {
match term { match term {
Expression::Variable(name) => self Expression::Variable(name) => self
.environment .environment
@ -158,7 +167,7 @@ impl Interpreter {
.iter() .iter()
.map(|term| self.evaluate(term)) .map(|term| self.evaluate(term))
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
b.call(arguments).map_err(Error::BuiltinError) Ok(b.call(arguments))
} }
_ => Err(Error::NotAFunction), _ => Err(Error::NotAFunction),
} }

View File

@ -1,3 +1,5 @@
//! Transform text into syntax.
use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError}; use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
use crate::syntax::{InteractiveEntry, Program}; use crate::syntax::{InteractiveEntry, Program};
@ -6,10 +8,12 @@ lalrpop_mod!(parser, "/parse/parser.rs");
type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>; type Result<'input, T> = std::result::Result<T, ParseError<usize, Token<'input>, &'static str>>;
/// Parse a series of items.
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)
} }
/// Parse a fragment of code.
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)
} }