parser.py 24 KB

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