Coverage for python/lsst/daf/butler/registry/queries/exprParser/exprTree.py : 40%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1# This file is part of daf_butler.
2#
3# Developed for the LSST Data Management System.
4# This product includes software developed by the LSST Project
5# (https://www.lsst.org).
6# See the COPYRIGHT file at the top-level directory of this distribution
7# for details of code ownership.
8#
9# This program is free software: you can redistribute it and/or modify
10# it under the terms of the GNU General Public License as published by
11# the Free Software Foundation, either version 3 of the License, or
12# (at your option) any later version.
13#
14# This program is distributed in the hope that it will be useful,
15# but WITHOUT ANY WARRANTY; without even the implied warranty of
16# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17# GNU General Public License for more details.
18#
19# You should have received a copy of the GNU General Public License
20# along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""Module which defines classes for intermediate representation of the
23expression tree produced by parser.
25The purpose of the intermediate representation is to be able to generate
26same expression as a part of SQL statement with the minimal changes. We
27will need to be able to replace identifiers in original expression with
28database-specific identifiers but everything else will probably be sent
29to database directly.
30"""
32__all__ = ['Node', 'BinaryOp', 'Identifier', 'IsIn', 'NumericLiteral',
33 'Parens', 'StringLiteral', 'UnaryOp']
35# -------------------------------
36# Imports of standard modules --
37# -------------------------------
38from abc import ABC, abstractmethod
40# -----------------------------
41# Imports for other modules --
42# -----------------------------
44# ----------------------------------
45# Local non-exported definitions --
46# ----------------------------------
48# ------------------------
49# Exported definitions --
50# ------------------------
53class Node(ABC):
54 """Base class of IR node in expression tree.
56 The purpose of this class is to simplify visiting of the
57 all nodes in a tree. It has a list of sub-nodes of this
58 node so that visiting code can navigate whole tree without
59 knowing exact types of each node.
61 Attributes
62 ----------
63 children : tuple of :py:class:`Node`
64 Possibly empty list of sub-nodes.
65 """
66 def __init__(self, children=None):
67 self.children = tuple(children or ())
69 @abstractmethod
70 def visit(self, visitor):
71 """Implement Visitor pattern for parsed tree.
73 Parameters
74 ----------
75 visitor : `TreeVisitor`
76 Instance of vistor type.
77 """
80class BinaryOp(Node):
81 """Node representing binary operator.
83 This class is used for representing all binary operators including
84 arithmetic and boolean operations.
86 Attributes
87 ----------
88 lhs : Node
89 Left-hand side of the operation
90 rhs : Node
91 Right-hand side of the operation
92 op : str
93 Operator name, e.g. '+', 'OR'
94 """
95 def __init__(self, lhs, op, rhs):
96 Node.__init__(self, (lhs, rhs))
97 self.lhs = lhs
98 self.op = op
99 self.rhs = rhs
101 def visit(self, visitor):
102 # Docstring inherited from Node.visit
103 lhs = self.lhs.visit(visitor)
104 rhs = self.rhs.visit(visitor)
105 return visitor.visitBinaryOp(self.op, lhs, rhs, self)
107 def __str__(self):
108 return "{lhs} {op} {rhs}".format(**vars(self))
111class UnaryOp(Node):
112 """Node representing unary operator.
114 This class is used for representing all unary operators including
115 arithmetic and boolean operations.
117 Attributes
118 ----------
119 op : str
120 Operator name, e.g. '+', 'NOT'
121 operand : Node
122 Operand.
123 """
124 def __init__(self, op, operand):
125 Node.__init__(self, (operand,))
126 self.op = op
127 self.operand = operand
129 def visit(self, visitor):
130 # Docstring inherited from Node.visit
131 operand = self.operand.visit(visitor)
132 return visitor.visitUnaryOp(self.op, operand, self)
134 def __str__(self):
135 return "{op} {operand}".format(**vars(self))
138class StringLiteral(Node):
139 """Node representing string literal.
141 Attributes
142 ----------
143 value : str
144 Literal value.
145 """
146 def __init__(self, value):
147 Node.__init__(self)
148 self.value = value
150 def visit(self, visitor):
151 # Docstring inherited from Node.visit
152 return visitor.visitStringLiteral(self.value, self)
154 def __str__(self):
155 return "'{value}'".format(**vars(self))
158class NumericLiteral(Node):
159 """Node representing string literal.
161 We do not convert literals to numbers, their text representation
162 is stored literally.
164 Attributes
165 ----------
166 value : str
167 Literal value.
168 """
169 def __init__(self, value):
170 Node.__init__(self)
171 self.value = value
173 def visit(self, visitor):
174 # Docstring inherited from Node.visit
175 return visitor.visitNumericLiteral(self.value, self)
177 def __str__(self):
178 return "{value}".format(**vars(self))
181class Identifier(Node):
182 """Node representing identifier.
184 Value of the identifier is its name, it may contain zero or one dot
185 character.
187 Attributes
188 ----------
189 name : str
190 Identifier name.
191 """
192 def __init__(self, name):
193 Node.__init__(self)
194 self.name = name
196 def visit(self, visitor):
197 # Docstring inherited from Node.visit
198 return visitor.visitIdentifier(self.name, self)
200 def __str__(self):
201 return "{name}".format(**vars(self))
204class RangeLiteral(Node):
205 """Node representing range literal appearing in `IN` list.
207 Range literal defines a range of integer numbers with start and
208 end of the range (with inclusive end) and optional stride value
209 (default is 1).
211 Attributes
212 ----------
213 start : `int`
214 Start value of a range.
215 stop : `int`
216 End value of a range, inclusive, same or higher than ``start``.
217 stride : `int` or `None`, optional
218 Stride value, must be positive, can be `None` which means that stride
219 was not specified. Consumers are supposed to treat `None` the same way
220 as stride=1 but for some consumers it may be useful to know that
221 stride was missing from literal.
222 """
223 def __init__(self, start, stop, stride=None):
224 self.start = start
225 self.stop = stop
226 self.stride = stride
228 def visit(self, visitor):
229 # Docstring inherited from Node.visit
230 return visitor.visitRangeLiteral(self.start, self.stop, self.stride, self)
232 def __str__(self):
233 res = f"{self.start}..{self.stop}" + (f":{self.stride}" if self.stride else "")
234 return res
237class IsIn(Node):
238 """Node representing IN or NOT IN expression.
240 Attributes
241 ----------
242 lhs : Node
243 Left-hand side of the operation
244 values : list of Node
245 List of values on the right side.
246 not_in : bool
247 If `True` then it is NOT IN expression, otherwise it is IN expression.
248 """
249 def __init__(self, lhs, values, not_in=False):
250 Node.__init__(self, (lhs,) + tuple(values))
251 self.lhs = lhs
252 self.values = values
253 self.not_in = not_in
255 def visit(self, visitor):
256 # Docstring inherited from Node.visit
257 lhs = self.lhs.visit(visitor)
258 values = [value.visit(visitor) for value in self.values]
259 return visitor.visitIsIn(lhs, values, self.not_in, self)
261 def __str__(self):
262 values = ", ".join(str(x) for x in self.values)
263 not_in = ""
264 if self.not_in:
265 not_in = "NOT "
266 return "{lhs} {not_in}IN ({values})".format(lhs=self.lhs,
267 not_in=not_in,
268 values=values)
271class Parens(Node):
272 """Node representing parenthesized expression.
274 Attributes
275 ----------
276 expr : Node
277 Expression inside parentheses.
278 """
279 def __init__(self, expr):
280 Node.__init__(self, (expr,))
281 self.expr = expr
283 def visit(self, visitor):
284 # Docstring inherited from Node.visit
285 expr = self.expr.visit(visitor)
286 return visitor.visitParens(expr, self)
288 def __str__(self):
289 return "({expr})".format(**vars(self))