Coverage for python/lsst/daf/butler/registry/queries/exprParser/parserYacc.py : 31%

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"""Syntax definition for user expression parser.
23"""
25__all__ = ["ParserYacc", "ParserYaccError", "ParseError", "ParserEOFError"]
27# -------------------------------
28# Imports of standard modules --
29# -------------------------------
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
39# ----------------------------------
40# Local non-exported definitions --
41# ----------------------------------
43# ------------------------
44# Exported definitions --
45# ------------------------
48class ParserYaccError(Exception):
49 """Base class for exceptions generated by parser.
50 """
51 pass
54class ParseError(ParserYaccError):
55 """Exception raised for parsing errors.
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 """
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)
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
92class ParserEOFError(ParserYaccError):
93 """Exception raised for EOF-during-parser.
94 """
96 def __init__(self):
97 Exception.__init__(self,
98 "End of input reached while expecting further input")
101class ParserYacc:
102 """Class which defines PLY grammar.
103 """
105 def __init__(self, **kwargs):
107 kw = dict(write_tables=0, debug=False)
108 kw.update(kwargs)
110 self.parser = yacc.yacc(module=self, **kw)
112 def parse(self, input, lexer=None, debug=False, tracking=False):
113 """Parse input expression ad return parsed tree object.
115 This is a trivial wrapper for yacc.LRParser.parse method which
116 provides lexer if not given in arguments.
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
137 tokens = ParserLex.tokens[:]
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 )
149 # this is the starting rule
150 def p_input(self, p):
151 """ input : expr
152 | empty
153 """
154 p[0] = p[1]
156 def p_empty(self, p):
157 """ empty :
158 """
159 p[0] = None
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]
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])
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]
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]]
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])
222 def p_simple_expr_lit(self, p):
223 """ simple_expr : literal
224 """
225 p[0] = p[1]
227 def p_simple_expr_id(self, p):
228 """ simple_expr : IDENTIFIER
229 """
230 p[0] = Identifier(p[1])
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])
238 def p_simple_expr_paren(self, p):
239 """ simple_expr : LPAREN expr RPAREN
240 """
241 p[0] = Parens(p[2])
243 def p_literal_num(self, p):
244 """ literal : NUMERIC_LITERAL
245 """
246 p[0] = NumericLiteral(p[1])
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])
254 def p_literal_str(self, p):
255 """ literal : STRING_LITERAL
256 """
257 p[0] = StringLiteral(p[1])
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)
266 # ---------- end of all grammar rules ----------
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)