2243 lines
72 KiB
Rust
2243 lines
72 KiB
Rust
use crate::arena::*;
|
|
use crate::atom_table::*;
|
|
use crate::parser::ast::*;
|
|
use crate::parser::dashu::base::RemEuclid;
|
|
use crate::parser::dashu::integer::Sign;
|
|
use crate::parser::dashu::{ibig, Integer, Rational};
|
|
|
|
use crate::forms::*;
|
|
use crate::heap_iter::*;
|
|
use crate::machine::heap::*;
|
|
use crate::machine::partial_string::*;
|
|
use crate::machine::stack::*;
|
|
use crate::machine::streams::*;
|
|
use crate::offset_table::*;
|
|
use crate::types::*;
|
|
|
|
use dashu::base::Signed;
|
|
use ordered_float::OrderedFloat;
|
|
|
|
use indexmap::IndexMap;
|
|
|
|
use std::cell::Cell;
|
|
use std::convert::TryFrom;
|
|
use std::iter::once;
|
|
use std::net::{IpAddr, TcpListener};
|
|
use std::rc::Rc;
|
|
|
|
/* contains the location, name, precision and Specifier of the parent op. */
|
|
#[derive(Debug, Copy, Clone)]
|
|
pub(crate) enum DirectedOp {
|
|
Left(Atom, OpDesc),
|
|
Right(Atom, OpDesc),
|
|
}
|
|
|
|
impl DirectedOp {
|
|
#[inline]
|
|
fn as_atom(&self) -> Atom {
|
|
match self {
|
|
&DirectedOp::Left(name, _) | &DirectedOp::Right(name, _) => name,
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn is_prefix(&self) -> bool {
|
|
match self {
|
|
&DirectedOp::Left(_name, cell) | &DirectedOp::Right(_name, cell) => {
|
|
cell.get_spec().is_prefix()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn is_negative_sign(&self) -> bool {
|
|
match self {
|
|
&DirectedOp::Left(name, cell) | &DirectedOp::Right(name, cell) => {
|
|
name == atom!("-") && cell.get_spec().is_prefix()
|
|
}
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn is_left(&self) -> bool {
|
|
matches!(self, DirectedOp::Left(..))
|
|
}
|
|
}
|
|
|
|
fn needs_bracketing(child_desc: OpDesc, op: &DirectedOp) -> bool {
|
|
match op {
|
|
DirectedOp::Left(name, cell) => {
|
|
let (priority, spec) = cell.get();
|
|
|
|
if &*name.as_str() == "-" {
|
|
let child_assoc = child_desc.get_spec();
|
|
if spec.is_prefix() && (child_assoc.is_postfix() || child_assoc.is_infix()) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
let is_strict_right = spec.is_strict_right();
|
|
child_desc.get_prec() > priority
|
|
|| (child_desc.get_prec() == priority && is_strict_right)
|
|
}
|
|
DirectedOp::Right(_, cell) => {
|
|
let (priority, spec) = cell.get();
|
|
let is_strict_left = spec.is_strict_left();
|
|
|
|
if child_desc.get_prec() > priority
|
|
|| (child_desc.get_prec() == priority && is_strict_left)
|
|
{
|
|
true
|
|
} else if (spec.is_postfix() || spec.is_infix()) && !child_desc.get_spec().is_postfix()
|
|
{
|
|
*cell != child_desc && child_desc.get_prec() == priority
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<'a, ElideLists> StackfulPreOrderHeapIter<'a, ElideLists> {
|
|
/*
|
|
* descend into the subtree where the iterator is currently parked
|
|
* and check that the leftmost leaf is a number, with every node
|
|
* encountered on the way an infix or postfix operator, unblocked
|
|
* by brackets.
|
|
*/
|
|
fn leftmost_leaf_has_property<P>(&self, op_dir: &OpDir, property_check: P) -> bool
|
|
where
|
|
P: Fn(HeapCellValue) -> bool,
|
|
{
|
|
let mut h = match self.stack_last() {
|
|
Some(h) => h,
|
|
None => return false,
|
|
};
|
|
|
|
let mut parent_spec = DirectedOp::Left(atom!("-"), OpDesc::build_with(200, FY));
|
|
|
|
loop {
|
|
let cell = self.read_cell(h);
|
|
|
|
read_heap_cell!(cell,
|
|
(HeapCellValueTag::Str, s) => {
|
|
read_heap_cell!(self.heap[s],
|
|
(HeapCellValueTag::Atom, (name, _arity)) => {
|
|
if let Some(spec) = fetch_atom_op_spec(name, None, op_dir) {
|
|
if spec.get_spec().is_postfix() || spec.get_spec().is_infix() {
|
|
if needs_bracketing(spec, &parent_spec) {
|
|
return false;
|
|
} else {
|
|
h = IterStackLoc::iterable_loc(s + 1, HeapOrStackTag::Heap);
|
|
parent_spec = DirectedOp::Right(name, spec);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
_ => {
|
|
return false;
|
|
}
|
|
)
|
|
}
|
|
_ => {
|
|
return property_check(cell);
|
|
}
|
|
)
|
|
}
|
|
}
|
|
|
|
fn immediate_leaf_has_property<P>(&self, property_check: P) -> bool
|
|
where
|
|
P: Fn(HeapCellValue) -> bool,
|
|
{
|
|
let cell = match self.stack_last() {
|
|
Some(h) => self.read_cell(h),
|
|
None => return false,
|
|
};
|
|
|
|
property_check(cell)
|
|
}
|
|
}
|
|
|
|
fn char_to_string(is_quoted: bool, c: char) -> String {
|
|
match c {
|
|
'\'' if is_quoted => "\\'".to_string(),
|
|
'\n' if is_quoted => "\\n".to_string(),
|
|
'\r' if is_quoted => "\\r".to_string(),
|
|
'\t' if is_quoted => "\\t".to_string(),
|
|
'\u{0b}' if is_quoted => "\\v".to_string(), // UTF-8 vertical tab
|
|
'\u{0c}' if is_quoted => "\\f".to_string(), // UTF-8 form feed
|
|
'\u{08}' if is_quoted => "\\b".to_string(), // UTF-8 backspace
|
|
'\u{07}' if is_quoted => "\\a".to_string(), // UTF-8 alert
|
|
'\\' if is_quoted => "\\\\".to_string(),
|
|
' ' | '\'' | '\n' | '\r' | '\t' | '\u{0b}' | '\u{0c}' | '\u{08}' | '\u{07}' | '"'
|
|
| '\\' => c.to_string(),
|
|
_ => {
|
|
if c.is_whitespace() || c.is_control() {
|
|
// print all other control and whitespace characters in hex.
|
|
format!("\\x{:x}\\", c as u32)
|
|
} else {
|
|
c.to_string()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Clone, Copy, Debug)]
|
|
enum NumberFocus {
|
|
Unfocused(Number),
|
|
Denominator(TypedArenaPtr<Rational>),
|
|
Numerator(TypedArenaPtr<Rational>),
|
|
}
|
|
|
|
impl NumberFocus {
|
|
fn is_negative(&self) -> bool {
|
|
match self {
|
|
NumberFocus::Unfocused(n) => n.is_negative(),
|
|
NumberFocus::Denominator(r) | NumberFocus::Numerator(r) => **r < Rational::from(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy)]
|
|
struct CommaSeparatedCharList {
|
|
// pstr: PartialString,
|
|
// offset: usize,
|
|
pstr_loc: usize,
|
|
max_depth: usize,
|
|
// end_cell: HeapCellValue,
|
|
// end_h: Option<usize>,
|
|
}
|
|
|
|
#[derive(Debug, Clone)]
|
|
enum TokenOrRedirect {
|
|
Atom(Atom),
|
|
BarAsOp,
|
|
Char(char),
|
|
Op(Atom, OpDesc),
|
|
NumberedVar(String),
|
|
CompositeRedirect(usize, DirectedOp),
|
|
CurlyBracketRedirect(usize),
|
|
FunctorRedirect(usize),
|
|
#[allow(unused)]
|
|
IpAddr(IpAddr),
|
|
NumberFocus(usize, NumberFocus, Option<DirectedOp>),
|
|
Open,
|
|
Close,
|
|
Comma,
|
|
RawPtr(*const ArenaHeader),
|
|
Space,
|
|
LeftCurly,
|
|
RightCurly,
|
|
ChildOpenList,
|
|
ChildCloseList,
|
|
OpenList(Rc<Cell<(bool, usize)>>),
|
|
CloseList(Rc<Cell<(bool, usize)>>),
|
|
HeadTailSeparator,
|
|
StackPop,
|
|
CommaSeparatedCharList(CommaSeparatedCharList),
|
|
}
|
|
|
|
pub(crate) fn requires_space(atom: &str, op: &str) -> bool {
|
|
match atom.chars().last() {
|
|
Some(ac) => op
|
|
.chars()
|
|
.next()
|
|
.map(|oc| {
|
|
if ac == '0' {
|
|
oc == '\'' || oc == '(' || alpha_numeric_char!(oc)
|
|
} else if alpha_numeric_char!(ac) {
|
|
oc == '(' || alpha_numeric_char!(oc)
|
|
} else if graphic_token_char!(ac) {
|
|
graphic_token_char!(oc)
|
|
} else if variable_indicator_char!(ac) || capital_letter_char!(ac) {
|
|
alpha_numeric_char!(oc)
|
|
} else if sign_char!(ac) {
|
|
sign_char!(oc) || decimal_digit_char!(oc)
|
|
} else if single_quote_char!(ac) {
|
|
single_quote_char!(oc)
|
|
} else {
|
|
false
|
|
}
|
|
})
|
|
.unwrap_or(false),
|
|
_ => false,
|
|
}
|
|
}
|
|
|
|
fn non_quoted_graphic_token<Iter: Iterator<Item = char>>(mut iter: Iter, c: char) -> bool {
|
|
if c == '/' {
|
|
match iter.next() {
|
|
None => true,
|
|
Some('*') => false, // if we start with comment token, we must quote.
|
|
Some(c) => {
|
|
if graphic_token_char!(c) {
|
|
iter.all(|c| graphic_token_char!(c))
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
} else if c == '.' {
|
|
match iter.next() {
|
|
None => false,
|
|
Some(c) => {
|
|
if graphic_token_char!(c) {
|
|
iter.all(|c| graphic_token_char!(c))
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
iter.all(|c| graphic_token_char!(c))
|
|
}
|
|
}
|
|
|
|
pub(super) fn non_quoted_token<Iter: Iterator<Item = char>>(mut iter: Iter) -> bool {
|
|
if let Some(c) = iter.next() {
|
|
if small_letter_char!(c) {
|
|
iter.all(|c| alpha_numeric_char!(c))
|
|
} else if graphic_token_char!(c) {
|
|
non_quoted_graphic_token(iter, c)
|
|
} else if semicolon_char!(c) || cut_char!(c) {
|
|
iter.next().is_none()
|
|
} else if c == '[' {
|
|
iter.next() == Some(']') && iter.next().is_none()
|
|
} else if c == '{' {
|
|
iter.next() == Some('}') && iter.next().is_none()
|
|
} else if solo_char!(c) {
|
|
!(c == '(' || c == ')' || c == '}' || c == ']' || c == ',' || c == '%' || c == '|')
|
|
} else {
|
|
false
|
|
}
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
#[allow(clippy::len_without_is_empty)]
|
|
pub trait HCValueOutputter {
|
|
type Output;
|
|
|
|
fn new() -> Self;
|
|
fn push_char(&mut self, c: char);
|
|
fn append(&mut self, s: &str);
|
|
fn result(self) -> Self::Output;
|
|
fn ends_with(&self, s: &str) -> bool;
|
|
fn len(&self) -> usize;
|
|
fn truncate(&mut self, len: usize);
|
|
fn as_str(&self) -> &str;
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct PrinterOutputter {
|
|
contents: String,
|
|
}
|
|
|
|
impl HCValueOutputter for PrinterOutputter {
|
|
type Output = String;
|
|
|
|
fn new() -> Self {
|
|
PrinterOutputter {
|
|
contents: String::new(),
|
|
}
|
|
}
|
|
|
|
fn append(&mut self, contents: &str) {
|
|
if requires_space(&self.contents, contents) {
|
|
self.push_char(' ');
|
|
}
|
|
|
|
self.contents += contents;
|
|
}
|
|
|
|
fn push_char(&mut self, c: char) {
|
|
self.contents.push(c);
|
|
}
|
|
|
|
fn result(self) -> Self::Output {
|
|
self.contents
|
|
}
|
|
|
|
fn ends_with(&self, s: &str) -> bool {
|
|
self.contents.ends_with(s)
|
|
}
|
|
|
|
fn len(&self) -> usize {
|
|
self.contents.len()
|
|
}
|
|
|
|
fn truncate(&mut self, len: usize) {
|
|
self.contents.truncate(len);
|
|
}
|
|
|
|
fn as_str(&self) -> &str {
|
|
&self.contents
|
|
}
|
|
}
|
|
|
|
#[inline(always)]
|
|
fn is_numbered_var(name: Atom, arity: usize) -> bool {
|
|
arity == 1 && name == atom!("$VAR")
|
|
}
|
|
|
|
#[inline]
|
|
fn negated_op_needs_bracketing(
|
|
iter: &StackfulPreOrderHeapIter<ListElider>,
|
|
f64_tbl: &F64Table,
|
|
op_dir: &OpDir,
|
|
op: &Option<DirectedOp>,
|
|
) -> bool {
|
|
if let Some(ref op) = op {
|
|
op.is_negative_sign()
|
|
&& iter.leftmost_leaf_has_property(op_dir, |addr| {
|
|
match Number::try_from((addr, f64_tbl)) {
|
|
Ok(Number::Fixnum(n)) => n.get_num() > 0,
|
|
Ok(Number::Float(OrderedFloat(f))) => f > 0f64,
|
|
Ok(Number::Integer(n)) => n.is_positive(),
|
|
Ok(Number::Rational(n)) => n.is_positive(),
|
|
_ => false,
|
|
}
|
|
})
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
macro_rules! push_char {
|
|
($self:ident, $c:expr) => {{
|
|
$self.outputter.push_char($c);
|
|
$self.last_item_idx = $self.outputter.len();
|
|
}};
|
|
}
|
|
|
|
macro_rules! append_str {
|
|
($self:ident, $s:expr) => {{
|
|
$self.last_item_idx = $self.outputter.len();
|
|
$self.outputter.append($s);
|
|
}};
|
|
}
|
|
|
|
macro_rules! print_char {
|
|
($self:ident, $is_quoted:expr, $c:expr) => {
|
|
if non_quoted_token(once($c)) {
|
|
let result = char_to_string(false, $c);
|
|
|
|
push_space_if_amb!($self, &result, {
|
|
append_str!($self, &result);
|
|
});
|
|
} else {
|
|
let mut result = String::new();
|
|
|
|
if $self.quoted {
|
|
result.push('\'');
|
|
result += &char_to_string($is_quoted, $c);
|
|
result.push('\'');
|
|
} else {
|
|
result += &char_to_string($is_quoted, $c);
|
|
}
|
|
|
|
push_space_if_amb!($self, &result, {
|
|
append_str!($self, result.as_str());
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
pub fn fmt_float(mut fl: f64) -> String {
|
|
if OrderedFloat(fl) == -0f64 {
|
|
fl = 0f64;
|
|
}
|
|
|
|
let mut buffer = ryu::Buffer::new();
|
|
let fl_str = buffer.format(fl);
|
|
|
|
/* When printing floats with zero fractional parts in scientific notation, ryu
|
|
* prints "{integer part}e{exponent}" without a ".0" preceding "e",
|
|
* which is not valid ISO Prolog syntax. Add ".0" manually in this
|
|
* case.
|
|
*/
|
|
|
|
if let Some(e_index) = fl_str.find('e') {
|
|
if !fl_str[0..e_index].contains('.') {
|
|
return fl_str[0..e_index].to_string() + ".0" + &fl_str[e_index..];
|
|
}
|
|
}
|
|
|
|
fl_str.to_string()
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct HCPrinter<'a, Outputter> {
|
|
outputter: Outputter,
|
|
iter: StackfulPreOrderHeapIter<'a, ListElider>,
|
|
arena: &'a Arena,
|
|
op_dir: &'a OpDir,
|
|
state_stack: Vec<TokenOrRedirect>,
|
|
toplevel_spec: Option<DirectedOp>,
|
|
last_item_idx: usize,
|
|
parent_of_first_op: Option<(DirectedOp, usize)>,
|
|
pub var_names: IndexMap<HeapCellValue, VarPtr>,
|
|
pub numbervars_offset: Integer,
|
|
pub numbervars: bool,
|
|
pub quoted: bool,
|
|
pub ignore_ops: bool,
|
|
pub max_depth: usize,
|
|
pub double_quotes: bool,
|
|
}
|
|
|
|
fn ambiguity_check(
|
|
outputter: &impl HCValueOutputter,
|
|
quoted: bool,
|
|
last_item_idx: usize,
|
|
atom: &str,
|
|
) -> bool {
|
|
let tail = &outputter.as_str()[last_item_idx..];
|
|
|
|
if atom == "," || !quoted || non_quoted_token(atom.chars()) {
|
|
requires_space(tail, atom)
|
|
} else {
|
|
requires_space(tail, "'")
|
|
}
|
|
}
|
|
|
|
macro_rules! push_space_if_amb {
|
|
($self:expr, $atom:expr, $action:block) => {
|
|
if ambiguity_check(&$self.outputter, $self.quoted, $self.last_item_idx, $atom) {
|
|
$self.outputter.push_char(' ');
|
|
$action;
|
|
} else {
|
|
$action;
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) fn numbervar(offset: &Integer, addr: HeapCellValue) -> Option<String> {
|
|
fn numbervar(n: Integer) -> String {
|
|
static CHAR_CODES: [char; 26] = [
|
|
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q',
|
|
'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
|
|
];
|
|
|
|
let i: usize = (&n).rem_euclid(ibig!(26)).try_into().unwrap();
|
|
let j = n / ibig!(26);
|
|
|
|
if j.is_zero() {
|
|
CHAR_CODES[i].to_string()
|
|
} else {
|
|
format!("{}{}", CHAR_CODES[i], j)
|
|
}
|
|
}
|
|
|
|
read_heap_cell!(addr,
|
|
(HeapCellValueTag::Cons, c) => {
|
|
match_untyped_arena_ptr!(c,
|
|
(ArenaHeaderTag::Integer, n) => {
|
|
if !n.is_negative() {
|
|
Some(numbervar(Integer::from(offset + &*n)))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => {
|
|
None
|
|
}
|
|
)
|
|
}
|
|
(HeapCellValueTag::Fixnum, n) => {
|
|
if n.get_num() >= 0 {
|
|
Some(numbervar(offset + Integer::from(n.get_num())))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
_ => {
|
|
None
|
|
}
|
|
)
|
|
}
|
|
|
|
impl<'a, Outputter: HCValueOutputter> HCPrinter<'a, Outputter> {
|
|
pub fn new(
|
|
heap: &'a mut Heap,
|
|
stack: &'a mut Stack,
|
|
arena: &'a Arena,
|
|
op_dir: &'a OpDir,
|
|
output: Outputter,
|
|
term_loc: usize,
|
|
) -> Self {
|
|
HCPrinter {
|
|
outputter: output,
|
|
iter: stackful_preorder_iter(heap, stack, term_loc),
|
|
arena,
|
|
op_dir,
|
|
state_stack: vec![],
|
|
toplevel_spec: None,
|
|
last_item_idx: 0,
|
|
parent_of_first_op: None,
|
|
numbervars: false,
|
|
numbervars_offset: Integer::from(0),
|
|
quoted: false,
|
|
ignore_ops: false,
|
|
var_names: IndexMap::new(),
|
|
max_depth: 0,
|
|
double_quotes: false,
|
|
}
|
|
}
|
|
|
|
fn set_parent_of_first_op(&mut self, parent_op: Option<DirectedOp>) {
|
|
if let Some(op) = parent_op {
|
|
if op.is_left() && op.is_prefix() {
|
|
self.parent_of_first_op = Some((op, self.last_item_idx));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn enqueue_op(&mut self, mut max_depth: usize, name: Atom, spec: OpDesc) {
|
|
if spec.get_spec().is_postfix() {
|
|
if self.max_depth_exhausted(max_depth) {
|
|
self.iter.pop_stack();
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
} else if self.check_max_depth(&mut max_depth) {
|
|
self.iter.pop_stack();
|
|
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
} else {
|
|
let right_directed_op = DirectedOp::Right(name, spec);
|
|
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
self.state_stack.push(TokenOrRedirect::CompositeRedirect(
|
|
max_depth,
|
|
right_directed_op,
|
|
));
|
|
}
|
|
} else if spec.get_spec().is_prefix() {
|
|
if self.max_depth_exhausted(max_depth) {
|
|
self.iter.pop_stack();
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
return;
|
|
} else if self.check_max_depth(&mut max_depth) {
|
|
self.iter.pop_stack();
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
} else {
|
|
let op = DirectedOp::Left(name, spec);
|
|
|
|
self.state_stack
|
|
.push(TokenOrRedirect::CompositeRedirect(max_depth, op));
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
}
|
|
} else {
|
|
if let "|" = &*name.as_str() {
|
|
self.format_bar_separator_op(max_depth, name, spec);
|
|
return;
|
|
};
|
|
|
|
if self.max_depth_exhausted(max_depth) {
|
|
self.iter.pop_stack();
|
|
self.iter.pop_stack();
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
} else if self.check_max_depth(&mut max_depth) {
|
|
if matches!(spec.get_spec(), XFY) {
|
|
let left_directed_op = DirectedOp::Left(name, spec);
|
|
|
|
self.state_stack
|
|
.push(TokenOrRedirect::CompositeRedirect(0, left_directed_op));
|
|
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
self.state_stack.push(TokenOrRedirect::StackPop);
|
|
} else {
|
|
// is_yfx!
|
|
let right_directed_op = DirectedOp::Right(name, spec);
|
|
|
|
self.state_stack.push(TokenOrRedirect::StackPop);
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
self.state_stack
|
|
.push(TokenOrRedirect::CompositeRedirect(0, right_directed_op));
|
|
}
|
|
} else {
|
|
let left_directed_op = DirectedOp::Left(name, spec);
|
|
let right_directed_op = DirectedOp::Right(name, spec);
|
|
|
|
self.state_stack.push(TokenOrRedirect::CompositeRedirect(
|
|
max_depth,
|
|
left_directed_op,
|
|
));
|
|
|
|
self.state_stack.push(TokenOrRedirect::Op(name, spec));
|
|
self.state_stack.push(TokenOrRedirect::CompositeRedirect(
|
|
max_depth,
|
|
right_directed_op,
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
fn format_struct(&mut self, mut max_depth: usize, arity: usize, name: Atom) -> bool {
|
|
if self.check_max_depth(&mut max_depth) {
|
|
for _ in 0..arity {
|
|
self.iter.pop_stack();
|
|
}
|
|
|
|
if arity > 0 {
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
}
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(name));
|
|
|
|
return false;
|
|
}
|
|
|
|
if arity > 0 {
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
|
|
for _ in 0..arity {
|
|
self.state_stack
|
|
.push(TokenOrRedirect::FunctorRedirect(max_depth));
|
|
self.state_stack.push(TokenOrRedirect::Comma);
|
|
}
|
|
|
|
self.state_stack.pop();
|
|
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
}
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(name));
|
|
|
|
true
|
|
}
|
|
|
|
fn format_bar_separator_op(&mut self, mut max_depth: usize, name: Atom, spec: OpDesc) {
|
|
if self.check_max_depth(&mut max_depth) {
|
|
self.iter.pop_stack();
|
|
self.iter.pop_stack();
|
|
|
|
let ellipsis_atom = atom!("...");
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(ellipsis_atom));
|
|
self.state_stack.push(TokenOrRedirect::BarAsOp);
|
|
self.state_stack.push(TokenOrRedirect::Atom(ellipsis_atom));
|
|
|
|
return;
|
|
}
|
|
|
|
let left_directed_op = DirectedOp::Left(name, spec);
|
|
let right_directed_op = DirectedOp::Right(name, spec);
|
|
|
|
self.state_stack.push(TokenOrRedirect::CompositeRedirect(
|
|
max_depth,
|
|
left_directed_op,
|
|
));
|
|
|
|
self.state_stack.push(TokenOrRedirect::BarAsOp);
|
|
|
|
self.state_stack.push(TokenOrRedirect::CompositeRedirect(
|
|
max_depth,
|
|
right_directed_op,
|
|
));
|
|
}
|
|
|
|
fn format_curly_braces(&mut self, mut max_depth: usize) -> bool {
|
|
if self.check_max_depth(&mut max_depth) {
|
|
self.iter.pop_stack();
|
|
|
|
let ellipsis_atom = atom!("...");
|
|
|
|
self.state_stack.push(TokenOrRedirect::RightCurly);
|
|
self.state_stack.push(TokenOrRedirect::Atom(ellipsis_atom));
|
|
self.state_stack.push(TokenOrRedirect::LeftCurly);
|
|
|
|
return false;
|
|
}
|
|
|
|
self.state_stack.push(TokenOrRedirect::RightCurly);
|
|
self.state_stack
|
|
.push(TokenOrRedirect::CurlyBracketRedirect(max_depth));
|
|
self.state_stack.push(TokenOrRedirect::LeftCurly);
|
|
|
|
true
|
|
}
|
|
|
|
fn format_numbered_vars(&mut self) -> bool {
|
|
let h = self.iter.stack_last().unwrap();
|
|
|
|
let cell = self.iter.read_cell(h);
|
|
let cell = heap_bound_store(self.iter.heap, heap_bound_deref(self.iter.heap, cell));
|
|
|
|
// 7.10.4
|
|
if let Some(var) = numbervar(&self.numbervars_offset, cell) {
|
|
self.iter.pop_stack();
|
|
self.state_stack.push(TokenOrRedirect::NumberedVar(var));
|
|
return true;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn format_clause(
|
|
&mut self,
|
|
max_depth: usize,
|
|
arity: usize,
|
|
name: Atom,
|
|
op_desc: Option<OpDesc>,
|
|
) -> bool {
|
|
if self.numbervars && is_numbered_var(name, arity) && self.format_numbered_vars() {
|
|
return true;
|
|
}
|
|
|
|
let dot_atom = atom!(".");
|
|
|
|
if let Some(spec) = op_desc {
|
|
if dot_atom == name && spec.get_spec().is_infix() && !self.ignore_ops {
|
|
self.push_list(max_depth);
|
|
return true;
|
|
}
|
|
|
|
if !self.ignore_ops && spec.get_prec() > 0 {
|
|
self.enqueue_op(max_depth, name, spec);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
match (name, arity) {
|
|
(atom!("{}"), 1) if !self.ignore_ops => self.format_curly_braces(max_depth),
|
|
_ => self.format_struct(max_depth, arity, name),
|
|
}
|
|
}
|
|
|
|
fn offset_as_string(&mut self, h: IterStackLoc) -> Option<String> {
|
|
let cell = self.iter.read_cell(h);
|
|
|
|
if let Some(var) = self.var_names.get(&cell) {
|
|
read_heap_cell!(cell,
|
|
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
|
|
return Some(var.borrow().to_string());
|
|
}
|
|
_ => {
|
|
self.iter.push_stack(h);
|
|
return None;
|
|
}
|
|
);
|
|
}
|
|
|
|
read_heap_cell!(cell,
|
|
(HeapCellValueTag::Lis | HeapCellValueTag::Str, h) => {
|
|
Some(format!("{h}"))
|
|
}
|
|
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar, h) => {
|
|
Some(format!("_{h}"))
|
|
}
|
|
(HeapCellValueTag::StackVar, h) => {
|
|
Some(format!("_s_{h}"))
|
|
}
|
|
_ => {
|
|
None
|
|
}
|
|
)
|
|
}
|
|
|
|
fn check_for_seen(&mut self, max_depth: &mut usize) -> Option<HeapCellValue> {
|
|
if let Some(mut orig_cell) = self.iter.next() {
|
|
loop {
|
|
let is_cyclic = orig_cell.get_forwarding_bit();
|
|
|
|
let cell =
|
|
heap_bound_store(self.iter.heap, heap_bound_deref(self.iter.heap, orig_cell));
|
|
let cell = unmark_cell_bits!(cell);
|
|
|
|
match self.var_names.get(&cell).cloned() {
|
|
Some(var) if cell.is_var() => {
|
|
// If cell is an unbound variable and maps to
|
|
// a name via heap_locs, append the name to
|
|
// the current output, and return None. None
|
|
// short-circuits handle_heap_term.
|
|
// self.iter.pop_stack();
|
|
|
|
let var_str = var.borrow().to_string();
|
|
|
|
push_space_if_amb!(self, &var_str, {
|
|
append_str!(self, &var_str);
|
|
});
|
|
|
|
return None;
|
|
}
|
|
var_opt => {
|
|
if is_cyclic && cell.is_compound(self.iter.heap) {
|
|
// self-referential variables are marked "cyclic".
|
|
|
|
match var_opt {
|
|
Some(var) => {
|
|
// If the term is bound to a named variable,
|
|
// print the variable's name to output.
|
|
let var_str = var.borrow().to_string();
|
|
|
|
push_space_if_amb!(self, &var_str, {
|
|
append_str!(self, &var_str);
|
|
});
|
|
}
|
|
None => {
|
|
if self.max_depth == 0 || *max_depth == 0 {
|
|
// otherwise, contract it to an ellipsis.
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
} else {
|
|
debug_assert!(cell.is_ref());
|
|
|
|
let h = match cell.get_tag() {
|
|
HeapCellValueTag::Lis | HeapCellValueTag::PStrLoc => {
|
|
self.iter.focus().value() as usize
|
|
}
|
|
_ => cell.get_value() as usize,
|
|
};
|
|
|
|
match cell.get_tag() {
|
|
HeapCellValueTag::Lis => {
|
|
self.state_stack
|
|
.push(TokenOrRedirect::Atom(atom!("...")));
|
|
return None;
|
|
}
|
|
HeapCellValueTag::PStrLoc => {
|
|
*max_depth -= 1;
|
|
}
|
|
_ => {}
|
|
}
|
|
|
|
self.iter.push_stack(IterStackLoc::iterable_loc(
|
|
h,
|
|
HeapOrStackTag::Heap,
|
|
));
|
|
|
|
if let Some(cell) = self.iter.next() {
|
|
orig_cell = cell;
|
|
continue;
|
|
}
|
|
|
|
return Some(cell);
|
|
};
|
|
}
|
|
}
|
|
|
|
return None;
|
|
}
|
|
|
|
return Some(cell);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
while self.iter.pop_stack().is_none() {}
|
|
None
|
|
}
|
|
}
|
|
|
|
fn print_impromptu_atom(&mut self, atom: Atom) {
|
|
let result = self.print_op_addendum(&atom.as_str());
|
|
|
|
push_space_if_amb!(self, result.as_str(), {
|
|
append_str!(self, &result);
|
|
});
|
|
}
|
|
|
|
fn print_op_addendum(&mut self, atom: &str) -> String {
|
|
if !self.quoted || non_quoted_token(atom.chars()) {
|
|
atom.to_string()
|
|
} else if atom == "''" {
|
|
"''".to_string()
|
|
} else {
|
|
let mut result = String::new();
|
|
|
|
if self.quoted {
|
|
result.push('\'');
|
|
}
|
|
|
|
for c in atom.chars() {
|
|
result += &char_to_string(self.quoted, c);
|
|
}
|
|
|
|
if self.quoted {
|
|
result.push('\'');
|
|
}
|
|
|
|
result
|
|
}
|
|
}
|
|
|
|
fn print_op(&mut self, atom: &str) {
|
|
let result = if atom == "," {
|
|
",".to_string()
|
|
} else {
|
|
self.print_op_addendum(atom)
|
|
};
|
|
|
|
push_space_if_amb!(self, &result, {
|
|
append_str!(self, &result);
|
|
});
|
|
}
|
|
|
|
#[inline]
|
|
fn print_ip_addr(&mut self, ip: IpAddr) {
|
|
push_char!(self, '\'');
|
|
append_str!(self, &format!("{ip}"));
|
|
push_char!(self, '\'');
|
|
}
|
|
|
|
#[inline]
|
|
fn print_raw_ptr(&mut self, ptr: *const ArenaHeader) {
|
|
append_str!(self, &format!("0x{:x}", ptr.addr()));
|
|
}
|
|
|
|
fn print_number(&mut self, max_depth: usize, n: NumberFocus, op: &Option<DirectedOp>) {
|
|
let (add_brackets, op_is_prefix) = if let Some(op) = op {
|
|
(op.is_negative_sign() && !n.is_negative(), op.is_prefix())
|
|
} else {
|
|
(false, false)
|
|
};
|
|
|
|
if add_brackets {
|
|
if op_is_prefix && !self.outputter.ends_with(" ") {
|
|
push_char!(self, ' ');
|
|
}
|
|
|
|
push_char!(self, '(');
|
|
}
|
|
|
|
match n {
|
|
NumberFocus::Unfocused(n) => match n {
|
|
Number::Float(OrderedFloat(fl)) => {
|
|
let output_str = fmt_float(fl);
|
|
|
|
push_space_if_amb!(self, &output_str, {
|
|
append_str!(self, &output_str);
|
|
});
|
|
}
|
|
Number::Rational(r) => {
|
|
self.print_rational(max_depth, r, *op);
|
|
}
|
|
n => {
|
|
let output_str = format!("{n}");
|
|
|
|
push_space_if_amb!(self, &output_str, {
|
|
append_str!(self, &output_str);
|
|
});
|
|
}
|
|
},
|
|
NumberFocus::Denominator(r) => {
|
|
let output_str = format!("{}", r.denominator());
|
|
|
|
push_space_if_amb!(self, &output_str, {
|
|
append_str!(self, &output_str);
|
|
});
|
|
}
|
|
NumberFocus::Numerator(r) => {
|
|
let output_str = format!("{}", r.numerator());
|
|
|
|
push_space_if_amb!(self, &output_str, {
|
|
append_str!(self, &output_str);
|
|
});
|
|
}
|
|
}
|
|
|
|
if add_brackets {
|
|
push_char!(self, ')');
|
|
}
|
|
}
|
|
|
|
fn print_rational(
|
|
&mut self,
|
|
mut max_depth: usize,
|
|
r: TypedArenaPtr<Rational>,
|
|
parent_op: Option<DirectedOp>,
|
|
) {
|
|
if self.check_max_depth(&mut max_depth) {
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("rdiv")));
|
|
|
|
return;
|
|
}
|
|
|
|
match self.op_dir.get(&(atom!("rdiv"), Fixity::In)) {
|
|
Some(op_desc) => {
|
|
if r.is_int() {
|
|
let output_str = format!("{r}");
|
|
|
|
push_space_if_amb!(self, &output_str, {
|
|
append_str!(self, &output_str);
|
|
});
|
|
|
|
return;
|
|
}
|
|
|
|
let rdiv_ct = atom!("rdiv");
|
|
|
|
let left_directed_op = if op_desc.get_prec() > 0 {
|
|
Some(DirectedOp::Left(rdiv_ct, *op_desc))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
let right_directed_op = if op_desc.get_prec() > 0 {
|
|
Some(DirectedOp::Right(rdiv_ct, *op_desc))
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if !self.ignore_ops && op_desc.get_prec() > 0 {
|
|
self.state_stack.push(TokenOrRedirect::NumberFocus(
|
|
max_depth,
|
|
NumberFocus::Denominator(r),
|
|
left_directed_op,
|
|
));
|
|
self.state_stack
|
|
.push(TokenOrRedirect::Op(rdiv_ct, *op_desc));
|
|
self.state_stack.push(TokenOrRedirect::NumberFocus(
|
|
max_depth,
|
|
NumberFocus::Numerator(r),
|
|
right_directed_op,
|
|
));
|
|
|
|
self.set_parent_of_first_op(parent_op);
|
|
} else {
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
|
|
self.state_stack.push(TokenOrRedirect::NumberFocus(
|
|
max_depth,
|
|
NumberFocus::Denominator(r),
|
|
None,
|
|
));
|
|
|
|
self.state_stack.push(TokenOrRedirect::Comma);
|
|
|
|
self.state_stack.push(TokenOrRedirect::NumberFocus(
|
|
max_depth,
|
|
NumberFocus::Numerator(r),
|
|
None,
|
|
));
|
|
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
self.state_stack.push(TokenOrRedirect::Atom(rdiv_ct));
|
|
}
|
|
}
|
|
_ => {
|
|
unreachable!()
|
|
}
|
|
}
|
|
}
|
|
|
|
// returns true if max_depth limit is reached and ellipsis is printed.
|
|
fn print_string_as_functor(&mut self, focus: usize, max_depth: &mut usize) -> bool {
|
|
let mut iter = HeapPStrIter::new(self.iter.heap, focus);
|
|
let mut char_count = 0;
|
|
|
|
while let Some(iteratee) = iter.next() {
|
|
if self.check_max_depth(max_depth) {
|
|
if char_count > 0 {
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
}
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
return true;
|
|
}
|
|
|
|
macro_rules! emit_char {
|
|
($c:expr) => {{
|
|
append_str!(self, "'.'");
|
|
push_char!(self, '(');
|
|
|
|
print_char!(self, self.quoted, $c);
|
|
push_char!(self, ',');
|
|
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
char_count += 1;
|
|
}};
|
|
}
|
|
|
|
match iteratee {
|
|
PStrIteratee::Char { value, .. } => {
|
|
emit_char!(value);
|
|
}
|
|
PStrIteratee::PStrSlice {
|
|
slice_loc,
|
|
slice_len,
|
|
} => {
|
|
let s = iter.heap.slice_to_str(slice_loc, slice_len);
|
|
|
|
for c in s.chars() {
|
|
emit_char!(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
// proper strings are terminal so there's no need for max_depth to
|
|
// be a mutable ref here.
|
|
fn print_proper_string(&mut self, focus: usize, max_depth: usize) {
|
|
push_char!(self, '"');
|
|
|
|
let mut iter = HeapPStrIter::new(self.iter.heap, focus);
|
|
|
|
let char_to_string = |c: char| {
|
|
// refrain from quoting characters other than '"' and '\'
|
|
// unless self.quoted is true.
|
|
match c {
|
|
'\\' => "\\\\".to_string(),
|
|
'"' => "\\\"".to_string(),
|
|
_ => char_to_string(self.quoted, c),
|
|
}
|
|
};
|
|
|
|
if max_depth == 0 {
|
|
while let Some(iteratee) = iter.next() {
|
|
let iter: Box<dyn Iterator<Item = char>> = match iteratee {
|
|
PStrIteratee::Char { value: c, .. } => Box::new(std::iter::once(c)),
|
|
PStrIteratee::PStrSlice {
|
|
slice_loc,
|
|
slice_len,
|
|
} => {
|
|
let s = iter.heap.slice_to_str(slice_loc, slice_len);
|
|
Box::new(s.chars())
|
|
}
|
|
};
|
|
|
|
for c in iter {
|
|
for c in char_to_string(c).chars() {
|
|
push_char!(self, c);
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
let mut char_count = 0;
|
|
|
|
while let Some(iteratee) = iter.next() {
|
|
let iter: Box<dyn Iterator<Item = char>> = match iteratee {
|
|
PStrIteratee::Char { value: c, .. } => Box::new(std::iter::once(c)),
|
|
PStrIteratee::PStrSlice {
|
|
slice_loc,
|
|
slice_len,
|
|
} => {
|
|
let s = iter.heap.slice_to_str(slice_loc, slice_len);
|
|
Box::new(s.chars())
|
|
}
|
|
};
|
|
|
|
for c in iter.take(max_depth - char_count) {
|
|
char_count += 1;
|
|
|
|
for c in char_to_string(c).chars() {
|
|
push_char!(self, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
if char_count == max_depth {
|
|
append_str!(self, " ...");
|
|
}
|
|
}
|
|
|
|
push_char!(self, '"');
|
|
}
|
|
|
|
fn remove_list_children(&mut self, h: usize) {
|
|
match self.iter.heap[h].get_tag() {
|
|
HeapCellValueTag::Lis => {
|
|
self.iter.pop_stack();
|
|
self.iter.pop_stack();
|
|
}
|
|
HeapCellValueTag::PStrLoc => {
|
|
self.iter.pop_stack();
|
|
}
|
|
_ => {
|
|
unreachable!();
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_list_like(&mut self, mut max_depth: usize) {
|
|
let focus = self.iter.focus();
|
|
let mut heap_pstr_iter = HeapPStrIter::new(self.iter.heap, focus.value() as usize);
|
|
|
|
let is_cyclic = if heap_pstr_iter.next().is_some() {
|
|
for _ in heap_pstr_iter.by_ref() {}
|
|
heap_pstr_iter.is_cyclic()
|
|
} else {
|
|
return self.push_list(max_depth);
|
|
};
|
|
|
|
let end_h = heap_pstr_iter.focus();
|
|
let end_cell = heap_pstr_iter.heap[end_h]; // heap_pstr_iter.focus;
|
|
|
|
if self.check_max_depth(&mut max_depth) {
|
|
self.remove_list_children(focus.value() as usize);
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
return;
|
|
}
|
|
|
|
let at_cdr = self.outputter.ends_with("|");
|
|
|
|
if self.double_quotes
|
|
&& !self.ignore_ops
|
|
&& !is_cyclic
|
|
&& end_cell.is_string_terminator(self.iter.heap)
|
|
{
|
|
self.remove_list_children(focus.value() as usize);
|
|
return self.print_proper_string(focus.value() as usize, max_depth);
|
|
}
|
|
|
|
if self.ignore_ops {
|
|
self.at_cdr(",");
|
|
self.remove_list_children(focus.value() as usize);
|
|
|
|
if !self.print_string_as_functor(focus.value() as usize, &mut max_depth) {
|
|
if end_cell == empty_list_as_cell!() {
|
|
if !self.at_cdr("") {
|
|
append_str!(self, "[]");
|
|
}
|
|
} else {
|
|
self.state_stack
|
|
.push(TokenOrRedirect::FunctorRedirect(max_depth));
|
|
self.iter
|
|
.push_stack(IterStackLoc::iterable_loc(end_h, HeapOrStackTag::Heap));
|
|
}
|
|
}
|
|
} else {
|
|
let value = heap_bound_store(
|
|
self.iter.heap,
|
|
heap_bound_deref(self.iter.heap, self.iter.read_cell(focus)),
|
|
);
|
|
|
|
read_heap_cell!(value,
|
|
(HeapCellValueTag::Lis) => {
|
|
self.push_list(max_depth)
|
|
}
|
|
(HeapCellValueTag::PStrLoc, h) => {
|
|
let switch = Rc::new(Cell::new((!at_cdr, 0)));
|
|
let switch = self.close_list(switch);
|
|
|
|
if !self.max_depth_exhausted(max_depth) {
|
|
self.state_stack.push(
|
|
TokenOrRedirect::CommaSeparatedCharList(
|
|
CommaSeparatedCharList {
|
|
pstr_loc: h, max_depth,
|
|
}
|
|
),
|
|
);
|
|
} else {
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
}
|
|
|
|
self.open_list(switch);
|
|
}
|
|
_ => {
|
|
unreachable!()
|
|
}
|
|
);
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn max_depth_exhausted(&self, max_depth: usize) -> bool {
|
|
self.max_depth > 0 && max_depth == 0
|
|
}
|
|
|
|
fn check_max_depth(&self, max_depth: &mut usize) -> bool {
|
|
if self.max_depth > 0 && *max_depth == 0 {
|
|
return true;
|
|
}
|
|
|
|
if *max_depth > 0 {
|
|
*max_depth -= 1;
|
|
}
|
|
|
|
false
|
|
}
|
|
|
|
fn close_list(&mut self, switch: Rc<Cell<(bool, usize)>>) -> Option<Rc<Cell<(bool, usize)>>> {
|
|
if let Some(TokenOrRedirect::Op(_, op_desc)) = self.state_stack.last() {
|
|
if op_desc.get_spec().is_postfix() || op_desc.get_spec().is_infix() {
|
|
self.state_stack.push(TokenOrRedirect::ChildCloseList);
|
|
return None;
|
|
}
|
|
}
|
|
|
|
self.state_stack
|
|
.push(TokenOrRedirect::CloseList(switch.clone()));
|
|
Some(switch)
|
|
}
|
|
|
|
fn open_list(&mut self, switch: Option<Rc<Cell<(bool, usize)>>>) {
|
|
self.state_stack.push(match switch {
|
|
Some(switch) => TokenOrRedirect::OpenList(switch),
|
|
None => TokenOrRedirect::ChildOpenList,
|
|
});
|
|
}
|
|
|
|
fn push_list(&mut self, mut max_depth: usize) {
|
|
if self.max_depth_exhausted(max_depth) {
|
|
self.iter.pop_stack();
|
|
self.iter.pop_stack();
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
|
|
return;
|
|
} else if self.check_max_depth(&mut max_depth) {
|
|
self.iter.pop_stack();
|
|
self.iter.pop_stack();
|
|
|
|
let cell = Rc::new(Cell::new((true, 0)));
|
|
|
|
let switch = self.close_list(cell);
|
|
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
self.open_list(switch);
|
|
|
|
return;
|
|
}
|
|
|
|
let cell = Rc::new(Cell::new((true, max_depth)));
|
|
|
|
let switch = self.close_list(cell);
|
|
|
|
self.state_stack
|
|
.push(TokenOrRedirect::FunctorRedirect(max_depth));
|
|
self.state_stack.push(TokenOrRedirect::HeadTailSeparator); // bar
|
|
self.state_stack
|
|
.push(TokenOrRedirect::FunctorRedirect(max_depth + 1));
|
|
|
|
self.open_list(switch);
|
|
}
|
|
|
|
#[allow(clippy::too_many_arguments)]
|
|
fn handle_op_as_struct(
|
|
&mut self,
|
|
name: Atom,
|
|
arity: usize,
|
|
op: Option<DirectedOp>,
|
|
is_functor_redirect: bool,
|
|
op_desc: OpDesc,
|
|
negated_operand: bool,
|
|
max_depth: usize,
|
|
) {
|
|
let add_brackets = if !self.ignore_ops {
|
|
negated_operand
|
|
|| if let Some(ref op) = op {
|
|
if self.numbervars && arity == 1 && name == atom!("$VAR") {
|
|
!self.iter.immediate_leaf_has_property(|addr| {
|
|
match Number::try_from((addr, &self.arena.f64_tbl)) {
|
|
Ok(Number::Integer(n)) => (*n).sign() == Sign::Positive,
|
|
Ok(Number::Fixnum(n)) => n.get_num() >= 0,
|
|
Ok(Number::Float(f)) => f >= OrderedFloat(0f64),
|
|
Ok(Number::Rational(r)) => (*r).sign() == Sign::Positive,
|
|
_ => false,
|
|
}
|
|
}) && needs_bracketing(op_desc, op)
|
|
} else {
|
|
needs_bracketing(op_desc, op)
|
|
}
|
|
} else {
|
|
is_functor_redirect && op_desc.get_prec() >= 1000
|
|
}
|
|
} else {
|
|
false
|
|
};
|
|
|
|
if add_brackets {
|
|
self.state_stack.push(TokenOrRedirect::Close);
|
|
self.format_clause(max_depth, arity, name, Some(op_desc));
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
|
|
if !self.outputter.ends_with(" ") {
|
|
let parent_op = self
|
|
.parent_of_first_op
|
|
.and_then(|(parent_op, last_item_idx)| {
|
|
// if parent_op isn't printed to the output string
|
|
// already, then it doesn't border the present op
|
|
// and we should return None.
|
|
if self.last_item_idx == last_item_idx {
|
|
Some(parent_op)
|
|
} else {
|
|
None
|
|
}
|
|
});
|
|
|
|
for op in &[op, parent_op] {
|
|
if let Some(ref op) = &op {
|
|
if op.is_left()
|
|
&& (op.is_prefix() || requires_space(&op.as_atom().as_str(), "("))
|
|
{
|
|
self.state_stack.push(TokenOrRedirect::Space);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} else {
|
|
self.format_clause(max_depth, arity, name, Some(op_desc));
|
|
}
|
|
}
|
|
|
|
fn print_tcp_listener(&mut self, tcp_listener: &TcpListener, max_depth: usize) {
|
|
let (ip, port) = if let Ok(addr) = tcp_listener.local_addr() {
|
|
(addr.ip(), addr.port())
|
|
} else {
|
|
let disconnected_atom = atom!("$disconnected_tcp_listener");
|
|
self.state_stack
|
|
.push(TokenOrRedirect::Atom(disconnected_atom));
|
|
|
|
return;
|
|
};
|
|
|
|
let tcp_listener_atom = atom!("$tcp_listener");
|
|
|
|
if self.format_struct(max_depth, 1, tcp_listener_atom) {
|
|
let atom = self.state_stack.pop().unwrap();
|
|
|
|
self.state_stack.pop();
|
|
self.state_stack.pop();
|
|
|
|
self.state_stack.push(TokenOrRedirect::NumberFocus(
|
|
max_depth,
|
|
NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(port))),
|
|
None,
|
|
));
|
|
self.state_stack.push(TokenOrRedirect::Comma);
|
|
self.state_stack.push(TokenOrRedirect::IpAddr(ip));
|
|
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
self.state_stack.push(atom);
|
|
}
|
|
}
|
|
|
|
fn print_index_ptr(&mut self, idx: CodeIndexOffset, max_depth: usize) {
|
|
if self.format_struct(max_depth, 1, atom!("$index_ptr")) {
|
|
let atom = self.state_stack.pop().unwrap();
|
|
|
|
self.state_stack.pop();
|
|
self.state_stack.pop();
|
|
|
|
let idx_ptr = self.arena.code_index_tbl.get_entry(idx);
|
|
|
|
let offset = if idx_ptr.is_undefined() || idx_ptr.is_dynamic_undefined() {
|
|
TokenOrRedirect::Atom(atom!("undefined"))
|
|
} else {
|
|
let idx_ptr_p = idx_ptr.p() as i64;
|
|
|
|
if let Ok(n) = Fixnum::build_with_checked(idx_ptr_p) {
|
|
TokenOrRedirect::NumberFocus(
|
|
max_depth,
|
|
NumberFocus::Unfocused(Number::Fixnum(n)),
|
|
None,
|
|
)
|
|
} else {
|
|
TokenOrRedirect::Atom(atom!("out_of_bounds_idx_ptr"))
|
|
}
|
|
};
|
|
|
|
self.state_stack.push(offset);
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
self.state_stack.push(atom);
|
|
}
|
|
}
|
|
|
|
fn print_stream(&mut self, stream: Stream, max_depth: usize) {
|
|
if let Some(alias) = stream.options().get_alias() {
|
|
self.print_impromptu_atom(alias);
|
|
} else {
|
|
let stream_atom = atom!("$stream");
|
|
|
|
if self.format_struct(max_depth, 1, stream_atom) {
|
|
let atom = if stream.is_stdout() || stream.is_stdin() {
|
|
TokenOrRedirect::Atom(atom!("user"))
|
|
} else {
|
|
TokenOrRedirect::RawPtr(stream.as_ptr())
|
|
};
|
|
|
|
let stream_root = self.state_stack.pop().unwrap();
|
|
|
|
self.state_stack.pop();
|
|
self.state_stack.pop();
|
|
|
|
self.state_stack.push(atom);
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
self.state_stack.push(stream_root);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn print_comma_separated_char_list(&mut self, char_list: CommaSeparatedCharList) {
|
|
let CommaSeparatedCharList {
|
|
pstr_loc,
|
|
max_depth,
|
|
} = char_list;
|
|
|
|
let c = self.iter.heap.char_at(pstr_loc);
|
|
|
|
if c != '\u{0}' {
|
|
if !self.max_depth_exhausted(max_depth) {
|
|
self.state_stack
|
|
.push(TokenOrRedirect::CommaSeparatedCharList(
|
|
CommaSeparatedCharList {
|
|
pstr_loc: pstr_loc + c.len_utf8(),
|
|
max_depth: max_depth.saturating_sub(1),
|
|
},
|
|
));
|
|
|
|
let max_depth_allows = self.max_depth == 0 || max_depth > 1;
|
|
let next_c = self.iter.heap.char_at(pstr_loc + c.len_utf8());
|
|
|
|
if max_depth_allows && next_c != '\u{0}' {
|
|
self.state_stack.push(TokenOrRedirect::Comma);
|
|
}
|
|
|
|
self.state_stack.push(TokenOrRedirect::Char(c));
|
|
} else {
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
self.state_stack.push(TokenOrRedirect::HeadTailSeparator);
|
|
}
|
|
} else if self.max_depth_exhausted(max_depth) {
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
self.state_stack.push(TokenOrRedirect::HeadTailSeparator);
|
|
} else {
|
|
self.state_stack
|
|
.push(TokenOrRedirect::FunctorRedirect(max_depth + 1));
|
|
self.state_stack.push(TokenOrRedirect::HeadTailSeparator);
|
|
}
|
|
}
|
|
|
|
fn handle_heap_term(
|
|
&mut self,
|
|
op: Option<DirectedOp>,
|
|
is_functor_redirect: bool,
|
|
mut max_depth: usize,
|
|
) {
|
|
let negated_operand =
|
|
negated_op_needs_bracketing(&self.iter, &self.arena.f64_tbl, self.op_dir, &op);
|
|
|
|
let addr = match self.check_for_seen(&mut max_depth) {
|
|
Some(addr) => addr,
|
|
None => return,
|
|
};
|
|
|
|
let print_struct = |printer: &mut Self, name: Atom, arity: usize| {
|
|
if name == atom!("[]") && arity == 0 {
|
|
if let Some(TokenOrRedirect::CloseList(_) | TokenOrRedirect::ChildCloseList) =
|
|
printer.state_stack.last()
|
|
{
|
|
if printer.at_cdr("") {
|
|
return;
|
|
}
|
|
}
|
|
|
|
append_str!(printer, "[]");
|
|
} else if arity > 0 {
|
|
if let Some(spec) = fetch_op_spec(name, arity, printer.op_dir) {
|
|
printer.handle_op_as_struct(
|
|
name,
|
|
arity,
|
|
op,
|
|
is_functor_redirect,
|
|
spec,
|
|
negated_operand,
|
|
max_depth,
|
|
);
|
|
} else {
|
|
push_space_if_amb!(printer, &*name.as_str(), {
|
|
printer.format_clause(max_depth, arity, name, None);
|
|
});
|
|
}
|
|
} else if fetch_op_spec(name, arity, printer.op_dir).is_some() {
|
|
let mut result = String::new();
|
|
|
|
if let Some(ref op) = op {
|
|
let op_is_prefix = op.is_prefix() && op.is_left();
|
|
|
|
if op_is_prefix
|
|
|| printer
|
|
.outputter
|
|
.ends_with(&format!(" {}", op.as_atom().as_str()))
|
|
{
|
|
result.push(' ');
|
|
}
|
|
|
|
result.push('(');
|
|
}
|
|
|
|
result += &printer.print_op_addendum(&name.as_str());
|
|
|
|
if op.is_some() {
|
|
result.push(')');
|
|
}
|
|
|
|
push_space_if_amb!(printer, &result, {
|
|
append_str!(printer, &result);
|
|
});
|
|
} else {
|
|
push_space_if_amb!(printer, &name.as_str(), {
|
|
printer.print_impromptu_atom(name);
|
|
});
|
|
}
|
|
};
|
|
|
|
if !addr.is_var()
|
|
&& !addr.is_compound(self.iter.heap)
|
|
&& self.max_depth_exhausted(max_depth)
|
|
{
|
|
if !(addr == atom_as_cell!(atom!("[]")) && self.at_cdr("")) {
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
read_heap_cell!(addr,
|
|
(HeapCellValueTag::Atom, (name, arity)) => {
|
|
print_struct(self, name, arity);
|
|
}
|
|
(HeapCellValueTag::Str, s) => {
|
|
let (name, arity) = cell_as_atom_cell!(self.iter.heap[s])
|
|
.get_name_and_arity();
|
|
|
|
if let Some(spec) = fetch_op_spec(name, arity, self.op_dir) {
|
|
self.handle_op_as_struct(
|
|
name,
|
|
arity,
|
|
op,
|
|
is_functor_redirect,
|
|
spec,
|
|
negated_operand,
|
|
max_depth,
|
|
);
|
|
} else {
|
|
push_space_if_amb!(self, &*name.as_str(), {
|
|
self.format_clause(max_depth, arity, name, None);
|
|
});
|
|
}
|
|
}
|
|
(HeapCellValueTag::CodeIndexOffset, idx) => {
|
|
self.print_index_ptr(idx, self.max_depth);
|
|
}
|
|
(HeapCellValueTag::Fixnum | HeapCellValueTag::CutPoint, n) => {
|
|
self.print_number(max_depth, NumberFocus::Unfocused(Number::Fixnum(n)), &op);
|
|
}
|
|
(HeapCellValueTag::F64Offset, offset) => {
|
|
let f = self.arena.f64_tbl.get_entry(offset);
|
|
self.print_number(max_depth, NumberFocus::Unfocused(Number::Float(f)), &op);
|
|
}
|
|
(HeapCellValueTag::PStrLoc) => {
|
|
self.print_list_like(max_depth);
|
|
}
|
|
(HeapCellValueTag::Lis) => {
|
|
if self.ignore_ops {
|
|
let period_atom = atom!(".");
|
|
self.format_struct(max_depth, 2, period_atom);
|
|
} else {
|
|
self.print_list_like(max_depth);
|
|
}
|
|
}
|
|
(HeapCellValueTag::Var | HeapCellValueTag::AttrVar | HeapCellValueTag::StackVar) => {
|
|
let h = self.iter.focus();
|
|
|
|
if let Some(offset_str) = self.offset_as_string(h) {
|
|
push_space_if_amb!(self, &offset_str, {
|
|
append_str!(self, offset_str.as_str());
|
|
})
|
|
}
|
|
}
|
|
(HeapCellValueTag::Cons, c) => {
|
|
match_untyped_arena_ptr!(c,
|
|
(ArenaHeaderTag::Integer, n) => {
|
|
self.print_number(max_depth, NumberFocus::Unfocused(Number::Integer(n)), &op);
|
|
}
|
|
(ArenaHeaderTag::Rational, r) => {
|
|
self.print_number(max_depth, NumberFocus::Unfocused(Number::Rational(r)), &op);
|
|
}
|
|
(ArenaHeaderTag::Stream, stream) => {
|
|
self.print_stream(stream, max_depth);
|
|
}
|
|
(ArenaHeaderTag::TcpListener, listener) => {
|
|
self.print_tcp_listener(&listener, max_depth);
|
|
}
|
|
(ArenaHeaderTag::Dropped, _value) => {
|
|
self.print_impromptu_atom(atom!("$dropped_value"));
|
|
}
|
|
(ArenaHeaderTag::ChildProcess, process) => {
|
|
|
|
let process_atom = atom!("$process");
|
|
|
|
if self.format_struct(max_depth, 1, process_atom) {
|
|
let atom = TokenOrRedirect::NumberFocus(max_depth, NumberFocus::Unfocused(Number::Fixnum(Fixnum::build_with(process.id()))), op);
|
|
|
|
let process_root = self.state_stack.pop().unwrap();
|
|
|
|
self.state_stack.pop();
|
|
self.state_stack.pop();
|
|
|
|
self.state_stack.push(atom);
|
|
self.state_stack.push(TokenOrRedirect::Open);
|
|
self.state_stack.push(process_root);
|
|
}
|
|
}
|
|
_ => {
|
|
}
|
|
);
|
|
}
|
|
_ => {
|
|
unreachable!()
|
|
}
|
|
);
|
|
}
|
|
|
|
fn at_cdr(&mut self, tr: &str) -> bool {
|
|
let len = self.outputter.len();
|
|
|
|
if self.outputter.ends_with("|") {
|
|
self.outputter.truncate(len - "|".len());
|
|
append_str!(self, tr);
|
|
true
|
|
} else {
|
|
false
|
|
}
|
|
}
|
|
|
|
pub fn print(mut self) -> Outputter {
|
|
let spec = self.toplevel_spec.take();
|
|
self.handle_heap_term(spec, false, self.max_depth);
|
|
|
|
while let Some(loc_data) = self.state_stack.pop() {
|
|
match loc_data {
|
|
TokenOrRedirect::Atom(atom) => self.print_impromptu_atom(atom),
|
|
TokenOrRedirect::BarAsOp => append_str!(self, "|"),
|
|
TokenOrRedirect::Char(c) => print_char!(self, self.quoted, c),
|
|
TokenOrRedirect::Op(atom, op) => {
|
|
self.print_op(&atom.as_str());
|
|
|
|
if op.get_spec().is_prefix() {
|
|
self.set_parent_of_first_op(Some(DirectedOp::Left(atom, op)));
|
|
}
|
|
}
|
|
TokenOrRedirect::NumberedVar(num_var) => append_str!(self, &num_var),
|
|
TokenOrRedirect::CompositeRedirect(max_depth, op) => {
|
|
self.handle_heap_term(Some(op), false, max_depth)
|
|
}
|
|
TokenOrRedirect::CurlyBracketRedirect(max_depth) => {
|
|
self.handle_heap_term(None, false, max_depth)
|
|
}
|
|
TokenOrRedirect::FunctorRedirect(max_depth) => {
|
|
self.handle_heap_term(None, true, max_depth)
|
|
}
|
|
TokenOrRedirect::Close => push_char!(self, ')'),
|
|
TokenOrRedirect::IpAddr(ip) => self.print_ip_addr(ip),
|
|
TokenOrRedirect::RawPtr(ptr) => self.print_raw_ptr(ptr),
|
|
TokenOrRedirect::Open => push_char!(self, '('),
|
|
TokenOrRedirect::ChildOpenList => {
|
|
push_char!(self, '[');
|
|
}
|
|
TokenOrRedirect::ChildCloseList => {
|
|
push_char!(self, ']');
|
|
}
|
|
TokenOrRedirect::OpenList(delimit) => {
|
|
if !self.at_cdr(",") {
|
|
push_char!(self, '[');
|
|
} else {
|
|
let (_, max_depth) = delimit.get();
|
|
delimit.set((false, max_depth));
|
|
}
|
|
}
|
|
TokenOrRedirect::CloseList(delimit) => {
|
|
if delimit.get().0 {
|
|
push_char!(self, ']');
|
|
}
|
|
}
|
|
TokenOrRedirect::HeadTailSeparator => append_str!(self, "|"),
|
|
TokenOrRedirect::NumberFocus(max_depth, n, op) => {
|
|
self.print_number(max_depth, n, &op);
|
|
}
|
|
TokenOrRedirect::Comma => append_str!(self, ","),
|
|
TokenOrRedirect::Space => push_char!(self, ' '),
|
|
TokenOrRedirect::LeftCurly => push_char!(self, '{'),
|
|
TokenOrRedirect::RightCurly => push_char!(self, '}'),
|
|
TokenOrRedirect::StackPop => {
|
|
self.iter.pop_stack();
|
|
self.state_stack.push(TokenOrRedirect::Atom(atom!("...")));
|
|
}
|
|
TokenOrRedirect::CommaSeparatedCharList(char_list) => {
|
|
self.print_comma_separated_char_list(char_list);
|
|
}
|
|
}
|
|
}
|
|
|
|
self.outputter
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
use crate::functor_macro::*;
|
|
use crate::machine::mock_wam::*;
|
|
|
|
#[test]
|
|
#[cfg_attr(miri, ignore = "it takes too long to run")]
|
|
fn term_printing_tests() {
|
|
let mut wam = MockWAM::new();
|
|
|
|
// clear the heap of resource error data etc
|
|
wam.machine_st.heap.clear();
|
|
|
|
let f_atom = atom!("f");
|
|
let a_atom = atom!("a");
|
|
let b_atom = atom!("b");
|
|
let c_atom = atom!("c");
|
|
|
|
let mut functor_writer = Heap::functor_writer(functor!(
|
|
f_atom,
|
|
[atom_as_cell(a_atom), atom_as_cell(b_atom)]
|
|
));
|
|
|
|
let cell = functor_writer(&mut wam.machine_st.heap).unwrap();
|
|
wam.machine_st.heap.push_cell(cell).unwrap();
|
|
|
|
{
|
|
let printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
3,
|
|
);
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "f(a,b)");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.machine_st.heap.clear();
|
|
|
|
let mut functor_writer = Heap::functor_writer(functor!(
|
|
f_atom,
|
|
[
|
|
atom_as_cell(a_atom),
|
|
atom_as_cell(b_atom),
|
|
atom_as_cell(a_atom),
|
|
str_loc_as_cell(0)
|
|
]
|
|
));
|
|
|
|
let cell = functor_writer(&mut wam.machine_st.heap).unwrap();
|
|
|
|
wam.machine_st.heap.push_cell(cell).unwrap();
|
|
|
|
{
|
|
let printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
5,
|
|
);
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "f(a,b,a,...)");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.machine_st.heap.clear();
|
|
|
|
let mut writer = wam.machine_st.heap.reserve(96).unwrap();
|
|
|
|
// print L = [L|L].
|
|
writer.write_with(|section| {
|
|
section.push_cell(list_loc_as_cell!(1));
|
|
section.push_cell(list_loc_as_cell!(1));
|
|
section.push_cell(list_loc_as_cell!(1));
|
|
});
|
|
|
|
{
|
|
let printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
0,
|
|
);
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[...|...]");
|
|
|
|
let mut printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
0,
|
|
);
|
|
|
|
printer
|
|
.var_names
|
|
.insert(list_loc_as_cell!(1), VarPtr::from("L"));
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[L|L]");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.machine_st.heap.clear();
|
|
|
|
let mut writer = wam.machine_st.heap.reserve(96).unwrap();
|
|
|
|
writer.write_with(|section| {
|
|
section.push_cell(list_loc_as_cell!(1));
|
|
section.push_cell(str_loc_as_cell!(5));
|
|
section.push_cell(list_loc_as_cell!(3));
|
|
section.push_cell(str_loc_as_cell!(5));
|
|
section.push_cell(empty_list_as_cell!());
|
|
});
|
|
|
|
let mut functor_writer = Heap::functor_writer(functor!(
|
|
f_atom,
|
|
[
|
|
atom_as_cell(a_atom),
|
|
atom_as_cell(b_atom),
|
|
atom_as_cell(b_atom)
|
|
]
|
|
));
|
|
|
|
functor_writer(&mut wam.machine_st.heap).unwrap();
|
|
|
|
{
|
|
let printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
0,
|
|
);
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[f(a,b,b),f(a,b,b)]");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.machine_st.heap[4] = list_loc_as_cell!(1);
|
|
|
|
{
|
|
let printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
0,
|
|
);
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[f(a,b,b),f(a,b,b)|...]");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
{
|
|
let mut printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
0,
|
|
);
|
|
|
|
printer
|
|
.var_names
|
|
.insert(list_loc_as_cell!(1), VarPtr::from("L"));
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[f(a,b,b),f(a,b,b)|L]");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
// issue #382
|
|
wam.machine_st.heap.clear();
|
|
|
|
let mut writer = wam.machine_st.heap.reserve(6002).unwrap();
|
|
|
|
writer.write_with(|section| {
|
|
section.push_cell(list_loc_as_cell!(1));
|
|
|
|
for idx in 0..3000 {
|
|
section.push_cell(heap_loc_as_cell!(2 * idx + 1));
|
|
section.push_cell(list_loc_as_cell!(2 * idx + 2 + 1));
|
|
}
|
|
|
|
section.push_cell(empty_list_as_cell!());
|
|
});
|
|
|
|
{
|
|
let mut printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
0,
|
|
);
|
|
|
|
printer.max_depth = 5;
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[_1,_3,_5,_7,_9|...]");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.machine_st.heap.clear();
|
|
|
|
wam.machine_st.heap.allocate_pstr("abc").unwrap();
|
|
|
|
wam.machine_st.heap.push_cell(heap_loc_as_cell!(1)).unwrap();
|
|
wam.machine_st.heap.push_cell(pstr_loc_as_cell!(0)).unwrap();
|
|
|
|
{
|
|
let printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
2,
|
|
);
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "[a,b,c|_1]");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
let mut writer = wam.machine_st.heap.reserve(96).unwrap();
|
|
|
|
writer.write_with(|section| {
|
|
section.push_cell(atom_as_cell!(a_atom));
|
|
section.push_cell(list_loc_as_cell!(5));
|
|
section.push_cell(atom_as_cell!(b_atom));
|
|
section.push_cell(list_loc_as_cell!(7));
|
|
section.push_cell(atom_as_cell!(c_atom));
|
|
section.push_cell(empty_list_as_cell!());
|
|
});
|
|
|
|
wam.machine_st.heap[1] = list_loc_as_cell!(3);
|
|
|
|
{
|
|
let mut printer = HCPrinter::new(
|
|
&mut wam.machine_st.heap,
|
|
&mut wam.machine_st.stack,
|
|
&wam.machine_st.arena,
|
|
&wam.op_dir,
|
|
PrinterOutputter::new(),
|
|
2,
|
|
);
|
|
|
|
printer.double_quotes = true;
|
|
|
|
let output = printer.print();
|
|
|
|
assert_eq!(output.result(), "\"abcabc\"");
|
|
}
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.machine_st.heap.clear();
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("=(X,[a,b,c|X]).").unwrap(),
|
|
"=(X,[a,b,c|X])"
|
|
);
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("[a,b,\"a\",[a,b,c]].").unwrap(),
|
|
"[a,b,[a],[a,b,c]]"
|
|
);
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("[\"abc\",e,f,[g,e,h,Y,v|[X,Y]]].")
|
|
.unwrap(),
|
|
"[[a,b,c],e,f,[g,e,h,Y,v,X,Y]]"
|
|
);
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
assert_eq!(&wam.parse_and_print_term("f((a,b)).").unwrap(), "f((a,b))");
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.op_dir
|
|
.insert((atom!("+"), Fixity::In), OpDesc::build_with(500, YFX));
|
|
wam.op_dir
|
|
.insert((atom!("*"), Fixity::In), OpDesc::build_with(400, YFX));
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("[a|[] + b].").unwrap(),
|
|
"[a|[]+b]"
|
|
);
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("[a|[b|c]*d].").unwrap(),
|
|
"[a|[b|c]*d]"
|
|
);
|
|
|
|
all_cells_unmarked(&wam.machine_st.heap);
|
|
|
|
wam.op_dir
|
|
.insert((atom!("fy"), Fixity::Pre), OpDesc::build_with(9, FY));
|
|
|
|
wam.op_dir
|
|
.insert((atom!("yf"), Fixity::Post), OpDesc::build_with(9, YF));
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("(fy (fy 1)yf)yf.").unwrap(),
|
|
"(fy (fy 1)yf)yf"
|
|
);
|
|
|
|
assert_eq!(
|
|
&wam.parse_and_print_term("fy(fy(yf(fy(1)))).").unwrap(),
|
|
"fy fy (fy 1)yf"
|
|
);
|
|
}
|
|
}
|