diff --git a/CHANGELOG.md b/CHANGELOG.md index 2aa7cc98..2910523f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project does not adhere to a particular versioning scheme at this point. +## Unreleased + +### Fixed + +- Fixed readback of numeric operations. ([#467][gh-467]) + ## [0.2.35] - 2024-06-06 ### Changed @@ -84,6 +90,7 @@ and this project does not adhere to a particular versioning scheme at this point [gh-451]: https://github.com/HigherOrderCO/Bend/issues/451 [gh-465]: https://github.com/HigherOrderCO/Bend/issues/465 [gh-466]: https://github.com/HigherOrderCO/Bend/issues/466 +[gh-467]: https://github.com/HigherOrderCO/Bend/issues/467 [gh-479]: https://github.com/HigherOrderCO/Bend/issues/479 [gh-502]: https://github.com/HigherOrderCO/Bend/issues/502 [gh-526]: https://github.com/HigherOrderCO/Bend/issues/526 diff --git a/src/fun/net_to_term.rs b/src/fun/net_to_term.rs index 74bfee97..290e689e 100644 --- a/src/fun/net_to_term.rs +++ b/src/fun/net_to_term.rs @@ -2,7 +2,7 @@ use crate::{ diagnostics::{DiagnosticOrigin, Diagnostics, Severity}, fun::{term_to_net::Labels, Book, FanKind, Name, Num, Op, Pattern, Tag, Term}, maybe_grow, - net::{CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT}, + net::{BendLab, CtrKind, INet, NodeId, NodeKind, Port, SlotId, ROOT}, }; use hvm::hvm::Numb; use std::collections::{BTreeSet, HashMap, HashSet}; @@ -86,206 +86,15 @@ impl Reader<'_> { return Term::Var { nam: Name::new("...") }; } - let node = next.node(); + let node = next.node_id(); match &self.net.node(node).kind { - 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... - NodeKind::Ctr(CtrKind::Con(lab)) => match next.slot() { - // If we're visiting a port 0, then it is a tuple or a lambda. - 0 => { - if self.is_tup(node) { - // A tuple - let lft = self.read_term(self.net.enter_port(Port(node, 1))); - let rgt = self.read_term(self.net.enter_port(Port(node, 2))); - Term::Fan { fan: FanKind::Tup, tag: self.labels.con.to_tag(*lab), els: vec![lft, rgt] } - } else { - // A lambda - let nam = self.namegen.decl_name(self.net, Port(node, 1)); - let bod = self.read_term(self.net.enter_port(Port(node, 2))); - Term::Lam { - tag: self.labels.con.to_tag(*lab), - pat: Box::new(Pattern::Var(nam)), - bod: Box::new(bod), - } - } - } - // If we're visiting a port 1, then it is a variable. - 1 => Term::Var { nam: self.namegen.var_name(next) }, - // If we're visiting a port 2, then it is an application. - 2 => { - let fun = self.read_term(self.net.enter_port(Port(node, 0))); - let arg = self.read_term(self.net.enter_port(Port(node, 1))); - Term::App { tag: self.labels.con.to_tag(*lab), fun: Box::new(fun), arg: Box::new(arg) } - } - _ => unreachable!(), - }, - NodeKind::Mat => match next.slot() { - 2 => { - // Read the matched expression - let arg = self.read_term(self.net.enter_port(Port(node, 0))); - let bnd = if let Term::Var { nam } = &arg { nam.clone() } else { self.namegen.unique() }; - - // Read the pattern matching node - let sel_node = self.net.enter_port(Port(node, 1)).node(); - - // We expect the pattern matching node to be a CON - let sel_kind = &self.net.node(sel_node).kind; - if sel_kind != &NodeKind::Ctr(CtrKind::Con(None)) { - // TODO: Is there any case where we expect a different node type here on readback? - self.error(ReadbackError::InvalidNumericMatch); - return Term::Err; - } - - 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))); - // Call expand_generated in case of succ_term be a lifted term - succ_term.expand_generated(self.book, self.recursive_defs); - - // Succ term should be a lambda - let (zero, succ) = match &mut succ_term { - Term::Lam { pat, bod, .. } => { - if let Pattern::Var(nam) = pat.as_ref() { - let mut bod = std::mem::take(bod.as_mut()); - if let Some(nam) = nam { - bod.subst(nam, &Term::Var { nam: Name::new(format!("{bnd}-1")) }); - } - (zero_term, bod) - } else { - // Readback should never generate non-var patterns for lambdas. - self.error(ReadbackError::InvalidNumericMatch); - (zero_term, succ_term) - } - } - _ => { - self.error(ReadbackError::InvalidNumericMatch); - (zero_term, succ_term) - } - }; - Term::Swt { - arg: Box::new(arg), - bnd: Some(bnd), - with_arg: vec![], - with_bnd: vec![], - pred: None, - arms: vec![zero, succ], - } - } - _ => { - self.error(ReadbackError::InvalidNumericMatch); - Term::Err - } - }, + NodeKind::Era => Term::Era, + NodeKind::Ctr(CtrKind::Con(lab)) => self.read_con(next, *lab), + NodeKind::Swi => self.read_swi(next), NodeKind::Ref { def_name } => Term::Ref { nam: def_name.clone() }, - // If we're visiting a fan node... - NodeKind::Ctr(kind @ (Dup(_) | Tup(_))) => { - let (fan, lab) = match *kind { - Tup(lab) => (FanKind::Tup, lab), - Dup(lab) => (FanKind::Dup, Some(lab)), - _ => unreachable!(), - }; - match next.slot() { - // If we're visiting a port 0, then it is a pair. - 0 => { - // If this superposition is in a readback path with a paired Dup, - // we resolve it by splitting the two sup values into the two Dup variables. - // If we find that it's not paired with a Dup, we just keep the Sup as a term. - // The latter are all the early returns. - - if fan != FanKind::Dup { - return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan { - fan, - tag: self.labels[fan].to_tag(lab), - els: vec![fst, snd], - }); - } - - let Some(dup_paths) = &mut self.dup_paths else { - return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan { - fan, - tag: self.labels[fan].to_tag(lab), - els: vec![fst, snd], - }); - }; - - let stack = dup_paths.entry(lab.unwrap()).or_default(); - let Some(slot) = stack.pop() else { - return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan { - fan, - tag: self.labels[fan].to_tag(lab), - els: vec![fst, snd], - }); - }; - - // Found a paired Dup, so we "decay" the superposition according to the original direction we came from the Dup. - let term = self.read_term(self.net.enter_port(Port(node, slot))); - self.dup_paths.as_mut().unwrap().get_mut(&lab.unwrap()).unwrap().push(slot); - term - } - // If we're visiting a port 1 or 2, then it is a variable. - // Also, that means we found a dup, so we store it to read later. - 1 | 2 => { - // If doing non-linear readback, we also store dup paths to try to resolve them later. - if let Some(dup_paths) = &mut self.dup_paths { - if fan == FanKind::Dup { - dup_paths.entry(lab.unwrap()).or_default().push(next.slot()); - let term = self.read_term(self.net.enter_port(Port(node, 0))); - self.dup_paths.as_mut().unwrap().entry(lab.unwrap()).or_default().pop().unwrap(); - return term; - } - } - // Otherwise, just store the new dup/let tup and return the variable. - if self.seen_fans.insert(node) { - self.scope.insert(node); - } - Term::Var { nam: self.namegen.var_name(next) } - } - _ => unreachable!(), - } - } + NodeKind::Ctr(kind @ (Dup(_) | Tup(_))) => self.read_fan(next, *kind), NodeKind::Num { val } => num_from_bits_with_type(*val, *val), - NodeKind::Opr => match next.slot() { - 2 => { - let port0_node = self.net.enter_port(Port(node, 0)).node(); - let port0_kind = self.net.node(port0_node).kind.clone(); - // two oper in a row - if port0_kind == NodeKind::Opr { - // TODO: allow for nested oper - let opr_node = self.net.enter_port(Port(port0_node, 0)).node(); - let opr_kind = self.net.node(opr_node).kind.clone(); - let opr = if let NodeKind::Num { val } = opr_kind { - let typ = hvm::hvm::Numb::get_typ(&Numb(val)); - if typ != hvm::hvm::TY_SYM { - self.error(ReadbackError::InvalidNumericOp); - return Term::Err; - } - if let Some(op) = Op::from_native_tag(typ, NumType::U24) { - op - } else { - self.error(ReadbackError::InvalidNumericOp); - return Term::Err; - } - } else { - self.error(ReadbackError::InvalidNumericOp); - return Term::Err; - }; - let fst = self.read_term(self.net.enter_port(Port(port0_node, 1))); - let snd = self.read_term(self.net.enter_port(Port(node, 1))); - Term::Oper { opr, fst: Box::new(fst), snd: Box::new(snd) } - } else { - // TODO: Fix - self.error(ReadbackError::InvalidNumericOp); - Term::Err - } - } - _ => { - self.error(ReadbackError::InvalidNumericOp); - Term::Err - } - }, + NodeKind::Opr => self.read_opr(next), NodeKind::Rot => { self.error(ReadbackError::ReachedRoot); Term::Err @@ -294,6 +103,384 @@ impl Reader<'_> { }) } + /// Reads a term from a CON node. + /// Could be a lambda, an application, a CON tuple or a CON tuple elimination. + fn read_con(&mut self, next: Port, label: Option) -> Term { + let node = next.node_id(); + match next.slot() { + // If we're visiting a port 0, then it is a tuple or a lambda. + 0 => { + if self.is_tup(node) { + // A tuple + let lft = self.read_term(self.net.enter_port(Port(node, 1))); + let rgt = self.read_term(self.net.enter_port(Port(node, 2))); + Term::Fan { fan: FanKind::Tup, tag: self.labels.con.to_tag(label), els: vec![lft, rgt] } + } else { + // A lambda + let nam = self.namegen.decl_name(self.net, Port(node, 1)); + let bod = self.read_term(self.net.enter_port(Port(node, 2))); + Term::Lam { + tag: self.labels.con.to_tag(label), + pat: Box::new(Pattern::Var(nam)), + bod: Box::new(bod), + } + } + } + // If we're visiting a port 1, then it is a variable. + 1 => Term::Var { nam: self.namegen.var_name(next) }, + // If we're visiting a port 2, then it is an application. + 2 => { + let fun = self.read_term(self.net.enter_port(Port(node, 0))); + let arg = self.read_term(self.net.enter_port(Port(node, 1))); + Term::App { tag: self.labels.con.to_tag(label), fun: Box::new(fun), arg: Box::new(arg) } + } + _ => unreachable!(), + } + } + + /// Reads a fan term from a DUP node. + /// Could be a superposition, a duplication, a DUP tuple or a DUP tuple elimination. + fn read_fan(&mut self, next: Port, kind: CtrKind) -> Term { + let node = next.node_id(); + let (fan, lab) = match kind { + CtrKind::Tup(lab) => (FanKind::Tup, lab), + CtrKind::Dup(lab) => (FanKind::Dup, Some(lab)), + _ => unreachable!(), + }; + match next.slot() { + // If we're visiting a port 0, then it is a pair. + 0 => { + // If this superposition is in a readback path with a paired Dup, + // we resolve it by splitting the two sup values into the two Dup variables. + // If we find that it's not paired with a Dup, we just keep the Sup as a term. + // The latter are all the early returns. + + if fan != FanKind::Dup { + return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan { + fan, + tag: self.labels[fan].to_tag(lab), + els: vec![fst, snd], + }); + } + + let Some(dup_paths) = &mut self.dup_paths else { + return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan { + fan, + tag: self.labels[fan].to_tag(lab), + els: vec![fst, snd], + }); + }; + + let stack = dup_paths.entry(lab.unwrap()).or_default(); + let Some(slot) = stack.pop() else { + return self.decay_or_get_ports(node).unwrap_or_else(|(fst, snd)| Term::Fan { + fan, + tag: self.labels[fan].to_tag(lab), + els: vec![fst, snd], + }); + }; + + // Found a paired Dup, so we "decay" the superposition according to the original direction we came from the Dup. + let term = self.read_term(self.net.enter_port(Port(node, slot))); + self.dup_paths.as_mut().unwrap().get_mut(&lab.unwrap()).unwrap().push(slot); + term + } + // If we're visiting a port 1 or 2, then it is a variable. + // Also, that means we found a dup, so we store it to read later. + 1 | 2 => { + // If doing non-linear readback, we also store dup paths to try to resolve them later. + if let Some(dup_paths) = &mut self.dup_paths { + if fan == FanKind::Dup { + dup_paths.entry(lab.unwrap()).or_default().push(next.slot()); + let term = self.read_term(self.net.enter_port(Port(node, 0))); + self.dup_paths.as_mut().unwrap().entry(lab.unwrap()).or_default().pop().unwrap(); + return term; + } + } + // Otherwise, just store the new dup/let tup and return the variable. + if self.seen_fans.insert(node) { + self.scope.insert(node); + } + Term::Var { nam: self.namegen.var_name(next) } + } + _ => unreachable!(), + } + } + + /// Reads an Opr term from an OPR node. + fn read_opr(&mut self, next: Port) -> Term { + /// Read one of the argument ports of an operation. + fn add_arg( + reader: &mut Reader, + port: Port, + args: &mut Vec>, + types: &mut Vec, + ops: &mut Vec, + ) { + if let NodeKind::Num { val } = reader.net.node(port.node_id()).kind { + match hvm::hvm::Numb::get_typ(&Numb(val)) { + // Contains an operation + hvm::hvm::TY_SYM => { + ops.push(hvm::hvm::Numb(val).get_sym()); + } + // Contains a number with a type + typ @ hvm::hvm::TY_U24..=hvm::hvm::TY_F24 => { + types.push(typ); + args.push(Ok(val)); + } + // Contains a partially applied number with operation and no type + op @ hvm::hvm::OP_ADD.. => { + ops.push(op); + args.push(Ok(val)); + } + } + } else { + // Some other non-number argument + let term = reader.read_term(port); + args.push(Err(term)); + } + } + + /// Creates an Opr term from the arguments of the subnet of an OPR node. + fn opr_term_from_hvm_args( + args: &mut Vec>, + types: &mut Vec, + ops: &mut Vec, + is_flipped: bool, + ) -> Term { + let typ = match types.as_slice() { + [typ] => *typ, + // Use U24 as default number type + [] => hvm::hvm::TY_U24, + _ => { + // Too many types + return Term::Err; + } + }; + match (args.as_slice(), ops.as_slice()) { + ([arg1, arg2], [op]) => { + // Correct number of arguments + let arg1 = match arg1 { + Ok(val) => num_from_bits_with_type(*val, typ as u32), + Err(val) => val.clone(), + }; + let arg2 = match arg2 { + Ok(val) => num_from_bits_with_type(*val, typ as u32), + Err(val) => val.clone(), + }; + let (arg1, arg2) = if is_flipped ^ op_is_flipped(*op) { (arg2, arg1) } else { (arg1, arg2) }; + let Some(op) = op_from_native_tag(*op, typ) else { + // Invalid operator + return Term::Err; + }; + Term::Oper { opr: op, fst: Box::new(arg1), snd: Box::new(arg2) } + } + _ => { + // Invalid number of arguments/types/operators + Term::Err + } + } + } + + fn op_is_flipped(op: hvm::hvm::Tag) -> bool { + [hvm::hvm::FP_DIV, hvm::hvm::FP_REM, hvm::hvm::FP_SHL, hvm::hvm::FP_SHR, hvm::hvm::FP_SUB].contains(&op) + } + + fn op_from_native_tag(val: hvm::hvm::Tag, typ: hvm::hvm::Tag) -> Option { + let op = match val { + hvm::hvm::OP_ADD => Op::ADD, + hvm::hvm::OP_SUB => Op::SUB, + hvm::hvm::FP_SUB => Op::SUB, + hvm::hvm::OP_MUL => Op::MUL, + hvm::hvm::OP_DIV => Op::DIV, + hvm::hvm::FP_DIV => Op::DIV, + hvm::hvm::OP_REM => Op::REM, + hvm::hvm::FP_REM => Op::REM, + hvm::hvm::OP_EQ => Op::EQ, + hvm::hvm::OP_NEQ => Op::NEQ, + hvm::hvm::OP_LT => Op::LT, + hvm::hvm::OP_GT => Op::GT, + hvm::hvm::OP_AND => { + if typ == hvm::hvm::TY_F24 { + Op::ATN + } else { + Op::AND + } + } + hvm::hvm::OP_OR => { + if typ == hvm::hvm::TY_F24 { + Op::LOG + } else { + Op::OR + } + } + hvm::hvm::OP_XOR => { + if typ == hvm::hvm::TY_F24 { + Op::POW + } else { + Op::XOR + } + } + hvm::hvm::OP_SHL => Op::SHL, + hvm::hvm::FP_SHL => Op::SHL, + hvm::hvm::OP_SHR => Op::SHR, + hvm::hvm::FP_SHR => Op::SHR, + _ => return None, + }; + Some(op) + } + + let node = next.node_id(); + match next.slot() { + 2 => { + // If port1 has a partially applied number, the operation has 1 node. + // Port0 has arg1 and port1 has arg2. + // The operation is interpreted as being pre-flipped (if its a FP_, they cancel and don't flip). + let port1_kind = self.net.node(self.net.enter_port(Port(node, 1)).node_id()).kind.clone(); + if let NodeKind::Num { val } = port1_kind { + match hvm::hvm::Numb::get_typ(&Numb(val)) { + hvm::hvm::OP_ADD.. => { + let x1_port = self.net.enter_port(Port(node, 0)); + let x2_port = self.net.enter_port(Port(node, 1)); + let mut args = vec![]; + let mut types = vec![]; + let mut ops = vec![]; + add_arg(self, x1_port, &mut args, &mut types, &mut ops); + add_arg(self, x2_port, &mut args, &mut types, &mut ops); + let term = opr_term_from_hvm_args(&mut args, &mut types, &mut ops, true); + if let Term::Err = term { + // Since that function doesn't have access to the reader, add the error here. + self.error(ReadbackError::InvalidNumericOp); + } + return term; + } + _ => { + // Not a partially applied number, handle it in the next case + } + } + } + + // If port0 has a partially applied number, it also has 1 node. + // The operation is interpreted as not pre-flipped. + let port0_kind = self.net.node(self.net.enter_port(Port(node, 0)).node_id()).kind.clone(); + if let NodeKind::Num { val } = port0_kind { + match hvm::hvm::Numb::get_typ(&Numb(val)) { + hvm::hvm::OP_ADD.. => { + let x1_port = self.net.enter_port(Port(node, 0)); + let x2_port = self.net.enter_port(Port(node, 1)); + let mut args = vec![]; + let mut types = vec![]; + let mut ops = vec![]; + add_arg(self, x1_port, &mut args, &mut types, &mut ops); + add_arg(self, x2_port, &mut args, &mut types, &mut ops); + let term = opr_term_from_hvm_args(&mut args, &mut types, &mut ops, false); + if let Term::Err = term { + // Since that function doesn't have access to the reader, add the error here. + self.error(ReadbackError::InvalidNumericOp); + } + return term; + } + _ => { + // Not a partially applied number, handle it in the next case + } + } + } + + // Otherwise, the operation has 2 nodes. + // Read the top node port0 and 1, bottom node port1. + // Args are in that order, skipping the operation. + let bottom_id = node; + let top_id = self.net.enter_port(Port(bottom_id, 0)).node_id(); + if let NodeKind::Opr = self.net.node(top_id).kind { + let x1_port = self.net.enter_port(Port(top_id, 0)); + let x2_port = self.net.enter_port(Port(top_id, 1)); + let x3_port = self.net.enter_port(Port(bottom_id, 1)); + let mut args = vec![]; + let mut types = vec![]; + let mut ops = vec![]; + add_arg(self, x1_port, &mut args, &mut types, &mut ops); + add_arg(self, x2_port, &mut args, &mut types, &mut ops); + add_arg(self, x3_port, &mut args, &mut types, &mut ops); + let term = opr_term_from_hvm_args(&mut args, &mut types, &mut ops, false); + if let Term::Err = term { + self.error(ReadbackError::InvalidNumericOp); + } + term + } else { + // Port 0 was not an OPR node, invalid. + self.error(ReadbackError::InvalidNumericOp); + Term::Err + } + } + _ => { + // Entered from a port other than 2, invalid. + self.error(ReadbackError::InvalidNumericOp); + Term::Err + } + } + } + + /// Reads a switch term from a SWI node. + fn read_swi(&mut self, next: Port) -> Term { + let node = next.node_id(); + match next.slot() { + 2 => { + // Read the matched expression + let arg = self.read_term(self.net.enter_port(Port(node, 0))); + let bnd = if let Term::Var { nam } = &arg { nam.clone() } else { self.namegen.unique() }; + + // Read the pattern matching node + let sel_node = self.net.enter_port(Port(node, 1)).node_id(); + + // We expect the pattern matching node to be a CON + let sel_kind = &self.net.node(sel_node).kind; + if sel_kind != &NodeKind::Ctr(CtrKind::Con(None)) { + // TODO: Is there any case where we expect a different node type here on readback? + self.error(ReadbackError::InvalidNumericMatch); + return Term::Err; + } + + 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))); + // Call expand_generated in case of succ_term be a lifted term + succ_term.expand_generated(self.book, self.recursive_defs); + + // Succ term should be a lambda + let (zero, succ) = match &mut succ_term { + Term::Lam { pat, bod, .. } => { + if let Pattern::Var(nam) = pat.as_ref() { + let mut bod = std::mem::take(bod.as_mut()); + if let Some(nam) = nam { + bod.subst(nam, &Term::Var { nam: Name::new(format!("{bnd}-1")) }); + } + (zero_term, bod) + } else { + // Readback should never generate non-var patterns for lambdas. + self.error(ReadbackError::InvalidNumericMatch); + (zero_term, succ_term) + } + } + _ => { + self.error(ReadbackError::InvalidNumericMatch); + (zero_term, succ_term) + } + }; + Term::Swt { + arg: Box::new(arg), + bnd: Some(bnd), + with_arg: vec![], + with_bnd: vec![], + pred: None, + arms: vec![zero, succ], + } + } + _ => { + self.error(ReadbackError::InvalidNumericMatch); + Term::Err + } + } + } + /// Enters both ports 1 and 2 of a node. Returns a Term if it is /// possible to simplify the net, or the Terms on the two ports of the node. /// The two possible outcomes are always equivalent. @@ -375,7 +562,7 @@ impl Reader<'_> { if !matches!(self.net.node(node).kind, NodeKind::Ctr(CtrKind::Con(_))) { return false; } - if self.net.node(self.net.enter_port(Port(node, 1)).node()).kind == NodeKind::Era { + if self.net.node(self.net.enter_port(Port(node, 1)).node_id()).kind == NodeKind::Era { return false; } let mut wires = HashSet::new(); @@ -383,7 +570,7 @@ impl Reader<'_> { while let Some(port) = to_check.pop() { match port.slot() { 0 => { - let node = port.node(); + let node = port.node_id(); let lft = self.net.enter_port(Port(node, 1)); let rgt = self.net.enter_port(Port(node, 2)); to_check.push(lft); @@ -404,49 +591,17 @@ impl Reader<'_> { } } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum NumType { - U24 = 1, - _I24 = 2, - F24 = 3, -} +/* Utils for numbers and numeric operations */ -impl Op { - fn from_native_tag(val: hvm::hvm::Tag, typ: NumType) -> Option { - let op = match val { - hvm::hvm::OP_ADD => Op::ADD, - hvm::hvm::OP_SUB => Op::SUB, - hvm::hvm::OP_MUL => Op::MUL, - hvm::hvm::OP_DIV => Op::DIV, - hvm::hvm::OP_REM => Op::REM, - hvm::hvm::OP_EQ => Op::EQ, - hvm::hvm::OP_NEQ => Op::NEQ, - hvm::hvm::OP_LT => Op::LT, - hvm::hvm::OP_GT => Op::GT, - hvm::hvm::OP_AND => { - if typ == NumType::F24 { - Op::ATN - } else { - Op::AND - } - } - hvm::hvm::OP_OR => { - if typ == NumType::F24 { - Op::LOG - } else { - Op::OR - } - } - hvm::hvm::OP_XOR => { - if typ == NumType::F24 { - Op::POW - } else { - Op::XOR - } - } - _ => return None, - }; - Some(op) +/// From an hvm number carrying the value and another carrying the type, return a Num term. +fn num_from_bits_with_type(val: u32, typ: u32) -> Term { + match hvm::hvm::Numb::get_typ(&Numb(typ)) { + // No type information, assume u24 by default + hvm::hvm::TY_SYM => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) }, + hvm::hvm::TY_U24 => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) }, + hvm::hvm::TY_I24 => Term::Num { val: Num::I24(Numb::get_i24(&Numb(val))) }, + hvm::hvm::TY_F24 => Term::Num { val: Num::F24(Numb::get_f24(&Numb(val))) }, + _ => Term::Err, } } @@ -510,17 +665,6 @@ impl Term { } } -fn num_from_bits_with_type(val: u32, typ: u32) -> Term { - match hvm::hvm::Numb::get_typ(&Numb(typ)) { - // No type information, assume u24 by default - hvm::hvm::TY_SYM => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) }, - hvm::hvm::TY_U24 => Term::Num { val: Num::U24(Numb::get_u24(&Numb(val))) }, - hvm::hvm::TY_I24 => Term::Num { val: Num::I24(Numb::get_i24(&Numb(val))) }, - hvm::hvm::TY_F24 => Term::Num { val: Num::F24(Numb::get_f24(&Numb(val))) }, - _ => Term::Err, - } -} - /* Variable name generation */ #[derive(Default)] @@ -543,7 +687,7 @@ impl NameGen { fn decl_name(&mut self, net: &INet, var_port: Port) -> Option { // 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; + let var_kind = &net.node(var_use.node_id()).kind; (*var_kind != NodeKind::Era).then(|| self.var_name(var_port)) } diff --git a/src/net/hvm_to_net.rs b/src/net/hvm_to_net.rs index 937bb8a3..18f7c189 100644 --- a/src/net/hvm_to_net.rs +++ b/src/net/hvm_to_net.rs @@ -97,7 +97,7 @@ fn tree_to_inodes(tree: &Tree, tree_root: String, net_root: &str, n_vars: &mut N inodes.push(INode { kind, ports: [subtree_root, fst, snd] }); } Tree::Swi { fst, snd } => { - let kind = NodeKind::Mat; + let kind = NodeKind::Swi; let fst = process_node_subtree(fst, net_root, &mut subtrees, n_vars); let snd = process_node_subtree(snd, net_root, &mut subtrees, n_vars); inodes.push(INode { kind, ports: [subtree_root, fst, snd] }); diff --git a/src/net/mod.rs b/src/net/mod.rs index 5e8082c7..17ff1e3f 100644 --- a/src/net/mod.rs +++ b/src/net/mod.rs @@ -36,7 +36,7 @@ pub enum NodeKind { /// Numeric operations Opr, /// Pattern matching on numbers - Mat, + Swi, } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -91,7 +91,7 @@ impl INet { /// Returns the value stored at a port, the port on the other side of the given one. pub fn enter_port(&self, port: Port) -> Port { - self.node(port.node()).port(port.slot()) + self.node(port.node_id()).port(port.slot()) } /// Links two ports. @@ -102,7 +102,7 @@ impl INet { /// Sets a port to point to another port pub fn set(&mut self, src: Port, dst: Port) { - *self.nodes[src.node() as usize].port_mut(src.slot()) = dst; + *self.nodes[src.node_id() as usize].port_mut(src.slot()) = dst; } } @@ -140,7 +140,7 @@ impl Node { impl Port { /// Returns the node address of a port. - pub fn node(self) -> NodeId { + pub fn node_id(self) -> NodeId { self.0 } diff --git a/tests/golden_tests/run_file/readback_num_ops.bend b/tests/golden_tests/run_file/readback_num_ops.bend new file mode 100644 index 00000000..e141fc3e --- /dev/null +++ b/tests/golden_tests/run_file/readback_num_ops.bend @@ -0,0 +1,9 @@ +main = [ + @a (+ a 1), + @a (+ 1 a), + @a @b (+ a b), + @a (- a 1), + @a (- 1 a), + @a @b @c (+ a (- b c)) + @a @b @c (+ (- a b) c) +] diff --git a/tests/snapshots/readback_hvm__addition.bend.snap b/tests/snapshots/readback_hvm__addition.bend.snap index 2aec60d2..deea1e2c 100644 --- a/tests/snapshots/readback_hvm__addition.bend.snap +++ b/tests/snapshots/readback_hvm__addition.bend.snap @@ -2,8 +2,4 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/readback_hvm/addition.bend --- -Warnings: -During readback: - Encountered an invalid numeric operation. - - +(+ 2 1) diff --git a/tests/snapshots/readback_hvm__invalid_op2_op2.bend.snap b/tests/snapshots/readback_hvm__invalid_op2_op2.bend.snap index 0c9be285..93077723 100644 --- a/tests/snapshots/readback_hvm__invalid_op2_op2.bend.snap +++ b/tests/snapshots/readback_hvm__invalid_op2_op2.bend.snap @@ -4,6 +4,6 @@ input_file: tests/golden_tests/readback_hvm/invalid_op2_op2.bend --- Warnings: During readback: - Encountered an invalid numeric operation. + Encountered an invalid numeric operation. (3 occurrences) λa diff --git a/tests/snapshots/readback_hvm__tup_add.bend.snap b/tests/snapshots/readback_hvm__tup_add.bend.snap index 5269e2d8..fa2ac817 100644 --- a/tests/snapshots/readback_hvm__tup_add.bend.snap +++ b/tests/snapshots/readback_hvm__tup_add.bend.snap @@ -2,8 +2,4 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/readback_hvm/tup_add.bend --- -Warnings: -During readback: - Encountered an invalid numeric operation. - - +(+ 1 2) diff --git a/tests/snapshots/run_file__lam_op2.bend.snap b/tests/snapshots/run_file__lam_op2.bend.snap index 2f67812e..ccf5836b 100644 --- a/tests/snapshots/run_file__lam_op2.bend.snap +++ b/tests/snapshots/run_file__lam_op2.bend.snap @@ -3,15 +3,7 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/run_file/lam_op2.bend --- NumScott: -Warnings: -During readback: - Encountered an invalid numeric operation. - -λa +λa (+ 2 a) Scott: -Warnings: -During readback: - Encountered an invalid numeric operation. - -λa +λa (+ 2 a) diff --git a/tests/snapshots/run_file__lam_op2_nested.bend.snap b/tests/snapshots/run_file__lam_op2_nested.bend.snap index 0e270043..455b9dac 100644 --- a/tests/snapshots/run_file__lam_op2_nested.bend.snap +++ b/tests/snapshots/run_file__lam_op2_nested.bend.snap @@ -3,15 +3,7 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/run_file/lam_op2_nested.bend --- NumScott: -Warnings: -During readback: - Encountered an invalid numeric operation. - -λa +λa (+ (* a a) (+ 3 (+ 2 a))) Scott: -Warnings: -During readback: - Encountered an invalid numeric operation. - -λa +λa (+ (* a a) (+ 3 (+ 2 a))) diff --git a/tests/snapshots/run_file__linearize_match.bend.snap b/tests/snapshots/run_file__linearize_match.bend.snap index c9140aa3..030f7d0d 100644 --- a/tests/snapshots/run_file__linearize_match.bend.snap +++ b/tests/snapshots/run_file__linearize_match.bend.snap @@ -3,15 +3,7 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/run_file/linearize_match.bend --- NumScott: -Warnings: -During readback: - Encountered an invalid numeric operation. - -λa switch a = a { 0: λb b; _: λd ; } +λa switch a = a { 0: λb b; _: λd (+ a-1 d); } Scott: -Warnings: -During readback: - Encountered an invalid numeric operation. - -λa switch a = a { 0: λb b; _: λd ; } +λa switch a = a { 0: λb b; _: λd (+ a-1 d); } diff --git a/tests/snapshots/run_file__match_mult_linearization.bend.snap b/tests/snapshots/run_file__match_mult_linearization.bend.snap index ba6ffeec..f2cf1801 100644 --- a/tests/snapshots/run_file__match_mult_linearization.bend.snap +++ b/tests/snapshots/run_file__match_mult_linearization.bend.snap @@ -3,15 +3,7 @@ source: tests/golden_tests.rs input_file: tests/golden_tests/run_file/match_mult_linearization.bend --- NumScott: -Warnings: -During readback: - Encountered an invalid numeric operation. (2 occurrences) - -λa switch a = a { 0: λb λc λd ; _: λf λg λh ; } +λa switch a = a { 0: λb λc λd (+ (+ b c) d); _: λf λg λh (+ (+ (+ a-1 f) g) h); } Scott: -Warnings: -During readback: - Encountered an invalid numeric operation. (2 occurrences) - -λa switch a = a { 0: λb λc λd ; _: λf λg λh ; } +λa switch a = a { 0: λb λc λd (+ (+ b c) d); _: λf λg λh (+ (+ (+ a-1 f) g) h); } diff --git a/tests/snapshots/run_file__readback_num_ops.bend.snap b/tests/snapshots/run_file__readback_num_ops.bend.snap new file mode 100644 index 00000000..ff15896b --- /dev/null +++ b/tests/snapshots/run_file__readback_num_ops.bend.snap @@ -0,0 +1,9 @@ +--- +source: tests/golden_tests.rs +input_file: tests/golden_tests/run_file/readback_num_ops.bend +--- +NumScott: +[λb (+ 1 b), λd (+ 1 d), λf λg (+ f g), λi (- i 1), λk (- 1 k), λm λn λo (+ m (- n o)), λq λr λs (+ (- q r) s)] + +Scott: +[λb (+ 1 b), λd (+ 1 d), λf λg (+ f g), λi (- i 1), λk (- 1 k), λm λn λo (+ m (- n o)), λq λr λs (+ (- q r) s)]