}
abstract double op( double d0, double d1 );
@Override void apply(Env global, Env local) {
// Expect we can broadcast across all functions as needed.
Env env = local == null ? global : local;
Frame fr0 = null, fr1 = null;
double d0=0, d1=0;
if( env.isAry() ) fr1 = (Frame) env.pop(); else d1 = (double)env.pop(); //String k0 = env.key();
if( env.isAry() ) fr0 = (Frame) env.pop(); else d0 = (double)env.pop(); //String k1 = env.key();
if( fr0==null && fr1==null ) {
env.push(op(d0, d1));
return;
}
final boolean lf = fr0 != null;
final boolean rf = fr1 != null;
final double df0 = d0, df1 = d1;
Frame fr = null; // Do-All frame
int ncols = 0; // Result column count
if( fr0 !=null ) { // Left?
ncols = fr0.numCols();
if( fr1 != null ) {
if( fr0.numCols() != fr1.numCols() ||
fr0.numRows() != fr1.numRows() )
throw new IllegalArgumentException("Arrays must be same size: "+fr0+" vs "+fr1);
fr = new Frame(fr0).add(fr1);
} else {
fr = fr0;
}
} else {
ncols = fr1.numCols();
fr = fr1;
}
final ASTBinOp bin = this; // Final 'this' so can use in closure
// Run an arbitrary binary op on one or two frames & scalars
Frame fr2 = new MRTask() {
@Override public void map( Chunk chks[], NewChunk nchks[] ) {
for( int i=0; i<nchks.length; i++ ) {
NewChunk n =nchks[i];
int rlen = chks[0].len();
Chunk c0 = chks[i];
if( (!c0.vec().isEnum() &&
!(lf && rf && chks[i+nchks.length].vec().isEnum())) ||
bin instanceof ASTEQ ||
bin instanceof ASTNE ) {
for( int r=0; r<rlen; r++ ) {
double lv; double rv;
if (lf) {
if(chks[i].isNA0(r)) { n.addNum(Double.NaN); continue; }
lv = chks[i].at0(r);
} else {
if (Double.isNaN(df0)) { n.addNum(Double.NaN); continue; }
lv = df0;
}
if (rf) {
if(chks[i].isNA0(r)) { n.addNum(Double.NaN); continue; }
rv = chks[i+(lf ? nchks.length:0)].at0(r);
} else {
if (Double.isNaN(df1)) { n.addNum(Double.NaN); continue; }
rv = df1;
}
n.addNum(bin.op(lv, rv));
}
} else {
for( int r=0; r<rlen; r++ ) n.addNA();
}
}
}
}.doAll(ncols,fr).outputFrame((lf ? fr0 : fr1)._names,null);
env.push(fr2);
}