parser.py 26 KB

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