parser.py 25 KB

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