"""
Parsing Module - NEW Based on PLY
This module defines the necessary elements to parse a string variable definition
into the recognized elements that are used to construct an Operation Graph.
Copyright 2017-2020, University Corporation for Atmospheric Research
LICENSE: See the LICENSE.rst file for details
"""
from collections import namedtuple
from ply import lex, yacc
tokens = ("UINT", "UFLOAT", "STRING", "NAME", "POW", "EQ", "LEQ", "GEQ")
literals = ("*", "/", "+", "-", "<", ">", "=", ",", ":", "(", ")", "[", "]")
t_ignore = " \t"
t_NAME = r"[a-zA-Z_][a-zA-Z0-9_]*"
t_POW = r"\*\*"
t_LEQ = r"<="
t_GEQ = r">="
t_EQ = r"=="
[docs]def t_UFLOAT(t):
r"(([0-9]+\.[0-9]*|[0-9]*\.[0-9]+)([eE][+-]?[0-9]+)?|[0-9]+[eE][+-]?[0-9]+)"
t.value = float(t.value)
return t
[docs]def t_UINT(t):
r"[0-9]+"
t.value = int(t.value)
return t
[docs]def t_STRING(t):
r'"([^"\\]*(\\.[^"\\]*)*)"|\'([^\'\\]*(\\.[^\'\\]*)*)\''
t.value = t.value[1:-1]
return t
[docs]def t_error(t):
raise TypeError("Unexpected string: {!r}".format(t.value))
lex.lex(debug=False)
[docs]def ind_str(index):
if isinstance(index, slice):
ind_list = [index.start, index.stop, index.step]
_str = ":".join("" if i is None else str(i) for i in ind_list)
return ":" if _str == "::" else _str
else:
return str(index)
[docs]def op_str(self):
if len(self.args) == 1:
return "({}{})".format(self.key, self.args[0])
elif len(self.args) == 2:
return "({}{}{})".format(self.args[0], self.key, self.args[1])
OpType = namedtuple("OpType", ["key", "args"])
OpType.__new__.__defaults__ = (None, [])
OpType.__str__ = lambda self: op_str(self)
FuncType = namedtuple("FuncType", ["key", "args", "kwds"])
FuncType.__new__.__defaults__ = (None, [], {})
FuncType.__str__ = lambda self: "{}({})".format(
self.key,
",".join(
[str(a) for a in self.args]
+ ["{}={}".format(k, self.kwds[k]) for k in self.kwds]
),
)
VarType = namedtuple("VarType", ["key", "ind"])
VarType.__new__.__defaults__ = (None, [])
VarType.__str__ = lambda self: "{}{}".format(
self.key,
""
if len(self.ind) == 0
else "[{}]".format(",".join([ind_str(a) for a in self.ind])),
)
precedence = (
("left", "EQ"),
("left", "<", ">", "LEQ", "GEQ"),
("left", "+", "-"),
("left", "*", "/"),
("right", "NEG", "POS"),
("left", "POW"),
)
[docs]def p_array_like(p):
"""
array_like : UFLOAT
array_like : UINT
array_like : function
array_like : variable
"""
p[0] = p[1]
[docs]def p_array_like_group(p):
"""
array_like : '(' array_like ')'
"""
p[0] = p[2]
[docs]def p_function_with_arguments_and_keywords(p):
"""
function : NAME '(' argument_list ',' keyword_dict ')'
"""
p[0] = FuncType(p[1], p[3], p[5])
[docs]def p_function_with_arguments_only(p):
"""
function : NAME '(' argument_list ')'
"""
p[0] = FuncType(p[1], p[3], {})
[docs]def p_function_with_keywords_only(p):
"""
function : NAME '(' keyword_dict ')'
"""
p[0] = FuncType(p[1], [], p[3])
[docs]def p_argument_list_append(p):
"""
argument_list : argument_list ',' argument
"""
p[0] = p[1] + [p[3]]
[docs]def p_single_item_argument_list(p):
"""
argument_list : argument
argument_list :
"""
p[0] = [p[1]] if len(p) > 1 else []
[docs]def p_argument(p):
"""
argument : array_like
argument : STRING
"""
p[0] = p[1]
[docs]def p_keyword_dict_setitem(p):
"""
keyword_dict : keyword_dict ',' NAME '=' argument
"""
p[1][p[3]] = p[5]
p[0] = p[1]
[docs]def p_single_item_keyword_dict(p):
"""
keyword_dict : NAME '=' argument
"""
p[0] = {p[1]: p[3]}
[docs]def p_variable(p):
"""
variable : NAME '[' index_list ']'
variable : NAME
"""
indices = p[3] if len(p) > 3 else []
p[0] = VarType(p[1], indices)
[docs]def p_index_list_append(p):
"""
index_list : index_list ',' index
"""
p[0] = p[1] + [p[3]]
[docs]def p_single_item_index_list(p):
"""
index_list : index
"""
p[0] = [p[1]]
[docs]def p_index(p):
"""
index : slice
index : array_like
"""
p[0] = p[1]
[docs]def p_slice(p):
"""
slice : slice_argument ':' slice_argument ':' slice_argument
slice : slice_argument ':' slice_argument
"""
p[0] = slice(*p[1::2])
[docs]def p_slice_argument(p):
"""
slice_argument : array_like
slice_argument :
"""
p[0] = p[1] if len(p) > 1 else None
[docs]def p_expression_unary(p):
"""
array_like : '-' array_like %prec NEG
array_like : '+' array_like %prec POS
"""
if p[1] == "+":
p[0] = p[2]
elif p[1] == "-":
if isinstance(p[2], (OpType, FuncType, VarType)):
p[0] = OpType(p[1], [p[2]])
else:
p[0] = -p[2]
[docs]def p_expression_binary(p):
"""
array_like : array_like POW array_like
array_like : array_like '-' array_like
array_like : array_like '+' array_like
array_like : array_like '*' array_like
array_like : array_like '/' array_like
array_like : array_like '<' array_like
array_like : array_like '>' array_like
array_like : array_like LEQ array_like
array_like : array_like GEQ array_like
array_like : array_like EQ array_like
"""
if isinstance(p[1], (OpType, FuncType, VarType)) or isinstance(
p[3], (OpType, FuncType, VarType)
):
p[0] = OpType(p[2], [p[1], p[3]])
elif p[2] == "**":
p[0] = p[1] ** p[3]
elif p[2] == "-":
p[0] = p[1] - p[3]
elif p[2] == "+":
p[0] = p[1] + p[3]
elif p[2] == "*":
p[0] = p[1] * p[3]
elif p[2] == "/":
p[0] = p[1] / p[3]
elif p[2] == "<":
p[0] = p[1] < p[3]
elif p[2] == ">":
p[0] = p[1] > p[3]
elif p[2] == "<=":
p[0] = p[1] <= p[3]
elif p[2] == ">=":
p[0] = p[1] >= p[3]
elif p[2] == "==":
p[0] = p[1] == p[3]
[docs]def p_error(p):
raise TypeError("Parsing error at {!r}".format(p.value))
yacc.yacc(debug=False)
[docs]def parse_definition(strexpr):
return yacc.parse(strexpr) # @UndefinedVariable