From 3c31eeff6c74d3720965980f9e5859cfc68713e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jasper=20Gr=C3=A4flich?= <graeflich@cyberagentur.de> Date: Thu, 16 May 2024 10:10:37 +0200 Subject: [PATCH] Branch all WIP from Cyberagentur --- use-based-refs/interpreter/README.md | 1 + use-based-refs/interpreter/src/aliascheck.rs | 113 +++++++++++++++++++ use-based-refs/interpreter/src/eval.rs | 2 +- use-based-refs/interpreter/src/grammar.pest | 2 +- use-based-refs/interpreter/src/lib.rs | 4 + use-based-refs/interpreter/src/main.rs | 15 ++- 6 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 use-based-refs/interpreter/src/aliascheck.rs diff --git a/use-based-refs/interpreter/README.md b/use-based-refs/interpreter/README.md index 60f7666..a27c017 100644 --- a/use-based-refs/interpreter/README.md +++ b/use-based-refs/interpreter/README.md @@ -31,6 +31,7 @@ Right now, the interpreter only executes a program hardcoded in `main`. Change t - [x] Dereference -- read - [x] Dereference -- write - [ ] Builtins + - [ ] "Parallel" execution with `||` (needs Pratt parsing (?) and product types for joining) - [ ] `NoAlias` specification - [ ] Alias checking, of course - [ ] Add tests for typechecking functions/function calls diff --git a/use-based-refs/interpreter/src/aliascheck.rs b/use-based-refs/interpreter/src/aliascheck.rs new file mode 100644 index 0000000..098cf97 --- /dev/null +++ b/use-based-refs/interpreter/src/aliascheck.rs @@ -0,0 +1,113 @@ +use std::collections::HashMap; + +use crate::{Error, Node, Result}; + +#[derive(PartialEq, Eq, Debug, Clone)] +pub struct Context { + counter: i32, // Region Id counter + idents: HashMap<String, Provenance>, +} + +impl Context { + /// Create a new empty context. + fn new() -> Self { + Self { + idents: HashMap::new(), + counter: 0, + } + } + + /// Add a fresh reference to the context. + /// + /// A reference is fresh if it doesn't alias with any existing reference, + /// that is, it points directly to a value. + fn fresh(&mut self, ident: String) { + self.idents + .insert(ident, Region::Concrete(self.counter).into()); + self.counter += 1; + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +struct Provenance(Vec<Region>); + +impl From<Region> for Provenance { + fn from(value: Region) -> Self { + Self(vec![value]) + } +} + +#[derive(PartialEq, Eq, Debug, Clone)] +pub enum Region { + Concrete(i32), + Abstract(i32), +} + +fn aliascheck_ctx(ast: &Node, ctx: &mut Context) -> Result<()> { + match ast { + // No references are created or used, therefore automatic pass + Node::Num(_) | Node::Unit | Node::Ident(_) => (), + // Check sequences ... sequentially + Node::Seq(nodes) => { + for node in nodes { + aliascheck_ctx(node, ctx)?; + } + } + // A `let` is okay if its expr is okay; also we need to store `ident` + // in the context if it is a reference. + Node::Let { ident, expr, ty } => todo!(), + // We take a reference. If it is a value + Node::Ref(_) => todo!(), + Node::DerefRead(_) => todo!(), + Node::DerefWrite { reference, expr } => todo!(), + Node::Func { + ident, + args, + ty, + body, + } => todo!(), + Node::Call { ident, args } => todo!(), + }; + Ok(()) +} + +/// Aliascheck given AST using a fresh context +/// +/// # Errors +/// +/// Returns error if aliaschecking fails. +pub fn aliascheck(ast: Node) -> Result<Node> { + aliascheck_ctx(&ast, &mut Context::new()).and(Ok(ast)) +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::parser::ast; + + fn test(input: &str) { + aliascheck(ast(input).expect("Parse in test unsuccessful")) + .expect("Aliascheck in test unsuccessful"); + } + + #[test] + fn num() { + test("4") + } + + #[test] + fn unit() { + test("{}") + } + + // #[test] + // fn no_refs() { + // test("let x = 4; x") + // } + + // #[test] + // fn no_refs_fn() { + // // If there are no references, there shouldn't be a problem + // test("fn foo(x: Num) -> Num = x; foo(3)"); + // } +} diff --git a/use-based-refs/interpreter/src/eval.rs b/use-based-refs/interpreter/src/eval.rs index e44715a..9dc21e0 100644 --- a/use-based-refs/interpreter/src/eval.rs +++ b/use-based-refs/interpreter/src/eval.rs @@ -159,7 +159,7 @@ fn eval_ctx(ast: Node, ctx: &mut Context) -> Result<Value> { // Eager evaluation of the arguments let mut evals = Vec::with_capacity(args.len()); for arg in args { - evals.push(eval_ctx(arg, ctx)?) + evals.push(eval_ctx(arg, ctx)?); } // Look up function diff --git a/use-based-refs/interpreter/src/grammar.pest b/use-based-refs/interpreter/src/grammar.pest index 7f6fb51..f21ac57 100644 --- a/use-based-refs/interpreter/src/grammar.pest +++ b/use-based-refs/interpreter/src/grammar.pest @@ -20,7 +20,7 @@ Seq = { (Expr ~ ";")* ~ Expr } // I must put DerefWrite first because of shenanigans; // I don't know the reason, but it fails to parse otherwise. Expr = _{ DerefWrite | Value | TypedLet | Let - | Func | Call | Ident | Block | DerefRead } + | Func | Call | Ident | Block | DerefRead } Value = _{ Unit | Num | Ref } diff --git a/use-based-refs/interpreter/src/lib.rs b/use-based-refs/interpreter/src/lib.rs index 917c2c9..8b92fa3 100644 --- a/use-based-refs/interpreter/src/lib.rs +++ b/use-based-refs/interpreter/src/lib.rs @@ -4,10 +4,12 @@ use thiserror::Error; +mod aliascheck; mod eval; mod parser; mod typecheck; +pub use aliascheck::aliascheck; pub use eval::eval; pub use parser::ast; pub use typecheck::typecheck; @@ -56,6 +58,8 @@ pub enum Error { General(String), #[error("Name error: {0}")] Name(String), + #[error("Alias error: {0}")] + Alias(String), } pub type Result<T> = std::result::Result<T, Error>; diff --git a/use-based-refs/interpreter/src/main.rs b/use-based-refs/interpreter/src/main.rs index 0a109b5..0c62240 100644 --- a/use-based-refs/interpreter/src/main.rs +++ b/use-based-refs/interpreter/src/main.rs @@ -1,11 +1,20 @@ #![warn(clippy::pedantic)] -use interpreter::{ast, eval, typecheck}; +use interpreter::{aliascheck, ast, eval, typecheck}; + +// This should fail since `x` and `y` may alias and mutate at the same time. +// "fn danger(x: &Num, y: &Num) -> {} = {x := 1 | y := 2; x}" +// This, on the other hand, is fine since x and y are marked as not aliasing. +// "fn fine[x ^ y](x: &Num, y: &Num) -> {} = {x := 1 | y := 2}" fn main() { - let program = "fn infinite() -> Num = {infinite()}; infinite()"; + let program = "let x = 3; let y = &x; y := 4; *y"; - match ast(program).and_then(typecheck).and_then(eval) { + match ast(program) + .and_then(typecheck) + // .and_then(aliascheck) + .and_then(eval) + { Ok(val) => println!("{val}"), Err(err) => eprintln!("{err}"), } -- GitLab