Compare commits
3 Commits
bcee1cf160
...
4e76f1990e
Author | SHA1 | Date |
---|---|---|
mat ess | 4e76f1990e | |
mat ess | e21f575207 | |
mat ess | bce8a006b0 |
|
@ -392,9 +392,21 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.8.1"
|
||||
version = "1.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "af83e617f331cc6ae2da5443c602dfa5af81e517212d9d611a5b3ba1777b5370"
|
||||
checksum = "b2eae68fc220f7cf2532e4494aded17545fce192d59cd996e0fe7887f4ceb575"
|
||||
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 = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
@ -403,9 +415,9 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.7.1"
|
||||
version = "0.7.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a5996294f19bd3aae0453a862ad728f60e6600695733dd5df01da90c54363a3c"
|
||||
checksum = "e5ea92a5b6195c6ef2a0295ea818b312502c6fc94dde986c5553242e18fd4ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustix"
|
||||
|
@ -482,18 +494,18 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "thiserror"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a35fc5b8971143ca348fa6df4f024d4d55264f3468c71ad1c2f365b0a4d58c42"
|
||||
checksum = "611040a08a0439f8248d1990b111c95baa9c704c805fa1f62104b39655fd7f90"
|
||||
dependencies = [
|
||||
"thiserror-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "thiserror-impl"
|
||||
version = "1.0.43"
|
||||
version = "1.0.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "463fe12d7993d3b327787537ce8dd4dfa058de32fc2b195ef3cde03dc4771e8f"
|
||||
checksum = "090198534930841fab3a5d1bb637cde49e339654e606195f8d9c76eeb081dc96"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
|
@ -7,8 +7,11 @@ edition = "2021"
|
|||
lalrpop = "0.20.0"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.72"
|
||||
anyhow = "1"
|
||||
lalrpop-util = { version = "0.20.0", features = ["lexer"] }
|
||||
pico-args = { version = "0.5.0", features = ["eq-separator", "short-space-opt", "combined-flags"] }
|
||||
regex = "1"
|
||||
thiserror = "1.0.43"
|
||||
thiserror = "1"
|
||||
|
||||
# [dev-dependencies]
|
||||
# goldentests = "1"
|
||||
|
|
|
@ -0,0 +1,22 @@
|
|||
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
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
|
||||
}
|
|
@ -0,0 +1,7 @@
|
|||
use goldentests::{TestConfig, TestResult};
|
||||
|
||||
#[test]
|
||||
fn run_golden_tests() -> TestResult<()> {
|
||||
let config = TestConfig::new("target/debug/mul", "tests/golden", "// ")?;
|
||||
config.run_tests()
|
||||
}
|
|
@ -3,10 +3,11 @@
|
|||
## stages
|
||||
- [x] parser
|
||||
- [ ] lossless syntax trees (rowan + ungrammar)
|
||||
- [ ] formatter (pretty-printer)
|
||||
- [ ] typechecker
|
||||
- [x] interpreter
|
||||
- [ ] virtual machine
|
||||
- [ ] code generator
|
||||
- [ ] formatter (pretty-printer)
|
||||
|
||||
## features
|
||||
- [ ] primitives
|
||||
|
|
74
flake.lock
74
flake.lock
|
@ -10,11 +10,11 @@
|
|||
"rust-overlay": "rust-overlay"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684468982,
|
||||
"narHash": "sha256-EoC1N5sFdmjuAP3UOkyQujSOT6EdcXTnRw8hPjJkEgc=",
|
||||
"lastModified": 1688772518,
|
||||
"narHash": "sha256-ol7gZxwvgLnxNSZwFTDJJ49xVY5teaSvF7lzlo3YQfM=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "99de890b6ef4b4aab031582125b6056b792a4a30",
|
||||
"rev": "8b08e96c9af8c6e3a2b69af5a7fa168750fcf88e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -60,11 +60,11 @@
|
|||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683560683,
|
||||
"narHash": "sha256-XAygPMN5Xnk/W2c1aW0jyEa6lfMDZWlQgiNtmHXytPc=",
|
||||
"lastModified": 1688466019,
|
||||
"narHash": "sha256-VeM2akYrBYMsb4W/MmBo1zmaMfgbL4cH3Pu8PGyIwJ0=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "006c75898cf814ef9497252b022e91c946ba8e17",
|
||||
"rev": "8e8d955c22df93dbe24f19ea04f47a74adbdc5ec",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -78,11 +78,11 @@
|
|||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1681202837,
|
||||
"narHash": "sha256-H+Rh19JDwRtpVPAWp64F+rlEtxUWBAQW28eAi3SRSzg=",
|
||||
"lastModified": 1687709756,
|
||||
"narHash": "sha256-Y5wKlQSkgEK2weWdOu4J3riRd+kV/VCgHsqLNTTWQ/0=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "cfacdce06f30d2b68473a46042957675eebb3401",
|
||||
"rev": "dbabf0ca0c0c4bce6ea5eaf65af5cb694d2082c7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -92,12 +92,15 @@
|
|||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems_2"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1667395993,
|
||||
"narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
|
||||
"lastModified": 1685518550,
|
||||
"narHash": "sha256-o2d0KcvaXzTrPRIo0kOLV0/QXHhDQ5DTi+OxcjO8xqY=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
|
||||
"rev": "a1720a10a6cfe8234c0e93907ffe81be440f4cef",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -129,11 +132,11 @@
|
|||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1684570954,
|
||||
"narHash": "sha256-FX5y4Sm87RWwfu9PI71XFvuRpZLowh00FQpIJ1WfXqE=",
|
||||
"lastModified": 1690640159,
|
||||
"narHash": "sha256-5DZUYnkeMOsVb/eqPYb9zns5YsnQXRJRC8Xx/nPMcno=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "3005f20ce0aaa58169cdee57c8aa12e5f1b6e1b3",
|
||||
"rev": "e6ab46982debeab9831236869539a507f670a129",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -146,11 +149,11 @@
|
|||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"dir": "lib",
|
||||
"lastModified": 1682879489,
|
||||
"narHash": "sha256-sASwo8gBt7JDnOOstnps90K1wxmVfyhsTPPNTGBPjjg=",
|
||||
"lastModified": 1688049487,
|
||||
"narHash": "sha256-100g4iaKC9MalDjUW9iN6Jl/OocTDtXdeAj7pEGIRh4=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "da45bf6ec7bbcc5d1e14d3795c025199f28e0de0",
|
||||
"rev": "4bc72cae107788bf3f24f30db2e2f685c9298dc9",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -163,16 +166,16 @@
|
|||
},
|
||||
"nixpkgs-stable": {
|
||||
"locked": {
|
||||
"lastModified": 1678872516,
|
||||
"narHash": "sha256-/E1YwtMtFAu2KUQKV/1+KFuReYPANM2Rzehk84VxVoc=",
|
||||
"lastModified": 1685801374,
|
||||
"narHash": "sha256-otaSUoFEMM+LjBI1XL/xGB5ao6IwnZOXc47qhIgJe8U=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "9b8e5abb18324c7fe9f07cb100c3cd4a29cda8b8",
|
||||
"rev": "c37ca420157f4abc31e26f436c1145f8951ff373",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-22.11",
|
||||
"ref": "nixos-23.05",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
|
@ -188,11 +191,11 @@
|
|||
"nixpkgs-stable": "nixpkgs-stable"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1684195081,
|
||||
"narHash": "sha256-IKnQUSBhQTChFERxW2AzuauVpY1HRgeVzAjNMAA4B6I=",
|
||||
"lastModified": 1690743255,
|
||||
"narHash": "sha256-dsJzQsyJGWCym1+LMyj2rbYmvjYmzeOrk7ypPrSFOPo=",
|
||||
"owner": "cachix",
|
||||
"repo": "pre-commit-hooks.nix",
|
||||
"rev": "96eabec58248ed8f4b0ad59e7ce9398018684fdc",
|
||||
"rev": "fcbf4705d98398d084e6cb1c826a0b90a91d22d7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -221,11 +224,11 @@
|
|||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1683080331,
|
||||
"narHash": "sha256-nGDvJ1DAxZIwdn6ww8IFwzoHb2rqBP4wv/65Wt5vflk=",
|
||||
"lastModified": 1688351637,
|
||||
"narHash": "sha256-CLTufJ29VxNOIZ8UTg0lepsn3X03AmopmaLTTeHDCL4=",
|
||||
"owner": "oxalica",
|
||||
"repo": "rust-overlay",
|
||||
"rev": "d59c3fa0cba8336e115b376c2d9e91053aa59e56",
|
||||
"rev": "f9b92316727af9e6c7fee4a761242f7f46880329",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
|
@ -248,6 +251,21 @@
|
|||
"repo": "default",
|
||||
"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",
|
||||
|
|
|
@ -31,7 +31,7 @@
|
|||
pre-commit.settings.hooks = {
|
||||
rustfmt.enable = true;
|
||||
clippy.enable = true;
|
||||
cargo-check.enable = true;
|
||||
# cargo-check.enable = true;
|
||||
};
|
||||
checks = { inherit package; };
|
||||
packages.default = package;
|
||||
|
@ -55,7 +55,7 @@
|
|||
buildInputs = builtins.attrValues {
|
||||
inherit (pkgs)
|
||||
rust-analyzer
|
||||
# rustfmt
|
||||
rustfmt
|
||||
clippy
|
||||
# profiling
|
||||
cargo-flamegraph
|
||||
|
@ -65,11 +65,6 @@
|
|||
cargo-edit
|
||||
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"
|
||||
'';
|
||||
});
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
//! Language builtins, wired into the implementation.
|
||||
|
||||
use std::fmt::Write;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::evaluate::{Dictionary, Value};
|
||||
use crate::dictionary::Dictionary;
|
||||
use crate::evaluate::Value;
|
||||
use crate::syntax::Type;
|
||||
|
||||
/// Builtin functions.
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub enum Builtin {
|
||||
Print,
|
||||
|
@ -16,44 +19,59 @@ 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> {
|
||||
#[track_caller]
|
||||
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}"),
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! int_op {
|
||||
($arguments:expr, $op:tt, $op_name:expr) => {
|
||||
Value::Integer(
|
||||
$arguments
|
||||
($arguments:expr, $op:tt, $op_name:expr) => {{
|
||||
let i = $arguments
|
||||
.into_iter()
|
||||
.map(unwrap_int_value($op_name))
|
||||
.reduce(|acc, v| Ok(acc? $op v?))
|
||||
.unwrap()?,
|
||||
)
|
||||
};
|
||||
.reduce(|acc, v| acc $op v)
|
||||
.unwrap();
|
||||
Value::Integer(i)
|
||||
}};
|
||||
}
|
||||
|
||||
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)
|
||||
}};
|
||||
}
|
||||
|
||||
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 {
|
||||
/// A mapping from runtime names to builtin sentinels.
|
||||
pub fn dictionary() -> Dictionary<Value> {
|
||||
[
|
||||
("_print", Builtin::Print),
|
||||
|
@ -69,6 +87,23 @@ impl Builtin {
|
|||
.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 {
|
||||
match self {
|
||||
Builtin::Print => 0,
|
||||
|
@ -81,6 +116,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 +130,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 +164,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);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
use std::collections::HashMap;
|
||||
|
||||
use crate::syntax::Name;
|
||||
|
||||
/// Mapping of names to some T.
|
||||
pub type Dictionary<T> = HashMap<Name, T>;
|
|
@ -1,12 +1,14 @@
|
|||
use std::{collections::HashMap, fmt::Display};
|
||||
//! Tree-walk interpreter for syntax.
|
||||
|
||||
use std::fmt::Display;
|
||||
|
||||
use thiserror::Error;
|
||||
|
||||
use crate::builtins::{self, Builtin};
|
||||
use crate::builtins::Builtin;
|
||||
use crate::dictionary::Dictionary;
|
||||
use crate::syntax::{Block, Expression, InteractiveEntry, Item, Name, Program};
|
||||
|
||||
pub type Dictionary<T> = HashMap<Name, T>;
|
||||
|
||||
/// Runtime values.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Value {
|
||||
Builtin(Builtin),
|
||||
|
@ -32,6 +34,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 +45,41 @@ 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>,
|
||||
}
|
||||
|
||||
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 {
|
||||
/// 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,16 +91,17 @@ 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,
|
||||
parameters,
|
||||
body,
|
||||
..
|
||||
} => {
|
||||
let closure = Value::Closure {
|
||||
environment: self.environment.clone(),
|
||||
parameters: parameters.clone(),
|
||||
parameters: parameters.iter().map(|p| p.name.clone()).collect(),
|
||||
result: Expression::Block(Box::new(body.clone())),
|
||||
};
|
||||
self.environment.insert(name.clone(), closure.clone());
|
||||
|
@ -93,17 +110,18 @@ 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),
|
||||
InteractiveEntry::Binding(binding) => {
|
||||
self.bind(binding.name.clone(), &binding.expression)
|
||||
bind!(self, binding.name.clone(), &binding.expression)
|
||||
}
|
||||
InteractiveEntry::Expression(term) => self.evaluate(term),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn evaluate(&mut self, term: &Expression) -> Result<Value> {
|
||||
fn evaluate(&mut self, term: &Expression) -> Result<Value> {
|
||||
match term {
|
||||
Expression::Variable(name) => self
|
||||
.environment
|
||||
|
@ -111,15 +129,18 @@ impl Interpreter {
|
|||
.ok_or(Error::UnboundName(name.clone()))
|
||||
.cloned(),
|
||||
Expression::Block(b) => self.evaluate_block(b.as_ref()),
|
||||
Expression::Lambda { parameters, result } => Ok(Value::Closure {
|
||||
Expression::Lambda {
|
||||
parameters, result, ..
|
||||
} => Ok(Value::Closure {
|
||||
environment: self.environment.clone(),
|
||||
parameters: parameters.clone(),
|
||||
parameters: parameters.iter().map(|p| p.name.clone()).collect(),
|
||||
result: *result.clone(),
|
||||
}),
|
||||
Expression::Call { callee, arguments } => {
|
||||
let callee = self.evaluate(callee.as_ref())?;
|
||||
self.apply_call(&callee, arguments)
|
||||
}
|
||||
Expression::Annotation { expression, .. } => self.evaluate(expression.as_ref()),
|
||||
Expression::Boolean(b) => Ok(Value::Boolean(*b)),
|
||||
Expression::Integer(i) => Ok(Value::Integer(*i)),
|
||||
Expression::Unit => Ok(Value::Unit),
|
||||
|
@ -139,7 +160,7 @@ impl Interpreter {
|
|||
let mut nested = Interpreter::nested(environment.clone());
|
||||
for (name, argument) in parameters.iter().zip(arguments) {
|
||||
// we don't want arguments to refer to each other, so use the parent interpreter
|
||||
self.bind_nested(name.clone(), argument, &mut nested)?;
|
||||
bind!(self, name.clone(), argument, to: nested)?;
|
||||
}
|
||||
nested.evaluate(result)
|
||||
}
|
||||
|
@ -158,7 +179,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),
|
||||
}
|
||||
|
@ -167,27 +188,10 @@ impl Interpreter {
|
|||
fn evaluate_block(&mut self, block: &Block) -> Result<Value> {
|
||||
let mut nested = self.clone();
|
||||
for binding in &block.bindings {
|
||||
nested.bind(binding.name.clone(), &binding.expression)?;
|
||||
bind!(nested, binding.name.clone(), &binding.expression)?;
|
||||
}
|
||||
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 {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
pub mod builtins;
|
||||
pub mod cli;
|
||||
pub mod dictionary;
|
||||
pub mod evaluate;
|
||||
pub mod parse;
|
||||
pub mod syntax;
|
||||
pub mod typecheck;
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ use crate::syntax::{
|
|||
Item,
|
||||
InteractiveEntry,
|
||||
Name,
|
||||
Parameter,
|
||||
Program,
|
||||
Type,
|
||||
};
|
||||
|
||||
grammar;
|
||||
|
@ -18,20 +20,26 @@ pub InteractiveEntry: InteractiveEntry = {
|
|||
<Expression> ";"? => InteractiveEntry::Expression(<>),
|
||||
}
|
||||
|
||||
Item: Item = {
|
||||
"fn" <name:Name> "(" <parameters:Comma<Name>> ")" <body:Block> ";"? => Item::Fn { <> },
|
||||
Type: Type = {
|
||||
Name => Type::Constructor(<>),
|
||||
"fn" "(" <parameters:Comma<Type>> ")" "->" <result:Boxed<Type>> => Type::Function { <> },
|
||||
"(" <Type> ")",
|
||||
}
|
||||
|
||||
Item: Item = {
|
||||
"fn" <name:Name> "(" <parameters:Comma<Parameter>> ")" <return_type:("->" <Type>)?> <body:Block> ";"? => Item::Fn { <> },
|
||||
}
|
||||
|
||||
Parameter: Parameter =
|
||||
<name:Name> <annotation:(":" <Type>)?> => Parameter { <> };
|
||||
|
||||
Binding: Binding =
|
||||
"let" <name:Name> "=" <expression:Expression> => Binding { <> };
|
||||
"let" <name:Name> <annotation:(":" <Type>)?> "=" <expression:Expression> => Binding { <> };
|
||||
|
||||
Expression: Expression = {
|
||||
Simple,
|
||||
"|" <parameters:Comma<Parameter>> "|" <return_type:("->" <Type>)?> <result:Boxed<Expression>> => Expression::Lambda { <> },
|
||||
Boxed<Block> => Expression::Block(<>),
|
||||
};
|
||||
|
||||
Simple: Expression = {
|
||||
"|" <parameters:Comma<Name>> "|" <result:Boxed<Expression>> => Expression::Lambda { <> },
|
||||
<expression:Boxed<Atom>> ":" <annotation:Type> => Expression::Annotation { <> },
|
||||
Atom,
|
||||
};
|
||||
|
||||
|
@ -40,8 +48,8 @@ Atom: Expression = {
|
|||
<callee:Boxed<Atom>> "(" <arguments:Comma<Expression>> ")" => Expression::Call { <> },
|
||||
Integer => Expression::Integer(<>),
|
||||
Boolean => Expression::Boolean(<>),
|
||||
"(" <Expression> ")",
|
||||
"(" ")" => Expression::Unit,
|
||||
"(" <Expression> ")",
|
||||
};
|
||||
|
||||
BlockBinding = <Binding> ";";
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
//! Syntax elements.
|
||||
|
||||
/// Identifiers.
|
||||
/// An identifier.
|
||||
pub type Name = String;
|
||||
|
||||
/// The rough equivalent of a module.
|
||||
|
@ -14,20 +14,49 @@ pub enum InteractiveEntry {
|
|||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Item {
|
||||
Fn {
|
||||
name: Name,
|
||||
parameters: Vec<Name>,
|
||||
parameters: Vec<Parameter>,
|
||||
return_type: Option<Type>,
|
||||
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.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Binding {
|
||||
pub name: Name,
|
||||
pub annotation: Option<Type>,
|
||||
pub expression: Expression,
|
||||
}
|
||||
|
||||
|
@ -37,13 +66,18 @@ pub enum Expression {
|
|||
Variable(Name),
|
||||
Block(Box<Block>),
|
||||
Lambda {
|
||||
parameters: Vec<Name>,
|
||||
parameters: Vec<Parameter>,
|
||||
return_type: Option<Type>,
|
||||
result: Box<Expression>,
|
||||
},
|
||||
Call {
|
||||
callee: Box<Expression>,
|
||||
arguments: Vec<Expression>,
|
||||
},
|
||||
Annotation {
|
||||
expression: Box<Expression>,
|
||||
annotation: Type,
|
||||
},
|
||||
Boolean(bool),
|
||||
Integer(i64),
|
||||
Unit,
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
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(())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue