parser.py 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. # This file is part of TRS (http://math.kompiler.org)
  2. #
  3. # TRS is free software: you can redistribute it and/or modify it under the
  4. # terms of the GNU Affero General Public License as published by the Free
  5. # Software Foundation, either version 3 of the License, or (at your option) any
  6. # later version.
  7. #
  8. # TRS is distributed in the hope that it will be useful, but WITHOUT ANY
  9. # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  10. # A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
  11. # details.
  12. #
  13. # You should have received a copy of the GNU Affero General Public License
  14. # along with TRS. If not, see <http://www.gnu.org/licenses/>.
  15. """
  16. This parser will parse the given input and build an expression tree. Grammar
  17. file for the supported mathematical expressions.
  18. """
  19. import os.path
  20. PYBISON_BUILD = os.path.realpath('build/external/pybison')
  21. EXTERNAL_MODS = os.path.realpath('external')
  22. import sys
  23. sys.path.insert(0, PYBISON_BUILD)
  24. sys.path.insert(1, EXTERNAL_MODS)
  25. from pybison import BisonParser, BisonSyntaxError
  26. from graph_drawing.line import pred
  27. from node import ExpressionNode as Node, \
  28. ExpressionLeaf as Leaf, OP_MAP, OP_DXDER, TOKEN_MAP, TYPE_OPERATOR, \
  29. OP_COMMA, OP_MUL, OP_POW, OP_LOG, OP_ADD, Scope, E, OP_ABS, \
  30. DEFAULT_LOGARITHM_BASE, SPECIAL_TOKENS, OP_INT, OP_INT_DEF, \
  31. INFINITY, OP_PRIME, OP_DIV
  32. from rules.utils import find_variable
  33. from rules.precedences import IMPLICIT_RULES
  34. from strategy import find_possibilities
  35. from possibilities import apply_suggestion
  36. import Queue
  37. import re
  38. # Rewriting an expression is stopped after this number of steps is passed.
  39. MAXIMUM_REWRITE_STEPS = 30
  40. # Precedence of the TIMES operator ("*")
  41. TIMES_PRED = pred(Node(OP_MUL))
  42. # Check for n-ary operator in child nodes
  43. def combine(op, op_type, *nodes):
  44. # At least return the operator.
  45. res = [op]
  46. for n in nodes:
  47. # Merge the children for all nodes which have the same operator.
  48. if n.type == TYPE_OPERATOR and n.op == op_type:
  49. res += n.nodes
  50. else:
  51. res.append(n)
  52. return res
  53. def find_integration_variable(exp):
  54. if not exp.is_op(OP_MUL):
  55. return exp, find_variable(exp)
  56. scope = Scope(exp)
  57. if len(scope) > 2 and scope[-2] == 'd' and scope[-1].is_identifier():
  58. x = scope[-1]
  59. scope.nodes = scope[:-2]
  60. return scope.as_nary_node(), x
  61. return exp, find_variable(exp)
  62. def apply_operator_negation(op, exp):
  63. exp.negated += len(op) - 1
  64. class Parser(BisonParser):
  65. """
  66. Implements the calculator parser. Grammar rules are defined in the method
  67. docstrings. Scanner rules are in the 'lexscript' attribute.
  68. """
  69. # Words to be ignored by preprocessor
  70. words = tuple(filter(lambda w: w.isalpha(), OP_MAP.iterkeys())) \
  71. + ('raise', 'graph') + tuple(SPECIAL_TOKENS)
  72. # Output directory of generated pybison files, including a trailing slash.
  73. buildDirectory = PYBISON_BUILD + '/'
  74. # ----------------------------------------------------------------
  75. # lexer tokens - these must match those in your lex script (below)
  76. # ----------------------------------------------------------------
  77. # TODO: add a runtime check to verify that this token list match the list
  78. # of tokens of the lex script.
  79. tokens = ['NUMBER', 'IDENTIFIER', 'NEWLINE', 'QUIT', 'RAISE', 'GRAPH',
  80. 'LPAREN', 'RPAREN', 'FUNCTION', 'LBRACKET', 'FUNCTION_PAREN',
  81. 'RBRACKET', 'LCBRACKET', 'RCBRACKET', 'PIPE'] \
  82. + filter(lambda t: t != 'FUNCTION', TOKEN_MAP.values())
  83. # ------------------------------
  84. # precedences
  85. # ------------------------------
  86. precedences = (
  87. ('left', ('COMMA', )),
  88. ('left', ('OR', )),
  89. ('left', ('AND', )),
  90. ('left', ('EQ', )),
  91. ('left', ('MINUS', 'PLUS')),
  92. ('nonassoc', ('INTEGRAL', 'DERIVATIVE')),
  93. ('left', ('TIMES', )),
  94. ('left', ('DIVIDE', )),
  95. ('nonassoc', ('PRIME', )),
  96. ('nonassoc', ('NEG', )),
  97. ('nonassoc', ('FUNCTION', 'LOGARITHM')),
  98. ('right', ('POW', 'SUB')),
  99. ('nonassoc', ('FUNCTION_PAREN', )),
  100. )
  101. def __init__(self, **kwargs):
  102. BisonParser.__init__(self, **kwargs)
  103. self.interactive = kwargs.get('interactive', 0)
  104. self.timeout = kwargs.get('timeout', 0)
  105. self.root_node = None
  106. self.possibilities = None
  107. self.reset()
  108. def reset(self):
  109. super(Parser, self).reset()
  110. self.read_buffer = ''
  111. self.read_queue = Queue.Queue()
  112. #self.subtree_map = {}
  113. self.set_root_node(None)
  114. self.possibilities = None
  115. def run(self, *args, **kwargs):
  116. self.reset()
  117. return super(Parser, self).run(*args, **kwargs)
  118. # Override default read method with a version that prompts for input.
  119. def read(self, nbytes):
  120. if self.file == sys.stdin and self.file.closed:
  121. return ''
  122. if not self.read_buffer and not self.read_queue.empty():
  123. self.read_buffer = self.read_queue.get_nowait() + '\n'
  124. if self.read_buffer:
  125. read_buffer = self.read_buffer[:nbytes]
  126. self.read_buffer = self.read_buffer[nbytes:]
  127. return read_buffer
  128. try:
  129. read_buffer = raw_input('>>> ' if self.interactive else '') + '\n'
  130. except EOFError:
  131. return ''
  132. self.read_buffer = read_buffer[nbytes:]
  133. return read_buffer[:nbytes]
  134. def hook_read_before(self):
  135. pass
  136. def hook_read_after(self, data):
  137. """
  138. This hook will be called when the read() method returned. The data
  139. argument points to the data read by the read() method. This hook
  140. function should return the data to be used by the parser.
  141. """
  142. if not data.strip():
  143. return data
  144. # Replace known keywords with escape sequences.
  145. words = list(self.words)
  146. words.insert(0xa, '\n')
  147. words.insert(0xc, '\f')
  148. words.insert(0xd, '\r')
  149. for i, keyword in enumerate(words):
  150. # FIXME: Why case-insensitivity?
  151. # FIXME: good question...
  152. data = re.sub(keyword, chr(i), data, flags=re.I)
  153. rsv = '\x00-\x09\x0b-\x0c\x0e-\x19'
  154. pattern = ('(?:([)\]])\s*([([])' # )( -> ) * (
  155. # )[ -> ) * [
  156. # ]( -> [ * (
  157. # ][ -> [ * [
  158. + '|([' + rsv + 'a-z0-9])\s*([([])' # a( -> a * (
  159. # a[ -> a * [
  160. + '|(\))\s*([' + rsv + 'a-z0-9])' # )a -> ) * a
  161. + '|([' + rsv + 'a-z])\s*'
  162. + '([' + rsv + 'a-z0-9])' # ab -> a * b
  163. + '|(\|)(\|)' # || -> | * |
  164. + '|([0-9])\s*([' + rsv + 'a-z])' # 4a -> 4 * a
  165. + '|([' + rsv + 'a-z])([0-9])' # a4 -> a ^ 4
  166. + '|([' + rsv + '0-9])(\s+[0-9]))' # 4 4 -> 4 * 4
  167. # FIXME: Last line is a bit useless
  168. )
  169. def preprocess_data(match):
  170. left, right = filter(None, match.groups())
  171. # Make sure there are no multiplication and exponentiation signs
  172. # inserted between a function and its argument(s): "sin x" should
  173. # not be written as "sin*x", because that is bogus.
  174. # Bugfix: omit 0x0c (pi) to prevent "pi a" (should be "pi*a")
  175. o = ord(left)
  176. if o <= 0x9 or o == 0xb:
  177. return left + ' ' + right
  178. # If all characters on the right are numbers. e.g. "a4", the
  179. # expression implies exponentiation. Make sure ")4" is not
  180. # converted into an exponentiation, because that's multiplication.
  181. #if left != ')' and not left.isdigit() and right.isdigit():
  182. # return '%s^%s' % (left, right)
  183. # match: ab | abc | abcd (where left = "a")
  184. return '*'.join([left] + list(re.sub('^ +', '', right)))
  185. if self.verbose: # pragma: nocover
  186. data_before = data
  187. # Iteratively replace all matches.
  188. i = 0
  189. while i < len(data):
  190. data = data[:i] + re.sub(pattern, preprocess_data, data[i:],
  191. flags=re.IGNORECASE)
  192. i += 1
  193. # Replace escape sequences with original keywords.
  194. for i, keyword in enumerate(words):
  195. data = data.replace(chr(i), keyword)
  196. # Remove TIMES operators around OR that the preprocessor put there
  197. data = re.sub(r'\*?vv\*?', 'vv', data)
  198. # Add parentheses to integrals with matching 'dx' so that the 'dx' acts
  199. # as a right parenthesis for the integral function
  200. #data = re.sub(r'(int(?:_.+(?:\^.+)?\*)?)(.+?)(\*d\*[a-z])',
  201. # '\\1(\\2)\\3', data)
  202. if self.verbose and data_before != data: # pragma: nocover
  203. print 'hook_read_after() modified the input data:'
  204. print 'before:', repr(data_before)
  205. print 'after :', repr(data)
  206. return data
  207. def hook_handler(self, target, option, names, values, retval):
  208. return retval
  209. def set_root_node(self, node):
  210. self.root_node = node
  211. self.possibilities = None
  212. def find_possibilities(self):
  213. if not self.root_node:
  214. raise RuntimeError('No expression')
  215. if self.possibilities is not None:
  216. if self.verbose: # pragma: nocover
  217. print 'Expression has not changed, not updating possibilities'
  218. return
  219. self.possibilities = find_possibilities(self.root_node)
  220. def display_hint(self):
  221. hint = self.give_hint()
  222. if hint:
  223. print str(hint).replace('`', '')
  224. else:
  225. print 'No further reduction is possible.'
  226. def give_hint(self):
  227. self.find_possibilities()
  228. if self.possibilities:
  229. return self.possibilities[0]
  230. def display_possibilities(self):
  231. self.find_possibilities()
  232. for i, p in enumerate(self.possibilities):
  233. print '%d %s' % (i, p)
  234. def rewrite(self, index=0, include_step=False, verbose=False,
  235. check_implicit=True):
  236. self.find_possibilities()
  237. if not self.possibilities:
  238. return
  239. suggestion = self.possibilities[index]
  240. if self.verbose: # pragma: nocover
  241. print 'EXPLICIT:', suggestion
  242. elif verbose: # pragma: nocover
  243. print suggestion
  244. self.set_root_node(apply_suggestion(self.root_node, suggestion))
  245. if self.verbose: # pragma: nocover
  246. print ' ', self.root_node
  247. # Only apply any remaining implicit hints if the suggestion itself is
  248. # not implicit
  249. if check_implicit and suggestion.handler not in IMPLICIT_RULES:
  250. self.find_possibilities()
  251. while self.possibilities:
  252. # Find the first implicit possibliity in the list
  253. # FIXME: Is it smart to apply a rule that is not a hint?
  254. # ANSWER: Yes, but there must be something like an extra list
  255. # that prevents deliberately generated implicit rules from
  256. # being applied
  257. #sugg = self.possibilities[0]
  258. #if sugg.handler not in IMPLICIT_RULES:
  259. # break
  260. sugg = None
  261. for pos in self.possibilities:
  262. if pos.handler in IMPLICIT_RULES:
  263. sugg = pos
  264. break
  265. if not sugg:
  266. break
  267. if self.verbose: # pragma: nocover
  268. print 'IMPLICIT:', sugg
  269. self.set_root_node(apply_suggestion(self.root_node, sugg))
  270. if self.verbose: # pragma: nocover
  271. print ' ', self.root_node
  272. self.find_possibilities()
  273. if verbose and not self.verbose: # pragma: nocover
  274. print self.root_node
  275. if include_step:
  276. # Make sure that the node is cloned, otherwise the next rewrite
  277. # attempt will modify the root node (since it's mutable).
  278. return suggestion, self.root_node.clone()
  279. return self.root_node
  280. def rewrite_all(self, include_steps=False, check_implicit=True,
  281. verbose=False):
  282. steps = []
  283. for i in range(MAXIMUM_REWRITE_STEPS):
  284. obj = self.rewrite(verbose=verbose, check_implicit=check_implicit,
  285. include_step=include_steps)
  286. if not obj:
  287. break
  288. if include_steps:
  289. steps.append(obj)
  290. if i > MAXIMUM_REWRITE_STEPS:
  291. print 'Too many rewrite steps, aborting...'
  292. if not verbose or not i:
  293. if include_steps:
  294. return steps
  295. return self.root_node
  296. def rewrite_and_count_all(self, check_implicit=True, verbose=False):
  297. steps = self.rewrite_all(include_steps=True,
  298. check_implicit=check_implicit, verbose=verbose)
  299. return self.root_node, len(steps)
  300. #def hook_run(self, filename, retval):
  301. # return retval
  302. # ---------------------------------------------------------------
  303. # These methods are the python handlers for the bison targets.
  304. # (which get called by the bison code each time the corresponding
  305. # parse target is unambiguously reached)
  306. #
  307. # WARNING - don't touch the method docstrings unless you know what
  308. # you are doing - they are in bison rule syntax, and are passed
  309. # verbatim to bison to build the parser engine library.
  310. # ---------------------------------------------------------------
  311. # Declare the start target here (by name)
  312. start = 'input'
  313. def on_input(self, target, option, names, values):
  314. """
  315. input :
  316. | input line
  317. """
  318. if option == 1:
  319. # Interactive mode is enabled if the term rewriting system is used
  320. # as a shell. In that case, it is useful that the shell prints the
  321. # output of the evaluation.
  322. if self.interactive and values[1]: # pragma: nocover
  323. print values[1]
  324. return values[1]
  325. def on_line(self, target, option, names, values):
  326. """
  327. line : NEWLINE
  328. | exp NEWLINE
  329. | debug NEWLINE
  330. | HINT NEWLINE
  331. | POSSIBILITIES NEWLINE
  332. | REWRITE NEWLINE
  333. | REWRITE NUMBER NEWLINE
  334. | REWRITE_ALL NEWLINE
  335. | REWRITE_ALL_VERBOSE NEWLINE
  336. | RAISE NEWLINE
  337. """
  338. if option in (1, 2): # rule: {exp,debug} NEWLINE
  339. self.set_root_node(values[0])
  340. return values[0]
  341. if option == 3: # rule: HINT NEWLINE
  342. self.display_hint()
  343. return
  344. if option == 4: # rule: POSSIBILITIES NEWLINE
  345. self.display_possibilities()
  346. return
  347. if option == 5: # rule: REWRITE NEWLINE
  348. return self.rewrite()
  349. if option == 6: # rule: REWRITE NUMBER NEWLINE
  350. self.rewrite(int(values[1]))
  351. return self.root_node
  352. if option in (7, 8): # rule: REWRITE_ALL NEWLINE
  353. return self.rewrite_all(verbose=(option == 8))
  354. if option == 9:
  355. raise RuntimeError('on_line: exception raised')
  356. def on_debug(self, target, option, names, values):
  357. """
  358. debug : GRAPH exp
  359. """
  360. if option == 0:
  361. print values[1].graph()
  362. return values[1]
  363. raise BisonSyntaxError('Unsupported option %d in target "%s".'
  364. % (option, target)) # pragma: nocover
  365. def on_exp(self, target, option, names, values):
  366. """
  367. exp : NUMBER
  368. | IDENTIFIER
  369. | LPAREN exp RPAREN
  370. | LBRACKET exp RBRACKET
  371. | LCBRACKET exp RCBRACKET
  372. | unary
  373. | binary
  374. | nary
  375. """
  376. if option == 0: # rule: NUMBER
  377. # TODO: A bit hacky, this achieves long integers and floats.
  378. value = float(values[0]) if '.' in values[0] else int(values[0])
  379. return Leaf(value)
  380. if option == 1: # rule: IDENTIFIER
  381. return Leaf(values[0])
  382. if 2 <= option <= 4: # rule: LPAREN exp RPAREN | LBRACKET exp RBRACKET
  383. # | LCBRACKET exp RCBRACKET
  384. values[1].parens = pred(values[1]) > TIMES_PRED
  385. return values[1]
  386. if 5 <= option <= 7: # rule: unary | binary | nary
  387. return values[0]
  388. raise BisonSyntaxError('Unsupported option %d in target "%s".'
  389. % (option, target)) # pragma: nocover
  390. def on_unary(self, target, option, names, values):
  391. """
  392. unary : MINUS exp %prec NEG
  393. | FUNCTION exp
  394. | FUNCTION_PAREN exp RPAREN
  395. | raised_function exp %prec FUNCTION
  396. | DERIVATIVE exp
  397. | exp PRIME
  398. | INTEGRAL exp
  399. | integral_bounds exp %prec INTEGRAL
  400. | PIPE exp PIPE
  401. | LOGARITHM exp
  402. | logarithm_subscript exp %prec LOGARITHM
  403. | TIMES exp
  404. """
  405. if option == 0: # rule: MINUS exp
  406. values[1].negated += 1
  407. return values[1]
  408. if option in (1, 2): # rule: FUNCTION exp | FUNCTION_PAREN exp RPAREN
  409. fun = values[0] if option == 1 else values[0][:-1].rstrip()
  410. if values[1].is_op(OP_COMMA):
  411. return Node(fun, *values[1])
  412. return Node(fun, values[1])
  413. if option == 3: # rule: raised_function exp
  414. func, exponent = values[0]
  415. if values[1].is_op(OP_COMMA):
  416. return Node(OP_POW, Node(func, *values[1]), exponent)
  417. return Node(OP_POW, Node(func, values[1]), exponent)
  418. if option == 4: # rule: DERIVATIVE exp
  419. # DERIVATIVE looks like 'd/d*x' -> extract the 'x'
  420. return Node(OP_DXDER, values[1], Leaf(values[0][-1]))
  421. if option == 5: # rule: exp PRIME
  422. return Node(OP_PRIME, values[0])
  423. if option == 6: # rule: INTEGRAL exp
  424. fx, x = find_integration_variable(values[1])
  425. return Node(OP_INT, fx, x)
  426. if option == 7: # rule: integral_bounds exp
  427. lbnd, ubnd = values[0]
  428. fx, x = find_integration_variable(values[1])
  429. return Node(OP_INT, fx, x, lbnd, ubnd)
  430. if option == 8: # rule: PIPE exp PIPE
  431. return Node(OP_ABS, values[1])
  432. if option == 9: # rule: LOGARITHM exp
  433. if values[1].is_op(OP_COMMA):
  434. return Node(OP_LOG, *values[1])
  435. if values[0] == 'ln':
  436. base = E
  437. else:
  438. base = DEFAULT_LOGARITHM_BASE
  439. return Node(OP_LOG, values[1], Leaf(base))
  440. if option == 10: # rule: logarithm_subscript exp
  441. if values[1].is_op(OP_COMMA):
  442. raise BisonSyntaxError('Shortcut logarithm base "log_%s" does '
  443. 'not support additional arguments.' % (values[0]))
  444. return Node(OP_LOG, values[1], values[0])
  445. if option == 11: # rule: TIMES exp
  446. return values[1]
  447. raise BisonSyntaxError('Unsupported option %d in target "%s".'
  448. % (option, target)) # pragma: nocover
  449. def on_raised_function(self, target, option, names, values):
  450. """
  451. raised_function : FUNCTION POW exp
  452. | LOGARITHM POW exp
  453. """
  454. # | logarithm_subscript POW exp
  455. if option in (0, 1): # rule: {FUNCTION,LOGARITHM} POW exp
  456. apply_operator_negation(values[1], values[2])
  457. return values[0], values[2]
  458. def on_logarithm_subscript(self, target, option, names, values):
  459. """
  460. logarithm_subscript : LOGARITHM SUB exp
  461. """
  462. if option == 0: # rule: LOGARITHM SUB exp
  463. apply_operator_negation(values[1], values[2])
  464. return values[2]
  465. def on_integral_bounds(self, target, option, names, values):
  466. """
  467. integral_bounds : INTEGRAL SUB exp
  468. """
  469. if option == 0: # rule: INTEGRAL SUB exp
  470. if values[2].is_op(OP_POW):
  471. lbnd, ubnd = values[2]
  472. lbnd.negated += values[2].negated
  473. else:
  474. lbnd = values[2]
  475. ubnd = Leaf(INFINITY)
  476. apply_operator_negation(values[1], lbnd)
  477. return lbnd, ubnd
  478. def on_binary(self, target, option, names, values):
  479. """
  480. binary : exp TIMES exp
  481. | exp PLUS exp
  482. | exp EQ exp
  483. | exp AND exp
  484. | exp OR exp
  485. | exp DIVIDE exp
  486. | exp MINUS exp
  487. | exp POW exp
  488. | exp SUB exp
  489. """
  490. if option == 0: # rule: exp TIMES exp
  491. first = values[0]
  492. node = Node(values[1], first, values[2])
  493. if first.negated and not first.parens:
  494. node.negated += first.negated
  495. first.negated = 0
  496. return node
  497. if 1 <= option <= 4: # rule: exp {PLUS,EQ,AND,OR} exp
  498. return Node(values[1], values[0], values[2])
  499. if option == 5: # rule: exp DIVIDE exp
  500. top = values[0]
  501. bottom = values[2]
  502. negated = 0
  503. if top.negated and not top.parens:
  504. negated = top.negated
  505. top.negated = 0
  506. if top.is_op(OP_MUL) and bottom.is_op(OP_MUL):
  507. dtop, fx = top
  508. dbot, x = bottom
  509. if dtop.is_identifier('d') and dbot.is_identifier('d') \
  510. and x.is_identifier():
  511. # (d (fx)) / (dx)
  512. return Node(OP_DXDER, fx, x, negated=negated)
  513. return Node(OP_DIV, top, bottom, negated=negated)
  514. if option == 6: # rule: exp MINUS exp
  515. right = values[2]
  516. right.negated += 1
  517. # Explicit call the hook handler on the created unary negation.
  518. self.hook_handler('unary', 0, names, values, right)
  519. return Node(OP_ADD, values[0], right)
  520. if option == 7: # rule: exp POW exp
  521. apply_operator_negation(values[1], values[2])
  522. return Node(OP_POW, values[0], values[2])
  523. if option == 8: # rule: exp SUB exp
  524. bounds = values[2]
  525. if bounds.is_op(OP_POW):
  526. lbnd, ubnd = bounds
  527. lbnd.negated += bounds.negated
  528. else:
  529. lbnd = bounds
  530. ubnd = Leaf(INFINITY)
  531. lbnd.negated += len(values[1]) - 1
  532. return Node(OP_INT_DEF, values[0], lbnd, ubnd)
  533. raise BisonSyntaxError('Unsupported option %d in target "%s".'
  534. % (option, target)) # pragma: nocover
  535. def on_nary(self, target, option, names, values):
  536. """
  537. nary : exp COMMA exp
  538. """
  539. if option == 0: # rule: exp COMMA exp
  540. return Node(*combine(',', OP_COMMA, values[0], values[2]))
  541. raise BisonSyntaxError('Unsupported option %d in target "%s".'
  542. % (option, target)) # pragma: nocover
  543. # -----------------------------------------
  544. # Special tokens and operator tokens
  545. # -----------------------------------------
  546. operators = ''
  547. functions = []
  548. for token in SPECIAL_TOKENS:
  549. if len(token) > 1:
  550. operators += '"%s"%s{ returntoken(IDENTIFIER); }\n' \
  551. % (token, ' ' * (8 - len(token)))
  552. for op_str, op in OP_MAP.iteritems():
  553. if TOKEN_MAP[op] == 'FUNCTION':
  554. functions.append(op_str)
  555. else:
  556. operators += '"%s"%s{ returntoken(%s); }\n' \
  557. % (op_str, ' ' * (8 - len(op_str)), TOKEN_MAP[op])
  558. # Put all functions in a single regex
  559. if functions:
  560. fun_or = '("' + '"|"'.join(functions) + '")'
  561. operators += fun_or + ' { returntoken(FUNCTION); }\n'
  562. operators += fun_or + '[ ]*\( { returntoken(FUNCTION_PAREN); }\n'
  563. # -----------------------------------------
  564. # raw lex script, verbatim here
  565. # -----------------------------------------
  566. lexscript = r"""
  567. %top{
  568. #include "Python.h"
  569. }
  570. %{
  571. #define YYSTYPE void *
  572. #include "tokens.h"
  573. extern void *py_parser;
  574. extern void (*py_input)(PyObject *parser, char *buf, int *result,
  575. int max_size);
  576. #define returntoken(tok) \
  577. yylval = PyString_FromString(strdup(yytext)); return (tok);
  578. #define YY_INPUT(buf,result,max_size) { \
  579. (*py_input)(py_parser, buf, &result, max_size); \
  580. }
  581. int yycolumn = 0;
  582. void reset_flex_buffer(void) {
  583. yycolumn = 0;
  584. yylineno = 0;
  585. YY_FLUSH_BUFFER;
  586. BEGIN(0);
  587. }
  588. #define YY_USER_ACTION \
  589. yylloc.first_line = yylloc.last_line = yylineno; \
  590. yylloc.first_column = yycolumn; \
  591. yylloc.last_column = yycolumn + yyleng; \
  592. yycolumn += yyleng;
  593. %}
  594. %option yylineno
  595. %%
  596. d[ ]*"/"[ ]*"d*"[a-z] { returntoken(DERIVATIVE); }
  597. [0-9]+"."?[0-9]* { returntoken(NUMBER); }
  598. [a-zA-Z] { returntoken(IDENTIFIER); }
  599. "(" { returntoken(LPAREN); }
  600. ")" { returntoken(RPAREN); }
  601. "[" { returntoken(LBRACKET); }
  602. "]" { returntoken(RBRACKET); }
  603. "{" { returntoken(LCBRACKET); }
  604. "}" { returntoken(RCBRACKET); }
  605. "|" { returntoken(PIPE); }
  606. """ + operators + r"""
  607. "raise" { returntoken(RAISE); }
  608. "graph" { returntoken(GRAPH); }
  609. "quit" { yyterminate(); returntoken(QUIT); }
  610. [ \t\v\f] { }
  611. [\n] { yycolumn = 0; returntoken(NEWLINE); }
  612. . { printf("unknown char %c ignored.\n", yytext[0]); }
  613. %%
  614. yywrap() { return(1); }
  615. """
  616. #_-+ { returntoken(SUB); }
  617. #"^"-+ { returntoken(POW); }