diff --git a/cspell.json b/cspell.json index ffd3a09c..1d78a959 100644 --- a/cspell.json +++ b/cspell.json @@ -61,6 +61,7 @@ "peekable", "postcondition", "prec", + "powi", "readback", "recursively", "redex", diff --git a/examples/bubble_sort.hvm b/examples/bubble_sort.hvm index 83d1e1fe..3ed4656a 100644 --- a/examples/bubble_sort.hvm +++ b/examples/bubble_sort.hvm @@ -13,8 +13,12 @@ (SwapGT _ v x xs) = (List.cons x (Insert v xs)) // Generates a random list -(Rnd 0 s) = [] -(Rnd n s) = (List.cons s (Rnd (- n 1) (% (+ (* s 1664525) 1013904223) 4294967295))) +(Rnd 0 s) = List.nil +(Rnd n s) = + let s = (^ s (* s 0b10000000000000)) + let s = (^ s (/ s 0b100000000000000000)) + let s = (^ s (* s 0b100000)) + (List.cons s (Rnd (- n 1) s)) // Sums a list (Sum []) = 0 @@ -22,7 +26,7 @@ (Main) = let n = 10 - (Sum (Sort (Rnd (* 2 50) n))) + (Sum (Sort (Rnd 0x100 n))) // Use an argument from cli -// (Main n) = (Sum (Sort (Rnd (* 2 50) n))) +// (Main n) = (Sum (Sort (Rnd 0x100 n))) diff --git a/examples/quick_sort.hvm b/examples/quick_sort.hvm index 5ad1b05c..cf72dcaf 100644 --- a/examples/quick_sort.hvm +++ b/examples/quick_sort.hvm @@ -17,8 +17,12 @@ data Tree = Leaf | (Node l m r) (Push _ x pair) = (pair λmin λmax λp (p min (List.cons x max))) // Generates a random list -(Rnd 0 s) = (List.nil) -(Rnd n s) = (List.cons s (Rnd (- n 1) (% (+ (* s 1664525) 1013904223) 4294967295))) +(Rnd 0 s) = List.nil +(Rnd n s) = + let s = (^ s (* s 0b10000000000000)) + let s = (^ s (/ s 0b100000000000000000)) + let s = (^ s (* s 0b100000)) + (List.cons s (Rnd (- n 1) s)) // Sums all elements in a concatenation tree (Sum Leaf) = 0 @@ -26,8 +30,7 @@ data Tree = Leaf | (Node l m r) // Sorts and sums n random numbers (Main) = - let n = 12 - (Sum (Sort (Rnd (* 2 n) 1))) + (Sum (Sort (Rnd 0x100 1))) // Use an argument from cli // (Main n) = (Sum (Sort (Rnd (<< 1 n) 1))) diff --git a/examples/radix_sort.hvm b/examples/radix_sort.hvm index e2cf96d8..8d041e1f 100644 --- a/examples/radix_sort.hvm +++ b/examples/radix_sort.hvm @@ -36,45 +36,44 @@ data Arr = Null | (Leaf x) | (Node a b) // Radix : U60 -> Map (Radix n) = let r = Used - let r = (Swap (& n 1) r Free) - let r = (Swap (& n 2) r Free) - let r = (Swap (& n 4) r Free) - let r = (Swap (& n 8) r Free) - let r = (Swap (& n 16) r Free) + let r = (Swap (& n 0x1) r Free) + let r = (Swap (& n 0x2) r Free) + let r = (Swap (& n 0x4) r Free) + let r = (Swap (& n 0x8) r Free) + let r = (Swap (& n 0x10) r Free) (Radix2 n r) (Radix2 n r) = - let r = (Swap (& n 32) r Free) - let r = (Swap (& n 64) r Free) - let r = (Swap (& n 128) r Free) - let r = (Swap (& n 256) r Free) - let r = (Swap (& n 512) r Free) + let r = (Swap (& n 0x20) r Free) + let r = (Swap (& n 0x40) r Free) + let r = (Swap (& n 0x80) r Free) + let r = (Swap (& n 0x100) r Free) + let r = (Swap (& n 0x200) r Free) (Radix3 n r) (Radix3 n r) = - let r = (Swap (& n 1024) r Free) - let r = (Swap (& n 2048) r Free) - let r = (Swap (& n 4096) r Free) - let r = (Swap (& n 8192) r Free) - let r = (Swap (& n 16384) r Free) + let r = (Swap (& n 0x400) r Free) + let r = (Swap (& n 0x800) r Free) + let r = (Swap (& n 0x1000) r Free) + let r = (Swap (& n 0x2000) r Free) + let r = (Swap (& n 0x4000) r Free) (Radix4 n r) (Radix4 n r) = - let r = (Swap (& n 32768) r Free) - let r = (Swap (& n 65536) r Free) - let r = (Swap (& n 131072) r Free) - let r = (Swap (& n 262144) r Free) - let r = (Swap (& n 524288) r Free) + let r = (Swap (& n 0x8000) r Free) + let r = (Swap (& n 0x10000) r Free) + let r = (Swap (& n 0x20000) r Free) + let r = (Swap (& n 0x40000) r Free) + let r = (Swap (& n 0x80000) r Free) (Radix5 n r) (Radix5 n r) = - let r = (Swap (& n 1048576) r Free) - let r = (Swap (& n 2097152) r Free) - let r = (Swap (& n 4194304) r Free) - let r = (Swap (& n 8388608) r Free) + let r = (Swap (& n 0x100000) r Free) + let r = (Swap (& n 0x200000) r Free) + let r = (Swap (& n 0x400000) r Free) + let r = (Swap (& n 0x800000) r Free) r - // Reverse : Arr -> Arr (Reverse Null) = Null (Reverse (Leaf a)) = (Leaf a) diff --git a/src/term/builtins.rs b/src/term/builtins.rs index 029d83eb..c38000fc 100644 --- a/src/term/builtins.rs +++ b/src/term/builtins.rs @@ -1,4 +1,4 @@ -use super::{parser::TermParser, Book, Name, NumType, Pattern, Term}; +use super::{parser::TermParser, Book, Name, Num, Pattern, Term}; use crate::maybe_grow; const BUILTINS: &str = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/src/term/builtins.hvm")); @@ -62,11 +62,11 @@ impl Term { pub fn encode_str(val: &str) -> Term { val.chars().rfold(Term::r#ref(SNIL), |acc, char| { - Term::call(Term::r#ref(SCONS), [Term::Num { typ: NumType::U24, val: char as u32 }, acc]) + Term::call(Term::r#ref(SCONS), [Term::Num { val: Num::U24(char as u32 & 0x00ff_ffff) }, acc]) }) } - pub fn encode_nat(val: u64) -> Term { + pub fn encode_nat(val: u32) -> Term { (0 .. val).fold(Term::r#ref(NAT_ZERO), |acc, _| Term::app(Term::r#ref(NAT_SUCC), acc)) } diff --git a/src/term/display.rs b/src/term/display.rs index 13b804a4..a4e8c2bc 100644 --- a/src/term/display.rs +++ b/src/term/display.rs @@ -1,4 +1,4 @@ -use super::{Book, Definition, FanKind, Name, Op, Pattern, Rule, Tag, Term}; +use super::{Book, Definition, FanKind, Name, Num, Op, Pattern, Rule, Tag, Term}; use crate::maybe_grow; use std::{fmt, ops::Deref}; @@ -114,7 +114,9 @@ impl fmt::Display for Term { Term::Fan { fan: FanKind::Tup, tag, els } => write!(f, "{}({})", tag, DisplayJoin(|| els.iter(), ", ")), Term::Fan { fan: FanKind::Dup, tag, els } => write!(f, "{}{{{}}}", tag, DisplayJoin(|| els, " ")), Term::Era => write!(f, "*"), - Term::Num { typ: _, val } => write!(f, "{val}"), + Term::Num { val: Num::U24(val) } => write!(f, "{val}"), + Term::Num { val: Num::I24(val) } => write!(f, "{}{}", if *val < 0 { "-" } else { "+" }, val.abs()), + Term::Num { val: Num::F24(val) } => write!(f, "{val:.3}"), Term::Nat { val } => write!(f, "#{val}"), Term::Str { val } => write!(f, "{val:?}"), Term::Opr { opr, fst, snd } => { @@ -374,7 +376,9 @@ impl Term { } Term::Nat { val } => write!(f, "#{val}"), - Term::Num { typ: _, val } => write!(f, "{val}"), + Term::Num { val: Num::U24(val) } => write!(f, "{val}"), + Term::Num { val: Num::I24(val) } => write!(f, "{}{}", if *val < 0 { "-" } else { "+" }, val.abs()), + Term::Num { val: Num::F24(val) } => write!(f, "{val:.3}"), Term::Str { val } => write!(f, "{val:?}"), Term::Ref { nam } => write!(f, "{nam}"), Term::Era => write!(f, "*"), diff --git a/src/term/flavour_py/order_kwargs.rs b/src/term/flavour_py/order_kwargs.rs index 1121a268..5f3dd140 100644 --- a/src/term/flavour_py/order_kwargs.rs +++ b/src/term/flavour_py/order_kwargs.rs @@ -2,7 +2,7 @@ use indexmap::IndexMap; use crate::term::Name; -use super::{Definition, Enum, Program, Stmt, Term, Variant}; +use super::{Definition, Enum, MBind, Program, Stmt, Term, Variant}; struct Ctx<'a> { variants: &'a IndexMap, @@ -58,9 +58,26 @@ impl Stmt { arm.rgt.order_kwargs(ctx); } } - Stmt::Switch { .. } => unimplemented!(), - Stmt::Fold { .. } => unimplemented!(), - Stmt::Do { .. } => unimplemented!(), + Stmt::Switch { arg, arms, .. } => { + arg.order_kwargs(ctx); + for arm in arms { + arm.order_kwargs(ctx); + } + } + Stmt::Fold { arg, arms, .. } => { + arg.order_kwargs(ctx); + for arm in arms { + arm.rgt.order_kwargs(ctx); + } + } + Stmt::Do { block, .. } => { + for bind in block { + match bind { + MBind::Ask { val, .. } => val.order_kwargs(ctx), + MBind::Stmt { stmt } => stmt.order_kwargs(ctx), + } + } + } Stmt::Return { term } => term.order_kwargs(ctx), } } diff --git a/src/term/flavour_py/to_lang.rs b/src/term/flavour_py/to_lang.rs index 065b32d1..8729df41 100644 --- a/src/term/flavour_py/to_lang.rs +++ b/src/term/flavour_py/to_lang.rs @@ -88,7 +88,7 @@ impl Term { match self { Term::None => lang::Term::Era, Term::Var { nam } => lang::Term::Var { nam }, - Term::Num { val } => lang::Term::Num { typ: lang::NumType::U24, val }, + Term::Num { val } => lang::Term::Num { val: lang::Num::U24(val) }, Term::Call { fun, args, kwargs } => { assert!(kwargs.is_empty()); let args = args.into_iter().map(Self::to_lang); diff --git a/src/term/mod.rs b/src/term/mod.rs index 23826cba..e94add29 100644 --- a/src/term/mod.rs +++ b/src/term/mod.rs @@ -7,7 +7,7 @@ use crate::{ use indexmap::{IndexMap, IndexSet}; use interner::global::{GlobalPool, GlobalString}; use itertools::Itertools; -use std::{borrow::Cow, collections::HashMap, ops::Deref}; +use std::{borrow::Cow, collections::HashMap, hash::Hash, ops::Deref}; pub mod builtins; pub mod check; @@ -63,7 +63,7 @@ pub struct Definition { } /// A pattern matching rule of a definition. -#[derive(Debug, Clone, Default, PartialEq, Eq, Hash)] +#[derive(Debug, Clone, Default, PartialEq)] pub struct Rule { pub pats: Vec, pub body: Term, @@ -110,11 +110,10 @@ pub enum Term { els: Vec, }, Num { - typ: NumType, - val: u32, + val: Num, }, Nat { - val: u64, + val: u32, }, Str { val: GlobalString, @@ -181,11 +180,11 @@ pub enum Op { POW, } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -pub enum NumType { - U24 = 1, - I24 = 2, - F24 = 3, +#[derive(Debug, Clone, Copy)] +pub enum Num { + U24(u32), + I24(i32), + F24(f32), } #[derive(Debug, Clone, PartialEq, Eq, Hash)] @@ -328,7 +327,7 @@ impl Clone for Term { Self::Use { nam, val, nxt } => Self::Use { nam: nam.clone(), val: val.clone(), nxt: nxt.clone() }, Self::App { tag, fun, arg } => Self::App { tag: tag.clone(), fun: fun.clone(), arg: arg.clone() }, Self::Fan { fan, tag, els } => Self::Fan { fan: *fan, tag: tag.clone(), els: els.clone() }, - Self::Num { typ, val } => Self::Num { typ: *typ, val: *val }, + Self::Num { val } => Self::Num { val: *val }, Self::Nat { val } => Self::Nat { val: *val }, Self::Str { val } => Self::Str { val: val.clone() }, Self::Lst { els } => Self::Lst { els: els.clone() }, @@ -436,19 +435,19 @@ impl Term { Term::Str { val: STRINGS.get(str) } } - pub fn sub_num(arg: Term, val: u32, typ: NumType) -> Term { - if val == 0 { + pub fn sub_num(arg: Term, val: Num) -> Term { + if val.is_zero() { arg } else { - Term::Opr { opr: Op::SUB, fst: Box::new(arg), snd: Box::new(Term::Num { typ, val }) } + Term::Opr { opr: Op::SUB, fst: Box::new(arg), snd: Box::new(Term::Num { val }) } } } - pub fn add_num(arg: Term, val: u32, typ: NumType) -> Term { - if val == 0 { + pub fn add_num(arg: Term, val: Num) -> Term { + if val.is_zero() { arg } else { - Term::Opr { opr: Op::ADD, fst: Box::new(arg), snd: Box::new(Term::Num { typ, val }) } + Term::Opr { opr: Op::ADD, fst: Box::new(arg), snd: Box::new(Term::Num { val }) } } } @@ -800,6 +799,71 @@ impl Term { } } +impl Num { + pub fn is_zero(&self) -> bool { + match self { + Num::U24(val) => *val == 0, + Num::I24(val) => *val == 0, + Num::F24(val) => *val == 0.0, + } + } + + pub fn to_bits(&self) -> u32 { + match self { + Num::U24(val) => { + assert!(*val <= 0xFFFFFF); + ((val & 0xFFFFFF) << 4) | 0x1 + } + Num::I24(val) => (((*val as u32) & 0xFFFFFF) << 4) | 0x2, + Num::F24(val) => { + let bits = val.to_bits(); + let sign = (bits >> 31) & 0x1; + let expo = (bits >> 23) & 0xFF; + let mantissa = bits & 0x7FFFFF; + assert!( + (expo == 0) || (expo == 255) || (64 ..= 127).contains(&expo) || (128 ..= 190).contains(&expo) + ); + let expo = (expo & 0b0011_1111) | ((expo >> 7) << 6); + let mantissa = mantissa >> 7; + let bits = (sign << 23) | (expo << 16) | mantissa; + (bits << 4) | 0x3 + } + } + } + + pub fn from_bits(bits: u32) -> Self { + match bits & 0xF { + 0x1 => Num::U24((bits >> 4) & 0xFFFFFF), + 0x2 => Num::I24((((bits >> 4) & 0xFFFFFF) as i32) << 8 >> 8), + 0x3 => { + let bits = (bits >> 4) & 0xFFFFFF; + let sign = (bits >> 23) & 0x1; + let expo = (bits >> 16) & 0x7F; + let mantissa = bits & 0xFFFF; + let i_exp = (expo as i32) - 63; + let bits = (sign << 31) | (((i_exp + 127) as u32) << 23) | (mantissa << 7); + let bits = if mantissa == 0 && i_exp == -63 { sign << 31 } else { bits }; + Num::F24(f32::from_bits(bits)) + } + _ => unreachable!("Invalid Num bits"), + } + } +} + +impl Hash for Num { + fn hash(&self, state: &mut H) { + self.to_bits().hash(state); + } +} + +impl PartialEq for Num { + fn eq(&self, other: &Self) -> bool { + self.to_bits() == other.to_bits() + } +} + +impl Eq for Num {} + impl Pattern { pub fn binds(&self) -> impl DoubleEndedIterator> + Clone { self.iter().filter_map(|pat| match pat { @@ -862,7 +926,7 @@ impl Pattern { Pattern::Ctr(ctr, args) => { Term::call(Term::Ref { nam: ctr.clone() }, args.iter().map(|arg| arg.to_term())) } - Pattern::Num(val) => Term::Num { typ: NumType::U24, val: *val }, + Pattern::Num(val) => Term::Num { val: Num::U24(*val) }, Pattern::Fan(fan, tag, args) => { Term::Fan { fan: *fan, tag: tag.clone(), els: args.iter().map(|p| p.to_term()).collect() } } @@ -960,3 +1024,35 @@ impl Book { } } } + +#[test] +fn num_to_from_bits() { + let a = [ + Num::U24(0), + Num::I24(0), + Num::F24(0.0), + Num::U24(0xFFFFFF), + Num::I24(0xFFFFFF), + Num::F24(0xFFFFFF as f32), + Num::U24(12345), + Num::I24(12345), + Num::I24(-12345), + Num::I24(-0), + Num::F24(0.0), + Num::F24(-0.0), + Num::F24(0.00123), + Num::F24(12345.023), + Num::F24(-1235.3849), + Num::F24(1.0), + Num::F24(-1.0), + Num::F24(12323658716.0), + Num::F24(-12323658716.0), + Num::F24(-0.00000000000000001), + Num::F24(0.00000000000000001), + Num::F24(5447856134985749851.3457896137815694178), + Num::F24(-5447856134985749851.3457896137815694178), + ]; + for b in a { + assert_eq!(b, Num::from_bits(Num::to_bits(&b))); + } +} diff --git a/src/term/net_to_term.rs b/src/term/net_to_term.rs index 7cb26761..c7c9928a 100644 --- a/src/term/net_to_term.rs +++ b/src/term/net_to_term.rs @@ -1,17 +1,12 @@ use crate::{ diagnostics::{DiagnosticOrigin, Diagnostics, Severity}, maybe_grow, - net::{ - CtrKind::*, - INet, NodeId, - NodeKind::{self, *}, - Port, SlotId, ROOT, - }, - term::{num_to_name, term_to_net::Labels, Book, FanKind, Name, Pattern, Tag, Term}, + net::{CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT}, + term::{num_to_name, term_to_net::Labels, Book, FanKind, Name, Op, Pattern, Tag, Term}, }; use std::collections::{BTreeSet, HashMap, HashSet}; -use super::{NumType, Op}; +use super::Num; /// Converts an Interaction-INet to a Lambda Calculus term pub fn net_to_term( @@ -41,8 +36,8 @@ pub fn net_to_term( let snd = reader.namegen.decl_name(net, Port(node, 2)); let (fan, tag) = match reader.net.node(node).kind { - Ctr(Tup(lab)) => (FanKind::Tup, reader.labels.tup.to_tag(lab)), - Ctr(Dup(lab)) => (FanKind::Dup, reader.labels.dup.to_tag(Some(lab))), + NodeKind::Ctr(CtrKind::Tup(lab)) => (FanKind::Tup, reader.labels.tup.to_tag(lab)), + NodeKind::Ctr(CtrKind::Dup(lab)) => (FanKind::Dup, reader.labels.dup.to_tag(Some(lab))), _ => unreachable!(), }; @@ -81,6 +76,8 @@ pub struct Reader<'a> { impl Reader<'_> { fn read_term(&mut self, next: Port) -> Term { + use CtrKind::*; + maybe_grow(|| { if self.dup_paths.is_none() && !self.seen.insert(next) { self.error(ReadbackError::Cyclic); @@ -88,15 +85,14 @@ impl Reader<'_> { } let node = next.node(); - match &self.net.node(node).kind { - Era => { + NodeKind::Era => { // Only the main port actually exists in an ERA, the aux ports are just an artifact of this representation. debug_assert!(next.slot() == 0); Term::Era } // If we're visiting a con node... - Ctr(Con(lab)) => match next.slot() { + NodeKind::Ctr(CtrKind::Con(lab)) => match next.slot() { // If we're visiting a port 0, then it is a lambda. 0 => { let nam = self.namegen.decl_name(self.net, Port(node, 1)); @@ -117,7 +113,7 @@ impl Reader<'_> { } _ => unreachable!(), }, - Mat => match next.slot() { + NodeKind::Mat => match next.slot() { 2 => { // Read the matched expression let arg = self.read_term(self.net.enter_port(Port(node, 0))); @@ -128,7 +124,7 @@ impl Reader<'_> { // We expect the pattern matching node to be a CON let sel_kind = &self.net.node(sel_node).kind; - let (zero, succ) = if *sel_kind == Ctr(Con(None)) { + let (zero, succ) = if *sel_kind == NodeKind::Ctr(Con(None)) { let zero_term = self.read_term(self.net.enter_port(Port(sel_node, 1))); let mut succ_term = self.read_term(self.net.enter_port(Port(sel_node, 2))); @@ -157,7 +153,7 @@ impl Reader<'_> { Term::Err } }, - Ref { def_name } => { + NodeKind::Ref { def_name } => { if def_name.is_generated() { // Dereference generated names since the user is not aware of them let def = &self.book.defs[def_name]; @@ -170,7 +166,7 @@ impl Reader<'_> { } } // If we're visiting a fan node... - Ctr(kind @ (Dup(_) | Tup(_))) => { + NodeKind::Ctr(kind @ (Dup(_) | Tup(_))) => { let (fan, lab) = match *kind { Tup(lab) => (FanKind::Tup, lab), Dup(lab) => (FanKind::Dup, Some(lab)), @@ -217,7 +213,7 @@ impl Reader<'_> { _ => unreachable!(), } } - Num { val: _ } => { + NodeKind::Num { val: _ } => { let (flp, arg) = self.read_opr_arg(next); match arg { NumArg::Sym(opr) => Term::Opr { @@ -225,26 +221,26 @@ impl Reader<'_> { fst: Box::new(Term::Err), snd: Box::new(Term::Err), }, - NumArg::Num(typ, val) => Term::Num { typ, val }, + NumArg::Num(typ, val) => Term::Num { val: Num::from_bits_and_type(val, typ) }, NumArg::Par(opr, val) => { if flp { Term::Opr { opr: Op::from_native_tag(opr, NumType::U24), - fst: Box::new(Term::Num { typ: NumType::U24, val }), + fst: Box::new(Term::Num { val: Num::from_bits_and_type(val, NumType::U24) }), snd: Box::new(Term::Err), } } else { Term::Opr { opr: Op::from_native_tag(opr, NumType::U24), fst: Box::new(Term::Err), - snd: Box::new(Term::Num { typ: NumType::U24, val }), + snd: Box::new(Term::Num { val: Num::from_bits_and_type(val, NumType::U24) }), } } } NumArg::Oth(_) => unreachable!(), } } - Opr => match next.slot() { + NodeKind::Opr => match next.slot() { 2 => { let port0_kind = self.net.node(self.net.enter_port(Port(node, 0)).node()).kind.clone(); if port0_kind == NodeKind::Opr { @@ -253,7 +249,7 @@ impl Reader<'_> { if let Term::Opr { opr, fst, snd: _ } = &fst { let (flip, arg) = self.read_opr_arg(self.net.enter_port(Port(node, 1))); let snd = Box::new(match arg { - NumArg::Num(typ, val) => Term::Num { typ, val }, + NumArg::Num(typ, val) => Term::Num { val: Num::from_bits_and_type(val, typ) }, NumArg::Oth(term) => term, NumArg::Sym(_) | NumArg::Par(_, _) => { self.error(ReadbackError::InvalidNumericOp); @@ -271,34 +267,36 @@ impl Reader<'_> { let (flip0, arg0) = self.read_opr_arg(self.net.enter_port(Port(node, 0))); let (flip1, arg1) = self.read_opr_arg(self.net.enter_port(Port(node, 1))); let (arg0, arg1) = if flip0 != flip1 { (arg1, arg0) } else { (arg0, arg1) }; - use NumArg::*; match (arg0, arg1) { - (Sym(opr), Num(typ, val)) | (Num(typ, val), Sym(opr)) => Term::Opr { + (NumArg::Sym(opr), NumArg::Num(typ, val)) | (NumArg::Num(typ, val), NumArg::Sym(opr)) => { + Term::Opr { + opr: Op::from_native_tag(opr, typ), + fst: Box::new(Term::Num { val: Num::from_bits_and_type(val, typ) }), + snd: Box::new(Term::Err), + } + } + (NumArg::Num(typ, num1), NumArg::Par(opr, num2)) + | (NumArg::Par(opr, num1), NumArg::Num(typ, num2)) => Term::Opr { opr: Op::from_native_tag(opr, typ), - fst: Box::new(Term::Num { typ, val }), - snd: Box::new(Term::Err), - }, - (Num(typ, num1), Par(opr, num2)) | (Par(opr, num1), Num(typ, num2)) => Term::Opr { - opr: Op::from_native_tag(opr, typ), - fst: Box::new(Term::Num { typ, val: num1 }), - snd: Box::new(Term::Num { typ, val: num2 }), + fst: Box::new(Term::Num { val: Num::from_bits_and_type(num1, typ) }), + snd: Box::new(Term::Num { val: Num::from_bits_and_type(num2, typ) }), }, // No type, so assuming u24 - (Sym(opr), Oth(term)) | (Oth(term), Sym(opr)) => Term::Opr { + (NumArg::Sym(opr), NumArg::Oth(term)) | (NumArg::Oth(term), NumArg::Sym(opr)) => Term::Opr { opr: Op::from_native_tag(opr, NumType::U24), fst: Box::new(term), snd: Box::new(Term::Err), }, - (Par(opr, num), Oth(term)) => Term::Opr { + (NumArg::Par(opr, num), NumArg::Oth(term)) => Term::Opr { opr: Op::from_native_tag(opr, NumType::U24), - fst: Box::new(Term::Num { typ: NumType::U24, val: num }), + fst: Box::new(Term::Num { val: Num::from_bits_and_type(num, NumType::U24) }), snd: Box::new(term), }, - (Oth(term), Par(opr, num)) => Term::Opr { + (NumArg::Oth(term), NumArg::Par(opr, num)) => Term::Opr { opr: Op::from_native_tag(opr, NumType::U24), fst: Box::new(term), - snd: Box::new(Term::Num { typ: NumType::U24, val: num }), + snd: Box::new(Term::Num { val: Num::from_bits_and_type(num, NumType::U24) }), }, _ => { self.error(ReadbackError::InvalidNumericOp); @@ -312,7 +310,7 @@ impl Reader<'_> { Term::Err } }, - Rot => { + NodeKind::Rot => { self.error(ReadbackError::ReachedRoot); Term::Err } @@ -323,7 +321,7 @@ impl Reader<'_> { fn read_opr_arg(&mut self, next: Port) -> (bool, NumArg) { let node = next.node(); match &self.net.node(node).kind { - Num { val } => { + NodeKind::Num { val } => { self.seen.insert(next); let flipped = ((val >> 28) & 0x1) != 0; let typ = val & 0xf; @@ -381,7 +379,7 @@ impl Reader<'_> { // Eta-reduce the readback inet. // This is not valid for all kinds of nodes, only CON/TUP/DUP, due to their interaction rules. - if matches!(node_kind, Ctr(_)) { + if matches!(node_kind, NodeKind::Ctr(_)) { match (fst_port, snd_port) { (Port(fst_node, 1), Port(snd_node, 2)) if fst_node == snd_node => { if self.net.node(fst_node).kind == *node_kind { @@ -427,6 +425,13 @@ enum NumArg { Oth(Term), } +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum NumType { + U24 = 1, + I24 = 2, + F24 = 3, +} + impl Op { fn from_native_tag(val: u32, typ: NumType) -> Op { match val { @@ -476,6 +481,12 @@ impl NumType { } } +impl Num { + fn from_bits_and_type(bits: u32, typ: NumType) -> Self { + Num::from_bits((bits & 0x00ff_ffff) << 4 | (typ as u32)) + } +} + /* Insertion of dups in the middle of the term */ /// Represents `let #tag(fst, snd) = val` / `let #tag{fst snd} = val` @@ -589,7 +600,7 @@ impl NameGen { // If port is linked to an erase node, return an unused variable let var_use = net.enter_port(var_port); let var_kind = &net.node(var_use.node()).kind; - (*var_kind != Era).then(|| self.var_name(var_port)) + (*var_kind != NodeKind::Era).then(|| self.var_name(var_port)) } pub fn unique(&mut self) -> Name { diff --git a/src/term/parser.rs b/src/term/parser.rs index 5e493083..b3807f85 100644 --- a/src/term/parser.rs +++ b/src/term/parser.rs @@ -1,15 +1,13 @@ use crate::{ maybe_grow, term::{ - display::DisplayFn, Adt, Book, Definition, FanKind, MatchRule, Name, Op, Pattern, Rule, Tag, Term, + display::DisplayFn, Adt, Book, Definition, FanKind, MatchRule, Name, Num, Op, Pattern, Rule, Tag, Term, STRINGS, }, }; use highlight_error::highlight_error; use TSPL::Parser; -use super::NumType; - // hvml grammar description: // ::= ( | )* // ::= "data" "=" ( | "(" ()* ")" )+ @@ -188,8 +186,8 @@ impl<'a> TermParser<'a> { // Number if self.peek_one().map_or(false, |c| c.is_ascii_digit()) { unexpected_tag(self)?; - let num = self.parse_u64()?; - return Ok(Pattern::Num(num as u32)); + let num = self.parse_u32()?; + return Ok(Pattern::Num(num)); } // Channel @@ -303,7 +301,7 @@ impl<'a> TermParser<'a> { if self.starts_with("#") { self.consume("#")?; unexpected_tag(self)?; - let val = self.parse_u64()?; + let val = self.parse_u32()?; return Ok(Term::Nat { val }); } @@ -318,14 +316,61 @@ impl<'a> TermParser<'a> { if self.starts_with("'") { unexpected_tag(self)?; let char = self.parse_quoted_char()?; - return Ok(Term::Num { typ: NumType::U24, val: char as u32 }); + return Ok(Term::Num { val: Num::U24(char as u32 & 0x00ff_ffff) }); } - // Native num - if self.peek_one().map_or(false, |c| c.is_ascii_digit()) { + // Native Number + if self.peek_one().map_or(false, |c| "0123456789+-".contains(c)) { unexpected_tag(self)?; - let val = self.parse_u64()?; - return Ok(Term::Num { typ: NumType::U24, val: val as u32 }); + + let ini_idx = *self.index(); + + // Parses sign + let sgn = if self.try_consume("+") { + Some(1) + } else if self.try_consume("-") { + Some(-1) + } else { + None + }; + + // Parses main value + let num = self.parse_u32()?; + + // Parses frac value (Float type) + // TODO: Will lead to some rounding errors + // TODO: Doesn't cover very large/small numbers + let fra = if let Some('.') = self.peek_one() { + self.consume(".")?; + let ini_idx = *self.index(); + let fra = self.parse_u32()? as f32; + let end_idx = *self.index(); + let fra = fra / 10f32.powi((end_idx - ini_idx) as i32); + Some(fra) + } else { + None + }; + + // F24 + if let Some(fra) = fra { + let sgn = sgn.unwrap_or(1); + return Ok(Term::Num { val: Num::F24(sgn as f32 * (num as f32 + fra)) }); + } + + // I24 + if let Some(sgn) = sgn { + let num = sgn * num as i32; + if !(-0x00800000 ..= 0x007fffff).contains(&num) { + return self.num_range_err(ini_idx, "I24"); + } + return Ok(Term::Num { val: Num::I24(num) }); + } + + // U24 + if num >= 1 << 24 { + return self.num_range_err(ini_idx, "U24"); + } + return Ok(Term::Num { val: Num::U24(num) }); } // Use @@ -493,7 +538,7 @@ impl<'a> TermParser<'a> { } } c if c.is_ascii_digit() => { - let val = self.parse_u64()?; + let val = self.parse_u32()?; if val != expected_num { return self.expected(&expected_num.to_string()); } @@ -509,6 +554,115 @@ impl<'a> TermParser<'a> { self.consume("}")?; Ok(Term::Swt { arg: Box::new(arg), bnd: Some(bnd), with, pred, arms }) } + + fn num_range_err(&mut self, ini_idx: usize, typ: &str) -> Result { + let ctx = highlight_error(ini_idx, *self.index(), self.input()); + Err(format!("\x1b[1mNumber literal outside of range for {}.\x1b[0m\n{}", typ, ctx)) + } + + /* Utils */ + + /// Checks if the next characters in the input start with the given string. + /// Skips trivia. + fn skip_starts_with(&mut self, text: &str) -> bool { + self.skip_trivia(); + self.starts_with(text) + } + + fn skip_peek_one(&mut self) -> Option { + self.skip_trivia(); + self.peek_one() + } + + /// Parses a list-like structure like "[x1, x2, x3,]". + /// + /// `parser` is a function that parses an element of the list. + /// + /// If `hard_sep` the separator between elements is mandatory. + /// Always accepts trailing separators. + /// + /// `min_els` determines how many elements must be parsed at minimum. + fn list_like( + &mut self, + parser: impl Fn(&mut Self) -> Result, + start: &str, + end: &str, + sep: &str, + hard_sep: bool, + min_els: usize, + ) -> Result, String> { + self.consume(start)?; + let mut els = vec![]; + for i in 0 .. min_els { + els.push(parser(self)?); + if hard_sep && !(i == min_els - 1 && self.skip_starts_with(end)) { + self.consume(sep)?; + } else { + self.try_consume(sep); + } + } + + while !self.try_consume(end) { + els.push(parser(self)?); + if hard_sep && !self.skip_starts_with(end) { + self.consume(sep)?; + } else { + self.try_consume(sep); + } + } + Ok(els) + } + + fn labelled( + &mut self, + parser: impl Fn(&mut Self) -> Result, + label: &str, + ) -> Result { + match parser(self) { + Ok(val) => Ok(val), + Err(_) => self.expected(label), + } + } + + fn expected_spanned(&mut self, exp: &str, ini_idx: usize, end_idx: usize) -> Result { + let ctx = highlight_error(ini_idx, end_idx, self.input()); + let is_eof = self.is_eof(); + let detected = DisplayFn(|f| if is_eof { write!(f, " end of input") } else { write!(f, "\n{ctx}") }); + Err(format!("\x1b[1m- expected:\x1b[0m {}\n\x1b[1m- detected:\x1b[0m{}", exp, detected)) + } + + /// Consumes text if the input starts with it. Otherwise, do nothing. + fn try_consume(&mut self, text: &str) -> bool { + self.skip_trivia(); + if self.starts_with(text) { + self.consume(text).unwrap(); + true + } else { + false + } + } + + fn parse_u32(&mut self) -> Result { + self.skip_trivia(); + let radix = match self.peek_many(2) { + Some("0x") => { + self.advance_many(2); + 16 + } + Some("0b") => { + self.advance_many(2); + 2 + } + _ => 10, + }; + let num_str = self.take_while(move |c| c.is_digit(radix) || c == '_'); + let num_str = num_str.chars().filter(|c| *c != '_').collect::(); + if num_str.is_empty() { + self.expected("numeric digit") + } else { + u32::from_str_radix(&num_str, radix).map_err(|e| e.to_string()) + } + } } impl<'a> Parser<'a> for TermParser<'a> { diff --git a/src/term/term_to_net.rs b/src/term/term_to_net.rs index e4554dfe..62f3b681 100644 --- a/src/term/term_to_net.rs +++ b/src/term/term_to_net.rs @@ -108,8 +108,8 @@ impl<'t, 'l> EncodeTermState<'t, 'l> { Term::Var { nam } => self.link_var(false, nam, up), Term::Lnk { nam } => self.link_var(true, nam, up), Term::Ref { nam } => self.link(up, Place::Tree(LoanedMut::new(Tree::Ref { nam: nam.to_string() }))), - Term::Num { typ, val } => { - let val = (*val << 4) | (*typ as u32); + Term::Num { val } => { + let val = val.to_bits(); self.link(up, Place::Tree(LoanedMut::new(Tree::Num { val }))) } // A lambda becomes to a con node. Ports: @@ -170,18 +170,20 @@ impl<'t, 'l> EncodeTermState<'t, 'l> { // Partially apply match (fst.as_ref(), snd.as_ref()) { // Put oper in fst - (Term::Num { typ: _, val }, snd) => { - let num_val = (*val << 4) | opr.to_native_tag(); - let fst = Place::Tree(LoanedMut::new(Tree::Num { val: num_val })); + (Term::Num { val }, snd) => { + let val = val.to_bits(); + let val = (val & 0xffff_fff0) | opr.to_native_tag(); + let fst = Place::Tree(LoanedMut::new(Tree::Num { val })); let node = self.new_opr(); self.link(fst, node.0); self.encode_term(snd, node.1); self.link(up, node.2); } // Put oper in snd - (fst, Term::Num { typ: _, val }) => { - let num_val = (*val << 4) | opr.to_native_tag(); - let snd = Place::Tree(LoanedMut::new(Tree::Num { val: num_val })); + (fst, Term::Num { val }) => { + let val = val.to_bits(); + let val = (val & 0xffff_fff0) | opr.to_native_tag(); + let snd = Place::Tree(LoanedMut::new(Tree::Num { val })); let node = self.new_opr(); self.encode_term(fst, node.0); self.link(snd, node.1); diff --git a/src/term/transform/desugar_match_defs.rs b/src/term/transform/desugar_match_defs.rs index bca1cb60..18881aa8 100644 --- a/src/term/transform/desugar_match_defs.rs +++ b/src/term/transform/desugar_match_defs.rs @@ -1,6 +1,6 @@ use crate::{ diagnostics::{Diagnostics, WarningType}, - term::{builtins, Adts, Constructors, Ctx, Definition, FanKind, Name, NumType, Pattern, Rule, Tag, Term}, + term::{builtins, Adts, Constructors, Ctx, Definition, FanKind, Name, Num, Pattern, Rule, Tag, Term}, }; use std::collections::{BTreeSet, HashSet}; @@ -280,7 +280,7 @@ fn num_rule( if let Some(var) = var { body = Term::Use { nam: Some(var.clone()), - val: Box::new(Term::Num { typ: NumType::U24, val: *num }), + val: Box::new(Term::Num { val: Num::U24(*num) }), nxt: Box::new(std::mem::take(&mut body)), }; } @@ -301,7 +301,7 @@ fn num_rule( let mut body = rule.body.clone(); if let Some(var) = var { let last_num = *nums.last().unwrap(); - let var_recovered = Term::add_num(Term::Var { nam: pred_var.clone() }, 1 + last_num, NumType::U24); + let var_recovered = Term::add_num(Term::Var { nam: pred_var.clone() }, Num::U24(1 + last_num)); body = Term::Use { nam: Some(var.clone()), val: Box::new(var_recovered), nxt: Box::new(body) }; } let rule = Rule { pats: rule.pats[1 ..].to_vec(), body }; @@ -319,10 +319,10 @@ fn num_rule( let val = if i > 0 { // switch arg = (pred +1 +num_i-1 - num_i) { 0: body_i; _: acc } // nums[i] >= nums[i-1]+1, so we do a sub here. - Term::sub_num(Term::Var { nam: pred_var.clone() }, nums[i] - 1 - nums[i - 1], NumType::U24) + Term::sub_num(Term::Var { nam: pred_var.clone() }, Num::U24(nums[i] - 1 - nums[i - 1])) } else { // switch arg = (arg -num_0) { 0: body_0; _: acc} - Term::sub_num(Term::Var { nam: arg.clone() }, nums[i], NumType::U24) + Term::sub_num(Term::Var { nam: arg.clone() }, Num::U24(nums[i])) }; Term::Swt { diff --git a/src/term/transform/fix_match_terms.rs b/src/term/transform/fix_match_terms.rs index 896f20b2..bfef1d3a 100644 --- a/src/term/transform/fix_match_terms.rs +++ b/src/term/transform/fix_match_terms.rs @@ -1,7 +1,7 @@ use crate::{ diagnostics::{Diagnostics, WarningType, ERR_INDENT_SIZE}, maybe_grow, - term::{Adts, Constructors, Ctx, MatchRule, Name, NumType, Term}, + term::{Adts, Constructors, Ctx, MatchRule, Name, Num, Term}, }; use std::collections::HashMap; @@ -105,9 +105,9 @@ impl Term { let n_nums = arms.len() - 1; for (i, arm) in arms.iter_mut().enumerate() { let orig = if i == n_nums { - Term::add_num(Term::Var { nam: pred.clone().unwrap() }, i as u32, NumType::U24) + Term::add_num(Term::Var { nam: pred.clone().unwrap() }, Num::U24(i as u32)) } else { - Term::Num { typ: NumType::U24, val: i as u32 } + Term::Num { val: Num::U24(i as u32) } }; *arm = Term::Use { nam: bnd.clone(), val: Box::new(orig), nxt: Box::new(std::mem::take(arm)) }; } diff --git a/tests/golden_tests/compile_term/f24_oper.hvm b/tests/golden_tests/compile_term/f24_oper.hvm new file mode 100644 index 00000000..c75c5151 --- /dev/null +++ b/tests/golden_tests/compile_term/f24_oper.hvm @@ -0,0 +1 @@ +(/ (* +124.0928 1.24) (+ 0.0 -235.12235)) \ No newline at end of file diff --git a/tests/golden_tests/compile_term/i24_oper.hvm b/tests/golden_tests/compile_term/i24_oper.hvm new file mode 100644 index 00000000..14995627 --- /dev/null +++ b/tests/golden_tests/compile_term/i24_oper.hvm @@ -0,0 +1 @@ +(* (+ +1 -1) (- -12 +14)) \ No newline at end of file diff --git a/tests/golden_tests/compile_term/number_too_large.hvm b/tests/golden_tests/compile_term/number_too_large.hvm new file mode 100644 index 00000000..172832ad --- /dev/null +++ b/tests/golden_tests/compile_term/number_too_large.hvm @@ -0,0 +1 @@ +0x10000000 \ No newline at end of file diff --git a/tests/golden_tests/compile_term/nums.hvm b/tests/golden_tests/compile_term/nums.hvm index 9c703be0..788167dc 100644 --- a/tests/golden_tests/compile_term/nums.hvm +++ b/tests/golden_tests/compile_term/nums.hvm @@ -1 +1 @@ -(+ 0xFFFF_FFFF (+ 0b101 1_000)) \ No newline at end of file +(+ 0xFF_FFFF (+ 0b101 1_000)) \ No newline at end of file diff --git a/tests/golden_tests/compile_term/wrong_nums.hvm b/tests/golden_tests/compile_term/wrong_nums.hvm index d34308fa..f13166fb 100644 --- a/tests/golden_tests/compile_term/wrong_nums.hvm +++ b/tests/golden_tests/compile_term/wrong_nums.hvm @@ -1 +1 @@ -(+ 0b0123456789 0FA) \ No newline at end of file +(+ 0b012345 0FA) \ No newline at end of file diff --git a/tests/snapshots/compile_term__f24_oper.hvm.snap b/tests/snapshots/compile_term__f24_oper.hvm.snap new file mode 100644 index 00000000..b41e0bd5 --- /dev/null +++ b/tests/snapshots/compile_term__f24_oper.hvm.snap @@ -0,0 +1,7 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/compile_term/f24_oper.hvm +--- +b + & $(1.240 $(:[/] $(a b))) ~ [*4583519] + & $(-235.121 a) ~ [+0] diff --git a/tests/snapshots/compile_term__i24_oper.hvm.snap b/tests/snapshots/compile_term__i24_oper.hvm.snap new file mode 100644 index 00000000..bae52882 --- /dev/null +++ b/tests/snapshots/compile_term__i24_oper.hvm.snap @@ -0,0 +1,7 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/compile_term/i24_oper.hvm +--- +b + & $(-1 $(:[*] $(a b))) ~ [+1] + & $(+14 a) ~ [-16777204] diff --git a/tests/snapshots/compile_term__number_too_large.hvm.snap b/tests/snapshots/compile_term__number_too_large.hvm.snap new file mode 100644 index 00000000..b9c40513 --- /dev/null +++ b/tests/snapshots/compile_term__number_too_large.hvm.snap @@ -0,0 +1,7 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/compile_term/number_too_large.hvm +--- +Errors: +Number literal outside of range for U24. + 1 | 0x10000000 diff --git a/tests/snapshots/compile_term__nums.hvm.snap b/tests/snapshots/compile_term__nums.hvm.snap index 9c8244da..49a8c343 100644 --- a/tests/snapshots/compile_term__nums.hvm.snap +++ b/tests/snapshots/compile_term__nums.hvm.snap @@ -3,5 +3,5 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/compile_term/nums.hvm --- b - & $(a b) ~ :[+16777215] + & $(a b) ~ [+16777215] & $(1000 a) ~ [+5] diff --git a/tests/snapshots/compile_term__wrong_nums.hvm.snap b/tests/snapshots/compile_term__wrong_nums.hvm.snap index 798b1f1e..c84cfc45 100644 --- a/tests/snapshots/compile_term__wrong_nums.hvm.snap +++ b/tests/snapshots/compile_term__wrong_nums.hvm.snap @@ -5,4 +5,4 @@ input_file: tests/golden_tests/compile_term/wrong_nums.hvm Errors: - expected: ')' - detected: - 1 | (+ 0b0123456789 0FA) + 1 | (+ 0b012345 0FA) diff --git a/tests/snapshots/examples__bubble_sort.hvm.snap b/tests/snapshots/examples__bubble_sort.hvm.snap index 2f041095..25addb44 100644 --- a/tests/snapshots/examples__bubble_sort.hvm.snap +++ b/tests/snapshots/examples__bubble_sort.hvm.snap @@ -2,4 +2,4 @@ source: tests/golden_tests.rs input_file: examples/bubble_sort.hvm --- -2721105 +5525035 diff --git a/tests/snapshots/examples__quick_sort.hvm.snap b/tests/snapshots/examples__quick_sort.hvm.snap index 7cfeb845..bf79282a 100644 --- a/tests/snapshots/examples__quick_sort.hvm.snap +++ b/tests/snapshots/examples__quick_sort.hvm.snap @@ -2,4 +2,4 @@ source: tests/golden_tests.rs input_file: examples/quick_sort.hvm --- -7311046 +12741879