diff --git a/crates/kind-checker/src/compiler/mod.rs b/crates/kind-checker/src/compiler/mod.rs index ef3ffa4f..c3ef24aa 100644 --- a/crates/kind-checker/src/compiler/mod.rs +++ b/crates/kind-checker/src/compiler/mod.rs @@ -99,10 +99,7 @@ fn range_to_num(range: Range) -> Box { fn set_origin(ident: &Ident) -> Box { 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 { 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"), } } diff --git a/crates/kind-driver/src/lib.rs b/crates/kind-driver/src/lib.rs index c6269542..9acb9308 100644 --- a/crates/kind-driver/src/lib.rs +++ b/crates/kind-driver/src/lib.rs @@ -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; diff --git a/crates/kind-pass/src/desugar/expr.rs b/crates/kind-pass/src/desugar/expr.rs index 5fc8da7e..b8444854 100644 --- a/crates/kind-pass/src/desugar/expr.rs +++ b/crates/kind-pass/src/desugar/expr.rs @@ -23,12 +23,7 @@ impl<'a> DesugarState<'a> { literal: &expr::Literal, ) -> Box { 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) diff --git a/crates/kind-pass/src/erasure/mod.rs b/crates/kind-pass/src/erasure/mod.rs index 84cbd049..1dd4a47d 100644 --- a/crates/kind-pass/src/erasure/mod.rs +++ b/crates/kind-pass/src/erasure/mod.rs @@ -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, diff --git a/crates/kind-pass/src/errors.rs b/crates/kind-pass/src/errors.rs index 6cdc4735..8dffee1e 100644 --- a/crates/kind-pass/src/errors.rs +++ b/crates/kind-pass/src/errors.rs @@ -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), }], diff --git a/crates/kind-target-hvm/src/lib.rs b/crates/kind-target-hvm/src/lib.rs index 8a657d95..f5ef4dec 100644 --- a/crates/kind-target-hvm/src/lib.rs +++ b/crates/kind-target-hvm/src/lib.rs @@ -17,6 +17,7 @@ pub fn compile_book(book: untyped::Book) -> File { } pub fn compile_term(expr: &untyped::Expr) -> Box { + 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 { 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), }); diff --git a/crates/kind-target-kdl/src/compile.rs b/crates/kind-target-kdl/src/compile.rs index 6bcf4989..b2b07363 100644 --- a/crates/kind-target-kdl/src/compile.rs +++ b/crates/kind-target-kdl/src/compile.rs @@ -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, - runs: Vec, + pub ctrs: LinkedHashMap, + pub funs: LinkedHashMap, + pub runs: Vec, } pub struct CompileCtx<'a> { @@ -32,6 +34,7 @@ impl<'a> CompileCtx<'a> { pub fn new(book: &'a untyped::Book, sender: Sender>) -> 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, } } diff --git a/crates/kind-target-kdl/src/errors.rs b/crates/kind-target-kdl/src/errors.rs index 3b1938ba..98ff8988 100644 --- a/crates/kind-target-kdl/src/errors.rs +++ b/crates/kind-target-kdl/src/errors.rs @@ -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 { }, } } -} \ No newline at end of file +} diff --git a/crates/kind-target-kdl/src/flatten.rs b/crates/kind-target-kdl/src/flatten.rs index 7bb86f80..c4e84c29 100644 --- a/crates/kind-target-kdl/src/flatten.rs +++ b/crates/kind-target-kdl/src/flatten.rs @@ -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 { let mut name_count = 0; - + let mut skip: FxHashSet = FxHashSet::default(); let mut new_entries: Vec = Vec::new(); let mut old_entry_rules: Vec = Vec::new(); @@ -195,7 +199,8 @@ fn flatten_entry(entry: &Entry) -> Vec { 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 { 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, + } } diff --git a/crates/kind-target-kdl/src/lib.rs b/crates/kind-target-kdl/src/lib.rs index 2f7afe97..a1a5139f 100644 --- a/crates/kind-target-kdl/src/lib.rs +++ b/crates/kind-target-kdl/src/lib.rs @@ -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>, namespace: &str) -> Option { +pub fn compile_book( + book: untyped::Book, + sender: Sender>, + namespace: &str, +) -> Option { + // 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) } diff --git a/crates/kind-target-kdl/src/linearize.rs b/crates/kind-target-kdl/src/linearize.rs new file mode 100644 index 00000000..d47c9b58 --- /dev/null +++ b/crates/kind-target-kdl/src/linearize.rs @@ -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_table: LinkedHashMap, + 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 = 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 { + 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 { + 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, body: Box) -> Box { + 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, vars: &mut Vec) -> Box { + 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) + } +} diff --git a/crates/kind-target-kdl/src/subst.rs b/crates/kind-target-kdl/src/subst.rs index 4b2969a8..23547507 100644 --- a/crates/kind-target-kdl/src/subst.rs +++ b/crates/kind-target-kdl/src/subst.rs @@ -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"), } } diff --git a/crates/kind-target-kdl/src/term.rs b/crates/kind-target-kdl/src/term.rs deleted file mode 100644 index 8b137891..00000000 --- a/crates/kind-target-kdl/src/term.rs +++ /dev/null @@ -1 +0,0 @@ -