| """Prettyprinter by Jurjen Bos. |
| (I hate spammers: mail me at pietjepuk314 at the reverse of ku.oc.oohay). |
| All objects have a method that create a "stringPict", |
| that can be used in the str method for pretty printing. |
| |
| Updates by Jason Gedge (email <my last name> at cs mun ca) |
| - terminal_string() method |
| - minor fixes and changes (mostly to prettyForm) |
| |
| TODO: |
| - Allow left/center/right alignment options for above/below and |
| top/center/bottom alignment options for left/right |
| """ |
|
|
| import shutil |
|
|
| from .pretty_symbology import hobj, vobj, xsym, xobj, pretty_use_unicode, line_width, center |
| from sympy.utilities.exceptions import sympy_deprecation_warning |
|
|
| _GLOBAL_WRAP_LINE = None |
|
|
| class stringPict: |
| """An ASCII picture. |
| The pictures are represented as a list of equal length strings. |
| """ |
| |
| LINE = 'line' |
|
|
| def __init__(self, s, baseline=0): |
| """Initialize from string. |
| Multiline strings are centered. |
| """ |
| self.s = s |
| |
| self.picture = stringPict.equalLengths(s.splitlines()) |
| |
| self.baseline = baseline |
| self.binding = None |
|
|
| @staticmethod |
| def equalLengths(lines): |
| |
| if not lines: |
| return [''] |
|
|
| width = max(line_width(line) for line in lines) |
| return [center(line, width) for line in lines] |
|
|
| def height(self): |
| """The height of the picture in characters.""" |
| return len(self.picture) |
|
|
| def width(self): |
| """The width of the picture in characters.""" |
| return line_width(self.picture[0]) |
|
|
| @staticmethod |
| def next(*args): |
| """Put a string of stringPicts next to each other. |
| Returns string, baseline arguments for stringPict. |
| """ |
| |
| objects = [] |
| for arg in args: |
| if isinstance(arg, str): |
| arg = stringPict(arg) |
| objects.append(arg) |
|
|
| |
| newBaseline = max(obj.baseline for obj in objects) |
| newHeightBelowBaseline = max( |
| obj.height() - obj.baseline |
| for obj in objects) |
| newHeight = newBaseline + newHeightBelowBaseline |
|
|
| pictures = [] |
| for obj in objects: |
| oneEmptyLine = [' '*obj.width()] |
| basePadding = newBaseline - obj.baseline |
| totalPadding = newHeight - obj.height() |
| pictures.append( |
| oneEmptyLine * basePadding + |
| obj.picture + |
| oneEmptyLine * (totalPadding - basePadding)) |
|
|
| result = [''.join(lines) for lines in zip(*pictures)] |
| return '\n'.join(result), newBaseline |
|
|
| def right(self, *args): |
| r"""Put pictures next to this one. |
| Returns string, baseline arguments for stringPict. |
| (Multiline) strings are allowed, and are given a baseline of 0. |
| |
| Examples |
| ======== |
| |
| >>> from sympy.printing.pretty.stringpict import stringPict |
| >>> print(stringPict("10").right(" + ",stringPict("1\r-\r2",1))[0]) |
| 1 |
| 10 + - |
| 2 |
| |
| """ |
| return stringPict.next(self, *args) |
|
|
| def left(self, *args): |
| """Put pictures (left to right) at left. |
| Returns string, baseline arguments for stringPict. |
| """ |
| return stringPict.next(*(args + (self,))) |
|
|
| @staticmethod |
| def stack(*args): |
| """Put pictures on top of each other, |
| from top to bottom. |
| Returns string, baseline arguments for stringPict. |
| The baseline is the baseline of the second picture. |
| Everything is centered. |
| Baseline is the baseline of the second picture. |
| Strings are allowed. |
| The special value stringPict.LINE is a row of '-' extended to the width. |
| """ |
| |
| objects = [] |
| for arg in args: |
| if arg is not stringPict.LINE and isinstance(arg, str): |
| arg = stringPict(arg) |
| objects.append(arg) |
|
|
| |
| newWidth = max( |
| obj.width() |
| for obj in objects |
| if obj is not stringPict.LINE) |
|
|
| lineObj = stringPict(hobj('-', newWidth)) |
|
|
| |
| for i, obj in enumerate(objects): |
| if obj is stringPict.LINE: |
| objects[i] = lineObj |
|
|
| |
| newPicture = [center(line, newWidth) for obj in objects for line in obj.picture] |
| newBaseline = objects[0].height() + objects[1].baseline |
| return '\n'.join(newPicture), newBaseline |
|
|
| def below(self, *args): |
| """Put pictures under this picture. |
| Returns string, baseline arguments for stringPict. |
| Baseline is baseline of top picture |
| |
| Examples |
| ======== |
| |
| >>> from sympy.printing.pretty.stringpict import stringPict |
| >>> print(stringPict("x+3").below( |
| ... stringPict.LINE, '3')[0]) #doctest: +NORMALIZE_WHITESPACE |
| x+3 |
| --- |
| 3 |
| |
| """ |
| s, baseline = stringPict.stack(self, *args) |
| return s, self.baseline |
|
|
| def above(self, *args): |
| """Put pictures above this picture. |
| Returns string, baseline arguments for stringPict. |
| Baseline is baseline of bottom picture. |
| """ |
| string, baseline = stringPict.stack(*(args + (self,))) |
| baseline = len(string.splitlines()) - self.height() + self.baseline |
| return string, baseline |
|
|
| def parens(self, left='(', right=')', ifascii_nougly=False): |
| """Put parentheses around self. |
| Returns string, baseline arguments for stringPict. |
| |
| left or right can be None or empty string which means 'no paren from |
| that side' |
| """ |
| h = self.height() |
| b = self.baseline |
|
|
| |
| if ifascii_nougly and not pretty_use_unicode(): |
| h = 1 |
| b = 0 |
|
|
| res = self |
|
|
| if left: |
| lparen = stringPict(vobj(left, h), baseline=b) |
| res = stringPict(*lparen.right(self)) |
| if right: |
| rparen = stringPict(vobj(right, h), baseline=b) |
| res = stringPict(*res.right(rparen)) |
|
|
| return ('\n'.join(res.picture), res.baseline) |
|
|
| def leftslash(self): |
| """Precede object by a slash of the proper size. |
| """ |
| |
| height = max( |
| self.baseline, |
| self.height() - 1 - self.baseline)*2 + 1 |
| slash = '\n'.join( |
| ' '*(height - i - 1) + xobj('/', 1) + ' '*i |
| for i in range(height) |
| ) |
| return self.left(stringPict(slash, height//2)) |
|
|
| def root(self, n=None): |
| """Produce a nice root symbol. |
| Produces ugly results for big n inserts. |
| """ |
| |
| |
| |
| result = self.above('_'*self.width()) |
| |
| height = self.height() |
| slash = '\n'.join( |
| ' ' * (height - i - 1) + '/' + ' ' * i |
| for i in range(height) |
| ) |
| slash = stringPict(slash, height - 1) |
| |
| if height > 2: |
| downline = stringPict('\\ \n \\', 1) |
| else: |
| downline = stringPict('\\') |
| |
| if n is not None and n.width() > downline.width(): |
| downline = downline.left(' '*(n.width() - downline.width())) |
| downline = downline.above(n) |
| |
| root = downline.right(slash) |
| |
| |
| |
| |
| |
| root.baseline = result.baseline - result.height() + root.height() |
| return result.left(root) |
|
|
| def render(self, * args, **kwargs): |
| """Return the string form of self. |
| |
| Unless the argument line_break is set to False, it will |
| break the expression in a form that can be printed |
| on the terminal without being broken up. |
| """ |
| if _GLOBAL_WRAP_LINE is not None: |
| kwargs["wrap_line"] = _GLOBAL_WRAP_LINE |
|
|
| if kwargs["wrap_line"] is False: |
| return "\n".join(self.picture) |
|
|
| if kwargs["num_columns"] is not None: |
| |
| ncols = kwargs["num_columns"] |
| else: |
| |
| ncols = self.terminal_width() |
|
|
| if ncols <= 0: |
| ncols = 80 |
|
|
| |
| if self.width() <= ncols: |
| return type(self.picture[0])(self) |
|
|
| """ |
| Break long-lines in a visually pleasing format. |
| without overflow indicators | with overflow indicators |
| | 2 2 3 | | 2 2 3 ↪| |
| |6*x *y + 4*x*y + | |6*x *y + 4*x*y + ↪| |
| | | | | |
| | 3 4 4 | |↪ 3 4 4 | |
| |4*y*x + x + y | |↪ 4*y*x + x + y | |
| |a*c*e + a*c*f + a*d | |a*c*e + a*c*f + a*d ↪| |
| |*e + a*d*f + b*c*e | | | |
| |+ b*c*f + b*d*e + b | |↪ *e + a*d*f + b*c* ↪| |
| |*d*f | | | |
| | | |↪ e + b*c*f + b*d*e ↪| |
| | | | | |
| | | |↪ + b*d*f | |
| """ |
|
|
| overflow_first = "" |
| if kwargs["use_unicode"] or pretty_use_unicode(): |
| overflow_start = "\N{RIGHTWARDS ARROW WITH HOOK} " |
| overflow_end = " \N{RIGHTWARDS ARROW WITH HOOK}" |
| else: |
| overflow_start = "> " |
| overflow_end = " >" |
|
|
| def chunks(line): |
| """Yields consecutive chunks of line_width ncols""" |
| prefix = overflow_first |
| width, start = line_width(prefix + overflow_end), 0 |
| for i, x in enumerate(line): |
| wx = line_width(x) |
| |
| |
| if width + wx > ncols: |
| yield prefix + line[start:i] + overflow_end |
| prefix = overflow_start |
| width, start = line_width(prefix + overflow_end), i |
| width += wx |
| yield prefix + line[start:] |
|
|
| |
| pictures = zip(*map(chunks, self.picture)) |
|
|
| |
| pictures = ["\n".join(picture) for picture in pictures] |
|
|
| |
| return "\n\n".join(pictures) |
|
|
| def terminal_width(self): |
| """Return the terminal width if possible, otherwise return 0. |
| """ |
| size = shutil.get_terminal_size(fallback=(0, 0)) |
| return size.columns |
|
|
| def __eq__(self, o): |
| if isinstance(o, str): |
| return '\n'.join(self.picture) == o |
| elif isinstance(o, stringPict): |
| return o.picture == self.picture |
| return False |
|
|
| def __hash__(self): |
| return super().__hash__() |
|
|
| def __str__(self): |
| return '\n'.join(self.picture) |
|
|
| def __repr__(self): |
| return "stringPict(%r,%d)" % ('\n'.join(self.picture), self.baseline) |
|
|
| def __getitem__(self, index): |
| return self.picture[index] |
|
|
| def __len__(self): |
| return len(self.s) |
|
|
|
|
| class prettyForm(stringPict): |
| """ |
| Extension of the stringPict class that knows about basic math applications, |
| optimizing double minus signs. |
| |
| "Binding" is interpreted as follows:: |
| |
| ATOM this is an atom: never needs to be parenthesized |
| FUNC this is a function application: parenthesize if added (?) |
| DIV this is a division: make wider division if divided |
| POW this is a power: only parenthesize if exponent |
| MUL this is a multiplication: parenthesize if powered |
| ADD this is an addition: parenthesize if multiplied or powered |
| NEG this is a negative number: optimize if added, parenthesize if |
| multiplied or powered |
| OPEN this is an open object: parenthesize if added, multiplied, or |
| powered (example: Piecewise) |
| """ |
| ATOM, FUNC, DIV, POW, MUL, ADD, NEG, OPEN = range(8) |
|
|
| def __init__(self, s, baseline=0, binding=0, unicode=None): |
| """Initialize from stringPict and binding power.""" |
| stringPict.__init__(self, s, baseline) |
| self.binding = binding |
| if unicode is not None: |
| sympy_deprecation_warning( |
| """ |
| The unicode argument to prettyForm is deprecated. Only the s |
| argument (the first positional argument) should be passed. |
| """, |
| deprecated_since_version="1.7", |
| active_deprecations_target="deprecated-pretty-printing-functions") |
| self._unicode = unicode or s |
|
|
| @property |
| def unicode(self): |
| sympy_deprecation_warning( |
| """ |
| The prettyForm.unicode attribute is deprecated. Use the |
| prettyForm.s attribute instead. |
| """, |
| deprecated_since_version="1.7", |
| active_deprecations_target="deprecated-pretty-printing-functions") |
| return self._unicode |
|
|
| |
|
|
| def __add__(self, *others): |
| """Make a pretty addition. |
| Addition of negative numbers is simplified. |
| """ |
| arg = self |
| if arg.binding > prettyForm.NEG: |
| arg = stringPict(*arg.parens()) |
| result = [arg] |
| for arg in others: |
| |
| if arg.binding > prettyForm.NEG: |
| arg = stringPict(*arg.parens()) |
| |
| if arg.binding != prettyForm.NEG: |
| result.append(' + ') |
| result.append(arg) |
| return prettyForm(binding=prettyForm.ADD, *stringPict.next(*result)) |
|
|
| def __truediv__(self, den, slashed=False): |
| """Make a pretty division; stacked or slashed. |
| """ |
| if slashed: |
| raise NotImplementedError("Can't do slashed fraction yet") |
| num = self |
| if num.binding == prettyForm.DIV: |
| num = stringPict(*num.parens()) |
| if den.binding == prettyForm.DIV: |
| den = stringPict(*den.parens()) |
|
|
| if num.binding==prettyForm.NEG: |
| num = num.right(" ")[0] |
|
|
| return prettyForm(binding=prettyForm.DIV, *stringPict.stack( |
| num, |
| stringPict.LINE, |
| den)) |
|
|
| def __mul__(self, *others): |
| """Make a pretty multiplication. |
| Parentheses are needed around +, - and neg. |
| """ |
| quantity = { |
| 'degree': "\N{DEGREE SIGN}" |
| } |
|
|
| if len(others) == 0: |
| return self |
|
|
| |
| arg = self |
| if arg.binding > prettyForm.MUL and arg.binding != prettyForm.NEG: |
| arg = stringPict(*arg.parens()) |
| result = [arg] |
| for arg in others: |
| if arg.picture[0] not in quantity.values(): |
| result.append(xsym('*')) |
| |
| if arg.binding > prettyForm.MUL and arg.binding != prettyForm.NEG: |
| arg = stringPict(*arg.parens()) |
| result.append(arg) |
|
|
| len_res = len(result) |
| for i in range(len_res): |
| if i < len_res - 1 and result[i] == '-1' and result[i + 1] == xsym('*'): |
| |
| result.pop(i) |
| result.pop(i) |
| result.insert(i, '-') |
| if result[0][0] == '-': |
| |
| |
| bin = prettyForm.NEG |
| if result[0] == '-': |
| right = result[1] |
| if right.picture[right.baseline][0] == '-': |
| result[0] = '- ' |
| else: |
| bin = prettyForm.MUL |
| return prettyForm(binding=bin, *stringPict.next(*result)) |
|
|
| def __repr__(self): |
| return "prettyForm(%r,%d,%d)" % ( |
| '\n'.join(self.picture), |
| self.baseline, |
| self.binding) |
|
|
| def __pow__(self, b): |
| """Make a pretty power. |
| """ |
| a = self |
| use_inline_func_form = False |
| if b.binding == prettyForm.POW: |
| b = stringPict(*b.parens()) |
| if a.binding > prettyForm.FUNC: |
| a = stringPict(*a.parens()) |
| elif a.binding == prettyForm.FUNC: |
| |
| if b.height() > 1: |
| a = stringPict(*a.parens()) |
| else: |
| use_inline_func_form = True |
|
|
| if use_inline_func_form: |
| |
| |
| b.baseline = a.prettyFunc.baseline + b.height() |
| func = stringPict(*a.prettyFunc.right(b)) |
| return prettyForm(*func.right(a.prettyArgs)) |
| else: |
| |
| |
| top = stringPict(*b.left(' '*a.width())) |
| bot = stringPict(*a.right(' '*b.width())) |
|
|
| return prettyForm(binding=prettyForm.POW, *bot.above(top)) |
|
|
| simpleFunctions = ["sin", "cos", "tan"] |
|
|
| @staticmethod |
| def apply(function, *args): |
| """Functions of one or more variables. |
| """ |
| if function in prettyForm.simpleFunctions: |
| |
| assert len( |
| args) == 1, "Simple function %s must have 1 argument" % function |
| arg = args[0].__pretty__() |
| if arg.binding <= prettyForm.DIV: |
| |
| return prettyForm(binding=prettyForm.FUNC, *arg.left(function + ' ')) |
| argumentList = [] |
| for arg in args: |
| argumentList.append(',') |
| argumentList.append(arg.__pretty__()) |
| argumentList = stringPict(*stringPict.next(*argumentList[1:])) |
| argumentList = stringPict(*argumentList.parens()) |
| return prettyForm(binding=prettyForm.ATOM, *argumentList.left(function)) |
|
|