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

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"""
32from __future__ import annotations
34__all__ = ['Node', 'BinaryOp', 'Identifier', 'IsIn', 'NumericLiteral',
35 'Parens', 'RangeLiteral', 'StringLiteral', 'TimeLiteral',
36 'UnaryOp']
38# -------------------------------
39# Imports of standard modules --
40# -------------------------------
41from abc import ABC, abstractmethod
42from typing import Any, List, Optional, Tuple, TYPE_CHECKING
44# -----------------------------
45# Imports for other modules --
46# -----------------------------
48# ----------------------------------
49# Local non-exported definitions --
50# ----------------------------------
52if TYPE_CHECKING: 52 ↛ 53line 52 didn't jump to line 53, because the condition on line 52 was never true
53 import astropy.time
54 from .treeVisitor import TreeVisitor
56# ------------------------
57# Exported definitions --
58# ------------------------
61class Node(ABC):
62 """Base class of IR node in expression tree.
64 The purpose of this class is to simplify visiting of the
65 all nodes in a tree. It has a list of sub-nodes of this
66 node so that visiting code can navigate whole tree without
67 knowing exact types of each node.
69 Attributes
70 ----------
71 children : tuple of :py:class:`Node`
72 Possibly empty list of sub-nodes.
73 """
74 def __init__(self, children: Tuple[Node, ...] = None):
75 self.children = tuple(children or ())
77 @abstractmethod
78 def visit(self, visitor: TreeVisitor) -> Any:
79 """Implement Visitor pattern for parsed tree.
81 Parameters
82 ----------
83 visitor : `TreeVisitor`
84 Instance of vistor type.
85 """
88class BinaryOp(Node):
89 """Node representing binary operator.
91 This class is used for representing all binary operators including
92 arithmetic and boolean operations.
94 Attributes
95 ----------
96 lhs : Node
97 Left-hand side of the operation
98 rhs : Node
99 Right-hand side of the operation
100 op : str
101 Operator name, e.g. '+', 'OR'
102 """
103 def __init__(self, lhs: Node, op: str, rhs: Node):
104 Node.__init__(self, (lhs, rhs))
105 self.lhs = lhs
106 self.op = op
107 self.rhs = rhs
109 def visit(self, visitor: TreeVisitor) -> Any:
110 # Docstring inherited from Node.visit
111 lhs = self.lhs.visit(visitor)
112 rhs = self.rhs.visit(visitor)
113 return visitor.visitBinaryOp(self.op, lhs, rhs, self)
115 def __str__(self) -> str:
116 return "{lhs} {op} {rhs}".format(**vars(self))
119class UnaryOp(Node):
120 """Node representing unary operator.
122 This class is used for representing all unary operators including
123 arithmetic and boolean operations.
125 Attributes
126 ----------
127 op : str
128 Operator name, e.g. '+', 'NOT'
129 operand : Node
130 Operand.
131 """
132 def __init__(self, op: str, operand: Node):
133 Node.__init__(self, (operand,))
134 self.op = op
135 self.operand = operand
137 def visit(self, visitor: TreeVisitor) -> Any:
138 # Docstring inherited from Node.visit
139 operand = self.operand.visit(visitor)
140 return visitor.visitUnaryOp(self.op, operand, self)
142 def __str__(self) -> str:
143 return "{op} {operand}".format(**vars(self))
146class StringLiteral(Node):
147 """Node representing string literal.
149 Attributes
150 ----------
151 value : str
152 Literal value.
153 """
154 def __init__(self, value: str):
155 Node.__init__(self)
156 self.value = value
158 def visit(self, visitor: TreeVisitor) -> Any:
159 # Docstring inherited from Node.visit
160 return visitor.visitStringLiteral(self.value, self)
162 def __str__(self) -> str:
163 return "'{value}'".format(**vars(self))
166class TimeLiteral(Node):
167 """Node representing time literal.
169 Attributes
170 ----------
171 value : `astropy.time.Time`
172 Literal string value.
173 """
174 def __init__(self, value: astropy.time.Time):
175 Node.__init__(self)
176 self.value = value
178 def visit(self, visitor: TreeVisitor) -> Any:
179 # Docstring inherited from Node.visit
180 return visitor.visitTimeLiteral(self.value, self)
182 def __str__(self) -> str:
183 return "'{value}'".format(**vars(self))
186class NumericLiteral(Node):
187 """Node representing string literal.
189 We do not convert literals to numbers, their text representation
190 is stored literally.
192 Attributes
193 ----------
194 value : str
195 Literal value.
196 """
197 def __init__(self, value: str):
198 Node.__init__(self)
199 self.value = value
201 def visit(self, visitor: TreeVisitor) -> Any:
202 # Docstring inherited from Node.visit
203 return visitor.visitNumericLiteral(self.value, self)
205 def __str__(self) -> str:
206 return "{value}".format(**vars(self))
209class Identifier(Node):
210 """Node representing identifier.
212 Value of the identifier is its name, it may contain zero or one dot
213 character.
215 Attributes
216 ----------
217 name : str
218 Identifier name.
219 """
220 def __init__(self, name: str):
221 Node.__init__(self)
222 self.name = name
224 def visit(self, visitor: TreeVisitor) -> Any:
225 # Docstring inherited from Node.visit
226 return visitor.visitIdentifier(self.name, self)
228 def __str__(self) -> str:
229 return "{name}".format(**vars(self))
232class RangeLiteral(Node):
233 """Node representing range literal appearing in `IN` list.
235 Range literal defines a range of integer numbers with start and
236 end of the range (with inclusive end) and optional stride value
237 (default is 1).
239 Attributes
240 ----------
241 start : `int`
242 Start value of a range.
243 stop : `int`
244 End value of a range, inclusive, same or higher than ``start``.
245 stride : `int` or `None`, optional
246 Stride value, must be positive, can be `None` which means that stride
247 was not specified. Consumers are supposed to treat `None` the same way
248 as stride=1 but for some consumers it may be useful to know that
249 stride was missing from literal.
250 """
251 def __init__(self, start: int, stop: int, stride: Optional[int] = None):
252 self.start = start
253 self.stop = stop
254 self.stride = stride
256 def visit(self, visitor: TreeVisitor) -> Any:
257 # Docstring inherited from Node.visit
258 return visitor.visitRangeLiteral(self.start, self.stop, self.stride, self)
260 def __str__(self) -> str:
261 res = f"{self.start}..{self.stop}" + (f":{self.stride}" if self.stride else "")
262 return res
265class IsIn(Node):
266 """Node representing IN or NOT IN expression.
268 Attributes
269 ----------
270 lhs : Node
271 Left-hand side of the operation
272 values : list of Node
273 List of values on the right side.
274 not_in : bool
275 If `True` then it is NOT IN expression, otherwise it is IN expression.
276 """
277 def __init__(self, lhs: Node, values: List[Node], not_in: bool = False):
278 Node.__init__(self, (lhs,) + tuple(values))
279 self.lhs = lhs
280 self.values = values
281 self.not_in = not_in
283 def visit(self, visitor: TreeVisitor) -> Any:
284 # Docstring inherited from Node.visit
285 lhs = self.lhs.visit(visitor)
286 values = [value.visit(visitor) for value in self.values]
287 return visitor.visitIsIn(lhs, values, self.not_in, self)
289 def __str__(self) -> str:
290 values = ", ".join(str(x) for x in self.values)
291 not_in = ""
292 if self.not_in:
293 not_in = "NOT "
294 return "{lhs} {not_in}IN ({values})".format(lhs=self.lhs,
295 not_in=not_in,
296 values=values)
299class Parens(Node):
300 """Node representing parenthesized expression.
302 Attributes
303 ----------
304 expr : Node
305 Expression inside parentheses.
306 """
307 def __init__(self, expr: Node):
308 Node.__init__(self, (expr,))
309 self.expr = expr
311 def visit(self, visitor: TreeVisitor) -> Any:
312 # Docstring inherited from Node.visit
313 expr = self.expr.visit(visitor)
314 return visitor.visitParens(expr, self)
316 def __str__(self) -> str:
317 return "({expr})".format(**vars(self))