diff --git a/mathics/builtin/evaluation.py b/mathics/builtin/evaluation.py index b0084b7975..788e977af6 100644 --- a/mathics/builtin/evaluation.py +++ b/mathics/builtin/evaluation.py @@ -406,13 +406,31 @@ class OutputSizeLimit(Predefined): To set no limit on output size, use $OutputSizeLimit = Infinity. - >> $OutputSizeLimit = 100; - >> Table[i, {i, 1, 100}] - = {1, 2, 3, 4, 5, <<90>>, 96, 97, 98, 99, 100} - >> $OutputSizeLimit = 10; + >> $OutputSizeLimit = 50; + >> Table[i, {i, 1, 100}] - = {1, <<98>>, 100} - >> $OutputSizeLimit = Infinity; + : Parts of this output were omitted (see <<71>>). To generate the whole output, please set $OutputSizeLimit = Infinity. + = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, <<71>>, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100} + + #> Take[Range[1000], 1001] + : Cannot take positions 1 through 1001 in {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, <<976>>, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000}. + : Parts of this output were omitted (see <<976>>). To generate the whole output, please set $OutputSizeLimit = Infinity. + = Take[{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, <<976>>, 989, 990, 991, 992, 993, 994, 995, 996, 997, 998, 999, 1000}, 1001] + + #> {} + = {} + + #> $OutputSizeLimit = 100; + + #> Table[Graphics[Table[Circle[],{10}]], {5}] + = {-Graphics-, -Graphics-, -Graphics-, -Graphics-, -Graphics-} + + #> Quiet[ImageAvailable = SameQ[Head[Image[{{0, 1}, {1, 0}}] // ToBoxes], ImageBox]]; + #> If[ImageAvailable, Table[Image[{{1, 0}, {0, 1}}], {5}], {"-Image-", "-Image-", "-Image-", "-Image-", "-Image-"}] + = {-Image-, -Image-, -Image-, -Image-, -Image-} + + #> $OutputSizeLimit = Infinity; + """ name = '$OutputSizeLimit' diff --git a/mathics/builtin/inout.py b/mathics/builtin/inout.py index 883fb630e7..54efd62855 100644 --- a/mathics/builtin/inout.py +++ b/mathics/builtin/inout.py @@ -12,6 +12,7 @@ import re import sympy import mpmath +from itertools import chain from mathics.builtin.base import ( Builtin, BinaryOperator, BoxConstruct, BoxConstructError, Operator) @@ -24,7 +25,6 @@ from_python, MachineReal, PrecisionReal, Omitted) from mathics.core.numbers import ( dps, prec, convert_base, machine_precision, reconstruct_digits) -from mathics.builtin.lists import riffle MULTI_NEWLINE_RE = re.compile(r"\n{2,}") @@ -82,20 +82,12 @@ def parenthesize(precedence, leaf, leaf_boxes, when_equal): def make_boxes_infix(leaves, ops, precedence, grouping, form, evaluation): - segment = [] - boxes = evaluation.make_boxes(leaves, form, segment) + def materialize(prefix, inner, suffix): + return Expression('RowBox', Expression('List', *list(chain(prefix, inner, suffix)))) - seg_shortened, seg_l, seg_r = segment - if seg_shortened: - leaves = leaves[:seg_l] + [Symbol('Null')] + leaves[seg_r:] - ops = ops[:seg_l] + ops[seg_r - 1:] # ellipsis item gets rightmost operator from ellipsed chunk - - result = [] - for index, leaf_box in enumerate(zip(leaves, boxes)): - leaf, box = leaf_box - - if index > 0: - result.append(ops[index - 1]) + def make_leaf(index): + leaf = leaves[index] + box = Expression('MakeBoxes', leaf, form) parenthesized = False if grouping == 'System`NonAssociative': @@ -105,13 +97,12 @@ def make_boxes_infix(leaves, ops, precedence, grouping, form, evaluation): elif grouping == 'System`Right' and index == 0: parenthesized = True - if seg_shortened and index == seg_l: - leaf = box # ellipsis item, do not parenthesize - else: - leaf = parenthesize(precedence, leaf, box, parenthesized) + return parenthesize(precedence, leaf, box, parenthesized) - result.append(leaf) - return Expression('RowBox', Expression('List', *result)) + return evaluation.make_boxes( + None, make_leaf, len(leaves), + None, None, ops, + materialize, form) def real_to_s_exp(expr, n): @@ -480,24 +471,24 @@ def apply_general(self, expr, f, evaluation): # Parenthesize infix operators at the head of expressions, # like (a + b)[x], but not f[a] in f[a][b]. - # - head_boxes = parenthesize(670, - head, MakeBoxes(head, f), False) - result = [head_boxes, String(left)] - - if len(leaves) > 1: - if f_name in ('System`InputForm', 'System`OutputForm', - 'System`FullForm'): - sep = ', ' - else: - sep = ',' - boxes = evaluation.make_boxes(leaves, f) - row = riffle(boxes, String(sep)) - result.append(RowBox(Expression('List', *row))) - elif len(leaves) == 1: - result.append(MakeBoxes(leaves[0], f)) - result.append(String(right)) - return RowBox(Expression('List', *result)) + prefix = parenthesize(670, head, Expression('MakeBoxes', head, f), False) + + def make_leaf(i): + return Expression('MakeBoxes', leaves[i], f) + + if f_name in ('System`InputForm', 'System`OutputForm', + 'System`FullForm'): + sep = ', ' + else: + sep = ',' + + def materialize(prefix, inner, suffix): + if len(inner) > 1: + inner = [Expression('RowBox', Expression('List', *inner))] + return Expression('RowBox', Expression('List', *list(chain(prefix, inner, suffix)))) + + return evaluation.make_boxes( + prefix, make_leaf, len(leaves), String(left), String(right), String(sep), materialize, f) def _apply_atom(self, x, f, evaluation): '''MakeBoxes[x_?AtomQ, @@ -797,20 +788,31 @@ def apply_makeboxes(self, array, f, evaluation, options): f:StandardForm|TraditionalForm|OutputForm]''' lengths = [len(row.leaves) for row in array.leaves] - segment = [] - boxes = evaluation.make_boxes([item for row in array.leaves for item in row.leaves], f, segment) - if segment[0]: # too long? - return Omitted('<<%d>>' % sum(lengths)) - else: - rows = [] - i = 0 - for l in lengths: - rows.append(Expression('List', *boxes[i:i + l])) - i += l - return Expression( - 'GridBox', - Expression('List', *rows), - *options_to_rules(options)) + n_leaves = sum(lengths) + + def materialize(boxes): + if len(boxes) == n_leaves: + rows = [] + i = 0 + for l in lengths: + rows.append(Expression('List', *boxes[i:i + l])) + i += l + return Expression( + 'GridBox', + Expression('List', *rows), + *options_to_rules(options)) + else: # too long + return Omitted('<<%d>>' % n_leaves) + + flat = [item for row in array.leaves for item in row.leaves] + + def make_leaf(i): + return Expression('MakeBoxes', flat[i], f) + + return evaluation.make_boxes( + None, make_leaf, n_leaves, + None, None, None, + materialize, f) class TableForm(Builtin): @@ -961,9 +963,11 @@ class Subscript(Builtin): def apply_makeboxes(self, x, y, f, evaluation): 'MakeBoxes[Subscript[x_, y__], f:StandardForm|TraditionalForm]' - y = y.get_sequence() - return Expression( - 'SubscriptBox', Expression('MakeBoxes', x, f), *list_boxes(y, f, evaluation)) + def materialize(prefix, inner, suffix): + return Expression('SubscriptBox', *list(chain(prefix, inner, suffix))) + + return list_boxes( + Expression('MakeBoxes', x, f), y.get_sequence(), materialize, f, evaluation) class SubscriptBox(Builtin): @@ -1140,6 +1144,12 @@ def apply_makeboxes(self, s, args, f, evaluation): '''MakeBoxes[StringForm[s_String, args___], f:StandardForm|TraditionalForm|OutputForm]''' + # StringForm does not call evaluation.make_boxes + # since we use it for messages and we never want + # to omit parts of the message. args are subject + # to MakeBoxes (see below) and thus can get parts + # omitted. + s = s.value args = args.get_sequence() result = [] @@ -1640,6 +1650,8 @@ class General(Builtin): 'invalidargs': "Invalid arguments.", 'notboxes': "`1` is not a valid box structure.", + 'omit': "Parts of this output were omitted (see `1`). " + + "To generate the whole output, please set $OutputSizeLimit = Infinity.", 'pyimport': "`1`[] is not available. Your Python installation misses the \"`2`\" module.", } diff --git a/mathics/builtin/lists.py b/mathics/builtin/lists.py index 8068d0a0d2..0ca9813222 100644 --- a/mathics/builtin/lists.py +++ b/mathics/builtin/lists.py @@ -59,8 +59,11 @@ def apply_makeboxes(self, items, f, evaluation): f:StandardForm|TraditionalForm|OutputForm|InputForm]''' items = items.get_sequence() - return Expression( - 'RowBox', Expression('List', *list_boxes(items, f, evaluation, "{", "}"))) + + def materialize(prefix, inner, suffix): + return Expression('RowBox', Expression('List', *list(chain(prefix, inner, suffix)))) + + return list_boxes(None, items, materialize, f, evaluation, "{", "}") class ListQ(Test): @@ -93,25 +96,22 @@ def test(self, expr): return expr.get_head_name() != 'System`List' -def list_boxes(items, f, evaluation, open=None, close=None): - result = evaluation.make_boxes(items, f) +def list_boxes(prefix, items, materialize, f, evaluation, open=None, close=None): + if open is not None: + open = String(open) + if close is not None: + close = String(close) + if f.get_name() in ('System`OutputForm', 'System`InputForm'): sep = ", " else: sep = "," - result = riffle(result, String(sep)) - if len(items) > 1: - result = Expression('RowBox', Expression('List', *result)) - elif items: - result = result[0] - if result: - result = [result] - else: - result = [] - if open is not None and close is not None: - return [String(open)] + result + [String(close)] - else: - return result + + def make_leaf(i): + return Expression('MakeBoxes', items[i], f) + + return evaluation.make_boxes( + prefix, make_leaf, len(items), open, close, sep, materialize, f) class Length(Builtin): @@ -822,14 +822,18 @@ def apply_makeboxes(self, list, i, f, evaluation): f:StandardForm|TraditionalForm|OutputForm|InputForm]''' i = i.get_sequence() + list = Expression('MakeBoxes', list, f) + if f.get_name() in ('System`OutputForm', 'System`InputForm'): open, close = "[[", "]]" else: open, close = "\u301a", "\u301b" - indices = list_boxes(i, f, evaluation, open, close) - result = Expression('RowBox', Expression('List', list, *indices)) - return result + + def materialize(prefix, inner, suffix): + return Expression('RowBox', Expression('List', *list(chain(prefix, inner, suffix)))) + + return list_boxes(list, i, materialize, f, evaluation, open, close) def apply(self, list, i, evaluation): 'Part[list_, i___]' diff --git a/mathics/core/evaluation.py b/mathics/core/evaluation.py index c4a3112de5..78372e3a6c 100644 --- a/mathics/core/evaluation.py +++ b/mathics/core/evaluation.py @@ -14,7 +14,7 @@ import itertools from mathics import settings -from mathics.core.expression import ensure_context, KeyComparable, make_boxes_strategy +from mathics.core.expression import ensure_context, KeyComparable, make_boxes_strategy, Omissions FORMATS = ['StandardForm', 'FullForm', 'TraditionalForm', 'OutputForm', 'InputForm', @@ -178,7 +178,7 @@ def __init__(self, definitions=None, self.quiet_all = False self.format = format - self.boxes_strategy = make_boxes_strategy(None, self) + self.boxes_strategy = make_boxes_strategy(None, None, self) self.catch_interrupt = catch_interrupt def parse(self, query): @@ -317,7 +317,7 @@ def get_stored_result(self, result): def stop(self): self.stopped = True - def format_output(self, expr, format=None): + def format_output(self, expr, format=None, warn_about_omitted=True): if format is None: format = self.format @@ -329,18 +329,24 @@ def format_output(self, expr, format=None): old_boxes_strategy = self.boxes_strategy try: capacity = self.definitions.get_config_value('System`$OutputSizeLimit') - self.boxes_strategy = make_boxes_strategy(capacity, self) + omissions = Omissions() + self.boxes_strategy = make_boxes_strategy(capacity, omissions, self) options = {} + # for xml/MathMLForm and tex/TexForm, output size limits are applied in the output + # form's apply methods (e.g. see MathMLForm.apply) and then passed through + # result.boxes_to_text which, in these two cases, must not apply any additional + # clipping (it would clip already clipped string material). + + # for text/OutputForm, on the other hand, the call to result.boxes_to_text is the + # only place there is to apply output size limits, which has us resort to setting + # options['output_size_limit']. NOTE: disabled right now, causes problems with + # long message outputs (see test case Table[i, {i, 1, 100}] under OutputSizeLimit). + if format == 'text': result = expr.format(self, 'System`OutputForm') - # for MathMLForm and TexForm, output size limits are applied in the form's apply - # methods (e.g. see MathMLForm.apply) and then passed through result.boxes_to_text - # which must, in these cases, not apply additional clipping, as this would clip - # already clipped string material. for OutputForm, on the other hand, the call to - # result.boxes_to_text is the only place we have to apply output size limits. - options['output_size_limit'] = capacity + # options['output_size_limit'] = capacity elif format == 'xml': result = Expression( 'StandardForm', expr).format(self, 'System`MathMLForm') @@ -356,6 +362,9 @@ def format_output(self, expr, format=None): self.message('General', 'notboxes', Expression('FullForm', result).evaluate(self)) boxes = None + + if warn_about_omitted: + omissions.warn(self) finally: self.boxes_strategy = old_boxes_strategy @@ -378,8 +387,9 @@ def get_quiet_messages(self): return [] return value.leaves - def make_boxes(self, items, form, segment=None): - return self.boxes_strategy.make(items, form, segment) + def make_boxes(self, prefix, make_leaf, n_leaves, left, right, sep, materialize, form): + return self.boxes_strategy.make( + prefix, make_leaf, n_leaves, left, right, sep, materialize, form) def message(self, symbol, tag, *args): from mathics.core.expression import (String, Symbol, Expression, @@ -415,7 +425,7 @@ def message(self, symbol, tag, *args): text = String("Message %s::%s not found." % (symbol_shortname, tag)) text = self.format_output(Expression( - 'StringForm', text, *(from_python(arg) for arg in args)), 'text') + 'StringForm', text, *(from_python(arg) for arg in args)), 'text', warn_about_omitted=False) self.out.append(Message(symbol_shortname, tag, text)) self.output.out(self.out[-1]) diff --git a/mathics/core/expression.py b/mathics/core/expression.py index c0da8685b4..7cfcc51fa5 100644 --- a/mathics/core/expression.py +++ b/mathics/core/expression.py @@ -62,6 +62,15 @@ def system_symbols_dict(d): return {ensure_context(k): v for k, v in six.iteritems(d)} +_layout_boxes = system_symbols( + 'RowBox', + 'SuperscriptBox', + 'SubscriptBox', + 'SubsuperscriptBox', + 'FractionBox', + 'SqrtBox') + + class BoxError(Exception): def __init__(self, box, form): super(BoxError, self).__init__( @@ -311,6 +320,12 @@ def format(self, evaluation, form): 'MakeBoxes', expr, Symbol(form)).evaluate(evaluation) return result + def output_cost(self): + # the cost of outputting this item, usually the number of + # characters without any formatting elements or whitespace; + # e.g. "a, b", would be counted as 3. + return 1 # fallback implementation: count as one character + def is_free(self, form, evaluation): from mathics.core.pattern import StopGenerator @@ -1155,6 +1170,27 @@ def block(tex, only_subsup=False): else: raise BoxError(self, 'tex') + def output_cost(self): + name = self.get_head_name() + + if name in ('System`ImageBox', 'System`GraphicsBox', 'System`Graphics3DBox'): + return 1 # count as expensive as one character + + leaves = self.leaves + cost_of_leaves = sum(leaf.output_cost() for leaf in leaves) + + if name in _layout_boxes: + return cost_of_leaves + else: + separator_cost = 1 # i.e. "," + n_separators = max(len(leaves) - 1, 0) + total_cost = 2 + cost_of_leaves + separator_cost * n_separators # {a, b, c}, [a, b, c] + + if name != 'System`List': + total_cost += self.head.output_cost() + + return total_cost + def default_format(self, evaluation, form): return '%s[%s]' % (self.head.default_format(evaluation, form), ', '.join([leaf.default_format(evaluation, form) @@ -1435,6 +1471,9 @@ def __str__(self): def do_copy(self): return Symbol(self.name) + def output_cost(self): + return len(self.name) + def boxes_to_text(self, **options): return str(self.name) @@ -1583,6 +1622,7 @@ def _NumberFormat(man, base, exp, options): 'NumberMultiplier': '\u00d7', } + class Integer(Number): def __new__(cls, value): n = int(value) @@ -1596,6 +1636,9 @@ def boxes_to_text(self, **options): def boxes_to_xml(self, **options): return self.make_boxes('MathMLForm').boxes_to_xml(**options) + def output_cost(self): + return len(str(self.value)) + def boxes_to_tex(self, **options): return str(self.value) @@ -1665,6 +1708,10 @@ def __new__(cls, numerator, denominator=None): self.value = sympy.Rational(numerator, denominator) return self + def output_cost(self): + numer, denom = self.value.as_numer_denom() + return len(str(numer)) + len(str(denom)) + def atom_to_boxes(self, f, evaluation): return self.format(evaluation, f.get_name()) @@ -1771,6 +1818,9 @@ def __new__(cls, value, p=None): else: return PrecisionReal.__new__(PrecisionReal, value) + def output_cost(self): + return len(self.boxes_to_text()) + def boxes_to_text(self, **options): return self.make_boxes('System`OutputForm').boxes_to_text(**options) @@ -1970,6 +2020,9 @@ def atom_to_boxes(self, f, evaluation): def __str__(self): return str(self.to_sympy()) + def output_cost(self): + return self.real.output_cost() + self.imag.output_cost() + 2 # "+", "I" + def to_sympy(self, **kwargs): return self.real.to_sympy() + sympy.I * self.imag.to_sympy() @@ -2134,6 +2187,9 @@ def __new__(cls, value): def __str__(self): return '"%s"' % self.value + def output_cost(self): + return len(self.value) + def boxes_to_text(self, show_string_characters=False, output_size_limit=None, **options): value = self.value if (not show_string_characters and # nopep8 @@ -2289,7 +2345,8 @@ def boxes_to_text(self, **options): def boxes_to_xml(self, **options): new_options = dict((k, v) for k, v in options.items() if k != 'output_size_limit') - return super(Omitted, self).boxes_to_xml(**new_options) + s = super(Omitted, self).boxes_to_xml(**new_options) + return "%s" % s def boxes_to_tex(self, **options): new_options = dict((k, v) for k, v in options.items() if k != 'output_size_limit') @@ -2338,21 +2395,62 @@ class _MakeBoxesStrategy(object): def capacity(self): raise NotImplementedError() - def make(self, items, form, segment=None): + def make(self, head, make_leaf, n_leaves, left, right, sep, materialize, form): raise NotImplementedError() +class Omissions: + def __init__(self): + self._omissions = [] + + def add(self, count): + n = len(self._omissions) + if n < 3: + self._omissions.append('<<%d>>' % count) + if n == 3: + self._omissions.append('...') + + def warn(self, evaluation): + if self._omissions: + evaluation.message('General', 'omit', ', '.join(self._omissions)) + + +def _riffle_separators(elements, separators): + yield elements[0] + for e, s in zip(elements[1:], separators): + yield s + yield e + + class _UnlimitedMakeBoxesStrategy(_MakeBoxesStrategy): def __init__(self): - pass + self.omissions_occured = False def capacity(self): return None - def make(self, items, form, segment=None): - if segment is not None: - segment.extend((False, 0, 0)) - return [Expression('MakeBoxes', item, form) for item in items] + def make(self, head, make_leaf, n_leaves, left, right, sep, materialize, form): + prefix = [] + if head is not None: + prefix.append(head) + if left is not None: + prefix.append(left) + + inner = [make_leaf(i) for i in range(n_leaves)] + + if sep is not None and n_leaves > 1: + if isinstance(sep, (list, tuple)): + inner = list(_riffle_separators(inner, sep)) + else: + from mathics.builtin.lists import riffle + inner = riffle(inner, sep) + + if right is not None: + suffix = [right] + else: + suffix = [] + + return materialize(prefix, inner, suffix) class _LimitedMakeBoxesState: @@ -2365,31 +2463,32 @@ def __init__(self, capacity, side, both_sides, depth): class _LimitedMakeBoxesStrategy(_MakeBoxesStrategy): - def __init__(self, capacity, evaluation): + def __init__(self, capacity, omissions, evaluation): self._capacity = capacity self._evaluation = evaluation self._state = _LimitedMakeBoxesState(self._capacity, 1, True, 1) self._unlimited = _UnlimitedMakeBoxesStrategy() + self._omissions = omissions def capacity(self): return self._capacity - def make(self, items, form, segment=None): + def make(self, head, make_leaf, n_leaves, left, right, sep, materialize, form): state = self._state capacity = state.capacity - if capacity is None or len(items) < 1: - return self._unlimited(items, form, segment) + if capacity is None or n_leaves <= 2: + return self._unlimited.make(head, make_leaf, n_leaves, left, right, sep, materialize, form) left_leaves = [] right_leaves = [] - middle = len(items) // 2 + middle = n_leaves // 2 # note that we use generator expressions, not list comprehensions, here, since we # might quit early in the loop below, and copying all leaves might prove inefficient. - from_left = ((leaf, left_leaves.append) for leaf in items[:middle]) - from_right = ((leaf, right_leaves.append) for leaf in reversed(items[middle:])) + from_left = ((leaf, left_leaves.append) for leaf in range(0, middle)) + from_right = ((leaf, right_leaves.append) for leaf in reversed(range(middle, n_leaves))) # specify at which side to start making boxes (either from the left or from the right). side = state.side @@ -2402,7 +2501,7 @@ def make(self, items, form, segment=None): # however, at the first real list (i.e. > 1 elements) we force evaluation of # both sides in order to make sure the start and the end portion is visible. both_sides = state.both_sides - if both_sides and len(items) > 1: + if both_sides and n_leaves > 1: delay_break = 1 both_sides = False # disable both_sides from this depth on else: @@ -2411,7 +2510,7 @@ def make(self, items, form, segment=None): depth = state.depth sum_of_costs = 0 - for i, (item, push) in enumerate(_interleave(*from_sides)): + for i, (index, push) in enumerate(_interleave(*from_sides)): # calling evaluate() here is a serious difference to the implementation # without $OutputSizeLimit. here, we evaluate MakeBoxes bottom up, i.e. # the leaves get evaluated first, since we need to estimate their size @@ -2429,8 +2528,8 @@ def make(self, items, form, segment=None): # runtime (quadratic in the depth of the tree). box, cost = self._evaluate( - item, - form, + make_leaf, + index, capacity=capacity // 2, # reserve rest half of capacity for other side side=side * -((i % 2) * 2 - 1), # flip between side and -side both_sides=both_sides, # force both-sides evaluation for deeper levels? @@ -2446,39 +2545,63 @@ def make(self, items, form, segment=None): if capacity <= 0: break - ellipsis_size = len(items) - (len(left_leaves) + len(right_leaves)) + ellipsis_size = n_leaves - (len(left_leaves) + len(right_leaves)) + if ellipsis_size > 0 and self._omissions: + self._omissions.add(ellipsis_size) ellipsis = [Omitted('<<%d>>' % ellipsis_size)] if ellipsis_size > 0 else [] - if segment is not None: - if ellipsis_size > 0: - segment.extend((True, len(left_leaves), len(items) - len(right_leaves))) + #if segment is not None: + # if ellipsis_size > 0: + # segment.extend((True, len(left_leaves), len(items) - len(right_leaves))) + # else: + # segment.extend((False, 0, 0)) + + inner = list(chain(left_leaves, ellipsis, reversed(right_leaves))) + + if sep is not None and n_leaves > 1: + if isinstance(sep, (list, tuple)): + # ellipsis item gets rightmost separator from ellipsed chunk + sep = sep[:len(left_leaves)] + sep[len(right_leaves) - 1:] + inner = list(_riffle_separators(inner, sep)) else: - segment.extend((False, 0, 0)) + from mathics.builtin.lists import riffle + inner = riffle(inner, sep) + + prefix = [] + if head is not None: + prefix.append(head) + if left is not None: + prefix.append(left) + + if right is not None: + suffix = [right] + else: + suffix = [] - return list(chain(left_leaves, ellipsis, reversed(right_leaves))) + return materialize(prefix, inner, suffix) - def _evaluate(self, item, form, **kwargs): + def _evaluate(self, make_leaf, index, **kwargs): old_state = self._state try: state = _LimitedMakeBoxesState(**kwargs) self._state = state - box = Expression('MakeBoxes', item, form).evaluate(self._evaluation) + box = make_leaf(index).evaluate(self._evaluation) # estimate the cost of the output related to box. always calling boxes_to_xml here is # the simple solution; the problem is that it's redundant, as for {{{a}, b}, c}, we'd # call boxes_to_xml first on {a}, then on {{a}, b}, then on {{{a}, b}, c}. a good fix # is not simple though, so let's keep it this way for now. - cost = len(box.boxes_to_xml(evaluation=self._evaluation)) # evaluate len as XML + cost = box.output_cost() return box, cost finally: self._state = old_state -def make_boxes_strategy(capacity, evaluation): +def make_boxes_strategy(capacity, omissions, evaluation): if capacity is None: return _UnlimitedMakeBoxesStrategy() else: - return _LimitedMakeBoxesStrategy(capacity, evaluation) + return _LimitedMakeBoxesStrategy(capacity, omissions, evaluation)