--- doc/src/modules/printing.txt | 96 +++++++++++ sympy/__init__.py | 2 +- sympy/printing/__init__.py | 1 + sympy/printing/fcode.py | 326 ++++++++++++++++++++++++++++++++++++ sympy/printing/tests/test_fcode.py | 217 ++++++++++++++++++++++++ 5 files changed, 641 insertions(+), 1 deletions(-) create mode 100644 sympy/printing/fcode.py create mode 100644 sympy/printing/tests/test_fcode.py
diff --git a/doc/src/modules/printing.txt b/doc/src/modules/printing.txt index d6178e3..d6714f0 100644 --- a/doc/src/modules/printing.txt +++ b/doc/src/modules/printing.txt @@ -84,6 +84,102 @@ This class implements Python printing. Usage:: x = Symbol('x') e = sin(x) + 5*x**3 +fcode +----- + +The fcode function translates a sympy expression into Fortran code. The main +purpose is to take away the burden of manually translating long mathematical +expressions. Therefore the resulting expression should also require no (or very +little) manual tweaking to make it compilable. The optional arguments of fcode +can be used to fine-tune the behavior of fcode in such a way that manual changes +in the result are no longer needed. + +.. module:: sympy.printing.fcode +.. autofunction:: fcode +.. autofunction:: print_fcode + +Two basic examples: + + >>> from sympy import * + >>> x = symbols("x") + >>> fcode(sqrt(1-x**2)) + ' sqrt(1 - x**2)' + >>> fcode((3 + 4*I)/(1 - conjugate(x))) + ' (cmplx(3,4))/(1 - conjg(x))' + +An example where line wrapping is required: + + >>> expr = sqrt(1-x**2).series(x,n=20).removeO() + >>> print fcode(expr) + 1 - x**2/2 - x**4/8 - x**6/16 - 5*x**8/128 - 7*x**10/256 - 21*x + @ **12/1024 - 33*x**14/2048 - 429*x**16/32768 - 715*x**18/65536 + +In case of line wrapping, it is handy to include the assignment so that lines +are wrapped properly when the assignment part is added. + + >>> print fcode(expr, assign_to="var") + var = 1 - x**2/2 - x**4/8 - x**6/16 - 5*x**8/128 - 7*x**10/256 - + @ 21*x**12/1024 - 33*x**14/2048 - 429*x**16/32768 - 715*x**18/65536 + +Also for piecewise functions, the assign_to option can be helpful: + + >>> print fcode(Piecewise((x,x<1),(x**2,True)), assign_to="var") + if (x < 1) then + var = x + else + var = x**2 + end if + +Note that only top-level piecewise functions are supported due to the lack of +a conditional operator in Fortran. Nested piecewise functions would require the +introduction of temporary variables, which is a type of expression manipulation +that goes beyond the scope of fcode. + +By default, number symbols such as ``pi`` and ``E`` are detected and defined as +Fortran parameters. The precision of the constants can be tuned with the +precision argument. Parameter definitions are easily avoided using the ``N`` +function. + + >>> print fcode(x - pi**2 - E) + parameter (E = 2.71828182845905) + parameter (pi = 3.14159265358979) + x - E - pi**2 + >>> print fcode(x - pi**2 - E, precision=25) + parameter (E = 2.718281828459045235360287) + parameter (pi = 3.141592653589793238462643) + x - E - pi**2 + >>> print fcode(N(x - pi**2, 25)) + -9.869604401089358618834491 + x + +When some functions are not part of the Fortran standard, it might be desirable +to introduce the names of user-defined functions in the Fortran expression. + + >>> print fcode(1 - gamma(x)**2, user_functions={gamma: 'mygamma'}) + 1 - mygamma(x)**2 + +However, when the user_functions argument is not provided, fcode attempts to +use a reasonable default and adds a comment to inform the user of the issue. + + >>> print fcode(1 - gamma(x)**2) + C Not Fortran 77: + C gamma(x) + 1 - gamma(x)**2 + +By default the output is human readable code, ready for copy and paste. With the +option ``human=False``, the return value is suitable for post-processing with +source code generators that write routines with multiple instructions. The +return value is a three-tuple containing: (i) the list of number symbols that +must be defined as 'Fortran parameters', (ii) a list functions that can not be +translated in pure Fortran and (iii) a string of Fortran code. A few examples: + + >>> fcode(1 - gamma(x)**2, human=False) + ([], set([gamma(x)]), ' 1 - gamma(x)**2') + >>> fcode(1 - sin(x)**2, human=False) + ([], set(), ' 1 - sin(x)**2') + >>> fcode(x - pi**2, human=False) + ([('pi', 3.14159265358979)], set(), ' x - pi**2') + + Preview ------- diff --git a/sympy/__init__.py b/sympy/__init__.py index 2cc73f8..81cd4a5 100644 --- a/sympy/__init__.py +++ b/sympy/__init__.py @@ -38,7 +38,7 @@ def __sympy_debug(): from plotting import Plot, textplot from printing import pretty, pretty_print, pprint, pprint_use_unicode, \ pprint_try_use_unicode, print_gtk, print_tree -from printing import ccode, latex, preview +from printing import ccode, fcode, latex, preview from printing import python, print_python, srepr, sstr, sstrrepr evalf._create_evalf_table() diff --git a/sympy/printing/__init__.py b/sympy/printing/__init__.py index 1ade76d..40065a0 100644 --- a/sympy/printing/__init__.py +++ b/sympy/printing/__init__.py @@ -5,6 +5,7 @@ from mathml import mathml, print_mathml from python import python, print_python from ccode import ccode, print_ccode +from fcode import fcode, print_fcode from gtk import * from preview import preview diff --git a/sympy/printing/fcode.py b/sympy/printing/fcode.py new file mode 100644 index 0000000..6211920 --- /dev/null +++ b/sympy/printing/fcode.py @@ -0,0 +1,326 @@ +""" +Fortran code printer + +The FCodePrinter converts single sympy expressions into single Fortran +expressions, using the functions defined in the Fortran 77 standard where +possible. Some useful pointers to Fortran can be found on wikipedia: + +http://en.wikipedia.org/wiki/Fortran + +Most of the code below is based on the "Professional Programmer\'s Guide to +Fortran77" by Clive G. Page: + +http://www.star.le.ac.uk/~cgp/prof77.html + +Fortran is a case-insensitive language. This might cause trouble because sympy +is case sensitive. The implementation below does not care and leaves the +responsibility for generating properly cased Fortran code to the user. +""" + + +from str import StrPrinter +from sympy.printing.precedence import precedence +from sympy.core import S, Add, I +from sympy.core.numbers import NumberSymbol +from sympy.functions import sin, cos, tan, asin, acos, atan, atan2, sinh, \ + cosh, tanh, sqrt, log, exp, abs, sign, conjugate, Piecewise +from sympy.utilities.iterables import postorder_traversal + + +implicit_functions = set([ + sin, cos, tan, asin, acos, atan, atan2, sinh, cosh, tanh, sqrt, log, exp, + abs, sign, conjugate +]) + + +class FCodePrinter(StrPrinter): + """A printer to convert sympy expressions to strings of Fortran code""" + printmethod = "_fcode_" + + def doprint(self, expr): + """Returns Fortran code for expr (as a string)""" + # keep a set of expressions that are not strictly translatable to + # Fortran. + self.not_fortran = set([]) + + lines = [] + if isinstance(expr, Piecewise): + # support for top-level Piecewise function + for i, (e, c) in enumerate(expr.args): + if i == 0: + lines.append(" if (%s) then" % self._print(c)) + elif i == len(expr.args)-1 and c == True: + lines.append(" else") + else: + lines.append(" else if (%s) then" % self._print(c)) + if self._settings["assign_to"] is None: + lines.append(" %s" % self._print(e)) + else: + lines.append(" %s = %s" % (self._settings["assign_to"], self._print(e))) + lines.append(" end if") + return "\n".join(lines) + else: + line = StrPrinter.doprint(self, expr) + if self._settings["assign_to"] is None: + return " %s" % line + else: + return " %s = %s" % (self._settings["assign_to"], line) + + def _print_Add(self, expr): + # purpose: print complex numbers nicely in Fortran. + # collect the purely real and purely imaginary parts: + pure_real = [] + pure_imaginary = [] + mixed = [] + for arg in expr.args: + if arg.is_real and arg.is_number: + pure_real.append(arg) + elif arg.is_imaginary and arg.is_number: + pure_imaginary.append(arg) + else: + mixed.append(arg) + if len(pure_imaginary) > 0: + if len(mixed) > 0: + PREC = precedence(expr) + term = Add(*mixed) + t = self._print(term) + if t.startswith('-'): + sign = "-" + t = t[1:] + else: + sign = "+" + if precedence(term) < PREC: + t = "(%s)" % t + + return "cmplx(%s,%s) %s %s" % ( + self._print(Add(*pure_real)), + self._print(-I*Add(*pure_imaginary)), + sign, t, + ) + else: + return "cmplx(%s,%s)" % ( + self._print(Add(*pure_real)), + self._print(-I*Add(*pure_imaginary)), + ) + else: + return StrPrinter._print_Add(self, expr) + + def _print_Function(self, expr): + name = self._settings["user_functions"].get(expr.__class__) + if name is None: + if expr.func == conjugate: + name = "conjg" + else: + name = expr.func.__name__ + if expr.func not in implicit_functions: + self.not_fortran.add(expr) + return "%s(%s)" % (name, self.stringify(expr.args, ", ")) + + _print_Factorial = _print_Function + + def _print_ImaginaryUnit(self, expr): + # purpose: print complex numbers nicely in Fortran. + return "cmplx(0,1)" + + def _print_int(self, expr): + return str(expr) + + def _print_Mul(self, expr): + # purpose: print complex numbers nicely in Fortran. + if expr.is_imaginary and expr.is_number: + return "cmplx(0,%s)" % ( + self._print(-I*expr) + ) + else: + return StrPrinter._print_Mul(self, expr) + + def _print_NumberSymbol(self, expr): + # Standard Fortran has no predefined constants. Write their string + # representation, and assume parameter statements are defined elsewhere + # in the code to make this work. + return str(expr) + + _print_Catalan = _print_NumberSymbol + _print_EulerGamma = _print_NumberSymbol + _print_Exp1 = _print_NumberSymbol + _print_GoldenRatio = _print_NumberSymbol + _print_Pi = _print_NumberSymbol + + def _print_Pow(self, expr): + PREC = precedence(expr) + if expr.exp is S.NegativeOne: + return '1.0/%s'%(self.parenthesize(expr.base, PREC)) + elif expr.exp == 0.5: + return 'sqrt(%s)' % self._print(expr.base) + else: + return StrPrinter._print_Pow(self, expr) + + def _print_Rational(self, expr): + p, q = int(expr.p), int(expr.q) + return '%d.0/%d.0' % (p, q) + + def _print_not_fortran(self, expr): + self.not_fortran.add(expr) + return StrPrinter.emptyPrinter(self, expr) + + # The following can not be simply translated into Fortran. + _print_Basic = _print_not_fortran + _print_ComplexInfinity = _print_not_fortran + _print_Derivative = _print_not_fortran + _print_dict = _print_not_fortran + _print_Dummy = _print_not_fortran + _print_ExprCondPair = _print_not_fortran + _print_GeometryEntity = _print_not_fortran + _print_Infinity = _print_not_fortran + _print_Integral = _print_not_fortran + _print_Interval = _print_not_fortran + _print_Limit = _print_not_fortran + _print_list = _print_not_fortran + _print_Matrix = _print_not_fortran + _print_DeferredVector = _print_not_fortran + _print_NaN = _print_not_fortran + _print_NegativeInfinity = _print_not_fortran + _print_Normal = _print_not_fortran + _print_Order = _print_not_fortran + _print_PDF = _print_not_fortran + _print_RootOf = _print_not_fortran + _print_RootsOf = _print_not_fortran + _print_RootSum = _print_not_fortran + _print_Sample = _print_not_fortran + _print_SMatrix = _print_not_fortran + _print_tuple = _print_not_fortran + _print_Uniform = _print_not_fortran + _print_Unit = _print_not_fortran + _print_Wild = _print_not_fortran + _print_WildFunction = _print_not_fortran + + +def wrap_fortran(lines): + """Wrap long Fortran lines + + Argument: + lines -- a list of lines (without \\n character) + + A comment line is split at white space. Code lines are split with a more + complex rule to give nice results. + """ + # routine to find split point in a code line + my_alnum = set("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_") + my_white = set(" \t()") + def split_pos_code(line, endpos): + if len(line) <= endpos: + return len(line) + pos = endpos + split = lambda pos: \ + (line[pos] in my_alnum and line[pos-1] not in my_alnum) or \ + (line[pos] not in my_alnum and line[pos-1] in my_alnum) or \ + (line[pos] in my_white and line[pos-1] not in my_white) or \ + (line[pos] not in my_white and line[pos-1] in my_white) + while not split(pos): + pos -= 1 + if pos == 0: + return endpos + return pos + # split line by line and add the splitted lines to result + result = [] + for line in lines: + if line.startswith(" "): + # code line + pos = split_pos_code(line, 72) + hunk = line[:pos].rstrip() + line = line[pos:].lstrip() + result.append(hunk) + while len(line) > 0: + pos = split_pos_code(line, 65) + hunk = line[:pos].rstrip() + line = line[pos:].lstrip() + result.append(" @ %s" % hunk) + elif line.startswith("C"): + # comment line + if len(line) > 72: + pos = line.rfind(" ", 6, 72) + if pos == -1: + pos = 72 + hunk = line[:pos] + line = line[pos:].lstrip() + result.append(hunk) + while len(line) > 0: + pos = line.rfind(" ", 0, 66) + if pos == -1: + pos = 66 + hunk = line[:pos] + line = line[pos:].lstrip() + result.append("C %s" % hunk) + else: + result.append(line) + else: + result.append(line) + return result + + +def fcode(expr, assign_to=None, precision=15, user_functions={}, human=True): + """Converts an expr to a string of Fortran 77 code + + Arguments: + expr -- a sympy expression to be converted + + Optional arguments: + assign_to -- When given, the argument is used as the name of the + variable to which the Fortran expression is assigned. + (This is helpful in case of line-wrapping.) + precision -- the precision for numbers such as pi [default=15] + user_functions -- A dictionary where keys are FunctionClass instances + and values are there string representations. + human -- If True, the result is a single string that may contain + some parameter statements for the number symbols. If + False, the same information is returned in a more + programmer-friendly data structure. + + >>> from sympy import fcode, symbols, Rational, pi, sin + >>> x, tau = symbols(["x", "tau"]) + >>> fcode((2*tau)**Rational(7,2)) + ' 8*sqrt(2)*tau**(7.0/2.0)' + >>> fcode(sin(x), assign_to="s") + ' s = sin(x)' + >>> print fcode(pi) + parameter (pi = 3.14159265358979) + pi + + """ + # find all number symbols + number_symbols = set([]) + for sub in postorder_traversal(expr): + if isinstance(sub, NumberSymbol): + number_symbols.add(sub) + number_symbols = [(str(ns), ns.evalf(precision)) for ns in sorted(number_symbols)] + # run the printer + profile = { + "full_prec": False, # programmers don't care about trailing zeros. + "assign_to": assign_to, + "user_functions": user_functions, + } + printer = FCodePrinter(profile) + result = printer.doprint(expr) + # format the output + if human: + lines = [] + if len(printer.not_fortran) > 0: + lines.append("C Not Fortran 77:") + for expr in sorted(printer.not_fortran): + lines.append("C %s" % expr) + for name, value in number_symbols: + lines.append(" parameter (%s = %s)" % (name, value)) + lines.extend(result.split("\n")) + lines = wrap_fortran(lines) + return "\n".join(lines) + else: + return number_symbols, printer.not_fortran, result + + +def print_fcode(expr, assign_to=None, precision=15, user_functions={}): + """Prints the Fortran representation of the given expression. + + See fcode for the meaning of the optional arguments. + """ + print fcode(expr, assign_to, precision, user_functions) + diff --git a/sympy/printing/tests/test_fcode.py b/sympy/printing/tests/test_fcode.py new file mode 100644 index 0000000..44cb24c --- /dev/null +++ b/sympy/printing/tests/test_fcode.py @@ -0,0 +1,217 @@ +from sympy import sin, cos, atan2, gamma, conjugate, sqrt, Factorial, \ + Integral, Piecewise, Add, diff, symbols, raises +from sympy import Catalan, EulerGamma, E, GoldenRatio, I, pi +from sympy import Function, Rational, Integer + +from sympy.printing.fcode import fcode, wrap_fortran + + +def test_printmethod(): + x = symbols('x') + class nint(Function): + def _fcode_(self, printer): + return "nint(%s)" % printer._print(self.args[0]) + assert fcode(nint(x)) == " nint(x)" + +def test_fcode_Pow(): + x, y = symbols('xy') + assert fcode(x**3) == " x**3" + assert fcode(x**(y**3)) == " x**(y**3)" + assert fcode(1/(sin(x)*3.5)**(x - y**x)/(x**2 + y)) == \ + " (3.5*sin(x))**(-x + y**x)/(y + x**2)" + assert fcode(sqrt(x)) == ' sqrt(x)' + assert fcode(x**0.5) == ' sqrt(x)' + assert fcode(x**Rational(1,2)) == ' sqrt(x)' + +def test_fcode_Rational(): + assert fcode(Rational(3,7)) == " 3.0/7.0" + assert fcode(Rational(18,9)) == " 2" + assert fcode(Rational(3,-7)) == " -3.0/7.0" + assert fcode(Rational(-3,-7)) == " 3.0/7.0" + +def test_fcode_Integer(): + assert fcode(Integer(67)) == " 67" + assert fcode(Integer(-1)) == " -1" + +def test_fcode_functions(): + x, y = symbols('xy') + assert fcode(sin(x) ** cos(y)) == " sin(x)**cos(y)" + +def test_fcode_NumberSymbol(): + assert fcode(Catalan) == ' parameter (Catalan = 0.915965594177219)\n Catalan' + assert fcode(EulerGamma) == ' parameter (EulerGamma = 0.577215664901533)\n EulerGamma' + assert fcode(E) == ' parameter (E = 2.71828182845905)\n E' + assert fcode(GoldenRatio) == ' parameter (GoldenRatio = 1.61803398874989)\n GoldenRatio' + assert fcode(pi) == ' parameter (pi = 3.14159265358979)\n pi' + assert fcode(pi,precision=5) == ' parameter (pi = 3.1416)\n pi' + assert fcode(Catalan,human=False) == ([('Catalan', Catalan.evalf(15))], set([]), ' Catalan') + assert fcode(EulerGamma,human=False) == ([('EulerGamma', EulerGamma.evalf(15))], set([]), ' EulerGamma') + assert fcode(E,human=False) == ([('E', E.evalf(15))], set([]), ' E') + assert fcode(GoldenRatio,human=False) == ([('GoldenRatio', GoldenRatio.evalf(15))], set([]), ' GoldenRatio') + assert fcode(pi,human=False) == ([('pi', pi.evalf(15))], set([]), ' pi') + assert fcode(pi,precision=5,human=False) == ([('pi', pi.evalf(5))], set([]), ' pi') + +def test_fcode_complex(): + assert fcode(I) == " cmplx(0,1)" + x = symbols('x') + assert fcode(4*I) == " cmplx(0,4)" + assert fcode(3+4*I) == " cmplx(3,4)" + assert fcode(3+4*I+x) == " cmplx(3,4) + x" + assert fcode(I*x) == " cmplx(0,1)*x" + assert fcode(3+4*I-x) == " cmplx(3,4) - x" + x = symbols('x', imaginary=True) + assert fcode(5*x) == " 5*x" + assert fcode(I*x) == " cmplx(0,1)*x" + assert fcode(3+x) == " 3 + x" + +def test_implicit(): + x, y = symbols('xy') + assert fcode(sin(x)) == " sin(x)" + assert fcode(atan2(x,y)) == " atan2(x, y)" + assert fcode(conjugate(x)) == " conjg(x)" + +def test_not_fortran(): + x = symbols('x') + g = Function('g') + assert fcode(gamma(x)) == "C Not Fortran 77:\nC gamma(x)\n gamma(x)" + assert fcode(Integral(sin(x))) == "C Not Fortran 77:\nC Integral(sin(x), x)\n Integral(sin(x), x)" + assert fcode(g(x)) == "C Not Fortran 77:\nC g(x)\n g(x)" + +def test_user_functions(): + x = symbols('x') + assert fcode(sin(x), user_functions={sin: "zsin"}) == " zsin(x)" + x = symbols('x') + assert fcode(gamma(x), user_functions={gamma: "mygamma"}) == " mygamma(x)" + g = Function('g') + assert fcode(g(x), user_functions={g: "great"}) == " great(x)" + n = symbols('n', integer=True) + assert fcode(Factorial(n), user_functions={Factorial: "fct"}) == " fct(n)" + +def test_assign_to(): + x = symbols('x') + assert fcode(sin(x), assign_to="s") == " s = sin(x)" + +def test_line_wrapping(): + x, y = symbols('xy') + assert fcode(((x+y)**10).expand(), assign_to="var") == ( + " var = 45*x**8*y**2 + 120*x**7*y**3 + 210*x**6*y**4 + 252*x**5*y**5\n" + " @ + 210*x**4*y**6 + 120*x**3*y**7 + 45*x**2*y**8 + 10*x*y**9 + 10*y\n" + " @ *x**9 + x**10 + y**10" + ) + e = [x**i for i in range(11)] + assert fcode(Add(*e)) == ( + " 1 + x + x**2 + x**3 + x**4 + x**5 + x**6 + x**7 + x**8 + x**9 + x\n" + " @ **10" + ) + +def test_fcode_Piecewise(): + x = symbols('x') + assert fcode(Piecewise((x,x<1),(x**2,True))) == ( + " if (x < 1) then\n" + " x\n" + " else\n" + " x**2\n" + " end if" + ) + assert fcode(Piecewise((x,x<1),(x**2,True)), assign_to="var") == ( + " if (x < 1) then\n" + " var = x\n" + " else\n" + " var = x**2\n" + " end if" + ) + a = cos(x)/x + b = sin(x)/x + for i in xrange(10): + a = diff(a, x) + b = diff(b, x) + assert fcode(Piecewise((a,x<0),(b,True)), assign_to="weird_name") == ( + " if (x < 0) then\n" + " weird_name = -cos(x)/x - 1814400*cos(x)/x**9 - 604800*sin(x)/x\n" + " @ **8 - 5040*cos(x)/x**5 - 720*sin(x)/x**4 + 10*sin(x)/x**2 + 90*\n" + " @ cos(x)/x**3 + 30240*sin(x)/x**6 + 151200*cos(x)/x**7 + 3628800*\n" + " @ cos(x)/x**11 + 3628800*sin(x)/x**10\n" + " else\n" + " weird_name = -sin(x)/x - 3628800*cos(x)/x**10 - 1814400*sin(x)/x\n" + " @ **9 - 30240*cos(x)/x**6 - 5040*sin(x)/x**5 - 10*cos(x)/x**2 + 90*\n" + " @ sin(x)/x**3 + 720*cos(x)/x**4 + 151200*sin(x)/x**7 + 604800*cos(x\n" + " @ )/x**8 + 3628800*sin(x)/x**11\n" + " end if" + ) + assert fcode(Piecewise((x,x<1),(x**2,x>1),(sin(x),True))) == ( + " if (x < 1) then\n" + " x\n" + " else if (1 < x) then\n" + " x**2\n" + " else\n" + " sin(x)\n" + " end if" + ) + assert fcode(Piecewise((x,x<1),(x**2,x>1),(sin(x),x>0))) == ( + " if (x < 1) then\n" + " x\n" + " else if (1 < x) then\n" + " x**2\n" + " else if (0 < x) then\n" + " sin(x)\n" + " end if" + ) + +def test_wrap_fortran(): + # "########################################################################" + lines = [ + "C This is a long comment on a single line that must be wrapped properly", + " this = is + a + long + and + nasty + fortran + statement + that * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)/must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)/must + be + wrapped + properly", + ] + wrapped_lines = wrap_fortran(lines) + expected_lines = [ + "C This is a long comment on a single line that must be wrapped", + "C properly", + " this = is + a + long + and + nasty + fortran + statement + that *", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that *", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ * must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that*", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ *must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement +", + " @ that*must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that**", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ **must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement + that", + " @ **must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement +", + " @ that**must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)/", + " @ must + be + wrapped + properly", + " this = is + a + long + and + nasty + fortran + statement(that)", + " @ /must + be + wrapped + properly", + ] + for line in wrapped_lines: + assert len(line) <= 72 + for w, e in zip(wrapped_lines, expected_lines): + assert w == e + assert len(wrapped_lines) == len(expected_lines) + -- 1.6.3.3 -- You received this message because you are subscribed to the Google Groups "sympy-patches" group. To post to this group, send email to sympy-patc...@googlegroups.com. To unsubscribe from this group, send email to sympy-patches+unsubscr...@googlegroups.com. For more options, visit this group at http://groups.google.com/group/sympy-patches?hl=en.