我一直在努力用 pyparsing 构建我的 DSL,并取得了很好的进展。我的第一个里程碑是评估包含算术运算符、数据库字段引用和一组函数(Avg、Stdev 等)的表达式。此外,我还实现了将表达式分配给变量,以便能够以模块化的方式构建复杂的表达式。到现在为止还挺好。
在尝试将变量作为参数计算函数时,我现在遇到了下一个主要障碍。具体来说,我的数据库引用(这是执行计算的构建块)需要将 Person 指定为查询的维度。当它们包含在函数中时,我不知道强制重新评估分配给这些变量的表达式的最佳方法。有问题的具体例子:
1) CustomAvg = Avg[Height] + Avg[Weight]
2) Avg[CustomAvg]
评估语句 2 在人员列表中无法按预期工作,因为 CustomAvg 被解析为一个常量值。
在这些场景中,我有一个人员列表,我遍历这些人员以计算 CustomAvg 的组件。但是,当我评估 Avg[CustomAvg] 时,CustomAvg 的值来自我的变量查找字典而不是被评估,因此我有效地迭代了一个常量值。在我的评估中引入“意识”的最佳方法是什么,以便重新评估函数中用作参数的变量而不是从查找表中获取?以下是精简的相关代码:
class EvalConstant(object):
var_ = {}
def __init__(self, tokens):
self.value = tokens[0]
def eval(self):
v = self.value
if self.var_.has_key(v):
return self.var_[v]
else:
return float(v)
class EvalDBref(object):
person_ = None
def __init__(self, tokens):
self.value = tokens[0]
def eval(self):
v = self.value
fieldRef = v.split(':')
source = fieldRef[0]
field = fieldRef[1]
rec = db[source].find_one({'Name' : self.person_}, { '_id' : 0, field : 1})
return rec[field]
class EvalFunction(object):
pop_ = {}
def __init__(self, tokens):
self.func_ = tokens.funcname
self.field_ = tokens.arg
self.pop_ = POPULATION
def eval(self):
v = self.field_.value
fieldRef = v.split(':')
source = fieldRef[0]
field = fieldRef[1]
val = self.field_.eval()
if self.func_ == 'ZS':
# If using zscore then fetch the field aggregates from stats
rec = db['Stats'].find_one({'_id' : field})
stdev = rec['value']['stddev']
avg = rec['value']['avg']
return (val - avg)/stdev
elif self.func_ == 'Ptile':
recs = list(db[source].find({'Name' : { '$in' : self.pop_}},{'_id' : 0, field : 1}))
recs = [r[field] for r in recs]
return percentileofscore(recs, val)
def assign_var(tokens):
ev = tokens.varvalue.eval()
EvalConstant.var_[tokens.varname] = ev
#--------------------
expr = Forward()
chars = Word(alphanums + "_-/")
integer = Word(nums)
real = Combine(Word(nums) + "." + Word(nums))
var = Word(alphas)
assign = var("varname") + "=" + expr("varvalue")
assign.setParseAction(assign_var)
dbRef = Combine(chars + OneOrMore(":") + chars)
dbRef.setParseAction(EvalDBref)
funcNames = Keyword("ZS") | Keyword("Avg") | Keyword("Stdev")
functionCall = funcNames("funcname") + "[" + expr("arg") + "]"
functionCall.setParseAction(EvalFunction)
operand = dbRef | functionCall | (real | integer| var).setParseAction(EvalConstant)
signop = oneOf('+ -')
multop = oneOf('* /')
plusop = oneOf('+ -')
expr << operatorPrecedence(operand,
[
(signop, 1, opAssoc.RIGHT, EvalSignOp),
(multop, 2, opAssoc.LEFT, EvalMultOp),
(plusop, 2, opAssoc.LEFT, EvalAddOp),
])
EvalDBref.person_ = ‘John Smith’
ret = (assign | expr).parseString(line)[0]
str(ret.eval())