Start working on typechecking

main
mat ess 2023-08-05 23:17:42 -04:00
parent e21f575207
commit 4e76f1990e
14 changed files with 239 additions and 95 deletions

28
Cargo.lock generated
View File

@ -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",

View File

@ -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"

3
_tests/golden/parse.mul Normal file
View File

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

7
_tests/goldentests.rs Normal file
View File

@ -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()
}

View File

@ -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

View File

@ -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",

View File

@ -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"
'';
});
};
};
};

View File

@ -2,7 +2,9 @@
use std::fmt::Write;
use crate::evaluate::{Dictionary, Value};
use crate::dictionary::Dictionary;
use crate::evaluate::Value;
use crate::syntax::Type;
/// Builtin functions.
#[derive(Debug, Copy, Clone)]
@ -17,6 +19,7 @@ pub enum Builtin {
LessThan,
}
#[track_caller]
fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> i64 {
move |value| match value {
Value::Integer(i) => i,
@ -25,15 +28,14 @@ fn unwrap_int_value(op_name: &'static str) -> impl Fn(Value) -> i64 {
}
macro_rules! int_op {
($arguments:expr, $op:tt, $op_name:expr) => {
Value::Integer(
$arguments
.into_iter()
.map(unwrap_int_value($op_name))
.reduce(|acc, v| acc $op v)
.unwrap(),
)
};
($arguments:expr, $op:tt, $op_name:expr) => {{
let i = $arguments
.into_iter()
.map(unwrap_int_value($op_name))
.reduce(|acc, v| acc $op v)
.unwrap();
Value::Integer(i)
}};
}
macro_rules! int_cmp {
@ -46,6 +48,28 @@ macro_rules! int_cmp {
}};
}
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> {
@ -63,6 +87,22 @@ 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 {

6
src/dictionary.rs Normal file
View File

@ -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>;

View File

@ -1,16 +1,13 @@
//! Tree-walk interpreter for syntax.
use std::collections::HashMap;
use std::fmt::Display;
use thiserror::Error;
use crate::builtins::Builtin;
use crate::dictionary::Dictionary;
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 {
@ -58,6 +55,17 @@ 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 {
@ -89,10 +97,11 @@ impl Interpreter {
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());
@ -106,7 +115,7 @@ impl Interpreter {
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),
}
@ -120,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),
@ -148,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)
}
@ -176,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 {

View File

@ -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;

View File

@ -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> ";";

View File

@ -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,

20
src/typecheck.rs Normal file
View File

@ -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(())
}
}