Coverage for /usr/lib/python3/dist-packages/sympy/parsing/sympy_parser.py: 28%
572 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1"""Transform a string with Python-like source code into SymPy expression. """
3from tokenize import (generate_tokens, untokenize, TokenError,
4 NUMBER, STRING, NAME, OP, ENDMARKER, ERRORTOKEN, NEWLINE)
6from keyword import iskeyword
8import ast
9import unicodedata
10from io import StringIO
11import builtins
12import types
13from typing import Tuple as tTuple, Dict as tDict, Any, Callable, \
14 List, Optional, Union as tUnion
16from sympy.assumptions.ask import AssumptionKeys
17from sympy.core.basic import Basic
18from sympy.core import Symbol
19from sympy.core.function import Function
20from sympy.utilities.misc import func_name
21from sympy.functions.elementary.miscellaneous import Max, Min
24null = ''
26TOKEN = tTuple[int, str]
27DICT = tDict[str, Any]
28TRANS = Callable[[List[TOKEN], DICT, DICT], List[TOKEN]]
30def _token_splittable(token_name: str) -> bool:
31 """
32 Predicate for whether a token name can be split into multiple tokens.
34 A token is splittable if it does not contain an underscore character and
35 it is not the name of a Greek letter. This is used to implicitly convert
36 expressions like 'xyz' into 'x*y*z'.
37 """
38 if '_' in token_name:
39 return False
40 try:
41 return not unicodedata.lookup('GREEK SMALL LETTER ' + token_name)
42 except KeyError:
43 return len(token_name) > 1
46def _token_callable(token: TOKEN, local_dict: DICT, global_dict: DICT, nextToken=None):
47 """
48 Predicate for whether a token name represents a callable function.
50 Essentially wraps ``callable``, but looks up the token name in the
51 locals and globals.
52 """
53 func = local_dict.get(token[1])
54 if not func:
55 func = global_dict.get(token[1])
56 return callable(func) and not isinstance(func, Symbol)
59def _add_factorial_tokens(name: str, result: List[TOKEN]) -> List[TOKEN]:
60 if result == [] or result[-1][1] == '(':
61 raise TokenError()
63 beginning = [(NAME, name), (OP, '(')]
64 end = [(OP, ')')]
66 diff = 0
67 length = len(result)
69 for index, token in enumerate(result[::-1]):
70 toknum, tokval = token
71 i = length - index - 1
73 if tokval == ')':
74 diff += 1
75 elif tokval == '(':
76 diff -= 1
78 if diff == 0:
79 if i - 1 >= 0 and result[i - 1][0] == NAME:
80 return result[:i - 1] + beginning + result[i - 1:] + end
81 else:
82 return result[:i] + beginning + result[i:] + end
84 return result
87class ParenthesisGroup(List[TOKEN]):
88 """List of tokens representing an expression in parentheses."""
89 pass
92class AppliedFunction:
93 """
94 A group of tokens representing a function and its arguments.
96 `exponent` is for handling the shorthand sin^2, ln^2, etc.
97 """
98 def __init__(self, function: TOKEN, args: ParenthesisGroup, exponent=None):
99 if exponent is None:
100 exponent = []
101 self.function = function
102 self.args = args
103 self.exponent = exponent
104 self.items = ['function', 'args', 'exponent']
106 def expand(self) -> List[TOKEN]:
107 """Return a list of tokens representing the function"""
108 return [self.function, *self.args]
110 def __getitem__(self, index):
111 return getattr(self, self.items[index])
113 def __repr__(self):
114 return "AppliedFunction(%s, %s, %s)" % (self.function, self.args,
115 self.exponent)
118def _flatten(result: List[tUnion[TOKEN, AppliedFunction]]):
119 result2: List[TOKEN] = []
120 for tok in result:
121 if isinstance(tok, AppliedFunction):
122 result2.extend(tok.expand())
123 else:
124 result2.append(tok)
125 return result2
128def _group_parentheses(recursor: TRANS):
129 def _inner(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
130 """Group tokens between parentheses with ParenthesisGroup.
132 Also processes those tokens recursively.
134 """
135 result: List[tUnion[TOKEN, ParenthesisGroup]] = []
136 stacks: List[ParenthesisGroup] = []
137 stacklevel = 0
138 for token in tokens:
139 if token[0] == OP:
140 if token[1] == '(':
141 stacks.append(ParenthesisGroup([]))
142 stacklevel += 1
143 elif token[1] == ')':
144 stacks[-1].append(token)
145 stack = stacks.pop()
147 if len(stacks) > 0:
148 # We don't recurse here since the upper-level stack
149 # would reprocess these tokens
150 stacks[-1].extend(stack)
151 else:
152 # Recurse here to handle nested parentheses
153 # Strip off the outer parentheses to avoid an infinite loop
154 inner = stack[1:-1]
155 inner = recursor(inner,
156 local_dict,
157 global_dict)
158 parenGroup = [stack[0]] + inner + [stack[-1]]
159 result.append(ParenthesisGroup(parenGroup))
160 stacklevel -= 1
161 continue
162 if stacklevel:
163 stacks[-1].append(token)
164 else:
165 result.append(token)
166 if stacklevel:
167 raise TokenError("Mismatched parentheses")
168 return result
169 return _inner
172def _apply_functions(tokens: List[tUnion[TOKEN, ParenthesisGroup]], local_dict: DICT, global_dict: DICT):
173 """Convert a NAME token + ParenthesisGroup into an AppliedFunction.
175 Note that ParenthesisGroups, if not applied to any function, are
176 converted back into lists of tokens.
178 """
179 result: List[tUnion[TOKEN, AppliedFunction]] = []
180 symbol = None
181 for tok in tokens:
182 if isinstance(tok, ParenthesisGroup):
183 if symbol and _token_callable(symbol, local_dict, global_dict):
184 result[-1] = AppliedFunction(symbol, tok)
185 symbol = None
186 else:
187 result.extend(tok)
188 elif tok[0] == NAME:
189 symbol = tok
190 result.append(tok)
191 else:
192 symbol = None
193 result.append(tok)
194 return result
197def _implicit_multiplication(tokens: List[tUnion[TOKEN, AppliedFunction]], local_dict: DICT, global_dict: DICT):
198 """Implicitly adds '*' tokens.
200 Cases:
202 - Two AppliedFunctions next to each other ("sin(x)cos(x)")
204 - AppliedFunction next to an open parenthesis ("sin x (cos x + 1)")
206 - A close parenthesis next to an AppliedFunction ("(x+2)sin x")\
208 - A close parenthesis next to an open parenthesis ("(x+2)(x+3)")
210 - AppliedFunction next to an implicitly applied function ("sin(x)cos x")
212 """
213 result: List[tUnion[TOKEN, AppliedFunction]] = []
214 skip = False
215 for tok, nextTok in zip(tokens, tokens[1:]):
216 result.append(tok)
217 if skip:
218 skip = False
219 continue
220 if tok[0] == OP and tok[1] == '.' and nextTok[0] == NAME:
221 # Dotted name. Do not do implicit multiplication
222 skip = True
223 continue
224 if isinstance(tok, AppliedFunction):
225 if isinstance(nextTok, AppliedFunction):
226 result.append((OP, '*'))
227 elif nextTok == (OP, '('):
228 # Applied function followed by an open parenthesis
229 if tok.function[1] == "Function":
230 tok.function = (tok.function[0], 'Symbol')
231 result.append((OP, '*'))
232 elif nextTok[0] == NAME:
233 # Applied function followed by implicitly applied function
234 result.append((OP, '*'))
235 else:
236 if tok == (OP, ')'):
237 if isinstance(nextTok, AppliedFunction):
238 # Close parenthesis followed by an applied function
239 result.append((OP, '*'))
240 elif nextTok[0] == NAME:
241 # Close parenthesis followed by an implicitly applied function
242 result.append((OP, '*'))
243 elif nextTok == (OP, '('):
244 # Close parenthesis followed by an open parenthesis
245 result.append((OP, '*'))
246 elif tok[0] == NAME and not _token_callable(tok, local_dict, global_dict):
247 if isinstance(nextTok, AppliedFunction) or \
248 (nextTok[0] == NAME and _token_callable(nextTok, local_dict, global_dict)):
249 # Constant followed by (implicitly applied) function
250 result.append((OP, '*'))
251 elif nextTok == (OP, '('):
252 # Constant followed by parenthesis
253 result.append((OP, '*'))
254 elif nextTok[0] == NAME:
255 # Constant followed by constant
256 result.append((OP, '*'))
257 if tokens:
258 result.append(tokens[-1])
259 return result
262def _implicit_application(tokens: List[tUnion[TOKEN, AppliedFunction]], local_dict: DICT, global_dict: DICT):
263 """Adds parentheses as needed after functions."""
264 result: List[tUnion[TOKEN, AppliedFunction]] = []
265 appendParen = 0 # number of closing parentheses to add
266 skip = 0 # number of tokens to delay before adding a ')' (to
267 # capture **, ^, etc.)
268 exponentSkip = False # skipping tokens before inserting parentheses to
269 # work with function exponentiation
270 for tok, nextTok in zip(tokens, tokens[1:]):
271 result.append(tok)
272 if (tok[0] == NAME and nextTok[0] not in [OP, ENDMARKER, NEWLINE]):
273 if _token_callable(tok, local_dict, global_dict, nextTok): # type: ignore
274 result.append((OP, '('))
275 appendParen += 1
276 # name followed by exponent - function exponentiation
277 elif (tok[0] == NAME and nextTok[0] == OP and nextTok[1] == '**'):
278 if _token_callable(tok, local_dict, global_dict): # type: ignore
279 exponentSkip = True
280 elif exponentSkip:
281 # if the last token added was an applied function (i.e. the
282 # power of the function exponent) OR a multiplication (as
283 # implicit multiplication would have added an extraneous
284 # multiplication)
285 if (isinstance(tok, AppliedFunction)
286 or (tok[0] == OP and tok[1] == '*')):
287 # don't add anything if the next token is a multiplication
288 # or if there's already a parenthesis (if parenthesis, still
289 # stop skipping tokens)
290 if not (nextTok[0] == OP and nextTok[1] == '*'):
291 if not(nextTok[0] == OP and nextTok[1] == '('):
292 result.append((OP, '('))
293 appendParen += 1
294 exponentSkip = False
295 elif appendParen:
296 if nextTok[0] == OP and nextTok[1] in ('^', '**', '*'):
297 skip = 1
298 continue
299 if skip:
300 skip -= 1
301 continue
302 result.append((OP, ')'))
303 appendParen -= 1
305 if tokens:
306 result.append(tokens[-1])
308 if appendParen:
309 result.extend([(OP, ')')] * appendParen)
310 return result
313def function_exponentiation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
314 """Allows functions to be exponentiated, e.g. ``cos**2(x)``.
316 Examples
317 ========
319 >>> from sympy.parsing.sympy_parser import (parse_expr,
320 ... standard_transformations, function_exponentiation)
321 >>> transformations = standard_transformations + (function_exponentiation,)
322 >>> parse_expr('sin**4(x)', transformations=transformations)
323 sin(x)**4
324 """
325 result: List[TOKEN] = []
326 exponent: List[TOKEN] = []
327 consuming_exponent = False
328 level = 0
329 for tok, nextTok in zip(tokens, tokens[1:]):
330 if tok[0] == NAME and nextTok[0] == OP and nextTok[1] == '**':
331 if _token_callable(tok, local_dict, global_dict):
332 consuming_exponent = True
333 elif consuming_exponent:
334 if tok[0] == NAME and tok[1] == 'Function':
335 tok = (NAME, 'Symbol')
336 exponent.append(tok)
338 # only want to stop after hitting )
339 if tok[0] == nextTok[0] == OP and tok[1] == ')' and nextTok[1] == '(':
340 consuming_exponent = False
341 # if implicit multiplication was used, we may have )*( instead
342 if tok[0] == nextTok[0] == OP and tok[1] == '*' and nextTok[1] == '(':
343 consuming_exponent = False
344 del exponent[-1]
345 continue
346 elif exponent and not consuming_exponent:
347 if tok[0] == OP:
348 if tok[1] == '(':
349 level += 1
350 elif tok[1] == ')':
351 level -= 1
352 if level == 0:
353 result.append(tok)
354 result.extend(exponent)
355 exponent = []
356 continue
357 result.append(tok)
358 if tokens:
359 result.append(tokens[-1])
360 if exponent:
361 result.extend(exponent)
362 return result
365def split_symbols_custom(predicate: Callable[[str], bool]):
366 """Creates a transformation that splits symbol names.
368 ``predicate`` should return True if the symbol name is to be split.
370 For instance, to retain the default behavior but avoid splitting certain
371 symbol names, a predicate like this would work:
374 >>> from sympy.parsing.sympy_parser import (parse_expr, _token_splittable,
375 ... standard_transformations, implicit_multiplication,
376 ... split_symbols_custom)
377 >>> def can_split(symbol):
378 ... if symbol not in ('list', 'of', 'unsplittable', 'names'):
379 ... return _token_splittable(symbol)
380 ... return False
381 ...
382 >>> transformation = split_symbols_custom(can_split)
383 >>> parse_expr('unsplittable', transformations=standard_transformations +
384 ... (transformation, implicit_multiplication))
385 unsplittable
386 """
387 def _split_symbols(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
388 result: List[TOKEN] = []
389 split = False
390 split_previous=False
392 for tok in tokens:
393 if split_previous:
394 # throw out closing parenthesis of Symbol that was split
395 split_previous=False
396 continue
397 split_previous=False
399 if tok[0] == NAME and tok[1] in ['Symbol', 'Function']:
400 split = True
402 elif split and tok[0] == NAME:
403 symbol = tok[1][1:-1]
405 if predicate(symbol):
406 tok_type = result[-2][1] # Symbol or Function
407 del result[-2:] # Get rid of the call to Symbol
409 i = 0
410 while i < len(symbol):
411 char = symbol[i]
412 if char in local_dict or char in global_dict:
413 result.append((NAME, "%s" % char))
414 elif char.isdigit():
415 chars = [char]
416 for i in range(i + 1, len(symbol)):
417 if not symbol[i].isdigit():
418 i -= 1
419 break
420 chars.append(symbol[i])
421 char = ''.join(chars)
422 result.extend([(NAME, 'Number'), (OP, '('),
423 (NAME, "'%s'" % char), (OP, ')')])
424 else:
425 use = tok_type if i == len(symbol) else 'Symbol'
426 result.extend([(NAME, use), (OP, '('),
427 (NAME, "'%s'" % char), (OP, ')')])
428 i += 1
430 # Set split_previous=True so will skip
431 # the closing parenthesis of the original Symbol
432 split = False
433 split_previous = True
434 continue
436 else:
437 split = False
439 result.append(tok)
441 return result
443 return _split_symbols
446#: Splits symbol names for implicit multiplication.
447#:
448#: Intended to let expressions like ``xyz`` be parsed as ``x*y*z``. Does not
449#: split Greek character names, so ``theta`` will *not* become
450#: ``t*h*e*t*a``. Generally this should be used with
451#: ``implicit_multiplication``.
452split_symbols = split_symbols_custom(_token_splittable)
455def implicit_multiplication(tokens: List[TOKEN], local_dict: DICT,
456 global_dict: DICT) -> List[TOKEN]:
457 """Makes the multiplication operator optional in most cases.
459 Use this before :func:`implicit_application`, otherwise expressions like
460 ``sin 2x`` will be parsed as ``x * sin(2)`` rather than ``sin(2*x)``.
462 Examples
463 ========
465 >>> from sympy.parsing.sympy_parser import (parse_expr,
466 ... standard_transformations, implicit_multiplication)
467 >>> transformations = standard_transformations + (implicit_multiplication,)
468 >>> parse_expr('3 x y', transformations=transformations)
469 3*x*y
470 """
471 # These are interdependent steps, so we don't expose them separately
472 res1 = _group_parentheses(implicit_multiplication)(tokens, local_dict, global_dict)
473 res2 = _apply_functions(res1, local_dict, global_dict)
474 res3 = _implicit_multiplication(res2, local_dict, global_dict)
475 result = _flatten(res3)
476 return result
479def implicit_application(tokens: List[TOKEN], local_dict: DICT,
480 global_dict: DICT) -> List[TOKEN]:
481 """Makes parentheses optional in some cases for function calls.
483 Use this after :func:`implicit_multiplication`, otherwise expressions
484 like ``sin 2x`` will be parsed as ``x * sin(2)`` rather than
485 ``sin(2*x)``.
487 Examples
488 ========
490 >>> from sympy.parsing.sympy_parser import (parse_expr,
491 ... standard_transformations, implicit_application)
492 >>> transformations = standard_transformations + (implicit_application,)
493 >>> parse_expr('cot z + csc z', transformations=transformations)
494 cot(z) + csc(z)
495 """
496 res1 = _group_parentheses(implicit_application)(tokens, local_dict, global_dict)
497 res2 = _apply_functions(res1, local_dict, global_dict)
498 res3 = _implicit_application(res2, local_dict, global_dict)
499 result = _flatten(res3)
500 return result
503def implicit_multiplication_application(result: List[TOKEN], local_dict: DICT,
504 global_dict: DICT) -> List[TOKEN]:
505 """Allows a slightly relaxed syntax.
507 - Parentheses for single-argument method calls are optional.
509 - Multiplication is implicit.
511 - Symbol names can be split (i.e. spaces are not needed between
512 symbols).
514 - Functions can be exponentiated.
516 Examples
517 ========
519 >>> from sympy.parsing.sympy_parser import (parse_expr,
520 ... standard_transformations, implicit_multiplication_application)
521 >>> parse_expr("10sin**2 x**2 + 3xyz + tan theta",
522 ... transformations=(standard_transformations +
523 ... (implicit_multiplication_application,)))
524 3*x*y*z + 10*sin(x**2)**2 + tan(theta)
526 """
527 for step in (split_symbols, implicit_multiplication,
528 implicit_application, function_exponentiation):
529 result = step(result, local_dict, global_dict)
531 return result
534def auto_symbol(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
535 """Inserts calls to ``Symbol``/``Function`` for undefined variables."""
536 result: List[TOKEN] = []
537 prevTok = (-1, '')
539 tokens.append((-1, '')) # so zip traverses all tokens
540 for tok, nextTok in zip(tokens, tokens[1:]):
541 tokNum, tokVal = tok
542 nextTokNum, nextTokVal = nextTok
543 if tokNum == NAME:
544 name = tokVal
546 if (name in ['True', 'False', 'None']
547 or iskeyword(name)
548 # Don't convert attribute access
549 or (prevTok[0] == OP and prevTok[1] == '.')
550 # Don't convert keyword arguments
551 or (prevTok[0] == OP and prevTok[1] in ('(', ',')
552 and nextTokNum == OP and nextTokVal == '=')
553 # the name has already been defined
554 or name in local_dict and local_dict[name] is not null):
555 result.append((NAME, name))
556 continue
557 elif name in local_dict:
558 local_dict.setdefault(null, set()).add(name)
559 if nextTokVal == '(':
560 local_dict[name] = Function(name)
561 else:
562 local_dict[name] = Symbol(name)
563 result.append((NAME, name))
564 continue
565 elif name in global_dict:
566 obj = global_dict[name]
567 if isinstance(obj, (AssumptionKeys, Basic, type)) or callable(obj):
568 result.append((NAME, name))
569 continue
571 result.extend([
572 (NAME, 'Symbol' if nextTokVal != '(' else 'Function'),
573 (OP, '('),
574 (NAME, repr(str(name))),
575 (OP, ')'),
576 ])
577 else:
578 result.append((tokNum, tokVal))
580 prevTok = (tokNum, tokVal)
582 return result
585def lambda_notation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
586 """Substitutes "lambda" with its SymPy equivalent Lambda().
587 However, the conversion does not take place if only "lambda"
588 is passed because that is a syntax error.
590 """
591 result: List[TOKEN] = []
592 flag = False
593 toknum, tokval = tokens[0]
594 tokLen = len(tokens)
596 if toknum == NAME and tokval == 'lambda':
597 if tokLen == 2 or tokLen == 3 and tokens[1][0] == NEWLINE:
598 # In Python 3.6.7+, inputs without a newline get NEWLINE added to
599 # the tokens
600 result.extend(tokens)
601 elif tokLen > 2:
602 result.extend([
603 (NAME, 'Lambda'),
604 (OP, '('),
605 (OP, '('),
606 (OP, ')'),
607 (OP, ')'),
608 ])
609 for tokNum, tokVal in tokens[1:]:
610 if tokNum == OP and tokVal == ':':
611 tokVal = ','
612 flag = True
613 if not flag and tokNum == OP and tokVal in ('*', '**'):
614 raise TokenError("Starred arguments in lambda not supported")
615 if flag:
616 result.insert(-1, (tokNum, tokVal))
617 else:
618 result.insert(-2, (tokNum, tokVal))
619 else:
620 result.extend(tokens)
622 return result
625def factorial_notation(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
626 """Allows standard notation for factorial."""
627 result: List[TOKEN] = []
628 nfactorial = 0
629 for toknum, tokval in tokens:
630 if toknum == ERRORTOKEN:
631 op = tokval
632 if op == '!':
633 nfactorial += 1
634 else:
635 nfactorial = 0
636 result.append((OP, op))
637 else:
638 if nfactorial == 1:
639 result = _add_factorial_tokens('factorial', result)
640 elif nfactorial == 2:
641 result = _add_factorial_tokens('factorial2', result)
642 elif nfactorial > 2:
643 raise TokenError
644 nfactorial = 0
645 result.append((toknum, tokval))
646 return result
649def convert_xor(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
650 """Treats XOR, ``^``, as exponentiation, ``**``."""
651 result: List[TOKEN] = []
652 for toknum, tokval in tokens:
653 if toknum == OP:
654 if tokval == '^':
655 result.append((OP, '**'))
656 else:
657 result.append((toknum, tokval))
658 else:
659 result.append((toknum, tokval))
661 return result
664def repeated_decimals(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
665 """
666 Allows 0.2[1] notation to represent the repeated decimal 0.2111... (19/90)
668 Run this before auto_number.
670 """
671 result: List[TOKEN] = []
673 def is_digit(s):
674 return all(i in '0123456789_' for i in s)
676 # num will running match any DECIMAL [ INTEGER ]
677 num: List[TOKEN] = []
678 for toknum, tokval in tokens:
679 if toknum == NUMBER:
680 if (not num and '.' in tokval and 'e' not in tokval.lower() and
681 'j' not in tokval.lower()):
682 num.append((toknum, tokval))
683 elif is_digit(tokval)and len(num) == 2:
684 num.append((toknum, tokval))
685 elif is_digit(tokval) and len(num) == 3 and is_digit(num[-1][1]):
686 # Python 2 tokenizes 00123 as '00', '123'
687 # Python 3 tokenizes 01289 as '012', '89'
688 num.append((toknum, tokval))
689 else:
690 num = []
691 elif toknum == OP:
692 if tokval == '[' and len(num) == 1:
693 num.append((OP, tokval))
694 elif tokval == ']' and len(num) >= 3:
695 num.append((OP, tokval))
696 elif tokval == '.' and not num:
697 # handle .[1]
698 num.append((NUMBER, '0.'))
699 else:
700 num = []
701 else:
702 num = []
704 result.append((toknum, tokval))
706 if num and num[-1][1] == ']':
707 # pre.post[repetend] = a + b/c + d/e where a = pre, b/c = post,
708 # and d/e = repetend
709 result = result[:-len(num)]
710 pre, post = num[0][1].split('.')
711 repetend = num[2][1]
712 if len(num) == 5:
713 repetend += num[3][1]
715 pre = pre.replace('_', '')
716 post = post.replace('_', '')
717 repetend = repetend.replace('_', '')
719 zeros = '0'*len(post)
720 post, repetends = [w.lstrip('0') for w in [post, repetend]]
721 # or else interpreted as octal
723 a = pre or '0'
724 b, c = post or '0', '1' + zeros
725 d, e = repetends, ('9'*len(repetend)) + zeros
727 seq = [
728 (OP, '('),
729 (NAME, 'Integer'),
730 (OP, '('),
731 (NUMBER, a),
732 (OP, ')'),
733 (OP, '+'),
734 (NAME, 'Rational'),
735 (OP, '('),
736 (NUMBER, b),
737 (OP, ','),
738 (NUMBER, c),
739 (OP, ')'),
740 (OP, '+'),
741 (NAME, 'Rational'),
742 (OP, '('),
743 (NUMBER, d),
744 (OP, ','),
745 (NUMBER, e),
746 (OP, ')'),
747 (OP, ')'),
748 ]
749 result.extend(seq)
750 num = []
752 return result
755def auto_number(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
756 """
757 Converts numeric literals to use SymPy equivalents.
759 Complex numbers use ``I``, integer literals use ``Integer``, and float
760 literals use ``Float``.
762 """
763 result: List[TOKEN] = []
765 for toknum, tokval in tokens:
766 if toknum == NUMBER:
767 number = tokval
768 postfix = []
770 if number.endswith('j') or number.endswith('J'):
771 number = number[:-1]
772 postfix = [(OP, '*'), (NAME, 'I')]
774 if '.' in number or (('e' in number or 'E' in number) and
775 not (number.startswith('0x') or number.startswith('0X'))):
776 seq = [(NAME, 'Float'), (OP, '('),
777 (NUMBER, repr(str(number))), (OP, ')')]
778 else:
779 seq = [(NAME, 'Integer'), (OP, '('), (
780 NUMBER, number), (OP, ')')]
782 result.extend(seq + postfix)
783 else:
784 result.append((toknum, tokval))
786 return result
789def rationalize(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
790 """Converts floats into ``Rational``. Run AFTER ``auto_number``."""
791 result: List[TOKEN] = []
792 passed_float = False
793 for toknum, tokval in tokens:
794 if toknum == NAME:
795 if tokval == 'Float':
796 passed_float = True
797 tokval = 'Rational'
798 result.append((toknum, tokval))
799 elif passed_float == True and toknum == NUMBER:
800 passed_float = False
801 result.append((STRING, tokval))
802 else:
803 result.append((toknum, tokval))
805 return result
808def _transform_equals_sign(tokens: List[TOKEN], local_dict: DICT, global_dict: DICT):
809 """Transforms the equals sign ``=`` to instances of Eq.
811 This is a helper function for ``convert_equals_signs``.
812 Works with expressions containing one equals sign and no
813 nesting. Expressions like ``(1=2)=False`` will not work with this
814 and should be used with ``convert_equals_signs``.
816 Examples: 1=2 to Eq(1,2)
817 1*2=x to Eq(1*2, x)
819 This does not deal with function arguments yet.
821 """
822 result: List[TOKEN] = []
823 if (OP, "=") in tokens:
824 result.append((NAME, "Eq"))
825 result.append((OP, "("))
826 for token in tokens:
827 if token == (OP, "="):
828 result.append((OP, ","))
829 continue
830 result.append(token)
831 result.append((OP, ")"))
832 else:
833 result = tokens
834 return result
837def convert_equals_signs(tokens: List[TOKEN], local_dict: DICT,
838 global_dict: DICT) -> List[TOKEN]:
839 """ Transforms all the equals signs ``=`` to instances of Eq.
841 Parses the equals signs in the expression and replaces them with
842 appropriate Eq instances. Also works with nested equals signs.
844 Does not yet play well with function arguments.
845 For example, the expression ``(x=y)`` is ambiguous and can be interpreted
846 as x being an argument to a function and ``convert_equals_signs`` will not
847 work for this.
849 See also
850 ========
851 convert_equality_operators
853 Examples
854 ========
856 >>> from sympy.parsing.sympy_parser import (parse_expr,
857 ... standard_transformations, convert_equals_signs)
858 >>> parse_expr("1*2=x", transformations=(
859 ... standard_transformations + (convert_equals_signs,)))
860 Eq(2, x)
861 >>> parse_expr("(1*2=x)=False", transformations=(
862 ... standard_transformations + (convert_equals_signs,)))
863 Eq(Eq(2, x), False)
865 """
866 res1 = _group_parentheses(convert_equals_signs)(tokens, local_dict, global_dict)
867 res2 = _apply_functions(res1, local_dict, global_dict)
868 res3 = _transform_equals_sign(res2, local_dict, global_dict)
869 result = _flatten(res3)
870 return result
873#: Standard transformations for :func:`parse_expr`.
874#: Inserts calls to :class:`~.Symbol`, :class:`~.Integer`, and other SymPy
875#: datatypes and allows the use of standard factorial notation (e.g. ``x!``).
876standard_transformations: tTuple[TRANS, ...] \
877 = (lambda_notation, auto_symbol, repeated_decimals, auto_number,
878 factorial_notation)
881def stringify_expr(s: str, local_dict: DICT, global_dict: DICT,
882 transformations: tTuple[TRANS, ...]) -> str:
883 """
884 Converts the string ``s`` to Python code, in ``local_dict``
886 Generally, ``parse_expr`` should be used.
887 """
889 tokens = []
890 input_code = StringIO(s.strip())
891 for toknum, tokval, _, _, _ in generate_tokens(input_code.readline):
892 tokens.append((toknum, tokval))
894 for transform in transformations:
895 tokens = transform(tokens, local_dict, global_dict)
897 return untokenize(tokens)
900def eval_expr(code, local_dict: DICT, global_dict: DICT):
901 """
902 Evaluate Python code generated by ``stringify_expr``.
904 Generally, ``parse_expr`` should be used.
905 """
906 expr = eval(
907 code, global_dict, local_dict) # take local objects in preference
908 return expr
911def parse_expr(s: str, local_dict: Optional[DICT] = None,
912 transformations: tUnion[tTuple[TRANS, ...], str] \
913 = standard_transformations,
914 global_dict: Optional[DICT] = None, evaluate=True):
915 """Converts the string ``s`` to a SymPy expression, in ``local_dict``.
917 Parameters
918 ==========
920 s : str
921 The string to parse.
923 local_dict : dict, optional
924 A dictionary of local variables to use when parsing.
926 global_dict : dict, optional
927 A dictionary of global variables. By default, this is initialized
928 with ``from sympy import *``; provide this parameter to override
929 this behavior (for instance, to parse ``"Q & S"``).
931 transformations : tuple or str
932 A tuple of transformation functions used to modify the tokens of the
933 parsed expression before evaluation. The default transformations
934 convert numeric literals into their SymPy equivalents, convert
935 undefined variables into SymPy symbols, and allow the use of standard
936 mathematical factorial notation (e.g. ``x!``). Selection via
937 string is available (see below).
939 evaluate : bool, optional
940 When False, the order of the arguments will remain as they were in the
941 string and automatic simplification that would normally occur is
942 suppressed. (see examples)
944 Examples
945 ========
947 >>> from sympy.parsing.sympy_parser import parse_expr
948 >>> parse_expr("1/2")
949 1/2
950 >>> type(_)
951 <class 'sympy.core.numbers.Half'>
952 >>> from sympy.parsing.sympy_parser import standard_transformations,\\
953 ... implicit_multiplication_application
954 >>> transformations = (standard_transformations +
955 ... (implicit_multiplication_application,))
956 >>> parse_expr("2x", transformations=transformations)
957 2*x
959 When evaluate=False, some automatic simplifications will not occur:
961 >>> parse_expr("2**3"), parse_expr("2**3", evaluate=False)
962 (8, 2**3)
964 In addition the order of the arguments will not be made canonical.
965 This feature allows one to tell exactly how the expression was entered:
967 >>> a = parse_expr('1 + x', evaluate=False)
968 >>> b = parse_expr('x + 1', evaluate=0)
969 >>> a == b
970 False
971 >>> a.args
972 (1, x)
973 >>> b.args
974 (x, 1)
976 Note, however, that when these expressions are printed they will
977 appear the same:
979 >>> assert str(a) == str(b)
981 As a convenience, transformations can be seen by printing ``transformations``:
983 >>> from sympy.parsing.sympy_parser import transformations
985 >>> print(transformations)
986 0: lambda_notation
987 1: auto_symbol
988 2: repeated_decimals
989 3: auto_number
990 4: factorial_notation
991 5: implicit_multiplication_application
992 6: convert_xor
993 7: implicit_application
994 8: implicit_multiplication
995 9: convert_equals_signs
996 10: function_exponentiation
997 11: rationalize
999 The ``T`` object provides a way to select these transformations:
1001 >>> from sympy.parsing.sympy_parser import T
1003 If you print it, you will see the same list as shown above.
1005 >>> str(T) == str(transformations)
1006 True
1008 Standard slicing will return a tuple of transformations:
1010 >>> T[:5] == standard_transformations
1011 True
1013 So ``T`` can be used to specify the parsing transformations:
1015 >>> parse_expr("2x", transformations=T[:5])
1016 Traceback (most recent call last):
1017 ...
1018 SyntaxError: invalid syntax
1019 >>> parse_expr("2x", transformations=T[:6])
1020 2*x
1021 >>> parse_expr('.3', transformations=T[3, 11])
1022 3/10
1023 >>> parse_expr('.3x', transformations=T[:])
1024 3*x/10
1026 As a further convenience, strings 'implicit' and 'all' can be used
1027 to select 0-5 and all the transformations, respectively.
1029 >>> parse_expr('.3x', transformations='all')
1030 3*x/10
1032 See Also
1033 ========
1035 stringify_expr, eval_expr, standard_transformations,
1036 implicit_multiplication_application
1038 """
1040 if local_dict is None:
1041 local_dict = {}
1042 elif not isinstance(local_dict, dict):
1043 raise TypeError('expecting local_dict to be a dict')
1044 elif null in local_dict:
1045 raise ValueError('cannot use "" in local_dict')
1047 if global_dict is None:
1048 global_dict = {}
1049 exec('from sympy import *', global_dict)
1051 builtins_dict = vars(builtins)
1052 for name, obj in builtins_dict.items():
1053 if isinstance(obj, types.BuiltinFunctionType):
1054 global_dict[name] = obj
1055 global_dict['max'] = Max
1056 global_dict['min'] = Min
1058 elif not isinstance(global_dict, dict):
1059 raise TypeError('expecting global_dict to be a dict')
1061 transformations = transformations or ()
1062 if isinstance(transformations, str):
1063 if transformations == 'all':
1064 _transformations = T[:]
1065 elif transformations == 'implicit':
1066 _transformations = T[:6]
1067 else:
1068 raise ValueError('unknown transformation group name')
1069 else:
1070 _transformations = transformations
1072 code = stringify_expr(s, local_dict, global_dict, _transformations)
1074 if not evaluate:
1075 code = compile(evaluateFalse(code), '<string>', 'eval') # type: ignore
1077 try:
1078 rv = eval_expr(code, local_dict, global_dict)
1079 # restore neutral definitions for names
1080 for i in local_dict.pop(null, ()):
1081 local_dict[i] = null
1082 return rv
1083 except Exception as e:
1084 # restore neutral definitions for names
1085 for i in local_dict.pop(null, ()):
1086 local_dict[i] = null
1087 raise e from ValueError(f"Error from parse_expr with transformed code: {code!r}")
1090def evaluateFalse(s: str):
1091 """
1092 Replaces operators with the SymPy equivalent and sets evaluate=False.
1093 """
1094 node = ast.parse(s)
1095 transformed_node = EvaluateFalseTransformer().visit(node)
1096 # node is a Module, we want an Expression
1097 transformed_node = ast.Expression(transformed_node.body[0].value)
1099 return ast.fix_missing_locations(transformed_node)
1102class EvaluateFalseTransformer(ast.NodeTransformer):
1103 operators = {
1104 ast.Add: 'Add',
1105 ast.Mult: 'Mul',
1106 ast.Pow: 'Pow',
1107 ast.Sub: 'Add',
1108 ast.Div: 'Mul',
1109 ast.BitOr: 'Or',
1110 ast.BitAnd: 'And',
1111 ast.BitXor: 'Not',
1112 }
1113 functions = (
1114 'Abs', 'im', 're', 'sign', 'arg', 'conjugate',
1115 'acos', 'acot', 'acsc', 'asec', 'asin', 'atan',
1116 'acosh', 'acoth', 'acsch', 'asech', 'asinh', 'atanh',
1117 'cos', 'cot', 'csc', 'sec', 'sin', 'tan',
1118 'cosh', 'coth', 'csch', 'sech', 'sinh', 'tanh',
1119 'exp', 'ln', 'log', 'sqrt', 'cbrt',
1120 )
1122 relational_operators = {
1123 ast.NotEq: 'Ne',
1124 ast.Lt: 'Lt',
1125 ast.LtE: 'Le',
1126 ast.Gt: 'Gt',
1127 ast.GtE: 'Ge',
1128 ast.Eq: 'Eq'
1129 }
1130 def visit_Compare(self, node):
1131 if node.ops[0].__class__ in self.relational_operators:
1132 sympy_class = self.relational_operators[node.ops[0].__class__]
1133 right = self.visit(node.comparators[0])
1134 left = self.visit(node.left)
1135 new_node = ast.Call(
1136 func=ast.Name(id=sympy_class, ctx=ast.Load()),
1137 args=[left, right],
1138 keywords=[ast.keyword(arg='evaluate', value=ast.NameConstant(value=False, ctx=ast.Load()))],
1139 starargs=None,
1140 kwargs=None
1141 )
1142 return new_node
1143 return node
1145 def flatten(self, args, func):
1146 result = []
1147 for arg in args:
1148 if isinstance(arg, ast.Call):
1149 arg_func = arg.func
1150 if isinstance(arg_func, ast.Call):
1151 arg_func = arg_func.func
1152 if arg_func.id == func:
1153 result.extend(self.flatten(arg.args, func))
1154 else:
1155 result.append(arg)
1156 else:
1157 result.append(arg)
1158 return result
1160 def visit_BinOp(self, node):
1161 if node.op.__class__ in self.operators:
1162 sympy_class = self.operators[node.op.__class__]
1163 right = self.visit(node.right)
1164 left = self.visit(node.left)
1166 rev = False
1167 if isinstance(node.op, ast.Sub):
1168 right = ast.Call(
1169 func=ast.Name(id='Mul', ctx=ast.Load()),
1170 args=[ast.UnaryOp(op=ast.USub(), operand=ast.Num(1)), right],
1171 keywords=[ast.keyword(arg='evaluate', value=ast.NameConstant(value=False, ctx=ast.Load()))],
1172 starargs=None,
1173 kwargs=None
1174 )
1175 elif isinstance(node.op, ast.Div):
1176 if isinstance(node.left, ast.UnaryOp):
1177 left, right = right, left
1178 rev = True
1179 left = ast.Call(
1180 func=ast.Name(id='Pow', ctx=ast.Load()),
1181 args=[left, ast.UnaryOp(op=ast.USub(), operand=ast.Num(1))],
1182 keywords=[ast.keyword(arg='evaluate', value=ast.NameConstant(value=False, ctx=ast.Load()))],
1183 starargs=None,
1184 kwargs=None
1185 )
1186 else:
1187 right = ast.Call(
1188 func=ast.Name(id='Pow', ctx=ast.Load()),
1189 args=[right, ast.UnaryOp(op=ast.USub(), operand=ast.Num(1))],
1190 keywords=[ast.keyword(arg='evaluate', value=ast.NameConstant(value=False, ctx=ast.Load()))],
1191 starargs=None,
1192 kwargs=None
1193 )
1195 if rev: # undo reversal
1196 left, right = right, left
1197 new_node = ast.Call(
1198 func=ast.Name(id=sympy_class, ctx=ast.Load()),
1199 args=[left, right],
1200 keywords=[ast.keyword(arg='evaluate', value=ast.NameConstant(value=False, ctx=ast.Load()))],
1201 starargs=None,
1202 kwargs=None
1203 )
1205 if sympy_class in ('Add', 'Mul'):
1206 # Denest Add or Mul as appropriate
1207 new_node.args = self.flatten(new_node.args, sympy_class)
1209 return new_node
1210 return node
1212 def visit_Call(self, node):
1213 new_node = self.generic_visit(node)
1214 if isinstance(node.func, ast.Name) and node.func.id in self.functions:
1215 new_node.keywords.append(ast.keyword(arg='evaluate', value=ast.NameConstant(value=False, ctx=ast.Load())))
1216 return new_node
1219_transformation = { # items can be added but never re-ordered
12200: lambda_notation,
12211: auto_symbol,
12222: repeated_decimals,
12233: auto_number,
12244: factorial_notation,
12255: implicit_multiplication_application,
12266: convert_xor,
12277: implicit_application,
12288: implicit_multiplication,
12299: convert_equals_signs,
123010: function_exponentiation,
123111: rationalize}
1233transformations = '\n'.join('%s: %s' % (i, func_name(f)) for i, f in _transformation.items())
1236class _T():
1237 """class to retrieve transformations from a given slice
1239 EXAMPLES
1240 ========
1242 >>> from sympy.parsing.sympy_parser import T, standard_transformations
1243 >>> assert T[:5] == standard_transformations
1244 """
1245 def __init__(self):
1246 self.N = len(_transformation)
1248 def __str__(self):
1249 return transformations
1251 def __getitem__(self, t):
1252 if not type(t) is tuple:
1253 t = (t,)
1254 i = []
1255 for ti in t:
1256 if type(ti) is int:
1257 i.append(range(self.N)[ti])
1258 elif type(ti) is slice:
1259 i.extend(range(*ti.indices(self.N)))
1260 else:
1261 raise TypeError('unexpected slice arg')
1262 return tuple([_transformation[_] for _ in i])
1264T = _T()