Compare commits

..

No commits in common. "4e76f1990e4c89fc6c97b91a17c00693b5ca2695" and "bcee1cf16078fdcb3fda1a17f8f66c5614082e9a" have entirely different histories.

17 changed files with 120 additions and 307 deletions

28
Cargo.lock generated
View File

@ -392,21 +392,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.9.1" version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575" checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.3.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@ -415,9 +403,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.7.4" version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2" checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
[[package]] [[package]]
name = "rustix" name = "rustix"
@ -494,18 +482,18 @@ dependencies = [
[[package]] [[package]]
name = "thiserror" name = "thiserror"
version = "1.0.44" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90" checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
dependencies = [ dependencies = [
"thiserror-impl", "thiserror-impl",
] ]
[[package]] [[package]]
name = "thiserror-impl" name = "thiserror-impl"
version = "1.0.44" version = "1.0.43"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96" checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",

View File

@ -7,11 +7,8 @@ edition = "2021"
lalrpop = "0.20.0" lalrpop = "0.20.0"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1.0.72"
lalrpop-util = { version = "0.20.0", features = ["lexer"] } lalrpop-util = { version = "0.20.0", features = ["lexer"] }
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] } pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
regex = "1" regex = "1"
thiserror = "1" thiserror = "1.0.43"
# [dev-dependencies]
# goldentests = "1"

View File

@ -1,22 +0,0 @@
CECI N'EST PAS UN PERMIS
Copyright mat ess. All rights reserved.
This software may not be used by anyone, for any reason.
As far as the law allows, this software comes as is, without any warranty or condition, and no contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim.
===
this is not a license.
free software as envisioned by rms and gnu has failed to defend the freedom of users.
open source software is a tool for corporate cooption, control, and profit.
software should be written for the benefit of the masses.
if you're still reading this: use, change, or share this code however you want. intellectual property is a farce.
https://asternova.top/LICENSE.txt
https://iliana.fyi/treachery
https://www.boringcactus.com/2021/09/29/anti-license-manifesto.html

View File

@ -1,3 +0,0 @@
fn main() {
}

View File

@ -1,7 +0,0 @@
use goldentests::{TestConfig, TestResult};
#[test]
fn run_golden_tests() -> TestResult<()> {
let config = TestConfig::new("target/debug/mul", "tests/golden", "// ")?;
config.run_tests()
}

View File

@ -3,11 +3,10 @@
## stages ## stages
- [x] parser - [x] parser
- [ ] lossless syntax trees (rowan + ungrammar) - [ ] lossless syntax trees (rowan + ungrammar)
- [ ] formatter (pretty-printer)
- [ ] typechecker - [ ] typechecker
- [x] interpreter - [x] interpreter
- [ ] virtual machine
- [ ] code generator - [ ] code generator
- [ ] formatter (pretty-printer)
## features ## features
- [ ] primitives - [ ] primitives

View File

@ -10,11 +10,11 @@
"rust-overlay": "rust-overlay" "rust-overlay": "rust-overlay"
}, },
"locked": { "locked": {
"lastModified": 1688772518, "lastModified": 1684468982,
"narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=", "narHash": "sha256-EoC1N5sFdmjuAP3UOkyQujSOT6EdcXTnRw8hPjJkEgc=",
"owner": "ipetkov", "owner": "ipetkov",
"repo": "crane", "repo": "crane",
"rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e", "rev": "99de890b6ef4b4aab031582125b6056b792a4a30",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -60,11 +60,11 @@
"nixpkgs-lib": "nixpkgs-lib" "nixpkgs-lib": "nixpkgs-lib"
}, },
"locked": { "locked": {
"lastModified": 1688466019, "lastModified": 1683560683,
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=", "narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
"owner": "hercules-ci", "owner": "hercules-ci",
"repo": "flake-parts", "repo": "flake-parts",
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec", "rev": "006c75898cf814ef9497252b022e91c946ba8e17",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -78,11 +78,11 @@
"systems": "systems" "systems": "systems"
}, },
"locked": { "locked": {
"lastModified": 1687709756, "lastModified": 1681202837,
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=", "narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7", "rev": "cfacdce06f30d2b68473a46042957675eebb3401",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -92,15 +92,12 @@
} }
}, },
"flake-utils_2": { "flake-utils_2": {
"inputs": {
"systems": "systems_2"
},
"locked": { "locked": {
"lastModified": 1685518550, "lastModified": 1667395993,
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=", "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
"owner": "numtide", "owner": "numtide",
"repo": "flake-utils", "repo": "flake-utils",
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef", "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -132,11 +129,11 @@
}, },
"nixpkgs": { "nixpkgs": {
"locked": { "locked": {
"lastModified": 1690640159, "lastModified": 1684570954,
"narHash": "sha256-5DZUYnkeMOsVb/eqPYb9zns5YsnQXRJRC8Xx/nPMcno=", "narHash": "sha256-FX5y4Sm87RWwfu9PI71XFvuRpZLowh00FQpIJ1WfXqE=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "e6ab46982debeab9831236869539a507f670a129", "rev": "3005f20ce0aaa58169cdee57c8aa12e5f1b6e1b3",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -149,11 +146,11 @@
"nixpkgs-lib": { "nixpkgs-lib": {
"locked": { "locked": {
"dir": "lib", "dir": "lib",
"lastModified": 1688049487, "lastModified": 1682879489,
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=", "narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9", "rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -166,16 +163,16 @@
}, },
"nixpkgs-stable": { "nixpkgs-stable": {
"locked": { "locked": {
"lastModified": 1685801374, "lastModified": 1678872516,
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=", "narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=",
"owner": "NixOS", "owner": "NixOS",
"repo": "nixpkgs", "repo": "nixpkgs",
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373", "rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8",
"type": "github" "type": "github"
}, },
"original": { "original": {
"owner": "NixOS", "owner": "NixOS",
"ref": "nixos-23.05", "ref": "nixos-22.11",
"repo": "nixpkgs", "repo": "nixpkgs",
"type": "github" "type": "github"
} }
@ -191,11 +188,11 @@
"nixpkgs-stable": "nixpkgs-stable" "nixpkgs-stable": "nixpkgs-stable"
}, },
"locked": { "locked": {
"lastModified": 1690743255, "lastModified": 1684195081,
"narHash": "sha256-dsJzQsyJGWCym1+LMyj2rbYmvjYmzeOrk7ypPrSFOPo=", "narHash": "sha256-IKnQUSBhQTChFERxW2AzuauVpY1HRgeVzAjNMAA4B6I=",
"owner": "cachix", "owner": "cachix",
"repo": "pre-commit-hooks.nix", "repo": "pre-commit-hooks.nix",
"rev": "fcbf4705d98398d084e6cb1c826a0b90a91d22d7", "rev": "96eabec58248ed8f4b0ad59e7ce9398018684fdc",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -224,11 +221,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1688351637, "lastModified": 1683080331,
"narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=", "narHash": "sha256-nGDvJ1DAxZIwdn6ww8IFwzoHb2rqBP4wv/65Wt5vflk=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "f9b92316727af9e6c7fee4a761242f7f46880329", "rev": "d59c3fa0cba8336e115b376c2d9e91053aa59e56",
"type": "github" "type": "github"
}, },
"original": { "original": {
@ -251,21 +248,6 @@
"repo": "default", "repo": "default",
"type": "github" "type": "github"
} }
},
"systems_2": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
} }
}, },
"root": "root", "root": "root",

View File

@ -31,7 +31,7 @@
pre-commit.settings.hooks = { pre-commit.settings.hooks = {
rustfmt.enable = true; rustfmt.enable = true;
clippy.enable = true; clippy.enable = true;
# cargo-check.enable = true; cargo-check.enable = true;
}; };
checks = { inherit package; }; checks = { inherit package; };
packages.default = package; packages.default = package;
@ -55,7 +55,7 @@
buildInputs = builtins.attrValues { buildInputs = builtins.attrValues {
inherit (pkgs) inherit (pkgs)
rust-analyzer rust-analyzer
rustfmt # rustfmt
clippy clippy
# profiling # profiling
cargo-flamegraph cargo-flamegraph
@ -65,6 +65,11 @@
cargo-edit cargo-edit
cargo-license cargo-license
; ;
rustfmt = pkgs.rustfmt.overrideAttrs (old: {
preFixup = pkgs.lib.optionalString pkgs.stdenv.isDarwin ''
install_name_tool -add_rpath "${pkgs.rustc}/lib" "$out/bin/rustfmt"
'';
});
}; };
}; };
}; };

View File

@ -1,12 +1,9 @@
//! Language builtins, wired into the implementation.
use std::fmt::Write; use std::fmt::Write;
use crate::dictionary::Dictionary; use thiserror::Error;
use crate::evaluate::Value;
use crate::syntax::Type; use crate::evaluate::{Dictionary, Value};
/// Builtin functions.
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum Builtin { pub enum Builtin {
Print, Print,
@ -19,59 +16,44 @@ pub enum Builtin {
LessThan, LessThan,
} }
#[track_caller] #[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) => i, Value::Integer(i) => Ok(i),
_ => panic!("got non-integer during {op_name}"), _ => Err(Error::NonInteger(op_name, value)),
} }
} }
macro_rules! int_op { macro_rules! int_op {
($arguments:expr, $op:tt, $op_name:expr) => {{ ($arguments:expr, $op:tt, $op_name:expr) => {
let i = $arguments Value::Integer(
$arguments
.into_iter() .into_iter()
.map(unwrap_int_value($op_name)) .map(unwrap_int_value($op_name))
.reduce(|acc, v| acc $op v) .reduce(|acc, v| Ok(acc? $op v?))
.unwrap(); .unwrap()?,
Value::Integer(i) )
}}; };
} }
macro_rules! int_cmp { 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)
}}; }};
} }
fn int_type() -> Type {
Type::Constructor("Integer".to_string())
}
fn bool_type() -> Type {
Type::Constructor("Boolean".to_string())
}
fn int_op_type() -> Type {
Type::Function {
parameters: vec![int_type(), int_type()],
result: Box::new(int_type()),
}
}
fn int_cmp_type() -> Type {
Type::Function {
parameters: vec![int_type(), int_type()],
result: Box::new(bool_type()),
}
}
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),
@ -87,23 +69,6 @@ impl Builtin {
.into() .into()
} }
/// A mapping from runtime names to builtin types.
pub fn type_dictionary() -> Dictionary<Type> {
[
("_print", Type::Print),
("_add", int_op_type()),
("_sub", int_op_type()),
("_mul", int_op_type()),
("_div", int_op_type()),
("_equals", Type::Equals),
("_greaterthan", int_cmp_type()),
("_lessthan", int_cmp_type()),
]
.map(|(name, ty)| (name.to_string(), ty))
.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,
@ -116,8 +81,6 @@ 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
@ -130,9 +93,8 @@ impl Builtin {
} }
} }
/// Execute the given builtin on the provided arguments. pub fn call(&self, arguments: Vec<Value>) -> Result<Value> {
pub fn call(&self, arguments: Vec<Value>) -> Value { let result = match self {
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();
@ -164,6 +126,7 @@ 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,12 +1,8 @@
//! Command line interface for the language binary. use std::{ffi::OsString, path::PathBuf};
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)
@ -20,7 +16,6 @@ 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}")]
@ -31,7 +26,6 @@ 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 },
@ -39,13 +33,8 @@ 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> {
Self::from_arguments(Arguments::from_env()) let mut args = 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,6 +0,0 @@
use std::collections::HashMap;
use crate::syntax::Name;
/// Mapping of names to some T.
pub type Dictionary<T> = HashMap<Name, T>;

View File

@ -1,14 +1,12 @@
//! Tree-walk interpreter for syntax. use std::{collections::HashMap, fmt::Display};
use std::fmt::Display;
use thiserror::Error; use thiserror::Error;
use crate::builtins::Builtin; use crate::builtins::{self, Builtin};
use crate::dictionary::Dictionary;
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program}; use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
/// Runtime values. pub type Dictionary<T> = HashMap<Name, T>;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Value { pub enum Value {
Builtin(Builtin), Builtin(Builtin),
@ -34,7 +32,6 @@ 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")]
@ -45,41 +42,28 @@ 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>,
} }
macro_rules! bind {
($interpreter:expr, $name:expr, $term:expr, to: $nested:expr) => {{
let value = $interpreter.evaluate($term)?;
$nested.environment.insert($name, value.clone());
Ok(value)
}};
($interpreter:expr, $name:expr, $term:expr) => {
bind!($interpreter, $name, $term, to: $interpreter)
};
}
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)?;
@ -91,17 +75,16 @@ impl Interpreter {
} }
} }
fn interpret_item(&mut self, item: &Item) -> Result<Value> { pub fn interpret_item(&mut self, item: &Item) -> Result<Value> {
match item { match item {
Item::Fn { Item::Fn {
name, name,
parameters, parameters,
body, body,
..
} => { } => {
let closure = Value::Closure { let closure = Value::Closure {
environment: self.environment.clone(), environment: self.environment.clone(),
parameters: parameters.iter().map(|p| p.name.clone()).collect(), parameters: parameters.clone(),
result: Expression::Block(Box::new(body.clone())), result: Expression::Block(Box::new(body.clone())),
}; };
self.environment.insert(name.clone(), closure.clone()); self.environment.insert(name.clone(), closure.clone());
@ -110,18 +93,17 @@ 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),
InteractiveEntry::Binding(binding) => { InteractiveEntry::Binding(binding) => {
bind!(self, binding.name.clone(), &binding.expression) self.bind(binding.name.clone(), &binding.expression)
} }
InteractiveEntry::Expression(term) => self.evaluate(term), InteractiveEntry::Expression(term) => self.evaluate(term),
} }
} }
fn evaluate(&mut self, term: &Expression) -> Result<Value> { pub fn evaluate(&mut self, term: &Expression) -> Result<Value> {
match term { match term {
Expression::Variable(name) => self Expression::Variable(name) => self
.environment .environment
@ -129,18 +111,15 @@ impl Interpreter {
.ok_or(Error::UnboundName(name.clone())) .ok_or(Error::UnboundName(name.clone()))
.cloned(), .cloned(),
Expression::Block(b) => self.evaluate_block(b.as_ref()), Expression::Block(b) => self.evaluate_block(b.as_ref()),
Expression::Lambda { Expression::Lambda { parameters, result } => Ok(Value::Closure {
parameters, result, ..
} => Ok(Value::Closure {
environment: self.environment.clone(), environment: self.environment.clone(),
parameters: parameters.iter().map(|p| p.name.clone()).collect(), parameters: parameters.clone(),
result: *result.clone(), result: *result.clone(),
}), }),
Expression::Call { callee, arguments } => { Expression::Call { callee, arguments } => {
let callee = self.evaluate(callee.as_ref())?; let callee = self.evaluate(callee.as_ref())?;
self.apply_call(&callee, arguments) self.apply_call(&callee, arguments)
} }
Expression::Annotation { expression, .. } => self.evaluate(expression.as_ref()),
Expression::Boolean(b) => Ok(Value::Boolean(*b)), Expression::Boolean(b) => Ok(Value::Boolean(*b)),
Expression::Integer(i) => Ok(Value::Integer(*i)), Expression::Integer(i) => Ok(Value::Integer(*i)),
Expression::Unit => Ok(Value::Unit), Expression::Unit => Ok(Value::Unit),
@ -160,7 +139,7 @@ impl Interpreter {
let mut nested = Interpreter::nested(environment.clone()); let mut nested = Interpreter::nested(environment.clone());
for (name, argument) in parameters.iter().zip(arguments) { for (name, argument) in parameters.iter().zip(arguments) {
// we don't want arguments to refer to each other, so use the parent interpreter // we don't want arguments to refer to each other, so use the parent interpreter
bind!(self, name.clone(), argument, to: nested)?; self.bind_nested(name.clone(), argument, &mut nested)?;
} }
nested.evaluate(result) nested.evaluate(result)
} }
@ -179,7 +158,7 @@ impl Interpreter {
.iter() .iter()
.map(|term| self.evaluate(term)) .map(|term| self.evaluate(term))
.collect::<Result<Vec<_>>>()?; .collect::<Result<Vec<_>>>()?;
Ok(b.call(arguments)) b.call(arguments).map_err(Error::BuiltinError)
} }
_ => Err(Error::NotAFunction), _ => Err(Error::NotAFunction),
} }
@ -188,10 +167,27 @@ impl Interpreter {
fn evaluate_block(&mut self, block: &Block) -> Result<Value> { fn evaluate_block(&mut self, block: &Block) -> Result<Value> {
let mut nested = self.clone(); let mut nested = self.clone();
for binding in &block.bindings { for binding in &block.bindings {
bind!(nested, binding.name.clone(), &binding.expression)?; nested.bind(binding.name.clone(), &binding.expression)?;
} }
nested.evaluate(&block.result) 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)
}
fn bind_nested(
&mut self,
name: Name,
term: &Expression,
nested: &mut Interpreter,
) -> Result<Value> {
let value = self.evaluate(term)?;
nested.environment.insert(name, value.clone());
Ok(value)
}
} }
impl Default for Interpreter { impl Default for Interpreter {

View File

@ -1,7 +1,5 @@
pub mod builtins; pub mod builtins;
pub mod cli; pub mod cli;
pub mod dictionary;
pub mod evaluate; pub mod evaluate;
pub mod parse; pub mod parse;
pub mod syntax; pub mod syntax;
pub mod typecheck;

View File

@ -1,5 +1,3 @@
//! 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};
@ -8,12 +6,10 @@ 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)
} }

View File

@ -5,9 +5,7 @@ use crate::syntax::{
Item, Item,
InteractiveEntry, InteractiveEntry,
Name, Name,
Parameter,
Program, Program,
Type,
}; };
grammar; grammar;
@ -20,26 +18,20 @@ pub InteractiveEntry: InteractiveEntry = {
<Expression> ";"? => InteractiveEntry::Expression(<>), <Expression> ";"? => InteractiveEntry::Expression(<>),
} }
Type: Type = {
Name => Type::Constructor(<>),
"fn" "(" <parameters:Comma<Type>> ")" "->" <result:Boxed<Type>> => Type::Function { <> },
"(" <Type> ")",
}
Item: Item = { Item: Item = {
"fn" <name:Name> "(" <parameters:Comma<Parameter>> ")" <return_type:("->" <Type>)?> <body:Block> ";"? => Item::Fn { <> }, "fn" <name:Name> "(" <parameters:Comma<Name>> ")" <body:Block> ";"? => Item::Fn { <> },
} }
Parameter: Parameter =
<name:Name> <annotation:(":" <Type>)?> => Parameter { <> };
Binding: Binding = Binding: Binding =
"let" <name:Name> <annotation:(":" <Type>)?> "=" <expression:Expression> => Binding { <> }; "let" <name:Name> "=" <expression:Expression> => Binding { <> };
Expression: Expression = { Expression: Expression = {
"|" <parameters:Comma<Parameter>> "|" <return_type:("->" <Type>)?> <result:Boxed<Expression>> => Expression::Lambda { <> }, Simple,
Boxed<Block> => Expression::Block(<>), Boxed<Block> => Expression::Block(<>),
<expression:Boxed<Atom>> ":" <annotation:Type> => Expression::Annotation { <> }, };
Simple: Expression = {
"|" <parameters:Comma<Name>> "|" <result:Boxed<Expression>> => Expression::Lambda { <> },
Atom, Atom,
}; };
@ -48,8 +40,8 @@ Atom: Expression = {
<callee:Boxed<Atom>> "(" <arguments:Comma<Expression>> ")" => Expression::Call { <> }, <callee:Boxed<Atom>> "(" <arguments:Comma<Expression>> ")" => Expression::Call { <> },
Integer => Expression::Integer(<>), Integer => Expression::Integer(<>),
Boolean => Expression::Boolean(<>), Boolean => Expression::Boolean(<>),
"(" ")" => Expression::Unit,
"(" <Expression> ")", "(" <Expression> ")",
"(" ")" => Expression::Unit,
}; };
BlockBinding = <Binding> ";"; BlockBinding = <Binding> ";";

View File

@ -1,6 +1,6 @@
//! Syntax elements. //! Syntax elements.
/// An identifier. /// Identifiers.
pub type Name = String; pub type Name = String;
/// The rough equivalent of a module. /// The rough equivalent of a module.
@ -14,49 +14,20 @@ pub enum InteractiveEntry {
Expression(Expression), Expression(Expression),
} }
/// Surface syntax types.
#[derive(Debug, Clone)]
pub enum Type {
/// A type variable.
Variable(Name),
/// A function type.
Function {
/// Parameter types.
parameters: Vec<Type>,
/// The return type.
result: Box<Type>,
},
/// A type constructor.
Constructor(Name),
/// Special typing sentinel for the print builtin.
Print,
/// Special typing sentinel for the equals builtin.
Equals,
}
/// A top level definition. /// A top level definition.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
pub enum Item { pub enum Item {
Fn { Fn {
name: Name, name: Name,
parameters: Vec<Parameter>, parameters: Vec<Name>,
return_type: Option<Type>,
body: Block, body: Block,
}, },
} }
/// Function parameter with an optional type.
#[derive(Debug, Clone)]
pub struct Parameter {
pub name: Name,
pub annotation: Option<Type>,
}
/// 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 struct Binding {
pub name: Name, pub name: Name,
pub annotation: Option<Type>,
pub expression: Expression, pub expression: Expression,
} }
@ -66,18 +37,13 @@ pub enum Expression {
Variable(Name), Variable(Name),
Block(Box<Block>), Block(Box<Block>),
Lambda { Lambda {
parameters: Vec<Parameter>, parameters: Vec<Name>,
return_type: Option<Type>,
result: Box<Expression>, result: Box<Expression>,
}, },
Call { Call {
callee: Box<Expression>, callee: Box<Expression>,
arguments: Vec<Expression>, arguments: Vec<Expression>,
}, },
Annotation {
expression: Box<Expression>,
annotation: Type,
},
Boolean(bool), Boolean(bool),
Integer(i64), Integer(i64),
Unit, Unit,

View File

@ -1,20 +0,0 @@
use crate::builtins::Builtin;
use crate::dictionary::Dictionary;
use crate::syntax::{Program, Type};
/// Typechecker state.
struct Typechecker {
environment: Dictionary<Type>,
}
impl Typechecker {
fn new() -> Self {
Typechecker {
environment: Builtin::type_dictionary(),
}
}
fn check_program(&mut self, program: &Program) -> Result<(), ()> {
Ok(())
}
}