semantics.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326
  1. # Copyright (c) 2017 by Rocky Bernstein
  2. from __future__ import print_function
  3. from gdbloc.parser import (
  4. parse_bp_location, parse_range, parse_arange
  5. )
  6. from gdbloc.parser import LocationError as PLocationError
  7. from gdbloc.scanner import ScannerError
  8. from spark_parser import GenericASTTraversal # , DEFAULT_DEBUG as PARSER_DEFAULT_DEBUG
  9. from collections import namedtuple
  10. Location = namedtuple("Location", "path line_number is_address method")
  11. BPLocation = namedtuple("BPLocation", "location condition")
  12. ListRange = namedtuple("ListRange", "first last")
  13. class LocationError(Exception):
  14. def __init__(self, errmsg):
  15. self.errmsg = errmsg
  16. def __str__(self):
  17. return self.errmsg
  18. class RangeError(Exception):
  19. def __init__(self, errmsg):
  20. self.errmsg = errmsg
  21. def __str__(self):
  22. return self.errmsg
  23. class LocationGrok(GenericASTTraversal, object):
  24. def __init__(self, text):
  25. GenericASTTraversal.__init__(self, None)
  26. self.text = text
  27. self.result = None
  28. return
  29. def n_addr_location(self, node):
  30. # addr_location ::= location
  31. # addr_location ::= ADDRESS
  32. path, line_number, method = None, None, None
  33. if node[0] == 'ADDRESS':
  34. assert node[0].value[0] == '*'
  35. line_number = int(node[0].value[1:])
  36. self.result = Location(path, line_number, True, method)
  37. node.location = Location(path, line_number, True, method)
  38. self.prune()
  39. else:
  40. assert node[0] == 'location'
  41. self.preorder(node[0])
  42. node.location = node[0].location
  43. def n_location(self, node):
  44. path, line_number, method = None, None, None
  45. if node[0] == 'FILENAME':
  46. path = node[0].value
  47. # If there is a line number, it is the last token of a location
  48. if len(node) > 1 and node[-1] == 'NUMBER':
  49. line_number = node[-1].value
  50. elif node[0] == 'FUNCNAME':
  51. method = node[0].value[:-2]
  52. elif node[0] == 'NUMBER':
  53. line_number = node[0].value
  54. else:
  55. assert True, "n_location: Something's is wrong; node[0] is %s" % node[0]
  56. self.result = Location(path, line_number, False, method)
  57. node.location = Location(path, line_number, False, method)
  58. self.prune()
  59. def n_NUMBER(self, node):
  60. self.result = Location(None, node.value, False, None)
  61. def n_FUNCNAME(self, node):
  62. self.result = Location(None, None, False, node.value[:-2])
  63. def n_location_if(self, node):
  64. location = None
  65. if node[0] == 'location':
  66. self.preorder(node[0])
  67. location = node[0].location
  68. if len(node) == 1:
  69. return
  70. if node[1] == 'IF':
  71. if_node = node[1]
  72. elif node[2] == 'IF':
  73. if_node = node[2]
  74. elif node[3] == 'IF':
  75. if_node = node[3]
  76. else:
  77. assert False, 'location_if: Something is wrong; cannot find "if"'
  78. condition = self.text[if_node.offset:]
  79. # Pick out condition from string and location inside "IF" token
  80. self.result = BPLocation(location, condition)
  81. self.prune()
  82. # FIXME: DRY with range
  83. def n_arange(self, arange_node):
  84. if arange_node[0] == 'range':
  85. arange_node = arange_node[0]
  86. length = len(arange_node)
  87. if 1 <= length <= 2:
  88. # arange ::= location
  89. # arange ::= DIRECTION
  90. # arange ::= FUNCNAME
  91. # arange ::= NUMBER
  92. # arange ::= OFFSET
  93. # arange ::= ADDRESS
  94. last_node = arange_node[-1]
  95. if last_node == 'location':
  96. self.preorder(arange_node[-1])
  97. self.result = ListRange(last_node.location, None)
  98. elif last_node == 'FUNCNAME':
  99. self.result = ListRange(Location(None, None, False, last_node.value[:-2]),
  100. None)
  101. elif last_node in ('NUMBER', 'OFFSET', 'ADDRESS'):
  102. if last_node == 'ADDRESS':
  103. assert last_node.value[0] == '*'
  104. is_address = True
  105. value = int(last_node.value[1:])
  106. else:
  107. is_address = False
  108. value = last_node.value
  109. self.result = ListRange(Location(None, value, is_address, None),
  110. None)
  111. else:
  112. assert last_node == 'DIRECTION'
  113. self.result = ListRange(None, last_node.value)
  114. pass
  115. self.prune()
  116. elif length == 3:
  117. # arange ::= COMMA opt_space location
  118. # arange ::= location opt_space COMMA
  119. if arange_node[0] == 'COMMA':
  120. assert arange_node[-1] == 'location'
  121. self.preorder(arange_node[-1])
  122. self.result = ListRange(None, self.result)
  123. self.prune()
  124. else:
  125. assert arange_node[-1] == 'COMMA'
  126. assert arange_node[0] == 'location'
  127. self.preorder(arange_node[0])
  128. self.result = ListRange(arange_node[0].location, None)
  129. self.prune()
  130. pass
  131. elif length == 5:
  132. # arrange ::= location opt_space COMMA opt_space {NUMBER | OFFSET | ADDRESS}
  133. assert arange_node[2] == 'COMMA'
  134. assert arange_node[-1] in ('NUMBER', 'OFFSET', 'ADDRESS')
  135. self.preorder(arange_node[0])
  136. self.result = ListRange(arange_node[0].location, arange_node[-1].value)
  137. self.prune()
  138. else:
  139. raise RangeError("Something is wrong")
  140. return
  141. def n_range(self, range_node):
  142. length = len(range_node)
  143. if 1 <= length <= 2:
  144. # range ::= location
  145. # range ::= DIRECTION
  146. # range ::= FUNCNAME
  147. # range ::= NUMBER
  148. # range ::= OFFSET
  149. last_node = range_node[-1]
  150. if last_node == 'location':
  151. self.preorder(range_node[-1])
  152. self.result = ListRange(last_node.location, None)
  153. elif last_node == 'FUNCNAME':
  154. self.result = ListRange(Location(None, None, False, last_node.value[:-2]),
  155. None)
  156. elif last_node in ('NUMBER', 'OFFSET'):
  157. self.result = ListRange(Location(None, last_node.value, False, None),
  158. None)
  159. else:
  160. assert last_node == 'DIRECTION'
  161. self.result = ListRange(None, last_node.value)
  162. pass
  163. self.prune()
  164. elif length == 3:
  165. # range ::= COMMA opt_space location
  166. # range ::= location opt_space COMMA
  167. if range_node[0] == 'COMMA':
  168. assert range_node[-1] == 'location'
  169. self.preorder(range_node[-1])
  170. self.result = ListRange(None, self.result)
  171. self.prune()
  172. else:
  173. assert range_node[-1] == 'COMMA'
  174. assert range_node[0] == 'location'
  175. self.preorder(range_node[0])
  176. self.result = ListRange(range_node[0].location, None)
  177. self.prune()
  178. pass
  179. elif length == 5:
  180. # range ::= location opt_space COMMA opt_space {NUMBER|OFFSET}
  181. assert range_node[2] == 'COMMA'
  182. assert range_node[-1] in ('NUMBER', 'OFFSET')
  183. self.preorder(range_node[0])
  184. self.result = ListRange(range_node[0].location, range_node[-1].value)
  185. self.prune()
  186. else:
  187. raise RangeError("Something is wrong")
  188. return
  189. def default(self, node):
  190. if node not in frozenset(("""opt_space tokens token bp_start range_start arange_start
  191. IF FILENAME COLON COMMA SPACE DIRECTION""".split())):
  192. assert False, ("Something's wrong: you missed a rule for %s" % node.kind)
  193. def traverse(self, node, ):
  194. return self.preorder(node)
  195. def build_bp_expr(string, show_tokens=False, show_ast=False, show_grammar=False):
  196. parser_debug = {'rules': False, 'transition': False,
  197. 'reduce': show_grammar,
  198. 'errorstack': True, 'context': True, 'dups': True }
  199. parsed = parse_bp_location(string, show_tokens=show_tokens,
  200. parser_debug=parser_debug)
  201. assert parsed == 'bp_start'
  202. if show_ast:
  203. print(parsed)
  204. walker = LocationGrok(string)
  205. walker.traverse(parsed)
  206. bp_expr = walker.result
  207. if isinstance(bp_expr, Location):
  208. bp_expr = BPLocation(bp_expr, None)
  209. location = bp_expr.location
  210. assert location.line_number is not None or location.method
  211. return bp_expr
  212. def build_range(string, show_tokens=True, show_ast=False, show_grammar=True):
  213. parser_debug = {'rules': False, 'transition': False,
  214. 'reduce': show_grammar,
  215. 'errorstack': True, 'context': True, 'dups': True }
  216. parsed = parse_range(string, show_tokens=show_tokens,
  217. parser_debug=parser_debug)
  218. if show_ast:
  219. print(parsed)
  220. assert parsed == 'range_start'
  221. walker = LocationGrok(string)
  222. walker.traverse(parsed)
  223. list_range = walker.result
  224. return list_range
  225. # FIXME: DRY with build_range
  226. def build_arange(string, show_tokens=False, show_ast=False, show_grammar=False):
  227. parser_debug = {'rules': False, 'transition': False,
  228. 'reduce': show_grammar,
  229. 'errorstack': None,
  230. 'context': True, 'dups': True
  231. }
  232. parsed = parse_arange(string, show_tokens=show_tokens,
  233. parser_debug=parser_debug)
  234. if show_ast:
  235. print(parsed)
  236. assert parsed == 'arange_start'
  237. walker = LocationGrok(string)
  238. walker.traverse(parsed)
  239. list_range = walker.result
  240. return list_range
  241. if __name__ == '__main__':
  242. # FIXME: make sure the below is in a test
  243. def doit(fn, line):
  244. print("=" * 30)
  245. print(line)
  246. print("+" * 30)
  247. try:
  248. result = fn(line)
  249. print(result)
  250. except ScannerError as e:
  251. print("Scanner error")
  252. print(e.text)
  253. print(e.text_cursor)
  254. except PLocationError as e:
  255. print("Parser error at or near")
  256. print(e.text)
  257. print(e.text_cursor)
  258. # lines = """
  259. # /tmp/foo.py:12
  260. # /tmp/foo.py line 12
  261. # 12
  262. # ../foo.py:5
  263. # gcd()
  264. # foo.py line 5 if x > 1
  265. # """.splitlines()
  266. # for line in lines:
  267. # if not line.strip():
  268. # continue
  269. # print("=" * 30)
  270. # print(line)
  271. # print("+" * 30)
  272. # bp_expr = build_bp_expr(line)
  273. # print(bp_expr)
  274. # lines = (
  275. # "/tmp/foo.py:12 , 5",
  276. # "-"
  277. # "+"
  278. # "../foo.py:5",
  279. # "../foo.py:5 ",
  280. # ", 5",
  281. # "6 , +2",
  282. # ", /foo.py:5",
  283. # )
  284. lines = (
  285. "*10",
  286. "/tmp/foo.py:1, *5",
  287. "../foo.py:0, +5",
  288. "../foo.py:5, *30",
  289. "*0, *10",
  290. ", /foo.py:5",
  291. )
  292. for line in lines:
  293. line = line.strip()
  294. if not line:
  295. continue
  296. doit(build_arange, line)