Merge pull request #427 from developedby/experimental

feat: Add kdl term linearization and numeric ops compilation
This commit is contained in:
Felipe G 2022-11-28 11:08:59 -03:00 committed by GitHub
commit d7963982a0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 593 additions and 110 deletions

View File

@ -99,10 +99,7 @@ fn range_to_num(range: Range) -> Box<Term> {
fn set_origin(ident: &Ident) -> Box<Term> {
mk_lifted_ctr(
"Kind.Term.set_origin".to_owned(),
vec![
range_to_num(ident.range),
mk_var(ident.to_str()),
],
vec![range_to_num(ident.range), mk_var(ident.to_str())],
)
}
@ -151,10 +148,16 @@ fn codegen_all_expr(
) -> Box<Term> {
use kind_tree::desugared::ExprKind::*;
match &expr.data {
Typ => mk_lifted_ctr(eval_ctr(quote, TermTag::Typ), vec![range_to_num(expr.range)]),
Typ => mk_lifted_ctr(
eval_ctr(quote, TermTag::Typ),
vec![range_to_num(expr.range)],
),
NumType {
typ: kind_tree::NumType::U60,
} => mk_lifted_ctr(eval_ctr(quote, TermTag::U60), vec![range_to_num(expr.range)]),
} => mk_lifted_ctr(
eval_ctr(quote, TermTag::U60),
vec![range_to_num(expr.range)],
),
NumType {
typ: kind_tree::NumType::U120,
} => mk_lifted_ctr(
@ -325,7 +328,10 @@ fn codegen_all_expr(
vec![range_to_num(expr.range), mk_u60(*num)],
),
Str { val } => codegen_all_expr(lhs_rule, lhs, num, quote, &desugar_str(val, expr.range)),
Hlp(_) => mk_lifted_ctr(eval_ctr(quote, TermTag::Hlp), vec![range_to_num(expr.range)]),
Hlp(_) => mk_lifted_ctr(
eval_ctr(quote, TermTag::Hlp),
vec![range_to_num(expr.range)],
),
Err => panic!("Internal Error: Was not expecting an ERR node inside the HVM checker"),
}
}

View File

@ -2,15 +2,12 @@ use checker::eval;
use errors::DriverError;
use fxhash::FxHashSet;
use kind_pass::{desugar, erasure, expand};
use kind_report::{data::Diagnostic, report::FileCache};
use kind_report::report::FileCache;
use kind_span::SyntaxCtxIndex;
use kind_tree::{backend, concrete, desugared, untyped};
use session::Session;
use std::{
path::{Path, PathBuf},
sync::mpsc::Sender,
};
use std::path::PathBuf;
use kind_checker as checker;

View File

@ -23,12 +23,7 @@ impl<'a> DesugarState<'a> {
literal: &expr::Literal,
) -> Box<desugared::Expr> {
match literal {
Literal::Number(kind_tree::Number::U120(num)) => {
if !self.check_implementation("U120.new", range, Sugar::U120) {
return desugared::Expr::err(range);
}
desugared::Expr::num120(range, *num)
}
Literal::Number(kind_tree::Number::U120(num)) => desugared::Expr::num120(range, *num),
Literal::String(string) => {
if !self.check_implementation("String.cons", range, Sugar::String)
|| !self.check_implementation("String.nil", range, Sugar::String)

View File

@ -511,7 +511,12 @@ impl<'a> ErasureState<'a> {
Box::new(untyped::Entry {
name: entry.name.clone(),
args: entry.args.iter().filter(|x| !x.erased).map(|x| (x.name.to_string(), x.range, false)).collect(),
args: entry
.args
.iter()
.filter(|x| !x.erased)
.map(|x| (x.name.to_string(), x.range, false))
.collect(),
rules,
attrs: entry.attrs.clone(),
range: entry.range,

View File

@ -9,7 +9,6 @@ pub enum Sugar {
Pair,
BoolIf,
String,
U120,
Match(String),
Open(String),
}
@ -220,7 +219,6 @@ impl Diagnostic for PassError {
Sugar::Pair => "You must implement 'Sigma' and 'Sigma.new' in order to use the sigma notation.".to_string(),
Sugar::BoolIf => "You must implement 'Bool.if' in order to use the if notation.".to_string(),
Sugar::String => "You must implement 'String.cons' in order to use the string notation.".to_string(),
Sugar::U120 => "You must implement 'U120.new' in order to use the u120 notation.".to_string(),
Sugar::Match(name) => format!("You must implement '{}.match' in order to use the match notation (or derive match with #derive[match]).", name),
Sugar::Open(name) => format!("You must implement '{}.open' in order to use the open notation (or derive open with #derive[open]).", name),
}],

View File

@ -17,6 +17,7 @@ pub fn compile_book(book: untyped::Book) -> File {
}
pub fn compile_term(expr: &untyped::Expr) -> Box<Term> {
use kind_tree::Number;
use untyped::ExprKind::*;
match &expr.data {
Var { name } => Box::new(Term::Var {
@ -41,10 +42,14 @@ pub fn compile_term(expr: &untyped::Expr) -> Box<Term> {
expr: compile_term(val),
body: compile_term(next),
}),
Num { num: kind_tree::Number::U60(numb) } => Box::new(Term::U6O {
Num {
num: Number::U60(numb),
} => Box::new(Term::U6O {
numb: u60::new(*numb),
}),
Num { num: kind_tree::Number::U120(numb) } => {
Num {
num: Number::U120(numb),
} => {
let hi = Box::new(Term::U6O {
numb: u60::new((numb >> 60) as u64),
});

View File

@ -11,11 +11,13 @@ pub use kindelia_lang::ast as kdl;
use crate::errors::KdlError;
pub const KDL_NAME_LEN: usize = 12;
const U60_MAX: kdl::U120 = kdl::U120(0xFFFFFFFFFFFFFFF);
#[derive(Debug)]
pub struct File {
funs: LinkedHashMap<String, kdl::Statement>,
runs: Vec<kdl::Statement>,
pub ctrs: LinkedHashMap<String, kdl::Statement>,
pub funs: LinkedHashMap<String, kdl::Statement>,
pub runs: Vec<kdl::Statement>,
}
pub struct CompileCtx<'a> {
@ -32,6 +34,7 @@ impl<'a> CompileCtx<'a> {
pub fn new(book: &'a untyped::Book, sender: Sender<Box<dyn Diagnostic>>) -> CompileCtx<'a> {
CompileCtx {
file: File {
ctrs: Default::default(),
funs: Default::default(),
runs: Default::default(),
},
@ -130,7 +133,7 @@ pub fn compile_rule(ctx: &mut CompileCtx, rule: &untyped::Rule) -> kindelia_lang
let arg = compile_expr(ctx, pat);
args.push(arg);
}
let lhs = kdl::Term::fun(name, args);
let lhs = kdl::Term::ctr(name, args);
let rhs = compile_expr(ctx, &rule.body);
let rule = kdl::Rule { lhs, rhs };
rule
@ -143,51 +146,178 @@ pub fn err_term() -> kindelia_lang::ast::Term {
}
pub fn compile_expr(ctx: &mut CompileCtx, expr: &untyped::Expr) -> kindelia_lang::ast::Term {
use crate::untyped::ExprKind::*;
use kdl::Term as T;
use crate::untyped::ExprKind as From;
use kdl::Term as To;
match &expr.data {
App { fun, args } => {
From::App { fun, args } => {
let mut expr = compile_expr(ctx, fun);
for binding in args {
let body = compile_expr(ctx, &binding);
expr = T::App {
expr = To::App {
func: Box::new(expr),
argm: Box::new(body),
};
}
expr
}
Binary { op, left, right } => {
// TODO: Special compilation for U60 ops
From::Binary { op, left, right } => {
use kind_tree::Operator as Op;
let oper = compile_oper(op);
let val0 = Box::new(compile_expr(ctx, left));
let val1 = Box::new(compile_expr(ctx, right));
T::Op2 { oper, val0, val1 }
match op {
// These operations occupy more bits on overflow
// So we truncate them
Op::Add | Op::Sub | Op::Mul => {
let val0 = Box::new(compile_expr(ctx, left));
let val1 = Box::new(compile_expr(ctx, right));
let expr = Box::new(To::Op2 { oper, val0, val1 });
let trunc = Box::new(To::Num { numb: U60_MAX });
To::Op2 {
oper: kdl::Oper::And,
val0: expr,
val1: trunc,
}
}
// These operations need to wrap around every 60 bits
// Eg: (<< n 60) = n
Op::Shl | Op::Shr => {
let val0 = Box::new(compile_expr(ctx, left));
let right = Box::new(compile_expr(ctx, right));
let sixty = Box::new(To::Num {
numb: kdl::U120(60),
});
let val1 = Box::new(To::Op2 {
oper: kdl::Oper::Mod,
val0: right,
val1: sixty,
});
To::Op2 { oper, val0, val1 }
}
// Other operations don't overflow
// Div, Mod, And, Or, Xor, Eql, Neq, Gtn, Gte, Ltn, Lte
_ => {
let val0 = Box::new(compile_expr(ctx, left));
let val1 = Box::new(compile_expr(ctx, right));
To::Op2 { oper, val0, val1 }
}
}
}
Ctr { name, args } => {
From::Ctr { name, args } => {
match name.to_str() {
// Special compilation for some numeric functions
// They have no rules because they're compilation defined,
// so they've been initially interpreted as Ctr
// Add with no boundary check is just a normal add
"U60.add_unsafe" => To::Op2 {
oper: kdl::Oper::Add,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
// U60s are already stored in 120 bits
"U60.to_u120" => compile_expr(ctx, &args[0]),
// Truncate to 60 bits
"U120.to_u60" => To::Op2 {
oper: kdl::Oper::And,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(To::Num { numb: U60_MAX }),
},
// Compilation for U120 numeric operations
"U120.add" => To::Op2 {
oper: kdl::Oper::Add,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.sub" => To::Op2 {
oper: kdl::Oper::Add,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.mul" => To::Op2 {
oper: kdl::Oper::Mul,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.div" => To::Op2 {
oper: kdl::Oper::Div,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.mod" => To::Op2 {
oper: kdl::Oper::Mod,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.num_equal" => To::Op2 {
oper: kdl::Oper::Eql,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.num_not_equal" => To::Op2 {
oper: kdl::Oper::Neq,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.shift_left" => To::Op2 {
oper: kdl::Oper::Shl,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.shift_right" => To::Op2 {
oper: kdl::Oper::Shr,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.num_less_than" => To::Op2 {
oper: kdl::Oper::Ltn,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.num_less_equal" => To::Op2 {
oper: kdl::Oper::Lte,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.num_greater_than" => To::Op2 {
oper: kdl::Oper::Gtn,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.num_greater_equal" => To::Op2 {
oper: kdl::Oper::Gte,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.bitwise_and" => To::Op2 {
oper: kdl::Oper::And,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.bitwise_or" => To::Op2 {
oper: kdl::Oper::Or,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
"U120.bitwise_xor" => To::Op2 {
oper: kdl::Oper::Xor,
val0: Box::new(compile_expr(ctx, &args[0])),
val1: Box::new(compile_expr(ctx, &args[1])),
},
// All other constructors have a normal compilation
_ => {
let name = ctx.kdl_names.get(name.to_str()).unwrap().clone();
let args = args.iter().map(|x| compile_expr(ctx, &x)).collect();
To::Ctr { name, args }
}
}
}
From::Fun { name, args } => {
let name = ctx.kdl_names.get(name.to_str()).unwrap().clone();
let mut new_args = Vec::new();
for arg in args {
new_args.push(compile_expr(ctx, &arg));
}
T::Ctr {
name,
args: new_args,
}
let args = args.iter().map(|x| compile_expr(ctx, x)).collect();
To::Fun { name, args }
}
Fun { name, args } => {
// TODO: Special compilation for U60 and U120 ops
let name = ctx.kdl_names.get(name.to_str()).unwrap().clone();
let mut new_args = Vec::new();
for arg in args {
new_args.push(compile_expr(ctx, &arg));
}
T::Fun {
name,
args: new_args,
}
}
Lambda {
From::Lambda {
param,
body,
erased: _,
@ -195,44 +325,44 @@ pub fn compile_expr(ctx: &mut CompileCtx, expr: &untyped::Expr) -> kindelia_lang
let name = kdl::Name::from_str(param.to_str());
if let Ok(name) = name {
let body = Box::new(compile_expr(ctx, &body));
T::Lam { name, body }
To::Lam { name, body }
} else {
ctx.send_err(Box::new(KdlError::InvalidVarName(param.range)));
err_term()
}
}
Let { name, val, next } => {
From::Let { name, val, next } => {
let res_name = kdl::Name::from_str(name.to_str());
if let Ok(name) = res_name {
let expr = Box::new(compile_expr(ctx, &val));
let func = Box::new(T::Lam { name, body: expr });
let func = Box::new(To::Lam { name, body: expr });
let argm = Box::new(compile_expr(ctx, next));
T::App { func, argm }
To::App { func, argm }
} else {
ctx.send_err(Box::new(KdlError::InvalidVarName(name.range)));
err_term()
}
}
Num {
From::Num {
num: Number::U60(numb),
} => T::Num {
} => To::Num {
numb: kdl::U120(*numb as u128),
},
Num {
From::Num {
num: Number::U120(numb),
} => T::Num {
} => To::Num {
numb: kdl::U120(*numb),
},
Var { name } => {
From::Var { name } => {
let res_name = kdl::Name::from_str(name.to_str());
if let Ok(name) = res_name {
T::Var { name }
To::Var { name }
} else {
ctx.send_err(Box::new(KdlError::InvalidVarName(name.range)));
err_term()
}
}
Str { val } => {
From::Str { val } => {
let nil = kdl::Term::Ctr {
name: ctx.kdl_names.get("String.nil").unwrap().clone(),
args: vec![],
@ -252,7 +382,7 @@ pub fn compile_expr(ctx: &mut CompileCtx, expr: &untyped::Expr) -> kindelia_lang
val.chars().rfold(nil, |rest, chr| cons(chr as u128, rest))
}
Err => unreachable!("Should not have errors inside generation"),
From::Err => unreachable!("Should not have errors inside generation"),
}
}
@ -285,13 +415,15 @@ pub fn compile_entry(ctx: &mut CompileCtx, entry: &untyped::Entry) {
}
if entry.rules.len() == 0 {
// Functions with no rules become Ctr
let sttm = kdl::Statement::Ctr {
name,
args,
sign: None,
};
ctx.file.funs.insert(entry.name.to_string(), sttm);
ctx.file.ctrs.insert(entry.name.to_string(), sttm);
} else {
// Functions with rules become Fun
let rules = entry
.rules
.iter()
@ -345,24 +477,24 @@ impl Display for File {
}
fn compile_oper(oper: &kind_tree::Operator) -> kdl::Oper {
use kdl::Oper as T;
use kind_tree::Operator as F;
use kdl::Oper as To;
use kind_tree::Operator as From;
match oper {
F::Add => T::Add,
F::Sub => T::Sub,
F::Mul => T::Mul,
F::Div => T::Div,
F::Mod => T::Mod,
F::Shl => T::Shl,
F::Shr => T::Shr,
F::Eql => T::Eql,
F::Neq => T::Neq,
F::Ltn => T::Ltn,
F::Lte => T::Lte,
F::Gte => T::Gte,
F::Gtn => T::Gtn,
F::And => T::And,
F::Xor => T::Xor,
F::Or => T::Or,
From::Add => To::Add,
From::Sub => To::Sub,
From::Mul => To::Mul,
From::Div => To::Div,
From::Mod => To::Mod,
From::Shl => To::Shl,
From::Shr => To::Shr,
From::Eql => To::Eql,
From::Neq => To::Neq,
From::Ltn => To::Ltn,
From::Lte => To::Lte,
From::Gte => To::Gte,
From::Gtn => To::Gtn,
From::And => To::And,
From::Xor => To::Xor,
From::Or => To::Or,
}
}

View File

@ -1,4 +1,4 @@
use kind_report::data::{Diagnostic, DiagnosticFrame, Severity, Marker, Color};
use kind_report::data::{Color, Diagnostic, DiagnosticFrame, Marker, Severity};
use kind_span::Range;
pub enum KdlError {
@ -15,7 +15,6 @@ impl Diagnostic for KdlError {
KdlError::ShouldNotHaveArguments(range) => Some(range.ctx),
KdlError::ShouldHaveOnlyOneRule(range) => Some(range.ctx),
KdlError::NoInitEntry(range) => Some(range.ctx),
}
}
@ -79,4 +78,4 @@ impl Diagnostic for KdlError {
},
}
}
}
}

View File

@ -1,7 +1,7 @@
use fxhash::{FxHashMap, FxHashSet};
use kind_span::Range;
use kind_tree::symbol::{Ident, QualifiedIdent};
use kind_tree::untyped::{self, Entry, Expr, ExprKind, Rule, Book};
use kind_tree::untyped::{self, Book, Entry, Expr, ExprKind, Rule};
use linked_hash_map::LinkedHashMap;
use crate::subst::subst;
@ -94,7 +94,9 @@ fn split_rule(
Expr::var(name)
}
ExprKind::Var { .. } => field.clone(),
_ => panic!("Internal Error: Cannot use this kind of expression during flattening"),
_ => panic!(
"Internal Error: Cannot use this kind of expression during flattening"
),
};
new_pat_args.push(arg.clone());
old_rule_body_args.push(arg);
@ -170,7 +172,9 @@ fn split_rule(
assert!(!new_entry_rules.is_empty());
let new_entry_args = (0..new_entry_rules[0].pats.len()).map(|n| (format!("x{}", n), Range::ghost_range(), false)).collect();
let new_entry_args = (0..new_entry_rules[0].pats.len())
.map(|n| (format!("x{}", n), Range::ghost_range(), false))
.collect();
let new_entry = Entry {
name: new_entry_name,
@ -186,7 +190,7 @@ fn split_rule(
fn flatten_entry(entry: &Entry) -> Vec<Entry> {
let mut name_count = 0;
let mut skip: FxHashSet<usize> = FxHashSet::default();
let mut new_entries: Vec<Entry> = Vec::new();
let mut old_entry_rules: Vec<Rule> = Vec::new();
@ -195,7 +199,8 @@ fn flatten_entry(entry: &Entry) -> Vec<Entry> {
if !skip.contains(&i) {
let rule = &entry.rules[i];
if must_split(rule) {
let (old_rule, split_entries) = split_rule(rule, &entry, i, &mut name_count, &mut skip);
let (old_rule, split_entries) =
split_rule(rule, &entry, i, &mut name_count, &mut skip);
old_entry_rules.push(old_rule);
new_entries.extend(split_entries);
} else {
@ -216,7 +221,7 @@ fn flatten_entry(entry: &Entry) -> Vec<Entry> {
new_entries
}
pub fn flatten(book: untyped::Book) -> untyped::Book {
pub fn flatten(book: untyped::Book) -> untyped::Book {
let mut book = book;
let mut names = FxHashMap::default();
let mut entrs = LinkedHashMap::default();
@ -229,7 +234,9 @@ pub fn flatten(book: untyped::Book) -> untyped::Book {
}
}
let book = Book { names, entrs, holes: book.holes };
book
Book {
names,
entrs,
holes: book.holes,
}
}

View File

@ -2,16 +2,27 @@ use std::sync::mpsc::Sender;
use flatten::flatten;
use kind_report::data::Diagnostic;
use kind_tree::{untyped};
use kind_tree::untyped;
pub use compile::File;
mod compile;
mod flatten;
mod subst;
mod errors;
mod flatten;
mod linearize;
mod subst;
pub fn compile_book(book: untyped::Book, sender: Sender<Box<dyn Diagnostic>>, namespace: &str) -> Option<compile::File> {
pub fn compile_book(
book: untyped::Book,
sender: Sender<Box<dyn Diagnostic>>,
namespace: &str,
) -> Option<compile::File> {
// TODO: Inlining
// TODO: Remove kdl_states (maybe check if they're ever called?)
// TODO: Don't erase kdl_state functions
// TODO: Convert to some sort of Kindelia.Contract
let flattened = flatten(book);
compile::compile_book(&flattened, sender, namespace)
let file = compile::compile_book(&flattened, sender, namespace)?;
let file = linearize::linearize_file(file);
Some(file)
}

View File

@ -0,0 +1,329 @@
// This modules makes all variable usages linear and with a unique name. That has the following effect:
// - All variables are renamed to have a global unique name.
// - All variables are linearized.
// - If they're used more than once, dups are inserted.
// - If they're used once, nothing changes.
// - If they're never used, their name is changed to "*"
// Example:
// - sanitizing: `(Foo a b) = (+ a a)`
// - results in: `(Foo x0 *) = dup x0.0 x0.1 = x0; (+ x0.0 x0.1)`
// The algorithm was copied from the hvm
// TODO: This is inserting unneeded `let`s for all linear rule variables
use crate::File;
use fxhash::FxHashMap;
use kindelia_lang::ast::{Func, Name, Rule, Statement, Term};
use linked_hash_map::LinkedHashMap;
pub struct LinearizeCtx {
uses: FxHashMap<Name, u64>,
name_table: LinkedHashMap<Name, Name>,
name_count: u64,
}
impl LinearizeCtx {
fn create_name(&mut self) -> Name {
let name = Name::from_str(&format!("x{}", self.name_count)).unwrap();
self.name_count += 1;
name
}
fn new() -> Self {
LinearizeCtx {
uses: Default::default(),
name_table: Default::default(),
name_count: 0,
}
}
// Pass through the lhs of the function generating new names
// for every variable found in the style described before with
// the fresh function. Also checks if rule's left side is valid.
fn create_param_names(&mut self, rule: &Rule) {
if let Term::Ctr { name: _, args } = &rule.lhs {
for arg in args {
match arg {
Term::Var { name } => {
let new_name = self.create_name();
self.name_table.insert(name.clone(), new_name);
}
Term::Ctr { name: _, args } => {
for arg in args {
if let Term::Var { name } = arg {
let new_name = self.create_name();
self.name_table.insert(name.clone(), new_name);
} else {
unreachable!(); // We expect a flat rule
}
}
}
Term::Num { .. } => (),
_ => unreachable!(
"Invalid left-hand side parameter. Expected Var, Ctr or Num, got {:?}",
arg
),
}
}
} else {
unreachable!(
"Invalid left-hand side term. Expected Ctr, got {:?}",
rule.lhs
);
}
}
}
pub fn linearize_file(file: File) -> File {
let mut runs = Vec::new();
for stmt in file.runs {
if let Statement::Run { expr, sign: _ } = stmt {
let expr = linearize_term_independent(&expr);
let stmt = Statement::Run {
expr: *expr,
sign: None,
};
runs.push(stmt);
} else {
unreachable!();
}
}
let mut funs: LinkedHashMap<_, _> = Default::default();
for (kind_name, stmt) in file.funs {
if let Statement::Fun {
name,
args,
func,
init,
sign: _,
} = stmt
{
let init = init.map(|x| *linearize_term_independent(&x));
let mut rules: Vec<_> = Default::default();
for rule in func.rules {
let rule = linearize_rule(rule);
rules.push(rule);
}
let func = Func { rules };
let stmt = Statement::Fun {
name,
args,
func,
init,
sign: None,
};
funs.insert(kind_name, stmt);
} else {
unreachable!("Expected list of Funs, found {:?}", stmt);
}
}
let ctrs = file.ctrs;
File { ctrs, funs, runs }
}
pub fn linearize_rule(rule: Rule) -> Rule {
let mut ctx = LinearizeCtx::new();
ctx.create_param_names(&rule);
let mut rhs = linearize_term(&mut ctx, &rule.rhs, false);
let lhs = linearize_term(&mut ctx, &rule.lhs, true);
let vals: Vec<Name> = ctx.name_table.values().map(Name::clone).collect();
for val in vals {
let expr = Box::new(Term::Var { name: val.clone() });
rhs = dup_var(&mut ctx, &val, expr, rhs);
}
Rule {
lhs: *lhs,
rhs: *rhs,
}
}
pub fn linearize_term(ctx: &mut LinearizeCtx, term: &Term, lhs: bool) -> Box<Term> {
let term = match term {
Term::Var { name } => {
if lhs {
let mut name = ctx.name_table.get(name).unwrap_or(name).clone();
rename_erased(ctx, &mut name);
Term::Var { name }
} else {
// create a var with the name generated before
// concatenated with '.{{times_used}}'
if let Some(name) = ctx.name_table.get(name) {
let used = *ctx
.uses
.entry(name.clone())
.and_modify(|x| *x += 1)
.or_insert(1);
let name = Name::from_str(&format!("{}.{}", name, used - 1)).unwrap(); // TODO: Think if this errs or not
Term::Var { name }
} else {
unreachable!("Unbound variable '{}' in kdl compilation", name.to_string());
}
}
}
Term::Dup {
nam0,
nam1,
expr,
body,
} => {
let new_nam0 = ctx.create_name();
let new_nam1 = ctx.create_name();
let expr = linearize_term(ctx, expr, lhs);
let got_0 = ctx.name_table.remove(nam0);
let got_1 = ctx.name_table.remove(nam0);
ctx.name_table.insert(nam0.clone(), new_nam0.clone());
ctx.name_table.insert(nam1.clone(), new_nam1.clone());
let body = linearize_term(ctx, body, lhs);
ctx.name_table.remove(nam0);
if let Some(x) = got_0 {
ctx.name_table.insert(nam0.clone(), x);
}
ctx.name_table.remove(nam1);
if let Some(x) = got_1 {
ctx.name_table.insert(nam1.clone(), x);
}
let nam0 = Name::from_str(&format!("{}{}", new_nam0, ".0")).unwrap();
let nam1 = Name::from_str(&format!("{}{}", new_nam1, ".0")).unwrap();
Term::Dup {
nam0,
nam1,
expr,
body,
}
}
Term::Lam { name, body } => {
let mut new_name = ctx.create_name();
let got_name = ctx.name_table.remove(name);
ctx.name_table.insert(name.clone(), new_name.clone());
let body = linearize_term(ctx, body, lhs);
ctx.name_table.remove(name);
if let Some(x) = got_name {
ctx.name_table.insert(name.clone(), x);
}
let expr = Box::new(Term::Var {
name: new_name.clone(),
});
let body = dup_var(ctx, &new_name, expr, body);
rename_erased(ctx, &mut new_name);
Term::Lam {
name: new_name,
body,
}
}
Term::App { func, argm } => {
let func = linearize_term(ctx, func, lhs);
let argm = linearize_term(ctx, argm, lhs);
Term::App { func, argm }
}
Term::Ctr { name, args } => {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
let arg = linearize_term(ctx, arg, lhs);
new_args.push(*arg);
}
Term::Ctr {
name: name.clone(),
args: new_args,
}
}
Term::Fun { name, args } => {
let mut new_args = Vec::with_capacity(args.len());
for arg in args {
let arg = linearize_term(ctx, arg, lhs);
new_args.push(*arg);
}
Term::Fun {
name: name.clone(),
args: new_args,
}
}
Term::Num { numb } => Term::Num { numb: *numb },
Term::Op2 { oper, val0, val1 } => {
let val0 = linearize_term(ctx, val0, lhs);
let val1 = linearize_term(ctx, val1, lhs);
Term::Op2 {
oper: *oper,
val0,
val1,
}
}
};
Box::new(term)
}
// Linearize a term that is not part of a rule, so it doesn't need a shared context
pub fn linearize_term_independent(term: &Term) -> Box<Term> {
linearize_term(&mut LinearizeCtx::new(), term, false)
}
pub fn rename_erased(ctx: &LinearizeCtx, name: &mut Name) {
if ctx.uses.get(name).copied() <= Some(0) {
*name = Name::NONE;
}
}
// Duplicates all variables that are used more than once.
// The process is done generating auxiliary variables and
// applying dup on them.
pub fn dup_var(ctx: &mut LinearizeCtx, name: &Name, expr: Box<Term>, body: Box<Term>) -> Box<Term> {
if let Some(amount) = ctx.uses.get(name).copied() {
match amount {
// if not used nothing is done
0 => body,
// if used once just make a let (lambda then app)
1 => {
let name = Name::from_str(&format!("{}.0", name)).unwrap(); // TODO: handle err
let func = Box::new(Term::Lam { name, body: expr });
let term = Term::App { func, argm: body };
Box::new(term)
}
// if used more than once, duplicate
_ => {
let dup_times = amount - 1;
let aux_amount = amount - 2; // quantity of aux variables
let mut vars = vec![];
// generate name for duplicated variables
for i in (aux_amount..(dup_times * 2)).rev() {
let i = i - aux_amount; // moved to 0,1,..
let key = Name::from_str(&format!("{}.{}", name, i)).unwrap();
vars.push(key);
}
// generate name for aux variables
for i in (0..aux_amount).rev() {
let key = Name::from_str(&format!("c.{}", i)).unwrap();
vars.push(key);
}
// use aux variables to duplicate the variable
let term = Term::Dup {
nam0: vars.pop().unwrap(),
nam1: vars.pop().unwrap(),
expr,
body: dup_var_go(1, dup_times, body, &mut vars),
};
Box::new(term)
}
}
} else {
body
}
}
// Recursive aux function to duplicate one variable
// an amount of times
fn dup_var_go(idx: u64, dup_times: u64, body: Box<Term>, vars: &mut Vec<Name>) -> Box<Term> {
if idx == dup_times {
body
} else {
let nam0 = vars.pop().unwrap();
let nam1 = vars.pop().unwrap();
let var_name = Name::from_str(&format!("c.{}", idx - 1)).unwrap();
let expr = Box::new(Term::Var { name: var_name });
let dup = Term::Dup {
nam0,
nam1,
expr,
body: dup_var_go(idx + 1, dup_times, body, vars),
};
Box::new(dup)
}
}

View File

@ -1,4 +1,4 @@
use kind_tree::{untyped::Expr, symbol::Ident};
use kind_tree::{symbol::Ident, untyped::Expr};
pub fn subst(term: &mut Expr, from: &Ident, to: &Expr) {
use kind_tree::untyped::ExprKind::*;
@ -24,19 +24,19 @@ pub fn subst(term: &mut Expr, from: &Ident, to: &Expr) {
subst(next, from, to);
}
}
Binary { op: _, left, right } => {
subst(left, from, to);
subst(right, from, to);
}
Lambda { param, body, .. } if param.to_str() != from.to_str() => subst(body, from, to),
Num { .. } => (),
Str { .. } => (),
Var { .. } => (),
Lambda { .. } => (),
Err => unreachable!("Err should not be used inside the compiledr"),
}
}

View File

@ -1 +0,0 @@