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};
|
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
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 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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue