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"""Syntax definition for user expression parser. 

23""" 

24 

25__all__ = ["ParserYacc", "ParserYaccError", "ParseError", "ParserEOFError"] 

26 

27# ------------------------------- 

28# Imports of standard modules -- 

29# ------------------------------- 

30 

31# ----------------------------- 

32# Imports for other modules -- 

33# ----------------------------- 

34from .exprTree import (BinaryOp, Identifier, IsIn, NumericLiteral, 

35 Parens, StringLiteral, RangeLiteral, UnaryOp) 

36from .ply import yacc 

37from .parserLex import ParserLex 

38 

39# ---------------------------------- 

40# Local non-exported definitions -- 

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

42 

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

44# Exported definitions -- 

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

46 

47 

48class ParserYaccError(Exception): 

49 """Base class for exceptions generated by parser. 

50 """ 

51 pass 

52 

53 

54class ParseError(ParserYaccError): 

55 """Exception raised for parsing errors. 

56 

57 Attributes 

58 ---------- 

59 expression : str 

60 Full initial expression being parsed 

61 token : str 

62 Current token at parsing position 

63 pos : int 

64 Current parsing position, offset from beginning of expression in 

65 characters 

66 lineno : int 

67 Current line number in the expression 

68 posInLine : int 

69 Parsing position in current line, 0-based 

70 """ 

71 

72 def __init__(self, expression, token, pos, lineno): 

73 self.expression = expression 

74 self.token = token 

75 self.pos = pos 

76 self.lineno = lineno 

77 self.posInLine = self._posInLine() 

78 msg = "Syntax error at or near '{0}' (line: {1}, pos: {2})" 

79 msg = msg.format(token, lineno, self.posInLine + 1) 

80 ParserYaccError.__init__(self, msg) 

81 

82 def _posInLine(self): 

83 """Return position in current line""" 

84 lines = self.expression.split('\n') 

85 pos = self.pos 

86 for line in lines[:self.lineno - 1]: 

87 # +1 for newline 

88 pos -= len(line) + 1 

89 return pos 

90 

91 

92class ParserEOFError(ParserYaccError): 

93 """Exception raised for EOF-during-parser. 

94 """ 

95 

96 def __init__(self): 

97 Exception.__init__(self, 

98 "End of input reached while expecting further input") 

99 

100 

101class ParserYacc: 

102 """Class which defines PLY grammar. 

103 """ 

104 

105 def __init__(self, **kwargs): 

106 

107 kw = dict(write_tables=0, debug=False) 

108 kw.update(kwargs) 

109 

110 self.parser = yacc.yacc(module=self, **kw) 

111 

112 def parse(self, input, lexer=None, debug=False, tracking=False): 

113 """Parse input expression ad return parsed tree object. 

114 

115 This is a trivial wrapper for yacc.LRParser.parse method which 

116 provides lexer if not given in arguments. 

117 

118 Parameters 

119 ---------- 

120 input : str 

121 Expression to parse 

122 lexer : object, optional 

123 Lexer instance, if not given then ParserLex.make_lexer() is 

124 called to create one. 

125 debug : bool, optional 

126 Set to True for debugging output. 

127 tracking : bool, optional 

128 Set to True for tracking line numbers in parser. 

129 """ 

130 # make lexer 

131 if lexer is None: 

132 lexer = ParserLex.make_lexer() 

133 tree = self.parser.parse(input=input, lexer=lexer, debug=debug, 

134 tracking=tracking) 

135 return tree 

136 

137 tokens = ParserLex.tokens[:] 

138 

139 precedence = ( 

140 ('left', 'OR'), 

141 ('left', 'AND'), 

142 ('nonassoc', 'EQ', 'NE'), # Nonassociative operators 

143 ('nonassoc', 'LT', 'LE', 'GT', 'GE'), # Nonassociative operators 

144 ('left', 'ADD', 'SUB'), 

145 ('left', 'MUL', 'DIV', 'MOD'), 

146 ('right', 'UPLUS', 'UMINUS', 'NOT'), # unary plus and minus 

147 ) 

148 

149 # this is the starting rule 

150 def p_input(self, p): 

151 """ input : expr 

152 | empty 

153 """ 

154 p[0] = p[1] 

155 

156 def p_empty(self, p): 

157 """ empty : 

158 """ 

159 p[0] = None 

160 

161 def p_expr(self, p): 

162 """ expr : expr OR expr 

163 | expr AND expr 

164 | NOT expr 

165 | bool_primary 

166 """ 

167 if len(p) == 4: 

168 p[0] = BinaryOp(lhs=p[1], op=p[2].upper(), rhs=p[3]) 

169 elif len(p) == 3: 

170 p[0] = UnaryOp(op=p[1].upper(), operand=p[2]) 

171 else: 

172 p[0] = p[1] 

173 

174 def p_bool_primary(self, p): 

175 """ bool_primary : bool_primary EQ predicate 

176 | bool_primary NE predicate 

177 | bool_primary LT predicate 

178 | bool_primary LE predicate 

179 | bool_primary GE predicate 

180 | bool_primary GT predicate 

181 | predicate 

182 """ 

183 if len(p) == 2: 

184 p[0] = p[1] 

185 else: 

186 p[0] = BinaryOp(lhs=p[1], op=p[2], rhs=p[3]) 

187 

188 def p_predicate(self, p): 

189 """ predicate : bit_expr IN LPAREN literal_list RPAREN 

190 | bit_expr NOT IN LPAREN literal_list RPAREN 

191 | bit_expr 

192 """ 

193 if len(p) == 6: 

194 p[0] = IsIn(lhs=p[1], values=p[4]) 

195 elif len(p) == 7: 

196 p[0] = IsIn(lhs=p[1], values=p[5], not_in=True) 

197 else: 

198 p[0] = p[1] 

199 

200 def p_literal_list(self, p): 

201 """ literal_list : literal_list COMMA literal 

202 | literal 

203 """ 

204 if len(p) == 2: 

205 p[0] = [p[1]] 

206 else: 

207 p[0] = p[1] + [p[3]] 

208 

209 def p_bit_expr(self, p): 

210 """ bit_expr : bit_expr ADD bit_expr 

211 | bit_expr SUB bit_expr 

212 | bit_expr MUL bit_expr 

213 | bit_expr DIV bit_expr 

214 | bit_expr MOD bit_expr 

215 | simple_expr 

216 """ 

217 if len(p) == 2: 

218 p[0] = p[1] 

219 else: 

220 p[0] = BinaryOp(lhs=p[1], op=p[2], rhs=p[3]) 

221 

222 def p_simple_expr_lit(self, p): 

223 """ simple_expr : literal 

224 """ 

225 p[0] = p[1] 

226 

227 def p_simple_expr_id(self, p): 

228 """ simple_expr : IDENTIFIER 

229 """ 

230 p[0] = Identifier(p[1]) 

231 

232 def p_simple_expr_unary(self, p): 

233 """ simple_expr : ADD simple_expr %prec UPLUS 

234 | SUB simple_expr %prec UMINUS 

235 """ 

236 p[0] = UnaryOp(op=p[1], operand=p[2]) 

237 

238 def p_simple_expr_paren(self, p): 

239 """ simple_expr : LPAREN expr RPAREN 

240 """ 

241 p[0] = Parens(p[2]) 

242 

243 def p_literal_num(self, p): 

244 """ literal : NUMERIC_LITERAL 

245 """ 

246 p[0] = NumericLiteral(p[1]) 

247 

248 def p_literal_num_signed(self, p): 

249 """ literal : ADD NUMERIC_LITERAL %prec UPLUS 

250 | SUB NUMERIC_LITERAL %prec UMINUS 

251 """ 

252 p[0] = NumericLiteral(p[1] + p[2]) 

253 

254 def p_literal_str(self, p): 

255 """ literal : STRING_LITERAL 

256 """ 

257 p[0] = StringLiteral(p[1]) 

258 

259 def p_literal_range(self, p): 

260 """ literal : RANGE_LITERAL 

261 """ 

262 # RANGE_LITERAL value is tuple of three numbers 

263 start, stop, stride = p[1] 

264 p[0] = RangeLiteral(start, stop, stride) 

265 

266 # ---------- end of all grammar rules ---------- 

267 

268 # Error rule for syntax errors 

269 def p_error(self, p): 

270 if p is None: 

271 raise ParserEOFError() 

272 else: 

273 raise ParseError(p.lexer.lexdata, p.value, p.lexpos, p.lineno)