Compare commits

...

3 Commits

Author SHA1 Message Date
mat ess 4e76f1990e Start working on typechecking 2023-08-05 23:17:42 -04:00
mat ess e21f575207 Update LICENSE 2023-07-27 00:18:36 -04:00
mat ess bce8a006b0 Add more documenation, minor tweaks 2023-07-24 23:46:12 -04:00
17 changed files with 308 additions and 121 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"

22
LICENSE.txt Normal file
View File

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

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

@ -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
.into_iter()
.map(unwrap_int_value($op_name))
.reduce(|acc, v| Ok(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 {
($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)
}
}
}

View File

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

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

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

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

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