Skip to content

Commit 675a80b

Browse files
committed
Fill out location tracking
1 parent 68987f0 commit 675a80b

11 files changed

Lines changed: 210 additions & 45 deletions

File tree

mathics/core/expression.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
)
2020

2121
import sympy
22-
from mathics_scanner.location import SourceRange
22+
from mathics_scanner.location import SourceRange, SourceRange2
2323

2424
from mathics.core.atoms import Integer1, String
2525
from mathics.core.attributes import (
@@ -277,7 +277,7 @@ class Expression(BaseElement, NumericOperators, EvalMixin):
277277
elements_properties: Optional[ElementsProperties]
278278
options: Optional[Dict[str, Any]]
279279
pattern_sequence: bool
280-
location: Optional[Union[SourceRange, MethodType]]
280+
location: Optional[Union[SourceRange, SourceRange2, MethodType]]
281281

282282
def __init__(
283283
self,

mathics/core/parser/ast.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ class Node:
2323
expression's leaves and a non-leaf nodes.
2424
"""
2525

26-
def __init__(self, head, *children):
26+
def __init__(self, head, *children, location=None):
2727
if isinstance(head, Node):
2828
self.head = head
2929
else:
3030
self.head = Symbol(head)
3131
self.value = None
3232
self.children = list(children)
3333
self.parenthesised = False
34+
self.location = location
3435

3536
def get_head_name(self):
3637
if isinstance(self.head, Symbol):
@@ -68,11 +69,12 @@ class Atom(Node):
6869
their own. You can however compare Atoms for equality.
6970
"""
7071

71-
def __init__(self, value):
72+
def __init__(self, value, location=None):
7273
self.head = Symbol(self.__class__.__name__)
7374
self.value = value
7475
self.children = []
7576
self.parenthesised = False
77+
self.location = location
7678

7779
def __repr__(self):
7880
return "%s[%s]" % (self.head, self.value)
@@ -90,7 +92,13 @@ class Number(Atom):
9092
"""
9193

9294
def __init__(
93-
self, value: str, sign: int = 1, base: int = 10, suffix=None, exp: int = 0
95+
self,
96+
value: str,
97+
sign: int = 1,
98+
base: int = 10,
99+
suffix=None,
100+
exp: int = 0,
101+
location=None,
94102
):
95103
assert isinstance(value, str)
96104
assert sign in (-1, 1)
@@ -104,6 +112,7 @@ def __init__(
104112
self.base = base
105113
self.suffix = suffix
106114
self.exp = exp
115+
self.location = location
107116

108117
def __repr__(self):
109118
result = self.value
@@ -132,10 +141,11 @@ class Symbol(Atom):
132141
are unique as they are say in Lisp, or Python.
133142
"""
134143

135-
def __init__(self, value: str, context: Optional[str] = "System"):
144+
def __init__(self, value: str, context: Optional[str] = "System", location=None):
136145
self.context = context
137146
self.value = value
138147
self.children = []
148+
self.location = location
139149

140150
# avoids recursive definition
141151
@property

mathics/core/parser/convert.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,6 @@ def convert(self, node, definitions) -> BaseElement:
194194
self.definitions = definitions
195195
result = self.do_convert(node)
196196
self.definitions = None
197-
self.location = None
198197
return result
199198

200199
def do_convert(self, node):

mathics/core/parser/feed.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,12 @@ def send_messages(self, evaluation) -> list:
2121

2222

2323
class MathicsSingleLineFeeder(SingleLineFeeder, MathicsLineFeeder):
24-
"A feeder that feeds lines from an open ``File`` object"
24+
"A feeder that feeds lines from an open location container object"
2525

2626

2727
class MathicsFileLineFeeder(FileLineFeeder, MathicsLineFeeder):
2828
"A feeder that feeds lines from an open ``File`` object"
2929

3030

3131
class MathicsMultiLineFeeder(MultiLineFeeder, MathicsLineFeeder):
32-
"A feeder that feeds lines from an open ``File`` object"
32+
"A feeder that feeds lines from an open contianer object"

mathics/core/parser/location.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
"""
2+
Provides location tracking in parser.
3+
"""
4+
5+
from typing import Callable, Optional
6+
7+
import mathics_scanner
8+
from mathics_scanner.location import (
9+
EVAL_METHODS,
10+
ContainerKind,
11+
SourceRange,
12+
SourceRange2,
13+
)
14+
15+
from mathics.core.parser.ast import Node
16+
17+
18+
def track_location(func: Callable) -> Callable:
19+
"""Python decorator for a parse method that adds location tracking
20+
on non-leaf-nodes when TRACK_LOCATION is set. Otherwise, we just
21+
run the parse method.
22+
23+
"""
24+
25+
def wrapper(self, *args) -> Optional[Node]:
26+
if not mathics_scanner.location.TRACK_LOCATIONS:
27+
return func(self, *args)
28+
29+
# Save location information
30+
# For expressions which make their way to
31+
# FunctionApplyRule, saving a position here is
32+
# extraneous because the FunctionApplyRule is
33+
# the position. But deal with this redundancy
34+
# after the dust settles, and we have experience
35+
# on what is desired.
36+
start_column = self.tokeniser.pos
37+
start_line = self.feeder.lineno
38+
parsed_node = func(self, *args)
39+
40+
if parsed_node is not None and hasattr(parsed_node, "location"):
41+
if self.feeder.container_kind == ContainerKind.PYTHON:
42+
parsed_node.location = self.feeder.container
43+
if self.feeder.container not in EVAL_METHODS:
44+
EVAL_METHODS.add(parsed_node.location)
45+
else:
46+
end_pos = self.tokeniser.pos
47+
end_line = self.feeder.lineno
48+
if start_line == end_line:
49+
parsed_node.location = SourceRange2(
50+
start_line=start_line,
51+
start_pos=start_column,
52+
end_pos=end_pos,
53+
container=self.feeder.container_index,
54+
)
55+
else:
56+
parsed_node.location = SourceRange(
57+
start_line=start_line,
58+
start_pos=start_column,
59+
end_line=end_line,
60+
end_pos=end_pos,
61+
container=self.feeder.container_index,
62+
)
63+
64+
return parsed_node
65+
66+
return wrapper
67+
68+
69+
def track_token_location(func: Callable) -> Callable:
70+
"""Python decorator for a parse method that adds location
71+
tracking to leaf-nodes, i.e. tokens. This happens though only TRACK_LOCATION is set. Otherwise, we just run the
72+
normal parse method.
73+
74+
"""
75+
76+
def wrapper(self, token) -> Optional[Node]:
77+
if not mathics_scanner.location.TRACK_LOCATIONS:
78+
return func(self, token)
79+
80+
# Save location information
81+
# For expressions which make their way to
82+
# FunctionApplyRule, saving a position here is
83+
# extraneous because the FunctionApplyRule is
84+
# the position. But deal with this redundancy
85+
# after the dust settles, and we have experience
86+
# on what is desired.
87+
start_column = token.pos
88+
start_line = self.feeder.lineno
89+
parsed_node = func(self, token)
90+
91+
if (
92+
self.feeder.container_kind == ContainerKind.PYTHON
93+
and self.feeder.container not in EVAL_METHODS
94+
):
95+
location = self.feeder.container
96+
EVAL_METHODS.add(location)
97+
98+
end_line = self.feeder.lineno
99+
if start_line == end_line:
100+
parsed_node.location = SourceRange2(
101+
start_line=start_line,
102+
start_pos=start_column,
103+
end_pos=start_column + len(token.text) - 1,
104+
container=self.feeder.container_index,
105+
)
106+
else:
107+
parsed_node.location = SourceRange(
108+
start_line=start_line,
109+
start_pos=start_column,
110+
end_line=self.feeder.lineno,
111+
end_pos=start_column + len(token.text) - 1,
112+
container=self.feeder.container_index,
113+
)
114+
return parsed_node
115+
116+
return wrapper

0 commit comments

Comments
 (0)