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 

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

33 'Parens', 'RangeLiteral', 'StringLiteral', 'TimeLiteral', 

34 'UnaryOp'] 

35 

36# ------------------------------- 

37# Imports of standard modules -- 

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

39from abc import ABC, abstractmethod 

40 

41# ----------------------------- 

42# Imports for other modules -- 

43# ----------------------------- 

44 

45# ---------------------------------- 

46# Local non-exported definitions -- 

47# ---------------------------------- 

48 

49# ------------------------ 

50# Exported definitions -- 

51# ------------------------ 

52 

53 

54class Node(ABC): 

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

56 

57 The purpose of this class is to simplify visiting of the 

58 all nodes in a tree. It has a list of sub-nodes of this 

59 node so that visiting code can navigate whole tree without 

60 knowing exact types of each node. 

61 

62 Attributes 

63 ---------- 

64 children : tuple of :py:class:`Node` 

65 Possibly empty list of sub-nodes. 

66 """ 

67 def __init__(self, children=None): 

68 self.children = tuple(children or ()) 

69 

70 @abstractmethod 

71 def visit(self, visitor): 

72 """Implement Visitor pattern for parsed tree. 

73 

74 Parameters 

75 ---------- 

76 visitor : `TreeVisitor` 

77 Instance of vistor type. 

78 """ 

79 

80 

81class BinaryOp(Node): 

82 """Node representing binary operator. 

83 

84 This class is used for representing all binary operators including 

85 arithmetic and boolean operations. 

86 

87 Attributes 

88 ---------- 

89 lhs : Node 

90 Left-hand side of the operation 

91 rhs : Node 

92 Right-hand side of the operation 

93 op : str 

94 Operator name, e.g. '+', 'OR' 

95 """ 

96 def __init__(self, lhs, op, rhs): 

97 Node.__init__(self, (lhs, rhs)) 

98 self.lhs = lhs 

99 self.op = op 

100 self.rhs = rhs 

101 

102 def visit(self, visitor): 

103 # Docstring inherited from Node.visit 

104 lhs = self.lhs.visit(visitor) 

105 rhs = self.rhs.visit(visitor) 

106 return visitor.visitBinaryOp(self.op, lhs, rhs, self) 

107 

108 def __str__(self): 

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

110 

111 

112class UnaryOp(Node): 

113 """Node representing unary operator. 

114 

115 This class is used for representing all unary operators including 

116 arithmetic and boolean operations. 

117 

118 Attributes 

119 ---------- 

120 op : str 

121 Operator name, e.g. '+', 'NOT' 

122 operand : Node 

123 Operand. 

124 """ 

125 def __init__(self, op, operand): 

126 Node.__init__(self, (operand,)) 

127 self.op = op 

128 self.operand = operand 

129 

130 def visit(self, visitor): 

131 # Docstring inherited from Node.visit 

132 operand = self.operand.visit(visitor) 

133 return visitor.visitUnaryOp(self.op, operand, self) 

134 

135 def __str__(self): 

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

137 

138 

139class StringLiteral(Node): 

140 """Node representing string literal. 

141 

142 Attributes 

143 ---------- 

144 value : str 

145 Literal value. 

146 """ 

147 def __init__(self, value): 

148 Node.__init__(self) 

149 self.value = value 

150 

151 def visit(self, visitor): 

152 # Docstring inherited from Node.visit 

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

154 

155 def __str__(self): 

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

157 

158 

159class TimeLiteral(Node): 

160 """Node representing time literal. 

161 

162 Attributes 

163 ---------- 

164 value : str 

165 Literal string value. 

166 """ 

167 def __init__(self, value): 

168 Node.__init__(self) 

169 self.value = value 

170 

171 def visit(self, visitor): 

172 # Docstring inherited from Node.visit 

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

174 

175 def __str__(self): 

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

177 

178 

179class NumericLiteral(Node): 

180 """Node representing string literal. 

181 

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

183 is stored literally. 

184 

185 Attributes 

186 ---------- 

187 value : str 

188 Literal value. 

189 """ 

190 def __init__(self, value): 

191 Node.__init__(self) 

192 self.value = value 

193 

194 def visit(self, visitor): 

195 # Docstring inherited from Node.visit 

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

197 

198 def __str__(self): 

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

200 

201 

202class Identifier(Node): 

203 """Node representing identifier. 

204 

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

206 character. 

207 

208 Attributes 

209 ---------- 

210 name : str 

211 Identifier name. 

212 """ 

213 def __init__(self, name): 

214 Node.__init__(self) 

215 self.name = name 

216 

217 def visit(self, visitor): 

218 # Docstring inherited from Node.visit 

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

220 

221 def __str__(self): 

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

223 

224 

225class RangeLiteral(Node): 

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

227 

228 Range literal defines a range of integer numbers with start and 

229 end of the range (with inclusive end) and optional stride value 

230 (default is 1). 

231 

232 Attributes 

233 ---------- 

234 start : `int` 

235 Start value of a range. 

236 stop : `int` 

237 End value of a range, inclusive, same or higher than ``start``. 

238 stride : `int` or `None`, optional 

239 Stride value, must be positive, can be `None` which means that stride 

240 was not specified. Consumers are supposed to treat `None` the same way 

241 as stride=1 but for some consumers it may be useful to know that 

242 stride was missing from literal. 

243 """ 

244 def __init__(self, start, stop, stride=None): 

245 self.start = start 

246 self.stop = stop 

247 self.stride = stride 

248 

249 def visit(self, visitor): 

250 # Docstring inherited from Node.visit 

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

252 

253 def __str__(self): 

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

255 return res 

256 

257 

258class IsIn(Node): 

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

260 

261 Attributes 

262 ---------- 

263 lhs : Node 

264 Left-hand side of the operation 

265 values : list of Node 

266 List of values on the right side. 

267 not_in : bool 

268 If `True` then it is NOT IN expression, otherwise it is IN expression. 

269 """ 

270 def __init__(self, lhs, values, not_in=False): 

271 Node.__init__(self, (lhs,) + tuple(values)) 

272 self.lhs = lhs 

273 self.values = values 

274 self.not_in = not_in 

275 

276 def visit(self, visitor): 

277 # Docstring inherited from Node.visit 

278 lhs = self.lhs.visit(visitor) 

279 values = [value.visit(visitor) for value in self.values] 

280 return visitor.visitIsIn(lhs, values, self.not_in, self) 

281 

282 def __str__(self): 

283 values = ", ".join(str(x) for x in self.values) 

284 not_in = "" 

285 if self.not_in: 

286 not_in = "NOT " 

287 return "{lhs} {not_in}IN ({values})".format(lhs=self.lhs, 

288 not_in=not_in, 

289 values=values) 

290 

291 

292class Parens(Node): 

293 """Node representing parenthesized expression. 

294 

295 Attributes 

296 ---------- 

297 expr : Node 

298 Expression inside parentheses. 

299 """ 

300 def __init__(self, expr): 

301 Node.__init__(self, (expr,)) 

302 self.expr = expr 

303 

304 def visit(self, visitor): 

305 # Docstring inherited from Node.visit 

306 expr = self.expr.visit(visitor) 

307 return visitor.visitParens(expr, self) 

308 

309 def __str__(self): 

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