scripts: csv.py: Adopted square brackets ([]) for by exprs
By fields are very different from normal fields in exprs (no type checking, restricted subexprs, etc), so it makes sense to give them separate syntaxes to clarify this distinction and improve readability. This commit adopts optional square brackets for by fields, mimicking generic/template specialization found in other languages: Before: -fx=enumerate() -fy=enumerate(a,b) -fz=accumulate(z,a,b) After: -fx=enumerate() -fy=enumerate[a,b]() -fz=accumulate[a,b](z) Hopefully the readability argument is pretty obvious. I went with square brackets to avoid parser ambiguities with <>. To be honest I've never understood why C++ went with <>, array/function confusion seems easier to resolve than ambiguous binary/index syntaxes, but what do I know.
This commit is contained in:
+51
-49
@@ -654,7 +654,12 @@ class CsvExpr:
|
|||||||
|
|
||||||
# expr node base class
|
# expr node base class
|
||||||
class Expr:
|
class Expr:
|
||||||
def __init__(self, *args):
|
def __init__(self, *args, by=None):
|
||||||
|
self.by = by or []
|
||||||
|
for k in self.by:
|
||||||
|
if not isinstance(k, CsvExpr.Field):
|
||||||
|
raise CsvExpr.Error("complicated by? %s" % k)
|
||||||
|
|
||||||
for k, v in zip('abcdefghijklmnopqrstuvwxyz', args):
|
for k, v in zip('abcdefghijklmnopqrstuvwxyz', args):
|
||||||
setattr(self, k, v)
|
setattr(self, k, v)
|
||||||
|
|
||||||
@@ -668,8 +673,10 @@ class CsvExpr:
|
|||||||
return sum(1 for _ in self)
|
return sum(1 for _ in self)
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '%s(%s)' % (
|
return '%s%s(%s)' % (
|
||||||
self.__class__.__name__,
|
self.__class__.__name__,
|
||||||
|
'[%s]' % (','.join(repr(k) for k in self.by))
|
||||||
|
if self.by else '',
|
||||||
','.join(repr(v) for v in self))
|
','.join(repr(v) for v in self))
|
||||||
|
|
||||||
def fields(self):
|
def fields(self):
|
||||||
@@ -741,9 +748,14 @@ class CsvExpr:
|
|||||||
|
|
||||||
# func expr helper
|
# func expr helper
|
||||||
def func(funcs):
|
def func(funcs):
|
||||||
def func(name, args="a"):
|
def func(name, by=None, args=None):
|
||||||
|
if by is None and args is None:
|
||||||
|
by, args = None, "a"
|
||||||
|
elif args is None:
|
||||||
|
by, args = None, by
|
||||||
def func(f):
|
def func(f):
|
||||||
f._func = name
|
f._func = name
|
||||||
|
f._fby = by
|
||||||
f._fargs = args
|
f._fargs = args
|
||||||
funcs[f._func] = f
|
funcs[f._func] = f
|
||||||
return f
|
return f
|
||||||
@@ -960,22 +972,16 @@ class CsvExpr:
|
|||||||
return CsvGStddev()([v.eval(fields, state) for v in self])
|
return CsvGStddev()([v.eval(fields, state) for v in self])
|
||||||
|
|
||||||
# enumerate exprs
|
# enumerate exprs
|
||||||
@func('enumerate', '[*by]')
|
@func('enumerate', 'by', '')
|
||||||
class Enumerate(Expr):
|
class Enumerate(Expr):
|
||||||
"""A [per by] number incremented for each result"""
|
"""A [per by] number incremented for each result"""
|
||||||
def fields(self):
|
def fields(self):
|
||||||
# don't typecheck by fields
|
|
||||||
return set()
|
return set()
|
||||||
|
|
||||||
def type(self, types={}):
|
def type(self, types={}):
|
||||||
# don't typecheck, but make sure we can read by fields
|
|
||||||
for v in self:
|
|
||||||
if not isinstance(v, CsvExpr.Field):
|
|
||||||
raise CsvExpr.Error("complicated by field? %s" % v)
|
|
||||||
return CsvInt
|
return CsvInt
|
||||||
|
|
||||||
def fold(self, types={}):
|
def fold(self, types={}):
|
||||||
# don't typecheck by fields
|
|
||||||
return CsvSum
|
return CsvSum
|
||||||
|
|
||||||
def eval(self, fields={}, state=None):
|
def eval(self, fields={}, state=None):
|
||||||
@@ -984,7 +990,7 @@ class CsvExpr:
|
|||||||
|
|
||||||
# enumerate
|
# enumerate
|
||||||
k = ('enumerate', id(self)) + tuple(
|
k = ('enumerate', id(self)) + tuple(
|
||||||
fields.get(v.a) for v in self)
|
fields.get(k.a) for k in self.by)
|
||||||
x = state.get(k)
|
x = state.get(k)
|
||||||
if x is None:
|
if x is None:
|
||||||
z = 0
|
z = 0
|
||||||
@@ -994,25 +1000,9 @@ class CsvExpr:
|
|||||||
state[k] = z
|
state[k] = z
|
||||||
return CsvInt(z)
|
return CsvInt(z)
|
||||||
|
|
||||||
@func('accumulate', 'a[, *by]')
|
@func('accumulate', 'by', 'a')
|
||||||
class Accumulate(Expr):
|
class Accumulate(Expr):
|
||||||
"""A [per by] running sum across results"""
|
"""A [per by] running sum across results"""
|
||||||
def fields(self):
|
|
||||||
# don't typecheck by fields
|
|
||||||
return self.a.fields()
|
|
||||||
|
|
||||||
def type(self, types={}):
|
|
||||||
# don't typecheck, but make sure we can read by fields
|
|
||||||
t = self.a.type(types)
|
|
||||||
for v in it.islice(self, 1, None):
|
|
||||||
if not isinstance(v, CsvExpr.Field):
|
|
||||||
raise CsvExpr.Error("complicated by field? %s" % v)
|
|
||||||
return t
|
|
||||||
|
|
||||||
def fold(self, types={}):
|
|
||||||
# don't typecheck by fields
|
|
||||||
return self.a.fold(types)
|
|
||||||
|
|
||||||
def eval(self, fields={}, state=None):
|
def eval(self, fields={}, state=None):
|
||||||
y = self.a.eval(fields, state)
|
y = self.a.eval(fields, state)
|
||||||
if state is None:
|
if state is None:
|
||||||
@@ -1020,7 +1010,7 @@ class CsvExpr:
|
|||||||
|
|
||||||
# accumulate
|
# accumulate
|
||||||
k = ('accumulate', id(self)) + tuple(
|
k = ('accumulate', id(self)) + tuple(
|
||||||
fields.get(v.a) for v in it.islice(self, 1, None))
|
fields.get(k.a) for k in self.by)
|
||||||
x = state.get(k)
|
x = state.get(k)
|
||||||
if x is None:
|
if x is None:
|
||||||
z = y
|
z = y
|
||||||
@@ -1030,25 +1020,9 @@ class CsvExpr:
|
|||||||
state[k] = z
|
state[k] = z
|
||||||
return z
|
return z
|
||||||
|
|
||||||
@func('delta', 'a[, *by]')
|
@func('delta', 'by', 'a')
|
||||||
class Delta(Expr):
|
class Delta(Expr):
|
||||||
"""A [per by] difference between subsequent results"""
|
"""A [per by] difference between subsequent results"""
|
||||||
def fields(self):
|
|
||||||
# don't typecheck by fields
|
|
||||||
return self.a.fields()
|
|
||||||
|
|
||||||
def type(self, types={}):
|
|
||||||
# don't typecheck, but make sure we can read by fields
|
|
||||||
t = self.a.type(types)
|
|
||||||
for v in it.islice(self, 1, None):
|
|
||||||
if not isinstance(v, CsvExpr.Field):
|
|
||||||
raise CsvExpr.Error("complicated by field? %s" % v)
|
|
||||||
return t
|
|
||||||
|
|
||||||
def fold(self, types={}):
|
|
||||||
# don't typecheck by fields
|
|
||||||
return self.a.fold(types)
|
|
||||||
|
|
||||||
def eval(self, fields={}, state=None):
|
def eval(self, fields={}, state=None):
|
||||||
y = self.a.eval(fields, state)
|
y = self.a.eval(fields, state)
|
||||||
if state is None:
|
if state is None:
|
||||||
@@ -1056,7 +1030,7 @@ class CsvExpr:
|
|||||||
|
|
||||||
# compute delta
|
# compute delta
|
||||||
k = ('delta', id(self)) + tuple(
|
k = ('delta', id(self)) + tuple(
|
||||||
fields.get(v.a) for v in it.islice(self, 1, None))
|
fields.get(k.a) for k in self.by)
|
||||||
x = state.get(k)
|
x = state.get(k)
|
||||||
if x is None:
|
if x is None:
|
||||||
z = y
|
z = y
|
||||||
@@ -1531,7 +1505,12 @@ class CsvExpr:
|
|||||||
print('funcs:')
|
print('funcs:')
|
||||||
for func in cls.funcs.keys():
|
for func in cls.funcs.keys():
|
||||||
print(' %-21s %s' % (
|
print(' %-21s %s' % (
|
||||||
'%s(%s)' % (func, CsvExpr.funcs[func]._fargs),
|
'%s%s(%s)' % (
|
||||||
|
func,
|
||||||
|
'[%s]' % CsvExpr.funcs[func]._fby
|
||||||
|
if CsvExpr.funcs[func]._fby
|
||||||
|
else '',
|
||||||
|
CsvExpr.funcs[func]._fargs),
|
||||||
CsvExpr.funcs[func].__doc__))
|
CsvExpr.funcs[func].__doc__))
|
||||||
|
|
||||||
# parse an expr
|
# parse an expr
|
||||||
@@ -1560,6 +1539,25 @@ class CsvExpr:
|
|||||||
elif p.match('[_a-zA-Z][_a-zA-Z0-9]*'):
|
elif p.match('[_a-zA-Z][_a-zA-Z0-9]*'):
|
||||||
a = p.chomp()
|
a = p.chomp()
|
||||||
|
|
||||||
|
by = None
|
||||||
|
if p.match('\['):
|
||||||
|
p.chomp()
|
||||||
|
if a not in CsvExpr.funcs:
|
||||||
|
raise CsvExpr.Error("unknown function? %s" % a)
|
||||||
|
by = []
|
||||||
|
while True:
|
||||||
|
if not p.match('\]'):
|
||||||
|
b = p_expr(p)
|
||||||
|
by.append(b)
|
||||||
|
if p.match(','):
|
||||||
|
p.chomp()
|
||||||
|
continue
|
||||||
|
if not p.match('\]'):
|
||||||
|
raise CsvExpr.Error("mismatched squares? %s" % p)
|
||||||
|
p.chomp()
|
||||||
|
break
|
||||||
|
|
||||||
|
args = None
|
||||||
if p.match('\('):
|
if p.match('\('):
|
||||||
p.chomp()
|
p.chomp()
|
||||||
if a not in CsvExpr.funcs:
|
if a not in CsvExpr.funcs:
|
||||||
@@ -1575,8 +1573,12 @@ class CsvExpr:
|
|||||||
if not p.match('\)'):
|
if not p.match('\)'):
|
||||||
raise CsvExpr.Error("mismatched parens? %s" % p)
|
raise CsvExpr.Error("mismatched parens? %s" % p)
|
||||||
p.chomp()
|
p.chomp()
|
||||||
a = CsvExpr.funcs[a](*args)
|
|
||||||
break
|
break
|
||||||
|
|
||||||
|
if args is not None:
|
||||||
|
a = CsvExpr.funcs[a](*args, by=by)
|
||||||
|
elif by is not None:
|
||||||
|
raise CsvExpr.Error("expected parens? %s" % p)
|
||||||
else:
|
else:
|
||||||
a = CsvExpr.Field(a)
|
a = CsvExpr.Field(a)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user