Hide keyboard shortcuts

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/>. 

21 

22"""Module which defines classes for intermediate representation of the 

23expression tree produced by parser. 

24 

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""" 

31 

32from __future__ import annotations 

33 

34__all__ = ['Node', 'BinaryOp', 'Identifier', 'IsIn', 'NumericLiteral', 

35 'Parens', 'RangeLiteral', 'StringLiteral', 'TimeLiteral', 

36 'UnaryOp'] 

37 

38# ------------------------------- 

39# Imports of standard modules -- 

40# ------------------------------- 

41from abc import ABC, abstractmethod 

42from typing import Any, List, Optional, Tuple, TYPE_CHECKING 

43 

44# ----------------------------- 

45# Imports for other modules -- 

46# ----------------------------- 

47 

48# ---------------------------------- 

49# Local non-exported definitions -- 

50# ---------------------------------- 

51 

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 

55 

56# ------------------------ 

57# Exported definitions -- 

58# ------------------------ 

59 

60 

61class Node(ABC): 

62 """Base class of IR node in expression tree. 

63 

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. 

68 

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 ()) 

76 

77 @abstractmethod 

78 def visit(self, visitor: TreeVisitor) -> Any: 

79 """Implement Visitor pattern for parsed tree. 

80 

81 Parameters 

82 ---------- 

83 visitor : `TreeVisitor` 

84 Instance of vistor type. 

85 """ 

86 

87 

88class BinaryOp(Node): 

89 """Node representing binary operator. 

90 

91 This class is used for representing all binary operators including 

92 arithmetic and boolean operations. 

93 

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 

108 

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) 

114 

115 def __str__(self) -> str: 

116 return "{lhs} {op} {rhs}".format(**vars(self)) 

117 

118 

119class UnaryOp(Node): 

120 """Node representing unary operator. 

121 

122 This class is used for representing all unary operators including 

123 arithmetic and boolean operations. 

124 

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 

136 

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) 

141 

142 def __str__(self) -> str: 

143 return "{op} {operand}".format(**vars(self)) 

144 

145 

146class StringLiteral(Node): 

147 """Node representing string literal. 

148 

149 Attributes 

150 ---------- 

151 value : str 

152 Literal value. 

153 """ 

154 def __init__(self, value: str): 

155 Node.__init__(self) 

156 self.value = value 

157 

158 def visit(self, visitor: TreeVisitor) -> Any: 

159 # Docstring inherited from Node.visit 

160 return visitor.visitStringLiteral(self.value, self) 

161 

162 def __str__(self) -> str: 

163 return "'{value}'".format(**vars(self)) 

164 

165 

166class TimeLiteral(Node): 

167 """Node representing time literal. 

168 

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 

177 

178 def visit(self, visitor: TreeVisitor) -> Any: 

179 # Docstring inherited from Node.visit 

180 return visitor.visitTimeLiteral(self.value, self) 

181 

182 def __str__(self) -> str: 

183 return "'{value}'".format(**vars(self)) 

184 

185 

186class NumericLiteral(Node): 

187 """Node representing string literal. 

188 

189 We do not convert literals to numbers, their text representation 

190 is stored literally. 

191 

192 Attributes 

193 ---------- 

194 value : str 

195 Literal value. 

196 """ 

197 def __init__(self, value: str): 

198 Node.__init__(self) 

199 self.value = value 

200 

201 def visit(self, visitor: TreeVisitor) -> Any: 

202 # Docstring inherited from Node.visit 

203 return visitor.visitNumericLiteral(self.value, self) 

204 

205 def __str__(self) -> str: 

206 return "{value}".format(**vars(self)) 

207 

208 

209class Identifier(Node): 

210 """Node representing identifier. 

211 

212 Value of the identifier is its name, it may contain zero or one dot 

213 character. 

214 

215 Attributes 

216 ---------- 

217 name : str 

218 Identifier name. 

219 """ 

220 def __init__(self, name: str): 

221 Node.__init__(self) 

222 self.name = name 

223 

224 def visit(self, visitor: TreeVisitor) -> Any: 

225 # Docstring inherited from Node.visit 

226 return visitor.visitIdentifier(self.name, self) 

227 

228 def __str__(self) -> str: 

229 return "{name}".format(**vars(self)) 

230 

231 

232class RangeLiteral(Node): 

233 """Node representing range literal appearing in `IN` list. 

234 

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). 

238 

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 

255 

256 def visit(self, visitor: TreeVisitor) -> Any: 

257 # Docstring inherited from Node.visit 

258 return visitor.visitRangeLiteral(self.start, self.stop, self.stride, self) 

259 

260 def __str__(self) -> str: 

261 res = f"{self.start}..{self.stop}" + (f":{self.stride}" if self.stride else "") 

262 return res 

263 

264 

265class IsIn(Node): 

266 """Node representing IN or NOT IN expression. 

267 

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 

282 

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) 

288 

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) 

297 

298 

299class Parens(Node): 

300 """Node representing parenthesized expression. 

301 

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 

310 

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) 

315 

316 def __str__(self) -> str: 

317 return "({expr})".format(**vars(self))