#![allow(clippy::new_without_default)] // annotating structs annotated with #[bitfield] doesn't work use crate::arena::*; use crate::atom_table::*; use crate::offset_table::*; use crate::parser::char_reader::*; use crate::types::HeapCellValueTag; use std::cell::{Cell, Ref, RefCell, RefMut}; use std::fmt; use std::hash::Hash; use std::hash::Hasher; use std::io::{Error as IOError, ErrorKind}; use std::ops::Not; use std::ops::RangeInclusive; use std::ops::{Deref, Neg}; use std::rc::Rc; use std::sync::Arc; use std::vec::Vec; use dashu::Integer; use dashu::Rational; use fxhash::FxBuildHasher; use indexmap::IndexMap; use scryer_modular_bitfield::error::OutOfBounds; use scryer_modular_bitfield::prelude::*; pub type Specifier = u32; pub const MAX_ARITY: usize = 255; #[allow(clippy::upper_case_acronyms)] #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum OpDeclSpec { XFX = 0x0001, XFY = 0x0002, YFX = 0x0004, XF = 0x0010, YF = 0x0020, FX = 0x0040, FY = 0x0080, } pub use OpDeclSpec::*; impl OpDeclSpec { pub const fn value(self) -> u32 { self as u32 } pub fn get_spec(self) -> Atom { match self { XFX => atom!("xfx"), XFY => atom!("xfy"), YFX => atom!("yfx"), FX => atom!("fx"), FY => atom!("fy"), XF => atom!("xf"), YF => atom!("yf"), } } pub const fn is_prefix(self) -> bool { matches!(self, Self::FX | Self::FY) } pub const fn is_postfix(self) -> bool { matches!(self, Self::XF | Self::YF) } pub const fn is_infix(self) -> bool { matches!(self, Self::XFX | Self::XFY | Self::YFX) } pub const fn is_strict_left(self) -> bool { matches!(self, Self::XFX | Self::XFY | Self::XF) } pub const fn is_strict_right(self) -> bool { matches!(self, Self::XFX | Self::YFX | Self::FX) } #[inline(always)] pub(crate) fn fixity(self) -> Fixity { match self { XFY | XFX | YFX => Fixity::In, XF | YF => Fixity::Post, FX | FY => Fixity::Pre, } } } impl From for u8 { fn from(value: OpDeclSpec) -> Self { value as u8 } } impl From for u32 { fn from(value: OpDeclSpec) -> Self { value as u32 } } impl TryFrom for OpDeclSpec { type Error = (); fn try_from(value: u8) -> Result { Ok(match value { 0x0001 => XFX, 0x0002 => XFY, 0x0004 => YFX, 0x0010 => XF, 0x0020 => YF, 0x0040 => FX, 0x0080 => FY, _ => return Err(()), }) } } impl TryFrom for OpDeclSpec { type Error = (); fn try_from(value: Atom) -> Result { Ok(match value { atom!("xfx") => Self::XFX, atom!("xfy") => Self::XFY, atom!("yfx") => Self::YFX, atom!("fx") => Self::FX, atom!("fy") => Self::FY, atom!("xf") => Self::XF, atom!("yf") => Self::YF, _ => return Err(()), }) } } pub const DELIMITER: u32 = 0x0100; pub const TERM: u32 = 0x1000; pub const LTERM: u32 = 0x3000; pub const BTERM: u32 = 0x11000; pub const NEGATIVE_SIGN: u32 = 0x0200; macro_rules! fixnum { ($wrapper:tt, $n:expr, $arena:expr) => { Fixnum::build_with_checked($n) .map(<$wrapper>::Fixnum) .unwrap_or_else(|_| <$wrapper>::Integer(arena_alloc!(Integer::from($n), $arena))) }; } macro_rules! is_term { ($x:expr) => { ($x as u32 & $crate::parser::ast::TERM) != 0 || is_negate!($x) }; } macro_rules! is_lterm { ($x:expr) => { ($x as u32 & $crate::parser::ast::LTERM) != 0 || is_negate!($x) }; } macro_rules! is_op { ($x:expr) => { $x as u32 & ($crate::parser::ast::XF as u32 | $crate::parser::ast::YF as u32 | $crate::parser::ast::FX as u32 | $crate::parser::ast::FY as u32 | $crate::parser::ast::XFX as u32 | $crate::parser::ast::XFY as u32 | $crate::parser::ast::YFX as u32) != 0 }; } macro_rules! is_negate { ($x:expr) => { ($x as u32 & $crate::parser::ast::NEGATIVE_SIGN) != 0 }; } macro_rules! is_prefix { ($x:expr) => { $x as u32 & ($crate::parser::ast::FX as u32 | $crate::parser::ast::FY as u32) != 0 }; } macro_rules! is_infix { ($x:expr) => { ($x as u32 & ($crate::parser::ast::XFX as u32 | $crate::parser::ast::XFY as u32 | $crate::parser::ast::YFX as u32)) != 0 }; } macro_rules! is_xfx { ($x:expr) => { ($x as u32 & $crate::parser::ast::XFX as u32) != 0 }; } macro_rules! is_xfy { ($x:expr) => { ($x as u32 & $crate::parser::ast::XFY as u32) != 0 }; } macro_rules! is_yfx { ($x:expr) => { ($x as u32 & $crate::parser::ast::YFX as u32) != 0 }; } macro_rules! is_yf { ($x:expr) => { ($x as u32 & $crate::parser::ast::YF as u32) != 0 }; } macro_rules! is_xf { ($x:expr) => { ($x as u32 & $crate::parser::ast::XF as u32) != 0 }; } macro_rules! is_fx { ($x:expr) => { ($x as u32 & $crate::parser::ast::FX as u32) != 0 }; } macro_rules! is_fy { ($x:expr) => { ($x as u32 & $crate::parser::ast::FY as u32) != 0 }; } #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum RegType { Perm(usize), Temp(usize), } impl Default for RegType { fn default() -> Self { RegType::Temp(0) } } impl RegType { pub fn reg_num(self) -> usize { match self { RegType::Perm(reg_num) | RegType::Temp(reg_num) => reg_num, } } pub fn is_perm(self) -> bool { matches!(self, RegType::Perm(_)) } } impl fmt::Display for RegType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { RegType::Perm(val) => write!(f, "Y{val}"), RegType::Temp(val) => write!(f, "X{val}"), } } } #[derive(Debug, PartialEq, Eq, Clone, Copy)] pub enum VarReg { ArgAndNorm(RegType, usize), Norm(RegType), } impl VarReg { pub fn norm(self) -> RegType { match self { VarReg::ArgAndNorm(reg, _) | VarReg::Norm(reg) => reg, } } } impl fmt::Display for VarReg { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { VarReg::Norm(RegType::Perm(reg)) => write!(f, "Y{reg}"), VarReg::Norm(RegType::Temp(reg)) => write!(f, "X{reg}"), VarReg::ArgAndNorm(RegType::Perm(reg), arg) => write!(f, "Y{reg} A{arg}"), VarReg::ArgAndNorm(RegType::Temp(reg), arg) => write!(f, "X{reg} A{arg}"), } } } impl Default for VarReg { fn default() -> Self { VarReg::Norm(RegType::default()) } } macro_rules! temp_v { ($x:expr) => { $crate::parser::ast::RegType::Temp($x) }; } #[bitfield] #[derive(Copy, Clone, Debug, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct OpDesc { prec: B11, spec: B8, #[allow(unused)] padding: B13, } impl OpDesc { #[inline] pub fn build_with(prec: u16, spec: OpDeclSpec) -> Self { OpDesc::new().with_spec(spec as u8).with_prec(prec) } #[inline] pub fn get(self) -> (u16, OpDeclSpec) { (self.prec(), self.get_spec()) } pub fn set(&mut self, prec: u16, spec: OpDeclSpec) { self.set_prec(prec); self.set_spec(spec as u8); } #[inline] pub fn get_prec(self) -> u16 { self.prec() } #[inline] pub fn get_spec(self) -> OpDeclSpec { OpDeclSpec::try_from(self.spec()).expect("OpDecl always contains a valud OpDeclSpec") } #[inline] pub fn arity(self) -> usize { if !self.get_spec().is_infix() { 1 } else { 2 } } } // name and fixity -> operator type and precedence. pub type OpDir = IndexMap<(Atom, Fixity), OpDesc, FxBuildHasher>; #[derive(Debug, Default, Clone, Copy)] pub struct MachineFlags { pub double_quotes: DoubleQuotes, pub unknown: Unknown, } #[derive(Debug, Default, Clone, Copy, PartialEq)] pub enum DoubleQuotes { Atom, #[default] Chars, Codes, } impl DoubleQuotes { pub fn is_chars(self) -> bool { matches!(self, DoubleQuotes::Chars) } pub fn is_codes(self) -> bool { matches!(self, DoubleQuotes::Codes) } } #[derive(Debug, Clone, Copy, Default)] pub enum Unknown { #[default] Error, Fail, Warn, } pub fn default_op_dir() -> OpDir { let mut op_dir = OpDir::with_hasher(FxBuildHasher::default()); op_dir.insert((atom!(":-"), Fixity::In), OpDesc::build_with(1200, XFX)); op_dir.insert((atom!(":-"), Fixity::Pre), OpDesc::build_with(1200, FX)); op_dir.insert((atom!("?-"), Fixity::Pre), OpDesc::build_with(1200, FX)); op_dir.insert((atom!(","), Fixity::In), OpDesc::build_with(1000, XFY)); op_dir } #[derive(Debug, Copy, Clone)] pub enum ArithmeticError { NonEvaluableFunctor(Literal, usize), UninstantiatedVar, } #[allow(dead_code)] #[derive(Debug)] pub enum ParserError { BackQuotedString(usize, usize), IO(IOError), IncompleteReduction(usize, usize), InfiniteFloat(usize, usize), InvalidSingleQuotedCharacter(char), LexicalError(lexical::Error), MissingQuote(usize, usize), NonPrologChar(usize, usize), ParseBigInt(usize, usize), UnexpectedChar(char, usize, usize), // UnexpectedEOF, Utf8Error(usize, usize), } impl ParserError { pub fn line_and_col_num(&self) -> Option<(usize, usize)> { match self { &ParserError::BackQuotedString(line_num, col_num) | &ParserError::IncompleteReduction(line_num, col_num) | &ParserError::InfiniteFloat(line_num, col_num) | &ParserError::MissingQuote(line_num, col_num) | &ParserError::NonPrologChar(line_num, col_num) | &ParserError::ParseBigInt(line_num, col_num) | &ParserError::UnexpectedChar(_, line_num, col_num) | &ParserError::Utf8Error(line_num, col_num) => Some((line_num, col_num)), _ => None, } } pub fn as_atom(&self) -> Atom { match self { ParserError::BackQuotedString(..) => atom!("back_quoted_string"), ParserError::IncompleteReduction(..) => atom!("incomplete_reduction"), ParserError::InvalidSingleQuotedCharacter(..) => { atom!("invalid_single_quoted_character") } ParserError::InfiniteFloat(..) => { atom!("infinite_float") } ParserError::IO(e) if e.kind() == ErrorKind::UnexpectedEof => { atom!("unexpected_end_of_file") } ParserError::IO(e) if e.kind() == ErrorKind::InvalidData => { atom!("invalid_data") } ParserError::IO(_) => atom!("input_output_error"), ParserError::LexicalError(_) => atom!("lexical_error"), ParserError::MissingQuote(..) => atom!("missing_quote"), ParserError::NonPrologChar(..) => atom!("non_prolog_character"), ParserError::ParseBigInt(..) => atom!("cannot_parse_big_int"), ParserError::UnexpectedChar(..) => atom!("unexpected_char"), ParserError::Utf8Error(..) => atom!("utf8_conversion_error"), } } #[inline] pub fn unexpected_eof() -> Self { ParserError::IO(std::io::Error::from(ErrorKind::UnexpectedEof)) } #[inline] pub fn is_unexpected_eof(&self) -> bool { if let ParserError::IO(e) = self { e.kind() == ErrorKind::UnexpectedEof } else { false } } } impl From for ParserError { fn from(e: lexical::Error) -> ParserError { ParserError::LexicalError(e) } } impl From for ParserError { fn from(e: IOError) -> ParserError { ParserError::IO(e) } } impl From<&IOError> for ParserError { fn from(error: &IOError) -> ParserError { if error.get_ref().filter(|e| e.is::()).is_some() { ParserError::Utf8Error(0, 0) } else { ParserError::IO(error.kind().into()) } } } #[derive(Debug, Clone, Copy)] pub struct CompositeOpDir<'a, 'b> { pub primary_op_dir: Option<&'b OpDir>, pub secondary_op_dir: &'a OpDir, } impl<'a, 'b> CompositeOpDir<'a, 'b> { #[inline] pub fn new(secondary_op_dir: &'a OpDir, primary_op_dir: Option<&'b OpDir>) -> Self { CompositeOpDir { primary_op_dir, secondary_op_dir, } } #[inline] pub(crate) fn get(&self, name: Atom, fixity: Fixity) -> Option { let entry = if let Some(primary_op_dir) = &self.primary_op_dir { primary_op_dir.get(&(name, fixity)) } else { None }; entry .or_else(move || self.secondary_op_dir.get(&(name, fixity))) .cloned() } } #[derive(Debug, Clone, Copy, Eq, Hash, PartialEq, PartialOrd, Ord)] pub enum Fixity { In, Post, Pre, } #[bitfield] #[repr(u64)] #[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)] pub struct Fixnum { num: B56, #[allow(unused)] f: bool, #[allow(unused)] m: bool, #[allow(unused)] tag: B6, } mod private { use dashu::Integer; pub(crate) trait FitsInFixnumSeal {} pub(crate) trait MightNotFitInFixnumSeal {} macro_rules! impl_fits_in_fixnum { ($t:ty) => { impl $crate::parser::ast::private::FitsInFixnumSeal for $t {} impl $crate::parser::ast::FitsInFixnum for $t { fn into_i56(self) -> i64 { self.into() } } }; } impl_fits_in_fixnum!(u8); impl_fits_in_fixnum!(i8); impl_fits_in_fixnum!(u16); impl_fits_in_fixnum!(i16); impl_fits_in_fixnum!(u32); impl_fits_in_fixnum!(i32); impl FitsInFixnumSeal for char {} impl super::FitsInFixnum for char { fn into_i56(self) -> i64 { u32::from(self) as i64 } } impl MightNotFitInFixnumSeal for i64 {} impl MightNotFitInFixnumSeal for &Integer {} impl MightNotFitInFixnumSeal for Integer {} impl MightNotFitInFixnumSeal for usize {} } #[allow(private_bounds)] pub trait FitsInFixnum: private::FitsInFixnumSeal { fn into_i56(self) -> i64; } #[allow(private_bounds)] pub trait MightNotFitInFixnum: private::MightNotFitInFixnumSeal { fn try_into_i56(self) -> Option; } impl MightNotFitInFixnum for T where T: private::MightNotFitInFixnumSeal + TryInto, { fn try_into_i56(self) -> Option { let val = self.try_into().ok()?; if Fixnum::RANGE.contains(&val) { Some(val) } else { None } } } impl Fixnum { pub(crate) const MIN: i64 = -(1 << 55); pub(crate) const MAX: i64 = (1 << 55) - 1; const RANGE: RangeInclusive = Self::MIN..=Self::MAX; // if you have a type that is not guaranteed to fit use `Fixnum::build_with_checked` or `Fixnum::build_with_unchecked` instead #[inline] pub fn build_with(num: impl FitsInFixnum) -> Self { // Safety: FitsInFixnum is only implemented by types that only have valid values // and FitsInFixnumSeal ensures no one outside this crate can violate that unsafe { Self::build_with_unchecked(num.into_i56()) } } #[inline] pub unsafe fn build_with_unchecked(num: i64) -> Self { debug_assert!( Self::RANGE.contains(&num), "{num} should be in the range {}..={}", Self::MIN, Self::MAX ); Fixnum::new() .with_num(u64::from_ne_bytes(num.to_ne_bytes()) & ((1 << 56) - 1)) .with_tag(HeapCellValueTag::Fixnum as u8) .with_m(false) .with_f(false) } #[inline] pub fn as_cutpoint(self) -> Self { self.with_tag(HeapCellValueTag::CutPoint as u8) } #[inline] pub fn get_tag(&self) -> HeapCellValueTag { use scryer_modular_bitfield::Specifier; HeapCellValueTag::from_bytes(self.tag()).unwrap() } // if you have a type that is guaranteed to fit use `Fixnum::build_with` instead #[inline] pub fn build_with_checked(num: impl MightNotFitInFixnum) -> Result { Ok(unsafe { // Safety: all MightNotFitInFixnum impls return None when the value is out-of-bounds // and MightNotFitInFixnumSeal ensures no one outside this crate can violate that Self::build_with_unchecked(num.try_into_i56().ok_or(OutOfBounds {})?) }) } #[inline] pub fn get_num(self) -> i64 { let n = self.num() as i64; let (n, overflowed) = (n << 8).overflowing_shr(8); debug_assert!(!overflowed); n } pub fn checked_abs(self) -> Option { Self::build_with_checked(self.get_num().abs()).ok() } } impl Neg for Fixnum { type Output = Self; #[inline] fn neg(self) -> Self::Output { // Safety: the truncating behaviour is correct unsafe { Self::build_with_unchecked(-self.get_num()) } } } impl Not for Fixnum { type Output = Self; #[inline] fn not(self) -> Self::Output { // Safety: the truncating behaviour is correct unsafe { Self::build_with_unchecked(!self.get_num()) } } } #[derive(Debug, Copy, Clone)] pub enum Literal { Atom(Atom), CodeIndexOffset(CodeIndexOffset), Fixnum(Fixnum), Integer(TypedArenaPtr), Rational(TypedArenaPtr), F64Offset(F64Offset), } /* impl From> for Literal { #[inline(always)] fn from(ptr: F64Ptr) -> Literal { Literal::Float(ptr.as_offset()) } } impl fmt::Display for Literal { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Literal::Atom(ref atom) => { write!(f, "{}", atom.flat_index()) } Literal::CodeIndexOffset(i) => write!(f, "{}", *i), Literal::Fixnum(n) => write!(f, "{}", n.get_num()), Literal::Integer(ref n) => write!(f, "{}", n), Literal::Rational(ref n) => write!(f, "{}", n), Literal::FloatOffset(ref n) => write!(f, "{}", *n), } } } */ impl Literal { pub fn as_atom(&self, atom_tbl: &Arc) -> Option { match self { Literal::Atom(atom) => Some(atom.defrock_brackets(atom_tbl)), _ => None, } } } #[derive(Debug, Clone, PartialEq, Eq)] pub struct VarPtr(Rc>); impl Hash for VarPtr { #[inline(always)] fn hash(&self, hasher: &mut H) { self.borrow().hash(hasher) } } impl Deref for VarPtr { type Target = RefCell; #[inline(always)] fn deref(&self) -> &Self::Target { self.0.deref() } } impl VarPtr { #[inline(always)] pub(crate) fn borrow(&self) -> Ref<'_, Var> { self.0.borrow() } #[inline(always)] pub(crate) fn borrow_mut(&self) -> RefMut<'_, Var> { self.0.borrow_mut() } pub(crate) fn to_var_num(&self) -> Option { match *self.borrow() { Var::Generated(var_num) => Some(var_num), _ => None, } } pub(crate) fn set(&self, var: Var) { let mut var_ref = self.borrow_mut(); *var_ref = var; } } impl From for VarPtr { #[inline(always)] fn from(value: Var) -> VarPtr { VarPtr(Rc::new(RefCell::new(value))) } } impl From for VarPtr { #[inline(always)] fn from(value: String) -> VarPtr { VarPtr::from(Var::from(value)) } } impl From<&str> for VarPtr { #[inline(always)] fn from(value: &str) -> VarPtr { VarPtr::from(value.to_owned()) } } #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub enum Var { Generated(usize), InSitu(usize), Named(Rc), } impl From for Var { #[inline(always)] fn from(value: String) -> Var { Var::Named(Rc::new(value)) } } impl From<&str> for Var { #[inline(always)] fn from(value: &str) -> Var { Var::Named(Rc::new(value.to_owned())) } } impl Var { #[allow(clippy::inherent_to_string)] #[inline(always)] pub fn to_string(&self) -> String { match self { Var::InSitu(n) | Var::Generated(n) => format!("_{n}"), Var::Named(value) => value.as_ref().clone(), } } } #[derive(Debug, Clone)] pub enum Term { AnonVar, Clause(Cell, Atom, Vec), Cons(Cell, Box, Box), Literal(Cell, Literal), // PartialString wraps a String in anticipation of it absorbing // other PartialString variants in as_partial_string. PartialString(Cell, Rc, Box), CompleteString(Cell, Rc), Var(Cell, VarPtr), } impl Term { pub fn first_arg(&self) -> Option<&Term> { match self { Term::Clause(_, _, ref terms) => terms.first(), _ => None, } } pub fn name(&self) -> Option { match *self { Term::Literal(_, Literal::Atom(atom)) => Some(atom), Term::Clause(_, atom, ..) => Some(atom), _ => None, } } pub fn arity(&self) -> usize { match self { Term::Clause(_, _, ref child_terms, ..) => child_terms.len(), _ => 0, } } } pub(crate) fn unfold_by_str_once(term: &mut Term, s: Atom) -> Option<(Term, Term)> { if let Term::Clause(_, ref name, ref mut subterms) = term { if let Some(Term::Literal(_, Literal::CodeIndexOffset(_))) = subterms.last() { subterms.pop(); } if name == &s && subterms.len() == 2 { let snd = subterms.pop().unwrap(); let fst = subterms.pop().unwrap(); return Some((fst, snd)); } } None } pub fn unfold_by_str(mut term: Term, s: Atom) -> Vec { let mut terms = vec![]; while let Some((fst, snd)) = unfold_by_str_once(&mut term, s) { terms.push(fst); term = snd; } terms.push(term); terms }