Skip to content

Commit e778cf2

Browse files
committed
(WIP) add node visitors
1 parent 3893e8c commit e778cf2

File tree

2 files changed

+252
-1
lines changed

2 files changed

+252
-1
lines changed

google/cloud/bigquery/ipython_magics/line_arg_parser/__init__.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@
1616
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import Lexer
1717
from google.cloud.bigquery.ipython_magics.line_arg_parser.lexer import TokenType
1818
from google.cloud.bigquery.ipython_magics.line_arg_parser.parser import Parser
19+
from google.cloud.bigquery.ipython_magics.line_arg_parser.visitors import (
20+
QueryParamsExtractor,
21+
TreePrinter,
22+
)
1923

2024

21-
__all__ = ("Lexer", "ParseError", "Parser", "TokenType")
25+
__all__ = (
26+
"Lexer",
27+
"ParseError",
28+
"Parser",
29+
"QueryParamsExtractor",
30+
"TokenType",
31+
"TreePrinter",
32+
)
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
# Copyright 2020 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
from __future__ import print_function
16+
17+
18+
class NodeVisitor(object):
19+
"""Base visitor class implementing the dispatch machinery."""
20+
21+
def visit(self, node):
22+
method_name = "visit_{}".format(type(node).__name__)
23+
visitor_method = getattr(self, method_name, self.method_missing)
24+
return visitor_method(node)
25+
26+
def method_missing(self, node):
27+
raise Exception("No visit_{} method".format(type(node).__name__))
28+
29+
30+
class TreePrinter(NodeVisitor):
31+
"""Print the values of the parsed cell magic arguments.
32+
33+
Primarily useful for debugging.
34+
"""
35+
36+
def __init__(self):
37+
self._indent = 0 # the current print indentation
38+
self.INDENT_SIZE = 4
39+
40+
def visit_InputLine(self, node):
41+
print("Input:")
42+
self._indent += self.INDENT_SIZE
43+
44+
self.visit(node.destination_var)
45+
self.visit(node.option_list)
46+
47+
self._indent -= self.INDENT_SIZE
48+
49+
def visit_DestinationVar(self, node):
50+
print(" " * self._indent, end="")
51+
52+
var_name = node.name if node.name is not None else "<NOT_GIVEN>"
53+
print("destination_var: ", var_name)
54+
55+
def visit_CmdOptionList(self, node):
56+
print(" " * self._indent, end="")
57+
print("Command options:")
58+
59+
self._indent += self.INDENT_SIZE
60+
61+
if not node.options:
62+
print(" " * self._indent, "<NONE_GIVEN>", end="")
63+
else:
64+
for opt in node.options:
65+
self.visit(opt)
66+
print()
67+
68+
self._indent -= self.INDENT_SIZE
69+
70+
def visit_CmdOption(self, node):
71+
print(" " * self._indent, end="")
72+
print("--", node.name, " ", sep="", end="")
73+
74+
if node.value is not None:
75+
self.visit(node.value)
76+
77+
def visit_CmdOptionValue(self, node):
78+
print(node.value, end="")
79+
80+
def visit_ParamsOption(self, node):
81+
print(" " * self._indent, end="")
82+
print("--", node.name, " ", sep="", end="")
83+
84+
self.visit(node.value)
85+
86+
def visit_PyVarExpansion(self, node):
87+
print(node.raw_value, end="")
88+
89+
def visit_PyDict(self, node):
90+
print("{", end="")
91+
92+
for i, item in enumerate(node.items):
93+
if i > 0:
94+
print(", ", end="")
95+
self.visit(item)
96+
97+
print("}", end="")
98+
99+
def visit_PyDictItem(self, node):
100+
self.visit(node.key)
101+
print(": ", end="")
102+
self.visit(node.value)
103+
104+
def visit_PyDictKey(self, node):
105+
print(node.key_value, end="")
106+
107+
def visit_PyScalarValue(self, node):
108+
print(node.raw_value, end="")
109+
110+
def visit_PyTuple(self, node):
111+
print("(", end="")
112+
113+
for i, item in enumerate(node.items):
114+
if i > 0:
115+
print(", ", end="")
116+
self.visit(item)
117+
118+
print(")", end="")
119+
120+
def visit_PyList(self, node):
121+
print("[", end="")
122+
123+
for i, item in enumerate(node.items):
124+
if i > 0:
125+
print(", ", end="")
126+
self.visit(item)
127+
128+
print("]", end="")
129+
130+
131+
class QueryParamsExtractor(NodeVisitor):
132+
"""A visitor that extracts the "--params <...>" part from input line arguments.
133+
"""
134+
135+
def visit_InputLine(self, node):
136+
params_dict_parts = []
137+
other_parts = []
138+
139+
dest_var_parts = self.visit(node.destination_var)
140+
params, other_options = self.visit(node.option_list)
141+
142+
if dest_var_parts:
143+
other_parts.extend(dest_var_parts)
144+
145+
if dest_var_parts and other_options:
146+
other_parts.append(" ")
147+
other_parts.extend(other_options)
148+
149+
params_dict_parts.extend(params)
150+
151+
return "".join(params_dict_parts), "".join(other_parts)
152+
153+
def visit_DestinationVar(self, node):
154+
return [node.name] if node.name is not None else []
155+
156+
def visit_CmdOptionList(self, node):
157+
params_opt_parts = []
158+
other_parts = []
159+
160+
# TODO: iterate directly and flatten the result?
161+
# TODO: more genrally, use generators and only combine the result at end?
162+
163+
for i, opt in enumerate(node.options):
164+
option_parts = self.visit(opt)
165+
list_to_extend = params_opt_parts if opt.name == "params" else other_parts
166+
167+
if list_to_extend:
168+
list_to_extend.append(" ")
169+
list_to_extend.extend(option_parts)
170+
171+
return params_opt_parts, other_parts
172+
173+
def visit_CmdOption(self, node):
174+
result = ["--{}".format(node.name)]
175+
176+
if node.value is not None:
177+
result.append(" ")
178+
value_parts = self.visit(node.value)
179+
result.extend(value_parts)
180+
181+
return result
182+
183+
def visit_CmdOptionValue(self, node):
184+
return [node.value]
185+
186+
def visit_ParamsOption(self, node):
187+
value_parts = self.visit(node.value)
188+
return value_parts
189+
190+
def visit_PyVarExpansion(self, node):
191+
return [node.raw_value]
192+
193+
def visit_PyDict(self, node):
194+
result = ["{"]
195+
196+
for i, item in enumerate(node.items):
197+
if i > 0:
198+
result.append(", ")
199+
item_parts = self.visit(item)
200+
result.extend(item_parts)
201+
202+
result.append("}")
203+
return result
204+
205+
def visit_PyDictItem(self, node):
206+
result = self.visit(node.key) # key parts
207+
result.append(": ")
208+
value_parts = self.visit(node.value)
209+
result.extend(value_parts)
210+
return result
211+
212+
def visit_PyDictKey(self, node):
213+
return [node.key_value] # TODO: does this contain quotes? it should...
214+
215+
def visit_PyScalarValue(self, node):
216+
return [node.raw_value]
217+
218+
def visit_PyTuple(self, node):
219+
result = ["("]
220+
221+
for i, item in enumerate(node.items):
222+
if i > 0:
223+
result.append(", ")
224+
item_parts = self.visit(item)
225+
result.extend(item_parts)
226+
227+
result.append(")")
228+
return result
229+
230+
def visit_PyList(self, node):
231+
result = ["["]
232+
233+
for i, item in enumerate(node.items):
234+
if i > 0:
235+
result.append(", ")
236+
item_parts = self.visit(item)
237+
result.extend(item_parts)
238+
239+
result.append("]")
240+
return result

0 commit comments

Comments
 (0)