Sync commit

This commit is contained in:
2022-12-08 17:52:57 +00:00
parent 3557107248
commit a500f8b87a
11 changed files with 338 additions and 275 deletions

View File

@@ -1,67 +1,79 @@
use mappable_rc::Mrc;
use crate::utils::collect_to_mrc;
use super::super::representations::typed::{Clause, Expr};
pub fn apply_lambda(body: Mrc<Expr>, arg: Mrc<Expr>) -> Mrc<Expr> {
apply_lambda_expr_rec(Mrc::clone(&body), arg, 0)
pub fn apply_lambda(id: u64, value: Mrc<Expr>, body: Mrc<Expr>) -> Mrc<Expr> {
apply_lambda_expr_rec(id, value, Mrc::clone(&body))
.unwrap_or(body)
}
fn apply_lambda_expr_rec(
item: Mrc<Expr>, arg: Mrc<Expr>, depth: usize
id: u64, value: Mrc<Expr>, expr: Mrc<Expr>
) -> Option<Mrc<Expr>> {
let Expr(clause, typ) = item.as_ref();
apply_lambda_clause_rec(clause.clone(), arg, depth)
.map(|c| Mrc::new(Expr(c, Mrc::clone(typ))))
let Expr(clause, typ) = expr.as_ref();
match clause {
Clause::Argument(arg_id) if *arg_id == id => {
let full_typ = collect_to_mrc(
value.1.iter()
.chain(typ.iter())
.cloned()
);
Some(Mrc::new(Expr(value.0.to_owned(), full_typ)))
}
cl => {
apply_lambda_clause_rec(id, value, clause.clone())
.map(|c| Mrc::new(Expr(c, Mrc::clone(typ))))
}
}
}
fn apply_lambda_clause_rec(
clause: Clause, arg: Mrc<Expr>, depth: usize
id: u64, value: Mrc<Expr>, clause: Clause
) -> Option<Clause> {
match clause {
// Only element actually manipulated
Clause::Argument(d) => {
if d == depth {Some(arg.0.clone())} // Resolve reference
// Application eliminates a layer of indirection
else if d > depth {Some(Clause::Argument(d - 1))}
else {None} // Undisturbed ancestry
}
Clause::Argument(id) => panic!(
"apply_lambda_expr_rec is supposed to eliminate this case"),
// Traverse, yield Some if either had changed.
Clause::Apply(f, x) => apply_lambda__traverse_call(arg, depth, f, x, Clause::Apply),
Clause::Explicit(f, t) => apply_lambda__traverse_call(arg, depth, f, t, Clause::Explicit),
Clause::Lambda(t, b) => apply_lambda__traverse_param(arg, depth, t, b, Clause::Lambda),
Clause::Auto(t, b) => apply_lambda__traverse_param(arg, depth, t, b, Clause::Auto),
Clause::Apply(f, x) => {
let new_f = apply_lambda_expr_rec(
id, Mrc::clone(&value), Mrc::clone(&f)
);
let new_x = apply_lambda_expr_rec(
id, value, Mrc::clone(&x)
);
match (new_f, new_x) { // Mind the shadows
(None, None) => None,
(None, Some(x)) => Some(Clause::Apply(f, x)),
(Some(f), None) => Some(Clause::Apply(f, x)),
(Some(f), Some(x)) => Some(Clause::Apply(f, x))
}
},
Clause::Lambda(own_id, t, b) => apply_lambda__traverse_param(id, value, own_id, t, b, Clause::Lambda),
Clause::Auto(own_id, t, b) => apply_lambda__traverse_param(id, value, own_id, t, b, Clause::Auto),
// Leaf nodes
Clause::Atom(_) | Clause::ExternFn(_) | Clause::Literal(_) => None
}
}
fn apply_lambda__traverse_call(
arg: Mrc<Expr>, depth: usize, f: Mrc<Expr>, x: Mrc<Expr>,
wrap: impl Fn(Mrc<Expr>, Mrc<Expr>) -> Clause
) -> Option<Clause> {
let new_f = apply_lambda_expr_rec(Mrc::clone(&f), Mrc::clone(&arg), depth);
let new_x = apply_lambda_expr_rec(Mrc::clone(&x), arg, depth);
match (new_f, new_x) {
(None, None) => None,
(None, Some(x)) => Some(wrap(f, x)),
(Some(f), None) => Some(wrap(f, x)),
(Some(f), Some(x)) => Some(wrap(f, x))
}
}
fn apply_lambda__traverse_param(
arg: Mrc<Expr>, depth: usize, t: Option<Mrc<Clause>>, b: Mrc<Expr>,
wrap: impl Fn(Option<Mrc<Clause>>, Mrc<Expr>) -> Clause
id: u64, value: Mrc<Expr>,
own_id: u64, t: Option<Mrc<Clause>>, b: Mrc<Expr>,
wrap: impl Fn(u64, Option<Mrc<Clause>>, Mrc<Expr>) -> Clause
) -> Option<Clause> {
let new_t = t.as_ref().and_then(|t| {
apply_lambda_clause_rec(t.as_ref().clone(), Mrc::clone(&arg), depth)
});
let new_b = apply_lambda_expr_rec(Mrc::clone(&b), arg, depth + 1);
match (new_t, new_b) {
let new_t = t.and_then(|t| apply_lambda_clause_rec(
id, Mrc::clone(&value), t.as_ref().clone()
));
// Respect shadowing
let new_b = if own_id == id {None} else {
apply_lambda_expr_rec(id, value, Mrc::clone(&b))
};
match (new_t, new_b) { // Mind the shadows
(None, None) => None,
(None, Some(b)) => Some(Clause::Lambda(t, b)),
(Some(t), None) => Some(Clause::Lambda(Some(Mrc::new(t)), b)),
(Some(t), Some(b)) => Some(Clause::Lambda(Some(Mrc::new(t)), b))
(None, Some(b)) => Some(wrap(own_id, t, b)),
(Some(t), None) => Some(wrap(own_id, Some(Mrc::new(t)), b)),
(Some(t), Some(b)) => Some(wrap(own_id, Some(Mrc::new(t)), b))
}
}

View File

@@ -1,33 +1,51 @@
use std::hash::{Hasher, Hash};
use itertools::Itertools;
use crate::utils::ProtoMap;
use super::super::representations::typed::{Clause, Expr};
use super::super::utils::Stackframe;
const PARAMETRICS_INLINE_COUNT:usize = 5;
type Parametrics<'a> = ProtoMap<'a, u64, bool, PARAMETRICS_INLINE_COUNT>;
/// Hash the parts of an expression that are required to be equal for syntactic equality.
pub fn partial_hash_rec<H: Hasher>(Expr(clause, _): &Expr, state: &mut H, is_auto: Stackframe<bool>) {
pub fn partial_hash_rec<H: Hasher>(
Expr(clause, _): &Expr, state: &mut H,
mut parametrics: Parametrics
) {
match clause {
// Skip autos and explicits
Clause::Auto(_, body) => partial_hash_rec(body, state, is_auto.push(true)),
Clause::Explicit(f, _) => partial_hash_rec(f, state, is_auto),
// Skip autos
Clause::Auto(id, _, body) => {
parametrics.set(id, true);
partial_hash_rec(body, state, parametrics)
}
// Annotate everything else with a prefix
// - Recurse into the tree of lambdas and calls - classic lambda calc
Clause::Lambda(_, body) => {
Clause::Lambda(id, _, body) => {
state.write_u8(0);
partial_hash_rec(body, state, is_auto.push(false))
parametrics.set(id, false);
partial_hash_rec(body, state, parametrics)
}
Clause::Apply(f, x) => {
state.write_u8(1);
partial_hash_rec(f, state, is_auto);
partial_hash_rec(x, state, is_auto);
partial_hash_rec(f, state, parametrics.clone());
partial_hash_rec(x, state, parametrics);
}
// - Only recognize the depth of an argument if it refers to a non-auto parameter
Clause::Argument(depth) => {
Clause::Argument(own_id) => {
let (pos, is_auto) = parametrics.iter()
.filter_map(|(id, is_auto)| is_auto.map(|is_auto| (*id, is_auto)))
.find_position(|(id, is_auto)| id == own_id)
.map(|(pos, (_, is_auto))| (pos, is_auto))
.unwrap_or((usize::MAX, false));
// If the argument references an auto, acknowledge its existence
if *is_auto.iter().nth(*depth).unwrap_or(&false) {
if is_auto {
state.write_u8(2)
} else {
state.write_u8(3);
state.write_usize(*depth)
state.write_usize(pos)
}
}
// - Hash leaves like normal

View File

@@ -10,48 +10,38 @@ use super::super::representations::typed::{Clause, Expr};
/// Call the function with the first Expression that isn't an Auto,
/// wrap all elements in the returned iterator back in the original sequence of Autos.
pub fn skip_autos<'a,
F: 'a + FnOnce(Mrc<Expr>, usize) -> I,
F: 'a + FnOnce(Mrc<Expr>) -> I,
I: Iterator<Item = Mrc<Expr>> + 'static
>(
depth: usize, expr: Mrc<Expr>, function: F
expr: Mrc<Expr>, function: F
) -> BoxedIter<'static, Mrc<Expr>> {
match expr.as_ref() {
Expr(Clause::Auto(arg, body), typ) => {
return Box::new(skip_autos(depth + 1, Mrc::clone(body), function).map({
let arg = arg.as_ref().map(Mrc::clone);
let typ = Mrc::clone(typ);
move |body| {
Mrc::new(Expr(Clause::Auto(
arg.as_ref().map(Mrc::clone),
body
), Mrc::clone(&typ)))
}
})) as BoxedIter<'static, Mrc<Expr>>
}
Expr(Clause::Explicit(expr, targ), typ) => {
return Box::new(skip_autos(depth, Mrc::clone(expr), function).map({
let (targ, typ) = (Mrc::clone(targ), Mrc::clone(typ));
move |expr| {
Mrc::new(Expr(Clause::Explicit(expr, Mrc::clone(&targ)), Mrc::clone(&typ)))
}
})) as BoxedIter<'static, Mrc<Expr>>
}
_ => ()
if let Expr(Clause::Auto(id, arg, body), typ) = expr.as_ref() {
return Box::new(skip_autos(Mrc::clone(body), function).map({
let arg = arg.as_ref().map(Mrc::clone);
let typ = Mrc::clone(typ);
move |body| {
Mrc::new(Expr(Clause::Auto(
*id,
arg.as_ref().map(Mrc::clone),
body
), Mrc::clone(&typ)))
}
})) as BoxedIter<'static, Mrc<Expr>>
}
Box::new(function(expr, depth))
Box::new(function(expr))
}
/// Produces an iterator of every expression that can be produced from this one through B-reduction.
fn direct_reductions(ex: Mrc<Expr>) -> impl Iterator<Item = Mrc<Expr>> {
skip_autos(0, ex, |mexpr, _| {
skip_autos(ex, |mexpr| {
let Expr(clause, typ_ref) = mexpr.as_ref();
match clause {
Clause::Apply(f, x) => box_chain!(
skip_autos(0, Mrc::clone(f), |mexpr, _| {
skip_autos(Mrc::clone(f), |mexpr| {
let Expr(f, _) = mexpr.as_ref();
match f {
Clause::Lambda(_, body) => box_once(
apply_lambda(Mrc::clone(body), Mrc::clone(x))
Clause::Lambda(id, _, body) => box_once(
apply_lambda(*id, Mrc::clone(x), Mrc::clone(body))
),
Clause::ExternFn(xfn) => {
let Expr(xval, xtyp) = x.as_ref();
@@ -63,8 +53,7 @@ fn direct_reductions(ex: Mrc<Expr>) -> impl Iterator<Item = Mrc<Expr>> {
Clause::Atom(..) | Clause::Argument(..) | Clause::Apply(..) => box_empty(),
Clause::Literal(lit) =>
panic!("Literal expression {lit:?} can't be applied as function"),
Clause::Auto(..) | Clause::Explicit(..) =>
unreachable!("skip_autos should have filtered these"),
Clause::Auto(..) => unreachable!("skip_autos should have filtered this"),
}
}),
direct_reductions(Mrc::clone(f)).map({
@@ -84,18 +73,24 @@ fn direct_reductions(ex: Mrc<Expr>) -> impl Iterator<Item = Mrc<Expr>> {
), Mrc::clone(&typ)))
})
),
Clause::Lambda(argt, body) => Box::new(direct_reductions(Mrc::clone(body)).map({
Clause::Lambda(id, argt, body) => {
let id = *id;
let typ = Mrc::clone(typ_ref);
let argt = argt.as_ref().map(Mrc::clone);
move |body| Mrc::new(Expr(Clause::Lambda(
argt.as_ref().map(Mrc::clone),
body
), Mrc::clone(&typ)))
})),
let body = Mrc::clone(body);
let body_reductions = direct_reductions(body)
.map(move |body| {
let argt = argt.as_ref().map(Mrc::clone);
Mrc::new(Expr(
Clause::Lambda(id, argt, body),
Mrc::clone(&typ)
))
});
Box::new(body_reductions)
},
Clause::Literal(..) | Clause::ExternFn(..) | Clause::Atom(..) | Clause::Argument(..) =>
box_empty(),
Clause::Auto(..) | Clause::Explicit(..) =>
unreachable!("skip_autos should have filtered these"),
Clause::Auto(..) => unreachable!("skip_autos should have filtered this"),
}
})
}

View File

@@ -1,110 +1,85 @@
use std::collections::HashMap;
use std::hash::{Hasher, Hash};
use std::iter;
use mappable_rc::Mrc;
use crate::utils::ProtoMap;
use crate::utils::{ProtoMap, Side};
use super::super::representations::typed::{Clause, Expr};
use super::super::utils::Stackframe;
pub fn swap<T, U>((t, u): (T, U)) -> (U, T) { (u, t) }
// @ @ (0, (foo 1)) ~ @ (0, 0)
// TODO:
// - get rid of leftovers from Explicit
// - adapt to new index-based system
// =@= =&= =%= =#= =$= =?= =!= =/=
// <@> <&> <%> <#> <$> <?> <!> </>
// |@| |&| |%| |#| |$| |?| |!| |/|
// {@} {&} {%} {#} {$} {?} {!} {/}
// (@) (&) (%) (#) ($) (?) (!) (/)
// [@] [&] [%] [#] [$] [?] [!] [/]
/// The context associates a given variable (by absolute index) on a given side to
/// an expression on the opposite side rooted at the specified depth.
/// The root depths are used to translate betwee de Brujin arguments and absolute indices.
struct Context(HashMap<u64, Mrc<Expr>>);
impl Context {
fn set(&mut self, id: u64, value: Mrc<Expr>) {
// If already defined, then it must be an argument
if let Some(value) = self.0.get(&id) {
if let Clause::Argument(opposite_up) ex.0
}
}
}
const IS_AUTO_INLINE:usize = 5;
// All data to be forwarded during recursion about one half of a unification task
#[derive(Clone)]
struct UnifHalfTask<'a> {
/// The expression to be unified
expr: &'a Expr,
/// Auto parameters with their values from the opposite side
ctx: &'a ProtoMap<'a, usize, Mrc<Expr>>,
/// Stores whether a given relative upreference is auto or lambda
is_auto: Option<Stackframe<'a, bool>>,
/// Metastack of explicit arguments not yet resolved. An explicit will always exactly pair with
/// the first auto below it. Disjoint autos always bubble with a left-to-right precedence.
explicits: Option<Stackframe<'a, Mrc<Expr>>>
/// Stores whether a given uid is auto or lambda
is_auto: ProtoMap<'a, usize, bool, IS_AUTO_INLINE>
}
impl<'a> UnifHalfTask<'a> {
fn push_auto(&self, body: &Expr) -> (Self, bool) {
if let Some(Stackframe{ prev, .. }) = self.explicits {(
Self{
expr: body,
is_auto: Stackframe::opush(&self.is_auto, false),
explicits: prev.cloned(),
..*self
},
true
)} else {(
Self{
expr: body,
is_auto: Stackframe::opush(&self.is_auto, true),
..*self
},
false
)}
fn push_auto(&mut self, body: &Expr, key: usize) {
self.expr = body;
self.is_auto.set(&key, true);
}
fn push_lambda(&self, body: &Expr) -> Self {Self{
expr: body,
is_auto: Stackframe::opush(&self.is_auto, false),
..*self
}}
fn push_explicit(&self, subexpr: &Expr, arg: Mrc<Expr>) -> Self {Self{
expr: subexpr,
explicits: Stackframe::opush(&self.explicits, arg),
..*self
}}
fn push_expr(&self, f: &Expr) -> Self {Self{
expr: f,
..*self
}}
}
#[derive(Default)]
struct UnifResult {
/// Collected identities for the given side
context: HashMap<usize, Mrc<Expr>>,
/// Number of explicits to be eliminated from task before forwarding to the next branch
usedExplicits: usize,
}
impl UnifResult {
fn useExplicit(self) -> Self{Self{
usedExplicits: self.usedExplicits + 1,
context: self.context.clone()
}}
fn dropUsedExplicits(&mut self, task: &mut UnifHalfTask) {
task.explicits = task.explicits.map(|s| {
s.pop(self.usedExplicits).expect("More explicits used than provided")
}).cloned();
self.usedExplicits = 0;
fn push_lambda(&mut self, body: &Expr, key: usize) {
self.expr = body;
self.is_auto.set(&key, false);
}
}
type Ctx = HashMap<usize, Mrc<Expr>>;
/// Ascertain syntactic equality. Syntactic equality means that
/// - lambda elements are verbatim equal
/// - auto constraints are pairwise syntactically equal after sorting
///
/// Context associates variables with subtrees resolved on the opposite side
pub fn unify_syntax_rec( // the stacks store true for autos, false for lambdas
ctx: &mut HashMap<(Side, usize), (usize, Mrc<Expr>)>,
ltask@UnifHalfTask{ expr: lexpr@Expr(lclause, _), .. }: UnifHalfTask,
rtask@UnifHalfTask{ expr: rexpr@Expr(rclause, _), .. }: UnifHalfTask
) -> Option<(UnifResult, UnifResult)> {
// Ensure that ex1 is a value-level construct
match lclause {
Clause::Auto(_, body) => {
Clause::Auto(id, _, body) => {
let res = unify_syntax_rec(ltask.push_auto(body).0, rtask);
return if ltask.explicits.is_some() {
res.map(|(r1, r2)| (r1.useExplicit(), r2))
} else {res}
}
Clause::Explicit(subexpr, arg) => {
let new_ltask = ltask.push_explicit(subexpr, Mrc::clone(arg));
return unify_syntax_rec(new_ltask, rtask)
}
_ => ()
};
// Reduce ex2's auto handling to ex1's. In the optimizer we trust