scripts: Added CsvFfrac type

A simple float variant of the CsvFrac type:

- frac(1.5,2)  => 1/2 (50.0%)
- ffrac(1.5,2) => 1.5/2.0 (75.0%)

Useful for `make bench-widths` (previously make bench-bus), where we
want to find the average buffer utilization:

  probe            readed              progged                 erased
  b_rbyd+create   1.0/1.0 (100.0%)  13.8/256.0 (5.4%)        ∞/4096.0 (∞%)
  b_rbyd+delete     ∞/1.0 (∞%)         ∞/256.0 (∞%)          ∞/4096.0 (∞%)
  b_rbyd+fetch    1.0/1.0 (100.0%)     ∞/256.0 (∞%)          ∞/4096.0 (∞%)
  b_rbyd+lookup   1.0/1.0 (100.0%)     ∞/256.0 (∞%)          ∞/4096.0 (∞%)
  b_rbyd+usage      ∞/1.0 (∞%)         ∞/256.0 (∞%)          ∞/4096.0 (∞%)
  b_wt_seq+w      1.0/1.0 (100.0%)  31.7/256.0 (12.4%)  4096.0/4096.0 (100.0%)
  b_wt_random+w   1.0/1.0 (100.0%)  15.3/256.0 (6.0%)   4096.0/4096.0 (100.0%)
  b_wt_logging+w  1.0/1.0 (100.0%)  15.4/256.0 (6.0%)   4096.0/4096.0 (100.0%)
  b_wt_many+w     1.0/1.0 (100.0%)  16.1/256.0 (6.3%)   4096.0/4096.0 (100.0%)
  TOTAL             ∞/1.0 (∞%)         ∞/256.0 (∞%)          ∞/4096.0 (∞%)

Now that we have 4 types, the cast matrix gets a bit complicated, but
this is side-stepped a bit by a custom __frac__ hook.

---

Some other tweaks to csv.py:

- Added CsvFold.type to typecheck folds _after_ we know the expr's final
  type.

- Adopted CsvFfrac as an output for most of the math functions/folds

- Stopped early termination of typechecking if we change type!

  This was broken: int(float(1.5) + int(1))
This commit is contained in:
Christopher Haster
2026-02-05 14:08:51 -06:00
parent 73e06612bf
commit b751981574
10 changed files with 365 additions and 76 deletions
+21 -6
View File
@@ -661,15 +661,30 @@ bench-ops: $(BENCH_CSV)
$(SUMMARYFLAGS)) $(SUMMARYFLAGS))
## Show average readed/progged/erased per width ## Show average readed/progged/erased per width
.PHONY: bench-bus .PHONY: bench-widths
bench-bus: SUMMARYFLAGS+=-Si bench-widths: SUMMARYFLAGS+=-Si
bench-bus: $(BENCH_CSV) bench-widths: $(BENCH_CSV)
$(strip ./scripts/csv.py $^ \ $(strip ./scripts/csv.py $^ \
-bprobe='%(case)s+%(probe)s' \ -bprobe='%(case)s+%(probe)s' \
-Fi='min(enumerate())' \ -Fi='min(enumerate())' \
-freaded='avg(float(bench_readed)/float(bench_reads))' \ -freaded="avg(ffrac( \
-fprogged='avg(float(bench_progged)/float(bench_progs))' \ float(bench_readed)/float(bench_reads), \
-ferased='avg(float(bench_erased)/float(bench_erases))' \ max(1, $$( \
./scripts/bench.py -R$(BENCH_RUNNER) $(BENCHFLAGS) \
--list-implicit-defines \
| sed -n 's/^READ_WIDTH=\(.*\)/\1/p'))))" \
-fprogged="avg(ffrac( \
float(bench_progged)/float(bench_progs), \
max(1, $$( \
./scripts/bench.py -R$(BENCH_RUNNER) $(BENCHFLAGS) \
--list-implicit-defines \
| sed -n 's/^PROG_WIDTH=\(.*\)/\1/p'))))" \
-ferased="avg(ffrac( \
float(bench_erased)/float(bench_erases), \
max(1, $$( \
./scripts/bench.py -R$(BENCH_RUNNER) $(BENCHFLAGS) \
--list-implicit-defines \
| sed -n 's/^ERASE_WIDTH=\(.*\)/\1/p'))))" \
$(SUMMARYFLAGS)) $(SUMMARYFLAGS))
+1 -1
View File
@@ -40,7 +40,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
+8 -3
View File
@@ -42,7 +42,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
@@ -150,9 +150,11 @@ class CsvFrac(co.namedtuple('CsvFrac', 'a,b')):
def __new__(cls, a=0, b=None): def __new__(cls, a=0, b=None):
if isinstance(a, CsvFrac) and b is None: if isinstance(a, CsvFrac) and b is None:
return a return a
if isinstance(a, str) and b is None: elif hasattr(a, '__frac__') and b is None:
a, b = a.__frac__()
elif isinstance(a, str) and b is None:
a, b = a.split('/', 1) a, b = a.split('/', 1)
if b is None: elif b is None:
b = a b = a
return super().__new__(cls, CsvInt(a), CsvInt(b)) return super().__new__(cls, CsvInt(a), CsvInt(b))
@@ -174,6 +176,9 @@ class CsvFrac(co.namedtuple('CsvFrac', 'a,b')):
def __float__(self): def __float__(self):
return float(self.a) return float(self.a)
def __frac__(self):
return self.a, self.b
none = '%11s' % '-' none = '%11s' % '-'
def table(self): def table(self):
return '%11s' % (self,) return '%11s' % (self,)
+329 -60
View File
@@ -65,7 +65,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
@@ -173,7 +173,7 @@ class CsvFloat(co.namedtuple('CsvFloat', 'a')):
def __new__(cls, a=0.0): def __new__(cls, a=0.0):
if isinstance(a, CsvFloat): if isinstance(a, CsvFloat):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = float(a) a = float(a)
except ValueError: except ValueError:
@@ -280,9 +280,11 @@ class CsvFrac(co.namedtuple('CsvFrac', 'a,b')):
def __new__(cls, a=0, b=None): def __new__(cls, a=0, b=None):
if isinstance(a, CsvFrac) and b is None: if isinstance(a, CsvFrac) and b is None:
return a return a
if isinstance(a, str) and b is None: elif hasattr(a, '__frac__') and b is None:
a, b = a.__frac__()
elif isinstance(a, str) and b is None:
a, b = a.split('/', 1) a, b = a.split('/', 1)
if b is None: elif b is None:
b = a b = a
return super().__new__(cls, CsvInt(a), CsvInt(b)) return super().__new__(cls, CsvInt(a), CsvInt(b))
@@ -304,6 +306,9 @@ class CsvFrac(co.namedtuple('CsvFrac', 'a,b')):
def __float__(self): def __float__(self):
return float(self.a) return float(self.a)
def __frac__(self):
return self.a, self.b
none = '%11s' % '-' none = '%11s' % '-'
def table(self): def table(self):
return '%11s' % (self,) return '%11s' % (self,)
@@ -380,40 +385,182 @@ class CsvFrac(co.namedtuple('CsvFrac', 'a,b')):
def __ge__(self, other): def __ge__(self, other):
return not self.__lt__(other) return not self.__lt__(other)
# fractional float fields, a/b
class CsvFfrac(co.namedtuple('CsvFfrac', 'a,b')):
__slots__ = ()
def __new__(cls, a=0, b=None):
if isinstance(a, CsvFfrac) and b is None:
return a
elif hasattr(a, '__frac__') and b is None:
a, b = a.__frac__()
elif isinstance(a, str) and b is None:
a, b = a.split('/', 1)
elif b is None:
b = a
return super().__new__(cls, CsvFloat(a), CsvFloat(b))
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.a.a, self.b.a)
def __str__(self):
return '%s/%s' % (self.a, self.b)
def __csv__(self):
return '%s/%s' % (self.a.__csv__(), self.b.__csv__())
def __bool__(self):
return bool(self.a)
def __int__(self):
return int(self.a)
def __float__(self):
return float(self.a)
def __frac__(self):
return self.a, self.b
none = '%11s' % '-'
def table(self):
return '%11s' % (self,)
def notes(self):
if self.b.a == 0 and self.a.a == 0:
t = 1.0
elif self.b.a == 0:
t = mt.copysign(mt.inf, self.a.a)
else:
t = self.a.a / self.b.a
return ['%' if t == +mt.inf
else '-∞%' if t == -mt.inf
else '%.1f%%' % (100*t)]
def diff(self, other):
new_a, new_b = self if self else (CsvFloat(0), CsvFloat(0))
old_a, old_b = other if other else (CsvFloat(0), CsvFloat(0))
return '%11s' % ('%s/%s' % (
new_a.diff(old_a).strip(),
new_b.diff(old_b).strip()))
def ratio(self, other):
new_a, new_b = self if self else (CsvFloat(0), CsvFloat(0))
old_a, old_b = other if other else (CsvFloat(0), CsvFloat(0))
new = new_a.a/new_b.a if new_b.a else 1.0
old = old_a.a/old_b.a if old_b.a else 1.0
return new - old
def __pos__(self):
return self.__class__(+self.a, +self.b)
def __neg__(self):
return self.__class__(-self.a, -self.b)
def __abs__(self):
return self.__class__(abs(self.a), abs(self.b))
def __add__(self, other):
return self.__class__(self.a + other.a, self.b + other.b)
def __sub__(self, other):
return self.__class__(self.a - other.a, self.b - other.b)
def __mul__(self, other):
return self.__class__(self.a * other.a, self.b * other.b)
def __truediv__(self, other):
return self.__class__(self.a / other.a, self.b / other.b)
def __mod__(self, other):
return self.__class__(self.a % other.a, self.b % other.b)
def __hash__(self):
return super().__hash__()
def __eq__(self, other):
return super().__eq__(other)
def __ne__(self, other):
return not self.__eq__(other)
def __lt__(self, other):
self_a, self_b = self if self.b.a else (CsvFloat(1), CsvFloat(1))
other_a, other_b = other if other.b.a else (CsvFloat(1), CsvFloat(1))
return self_a * other_b < other_a * self_b
def __gt__(self, other):
return self.__class__.__lt__(other, self)
def __le__(self, other):
return not self.__gt__(other)
def __ge__(self, other):
return not self.__lt__(other)
# various fold operations # various fold operations
class CsvSum: class CsvFold:
def type(self, type):
return type
def __call__(self, xs):
assert False
class CsvSum(CsvFold):
def __call__(self, xs): def __call__(self, xs):
return sum(xs[1:], start=xs[0]) return sum(xs[1:], start=xs[0])
class CsvProd: class CsvProd(CsvFold):
def __call__(self, xs): def __call__(self, xs):
return mt.prod(xs[1:], start=xs[0]) return mt.prod(xs[1:], start=xs[0])
class CsvMin: class CsvMin(CsvFold):
def __call__(self, xs): def __call__(self, xs):
return min(xs) return min(xs)
class CsvMax: class CsvMax(CsvFold):
def __call__(self, xs): def __call__(self, xs):
return max(xs) return max(xs)
class CsvAvg: class CsvAvg(CsvFold):
def type(self, t):
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def __call__(self, xs): def __call__(self, xs):
if hasattr(xs[0], '__frac__'):
return CsvFfrac(self([x.a for x in xs]), self([x.b for x in xs]))
return CsvFloat(sum(float(x) for x in xs) / len(xs)) return CsvFloat(sum(float(x) for x in xs) / len(xs))
class CsvStddev: class CsvStddev(CsvFold):
def type(self, t):
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def __call__(self, xs): def __call__(self, xs):
if hasattr(xs[0], '__frac__'):
return CsvFfrac(self([x.a for x in xs]), self([x.b for x in xs]))
avg = sum(float(x) for x in xs) / len(xs) avg = sum(float(x) for x in xs) / len(xs)
return CsvFloat(mt.sqrt( return CsvFloat(mt.sqrt(
sum((float(x) - avg)**2 for x in xs) / len(xs))) sum((float(x) - avg)**2 for x in xs) / len(xs)))
class CsvGMean: class CsvGMean(CsvFold):
def type(self, t):
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def __call__(self, xs): def __call__(self, xs):
if hasattr(xs[0], '__frac__'):
return CsvFfrac(self([x.a for x in xs]), self([x.b for x in xs]))
return CsvFloat(mt.prod(float(x) for x in xs)**(1/len(xs))) return CsvFloat(mt.prod(float(x) for x in xs)**(1/len(xs)))
class CsvGStddev: class CsvGStddev(CsvFold):
def type(self, t):
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def __call__(self, xs): def __call__(self, xs):
if hasattr(xs[0], '__frac__'):
return CsvFfrac(self([x.a for x in xs]), self([x.b for x in xs]))
gmean = mt.prod(float(x) for x in xs)**(1/len(xs)) gmean = mt.prod(float(x) for x in xs)**(1/len(xs))
return CsvFloat( return CsvFloat(
mt.exp(mt.sqrt( mt.exp(mt.sqrt(
@@ -554,7 +701,7 @@ class CsvExpr:
return CsvInt return CsvInt
def fold(self, types={}): def fold(self, types={}):
return CsvSum, None return CsvSum
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
return self.a return self.a
@@ -567,7 +714,7 @@ class CsvExpr:
return CsvFloat return CsvFloat
def fold(self, types={}): def fold(self, types={}):
return CsvSum, None return CsvSum
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
return self.a return self.a
@@ -585,7 +732,7 @@ class CsvExpr:
def fold(self, types={}): def fold(self, types={}):
if self.a not in types: if self.a not in types:
raise CsvExpr.Error("unfoldable field? %s" % self.a) raise CsvExpr.Error("unfoldable field? %s" % self.a)
return CsvSum, None return CsvSum
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a not in fields: if self.a not in fields:
@@ -611,6 +758,7 @@ class CsvExpr:
class Int(Expr): class Int(Expr):
"""Convert to an integer""" """Convert to an integer"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -620,6 +768,7 @@ class CsvExpr:
class Float(Expr): class Float(Expr):
"""Convert to a float""" """Convert to a float"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvFloat return CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -629,6 +778,9 @@ class CsvExpr:
class Frac(Expr): class Frac(Expr):
"""Convert to a fraction""" """Convert to a fraction"""
def type(self, types={}): def type(self, types={}):
self.a.type(types)
if len(self) > 1:
self.b.type(types)
return CsvFrac return CsvFrac
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -640,13 +792,31 @@ class CsvExpr:
self.a.eval(fields, state), self.a.eval(fields, state),
self.b.eval(fields, state)) self.b.eval(fields, state))
@func('ffrac', 'a[, b]')
class Ffrac(Expr):
"""Convert to a float fraction"""
def type(self, types={}):
self.a.type(types)
if len(self) > 1:
self.b.type(types)
return CsvFfrac
def eval(self, fields={}, state=None):
if len(self) == 1:
return CsvFfrac(
self.a.eval(fields, state))
else:
return CsvFfrac(
self.a.eval(fields, state),
self.b.eval(fields, state))
# fold exprs # fold exprs
@func('sum', 'a[, ...]') @func('sum', 'a[, ...]')
class Sum(Expr): class Sum(Expr):
"""Find the sum of this column or fields""" """Find the sum of this column or fields"""
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvSum, None return CsvSum
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -661,7 +831,7 @@ class CsvExpr:
"""Find the product of this column or fields""" """Find the product of this column or fields"""
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return Prod, None return Prod
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -676,7 +846,7 @@ class CsvExpr:
"""Find the minimum of this column or fields""" """Find the minimum of this column or fields"""
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvMin, None return CsvMin
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -691,7 +861,7 @@ class CsvExpr:
"""Find the maximum of this column or fields""" """Find the maximum of this column or fields"""
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvMax, None return CsvMax
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -705,14 +875,15 @@ class CsvExpr:
class Avg(Expr): class Avg(Expr):
"""Find the average of this column or fields""" """Find the average of this column or fields"""
def type(self, types={}): def type(self, types={}):
t = super().type(types)
if len(self) == 1: if len(self) == 1:
return self.a.type(types) return t
else: else:
return CsvFloat return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvAvg, CsvFloat return CsvAvg
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -726,14 +897,15 @@ class CsvExpr:
class Stddev(Expr): class Stddev(Expr):
"""Find the standard deviation of this column or fields""" """Find the standard deviation of this column or fields"""
def type(self, types={}): def type(self, types={}):
t = super().type(types)
if len(self) == 1: if len(self) == 1:
return self.a.type(types) return t
else: else:
return CsvFloat return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvStddev, CsvFloat return CsvStddev
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -747,14 +919,15 @@ class CsvExpr:
class GMean(Expr): class GMean(Expr):
"""Find the geometric mean of this column or fields""" """Find the geometric mean of this column or fields"""
def type(self, types={}): def type(self, types={}):
t = super().type(types)
if len(self) == 1: if len(self) == 1:
return self.a.type(types) return t
else: else:
return CsvFloat return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvGMean, CsvFloat return CsvGMean
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -768,14 +941,15 @@ class CsvExpr:
class GStddev(Expr): class GStddev(Expr):
"""Find the geometric stddev of this column or fields""" """Find the geometric stddev of this column or fields"""
def type(self, types={}): def type(self, types={}):
t = super().type(types)
if len(self) == 1: if len(self) == 1:
return self.a.type(types) return t
else: else:
return CsvFloat return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def fold(self, types={}): def fold(self, types={}):
if len(self) == 1: if len(self) == 1:
return CsvGStddev, CsvFloat return CsvGStddev
else: else:
return self.a.fold(types) return self.a.fold(types)
@@ -802,7 +976,7 @@ class CsvExpr:
def fold(self, types={}): def fold(self, types={}):
# don't typecheck by fields # don't typecheck by fields
return CsvSum, None return CsvSum
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if state is None: if state is None:
@@ -897,13 +1071,16 @@ class CsvExpr:
class Ratio(Expr): class Ratio(Expr):
"""Ratio of a fraction as a float""" """Ratio of a fraction as a float"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvFloat return CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
v = CsvFrac(self.a.eval(fields, state)) v = self.a.eval(fields, state)
if not float(v.b) and not float(v.a): if not hasattr(v, '__frac__'):
return CsvFloat(1) # emulates cast + eval
elif not v.b and not v.a:
return CsvFloat(1) return CsvFloat(1)
elif not float(v.b): elif not v.b:
return CsvFloat(mt.copysign(mt.inf, float(v.a))) return CsvFloat(mt.copysign(mt.inf, float(v.a)))
else: else:
return CsvFloat(float(v.a) / float(v.b)) return CsvFloat(float(v.a) / float(v.b))
@@ -912,10 +1089,15 @@ class CsvExpr:
class Total(Expr): class Total(Expr):
"""Total part of a fraction""" """Total part of a fraction"""
def type(self, types={}): def type(self, types={}):
return CsvInt t = super().type(types)
return CsvFloat if t in {CsvFloat, CsvFfrac} else CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
return CsvFrac(self.a.eval(fields, state)).b v = self.a.eval(fields, state)
if not hasattr(v, '__frac__'):
return v # emulates cast + eval
else:
return v.b
@func('abs', 'a') @func('abs', 'a')
class Abs(Expr): class Abs(Expr):
@@ -927,63 +1109,107 @@ class CsvExpr:
class Ceil(Expr): class Ceil(Expr):
"""Round up to nearest integer""" """Round up to nearest integer"""
def type(self, types={}): def type(self, types={}):
return CsvFloat t = super().type(types)
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
return CsvFloat(mt.ceil(float(self.a.eval(fields, state)))) v = self.a.eval(fields, state)
if hasattr(v, '__frac__'):
return CsvFfrac(mt.ceil(float(v.a)), mt.ceil(float(v.b)))
else:
return CsvFloat(mt.ceil(float(v)))
@func('floor', 'a') @func('floor', 'a')
class Floor(Expr): class Floor(Expr):
"""Round down to nearest integer""" """Round down to nearest integer"""
def type(self, types={}): def type(self, types={}):
return CsvFloat t = super().type(types)
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
return CsvFloat(mt.floor(float(self.a.eval(fields, state)))) v = self.a.eval(fields, state)
if hasattr(v, '__frac__'):
return CsvFfrac(mt.floor(float(v.a)), mt.floor(float(v.b)))
else:
return CsvFloat(mt.floor(float(v)))
@func('log', 'a[, b]') @func('log', 'a[, b]')
class Log(Expr): class Log(Expr):
"""Log of a with base e, or log of a with base b""" """Log of a with base e, or log of a with base b"""
def type(self, types={}): def type(self, types={}):
return CsvFloat t = self.a.type(types)
if len(self) > 1:
self.b.type(types)
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
v = self.a.eval(fields, state)
if len(self) == 1: if len(self) == 1:
return CsvFloat(mt.log( if hasattr(v, '__frac__'):
float(self.a.eval(fields, state)))) return CsvFfrac(
mt.log(float(v.a)),
mt.log(float(v.b)))
else:
return CsvFloat(
mt.log(float(v)))
else: else:
return CsvFloat(mt.log( e = float(self.b.eval(fields, state))
float(self.a.eval(fields, state)), if hasattr(v, '__frac__'):
float(self.b.eval(fields, state)))) return CsvFfrac(
mt.log(float(v.a), e),
mt.log(float(v.b), e))
else:
return CsvFloat(
mt.log(float(v), e))
@func('pow', 'a[, b]') @func('pow', 'a[, b]')
class Pow(Expr): class Pow(Expr):
"""e to the power of a, or a to the power of b""" """e to the power of a, or a to the power of b"""
def type(self, types={}): def type(self, types={}):
return CsvFloat t = self.a.type(types)
if len(self) > 1:
self.b.type(types)
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
v = self.a.eval(fields, state)
if len(self) == 1: if len(self) == 1:
return CsvFloat(mt.exp( if hasattr(v, '__frac__'):
float(self.a.eval(fields, state)))) return CsvFfrac(
mt.exp(float(v.a)),
mt.exp(float(v.b)))
else:
return CsvFloat(
mt.exp(float(v)))
else: else:
return CsvFloat(mt.pow( e = float(self.b.eval(fields, state))
float(self.a.eval(fields, state)), if hasattr(v, '__frac__'):
float(self.b.eval(fields, state)))) return CsvFfrac(
mt.pow(float(v.a), e),
mt.pow(float(v.b), e))
else:
return CsvFloat(
mt.pow(float(v), e))
@func('sqrt', 'a') @func('sqrt', 'a')
class Sqrt(Expr): class Sqrt(Expr):
"""Square root""" """Square root"""
def type(self, types={}): def type(self, types={}):
return CsvFloat t = super().type(types)
return CsvFfrac if hasattr(t, '__frac__') else CsvFloat
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
return CsvFloat(mt.sqrt(float(self.a.eval(fields, state)))) v = self.a.eval(fields, state)
if hasattr(v, '__frac__'):
return CsvFfrac(mt.sqrt(float(v.a)), mt.sqrt(float(v.b)))
else:
return CsvFloat(mt.sqrt(float(v)))
@func('isint', 'a') @func('isint', 'a')
class IsInt(Expr): class IsInt(Expr):
"""1 if a is an integer, otherwise 0""" """1 if a is an integer, otherwise 0"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -996,6 +1222,7 @@ class CsvExpr:
class IsFloat(Expr): class IsFloat(Expr):
"""1 if a is a float, otherwise 0""" """1 if a is a float, otherwise 0"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -1008,6 +1235,7 @@ class CsvExpr:
class IsFrac(Expr): class IsFrac(Expr):
"""1 if a is a fraction, otherwise 0""" """1 if a is a fraction, otherwise 0"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -1016,10 +1244,24 @@ class CsvExpr:
else: else:
return CsvInt(0) return CsvInt(0)
@func('isffrac', 'a')
class IsFfrac(Expr):
"""1 if a is a fraction, otherwise 0"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None):
if isinstance(self.a.eval(fields, state), CsvFfrac):
return CsvInt(1)
else:
return CsvInt(0)
@func('isinf', 'a') @func('isinf', 'a')
class IsInf(Expr): class IsInf(Expr):
"""1 if a is infinite, otherwise 0""" """1 if a is infinite, otherwise 0"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -1032,6 +1274,7 @@ class CsvExpr:
class IsNan(Expr): class IsNan(Expr):
"""1 if a is a NAN, otherwise 0""" """1 if a is a NAN, otherwise 0"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -1070,6 +1313,7 @@ class CsvExpr:
class NotNot(Expr): class NotNot(Expr):
"""1 if a is zero, otherwise 0""" """1 if a is zero, otherwise 0"""
def type(self, types={}): def type(self, types={}):
super().type(types)
return CsvInt return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
@@ -1128,6 +1372,10 @@ class CsvExpr:
@bop('==', 4) @bop('==', 4)
class Eq(Expr): class Eq(Expr):
"""1 if a equals b, otherwise 0""" """1 if a equals b, otherwise 0"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a.eval(fields, state) == self.b.eval(fields, state): if self.a.eval(fields, state) == self.b.eval(fields, state):
return CsvInt(1) return CsvInt(1)
@@ -1137,6 +1385,10 @@ class CsvExpr:
@bop('!=', 4) @bop('!=', 4)
class Ne(Expr): class Ne(Expr):
"""1 if a does not equal b, otherwise 0""" """1 if a does not equal b, otherwise 0"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a.eval(fields, state) != self.b.eval(fields, state): if self.a.eval(fields, state) != self.b.eval(fields, state):
return CsvInt(1) return CsvInt(1)
@@ -1146,6 +1398,10 @@ class CsvExpr:
@bop('<', 4) @bop('<', 4)
class Lt(Expr): class Lt(Expr):
"""1 if a is less than b""" """1 if a is less than b"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a.eval(fields, state) < self.b.eval(fields, state): if self.a.eval(fields, state) < self.b.eval(fields, state):
return CsvInt(1) return CsvInt(1)
@@ -1155,6 +1411,10 @@ class CsvExpr:
@bop('<=', 4) @bop('<=', 4)
class Le(Expr): class Le(Expr):
"""1 if a is less than or equal to b""" """1 if a is less than or equal to b"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a.eval(fields, state) <= self.b.eval(fields, state): if self.a.eval(fields, state) <= self.b.eval(fields, state):
return CsvInt(1) return CsvInt(1)
@@ -1164,6 +1424,10 @@ class CsvExpr:
@bop('>', 4) @bop('>', 4)
class Gt(Expr): class Gt(Expr):
"""1 if a is greater than b""" """1 if a is greater than b"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a.eval(fields, state) > self.b.eval(fields, state): if self.a.eval(fields, state) > self.b.eval(fields, state):
return CsvInt(1) return CsvInt(1)
@@ -1173,6 +1437,10 @@ class CsvExpr:
@bop('>=', 4) @bop('>=', 4)
class Ge(Expr): class Ge(Expr):
"""1 if a is greater than or equal to b""" """1 if a is greater than or equal to b"""
def type(self, types={}):
super().type(types)
return CsvInt
def eval(self, fields={}, state=None): def eval(self, fields={}, state=None):
if self.a.eval(fields, state) >= self.b.eval(fields, state): if self.a.eval(fields, state) >= self.b.eval(fields, state):
return CsvInt(1) return CsvInt(1)
@@ -1661,7 +1929,7 @@ def compile(fields_, results,
file=sys.stderr) file=sys.stderr)
sys.exit(2) sys.exit(2)
for t in [CsvInt, CsvFloat, CsvFrac]: for t in [CsvInt, CsvFloat, CsvFrac, CsvFfrac]:
for r in results: for r in results:
if k in r and r[k].strip(): if k in r and r[k].strip():
try: try:
@@ -1683,11 +1951,12 @@ def compile(fields_, results,
types___[k] = expr.type(types__) types___[k] = expr.type(types__)
# foldcheck field exprs # foldcheck field exprs
folds___ = {k: (CsvSum, t) for k, v in types__.items()} folds___ = {k: CsvSum for k, v in types__.items()}
for k, expr in exprs.items(): for k, expr in exprs.items():
folds___[k] = expr.fold(types__) folds___[k] = expr.fold(types__)
# instantiate folds and resolve fold types # instantiate folds and resolve fold types
folds___ = {k: (f(), t or types___[k]) for k, (f, t) in folds___.items()} folds___ = {k: f() for k, f in folds___.items()}
folds___ = {k: (f, f.type(types___[k])) for k, f in folds___.items()}
# create result class # create result class
def __new__(cls, _state=None, **r): def __new__(cls, _state=None, **r):
@@ -2410,7 +2679,7 @@ def list_fields(csv_paths, **args):
types_ = {} types_ = {}
for k in fields_: for k in fields_:
try: try:
for t in [CsvInt, CsvFloat, CsvFrac]: for t in [CsvInt, CsvFloat, CsvFrac, CsvFfrac]:
for r in results: for r in results:
if k in r and r[k].strip(): if k in r and r[k].strip():
try: try:
@@ -2466,7 +2735,7 @@ def list_computed(fields_, results, Result, **args):
types_ = {} types_ = {}
for k in fields_: for k in fields_:
try: try:
for t in [CsvInt, CsvFloat, CsvFrac]: for t in [CsvInt, CsvFloat, CsvFrac, CsvFfrac]:
for r in results: for r in results:
if k in r and r[k].strip(): if k in r and r[k].strip():
try: try:
+1 -1
View File
@@ -36,7 +36,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
+1 -1
View File
@@ -40,7 +40,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
+1 -1
View File
@@ -50,7 +50,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
+1 -1
View File
@@ -41,7 +41,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
+1 -1
View File
@@ -36,7 +36,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError:
+1 -1
View File
@@ -36,7 +36,7 @@ class CsvInt(co.namedtuple('CsvInt', 'a')):
def __new__(cls, a=0): def __new__(cls, a=0):
if isinstance(a, CsvInt): if isinstance(a, CsvInt):
return a return a
if isinstance(a, str): elif isinstance(a, str):
try: try:
a = int(a, 0) a = int(a, 0)
except ValueError: except ValueError: