Add more documenation, minor tweaks
parent
bcee1cf160
commit
bce8a006b0
|
@ -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};
|
||||
|
||||
/// Builtin functions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Builtin {
|
||||
Print,
|
||||
|
@ -16,18 +17,10 @@ pub enum Builtin {
|
|||
LessThan,
|
||||
}
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
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> {
|
||||
fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> i64 {
|
||||
move |value| match value {
|
||||
Value::Integer(i) => Ok(i),
|
||||
_ => Err(Error::NonInteger(op_name, value)),
|
||||
Value::Integer(i) => i,
|
||||
_ => panic!("got non-integer during {op_name}"),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -37,8 +30,8 @@ macro_rules! int_op {
|
|||
$arguments
|
||||
.into_iter()
|
||||
.map(unwrap_int_value($op_name))
|
||||
.reduce(|acc, v| Ok(acc? $op v?))
|
||||
.unwrap()?,
|
||||
.reduce(|acc, v| acc $op v)
|
||||
.unwrap(),
|
||||
)
|
||||
};
|
||||
}
|
||||
|
@ -47,13 +40,14 @@ macro_rules! int_cmp {
|
|||
($arguments:expr, $op:tt) => {{
|
||||
let mut arguments = $arguments;
|
||||
let unwrap = unwrap_int_value("compare");
|
||||
let y = unwrap(arguments.pop().unwrap())?;
|
||||
let x = unwrap(arguments.pop().unwrap())?;
|
||||
let y = unwrap(arguments.pop().unwrap());
|
||||
let x = unwrap(arguments.pop().unwrap());
|
||||
Value::Boolean(x $op y)
|
||||
}};
|
||||
}
|
||||
|
||||
impl Builtin {
|
||||
/// A mapping from runtime names to builtin sentinels.
|
||||
pub fn dictionary() -> Dictionary<Value> {
|
||||
[
|
||||
("_print", Builtin::Print),
|
||||
|
@ -69,6 +63,7 @@ impl Builtin {
|
|||
.into()
|
||||
}
|
||||
|
||||
/// Minimum # of required arguments for a builtin.
|
||||
pub fn min_parameters(&self) -> usize {
|
||||
match self {
|
||||
Builtin::Print => 0,
|
||||
|
@ -81,6 +76,8 @@ impl Builtin {
|
|||
| Builtin::LessThan => 2,
|
||||
}
|
||||
}
|
||||
|
||||
/// Maximum # of parameters allowed, if any.
|
||||
pub fn max_parameters(&self) -> Option<usize> {
|
||||
match self {
|
||||
Builtin::Print
|
||||
|
@ -93,8 +90,9 @@ impl Builtin {
|
|||
}
|
||||
}
|
||||
|
||||
pub fn call(&self, arguments: Vec<Value>) -> Result<Value> {
|
||||
let result = match self {
|
||||
/// Execute the given builtin on the provided arguments.
|
||||
pub fn call(&self, arguments: Vec<Value>) -> Value {
|
||||
match self {
|
||||
Builtin::Print => {
|
||||
let mut output = String::new();
|
||||
let mut arguments = arguments.into_iter().peekable();
|
||||
|
@ -126,7 +124,6 @@ impl Builtin {
|
|||
}
|
||||
Builtin::GreaterThan => int_cmp!(arguments, >),
|
||||
Builtin::LessThan => int_cmp!(arguments, <),
|
||||
};
|
||||
Ok(result)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
15
src/cli.rs
15
src/cli.rs
|
@ -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 thiserror::Error;
|
||||
|
||||
/// Usage help text.
|
||||
pub const USAGE: &str = "
|
||||
USAGE:
|
||||
mul [-h|--help] (parse <FILE> | evaluate <FILE> | repl)
|
||||
|
@ -16,6 +20,7 @@ COMMANDS:
|
|||
repl Start the Read-Evaluate-Print-Loop
|
||||
";
|
||||
|
||||
/// Errors that may occur during command line parsing.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("Problem while parsing arguments, {0}")]
|
||||
|
@ -26,6 +31,7 @@ pub enum Error {
|
|||
UnrecognizedArguments(Vec<OsString>),
|
||||
}
|
||||
|
||||
/// Possible top level commands.
|
||||
pub enum Command {
|
||||
Parse { path: PathBuf },
|
||||
Run { path: PathBuf },
|
||||
|
@ -33,8 +39,13 @@ pub enum Command {
|
|||
}
|
||||
|
||||
impl Command {
|
||||
/// Parse the command line from the current environment.
|
||||
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"]) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
|
|
@ -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 crate::builtins::{self, Builtin};
|
||||
use crate::builtins::Builtin;
|
||||
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
|
||||
|
||||
/// Mapping of names to some T.
|
||||
pub type Dictionary<T> = HashMap<Name, T>;
|
||||
|
||||
/// Runtime values.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
Builtin(Builtin),
|
||||
|
@ -32,6 +37,7 @@ impl Display for Value {
|
|||
}
|
||||
}
|
||||
|
||||
/// Errors that may occur during evaluation.
|
||||
#[derive(Debug, Error)]
|
||||
pub enum Error {
|
||||
#[error("The name '{0} was not bound to any value in this context")]
|
||||
|
@ -42,28 +48,30 @@ pub enum Error {
|
|||
ArgumentsMismatch(usize, usize),
|
||||
#[error("No function called main to run")]
|
||||
MissingMainFunction,
|
||||
#[error("Problem with builtin call: {0}")]
|
||||
BuiltinError(#[from] builtins::Error),
|
||||
}
|
||||
|
||||
type Result<T> = std::result::Result<T, Error>;
|
||||
|
||||
/// AST walking interpreter.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Interpreter {
|
||||
environment: Dictionary<Value>,
|
||||
}
|
||||
|
||||
impl Interpreter {
|
||||
/// Create a fresh interpreter populated with builtins.
|
||||
pub fn new() -> Self {
|
||||
Interpreter {
|
||||
environment: Builtin::dictionary(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a nested interpreter given an existing environment.
|
||||
fn nested(environment: Dictionary<Value>) -> Self {
|
||||
Interpreter { environment }
|
||||
}
|
||||
|
||||
/// Run a program. Expects a main function to exist.
|
||||
pub fn run(&mut self, program: &Program) -> Result<Value> {
|
||||
for item in program {
|
||||
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 {
|
||||
Item::Fn {
|
||||
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> {
|
||||
match entry {
|
||||
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 {
|
||||
Expression::Variable(name) => self
|
||||
.environment
|
||||
|
@ -158,7 +167,7 @@ impl Interpreter {
|
|||
.iter()
|
||||
.map(|term| self.evaluate(term))
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
b.call(arguments).map_err(Error::BuiltinError)
|
||||
Ok(b.call(arguments))
|
||||
}
|
||||
_ => Err(Error::NotAFunction),
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
//! Transform text into syntax.
|
||||
|
||||
use lalrpop_util::{lalrpop_mod, lexer::Token, ParseError};
|
||||
|
||||
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>>;
|
||||
|
||||
/// Parse a series of items.
|
||||
pub fn parse_program(input: &str) -> Result<Program> {
|
||||
parser::ProgramParser::new().parse(input)
|
||||
}
|
||||
|
||||
/// Parse a fragment of code.
|
||||
pub fn parse_interactive_entry(input: &str) -> Result<InteractiveEntry> {
|
||||
parser::InteractiveEntryParser::new().parse(input)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue