Skip to content

Commit

Permalink
feat(#1): use simple expression as parameter
Browse files Browse the repository at this point in the history
  • Loading branch information
Justin-ZS committed Dec 23, 2020
1 parent 9358d52 commit c4a5707
Show file tree
Hide file tree
Showing 14 changed files with 141 additions and 125 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Inspired by [dplyr](https://dplyr.tidyverse.org/) and [Arquero](https://github.c
### Run locally
1. Clone this [repo](https://github.com/Justin-ZS/bow)
1. Run `npm i`
1. Run `npm run server`
1. Run `npm start`
1. Do whatever you like

### Develop RoadMap
Expand Down
2 changes: 1 addition & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ <h2>Example</h2>
age: [12, 12, 12, 14, 14],
})
.groupBy('name', 'age')
.summarize({ sumOfValue: 'sum(value)' })
.summarize({ sumOfValue: bow.Op.sum('value') })
.orderBy((a, b) => b.age - a.age)
.addColumns({
added: ({ age, sumOfValue }) => age + sumOfValue
Expand Down
22 changes: 20 additions & 2 deletions src/expression/parse.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,35 @@
import { parse } from 'acorn';
import * as ESTree from 'estree';
import transformAST from './transform';

const PARSE_OPTS: acorn.Options = {
ecmaVersion: 'latest',
sourceType: 'script',
};
export const FUNC_PREFIX = '_is_Fn_\u001d';

const replacer = (key, value) => {
if (typeof value === 'function') return `${FUNC_PREFIX}${value}`;
return value;
};

const transformers = {
'Literal': (node) => {
if (node.value.startsWith(FUNC_PREFIX)) {
const funcAST = parse(JSON.parse(node.raw).slice(FUNC_PREFIX.length), PARSE_OPTS);
return funcAST;
}
return node;
}
};

// Expression -> ES AST
export const parseES = (expr: string | Function): ESTree.Node => {
if (!expr) throw Error("ParseES: Empty Expression");
try {
const ast: any = parse(`expr=${expr}`, PARSE_OPTS);
return ast.body[0].expression.right;
const ast: any = parse(`expr=${JSON.stringify(expr, replacer)}`, PARSE_OPTS);
const content = ast.body[0].expression.right;
return transformAST(content, null, transformers);
} catch(err) {
throw Error(`ParseES: ${err}`);
}
Expand Down
7 changes: 4 additions & 3 deletions src/expression/transform/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,20 @@ import * as ESTree from 'estree';
import baseTransformers, { RecursiveTransformer } from './base';
import customTransformers from './custom';

const getTransformer = (node: ESTree.Node) => {
const getTransformer = (node: ESTree.Node, custom) => {
const type = node.type;
return customTransformers[type] ?? baseTransformers[type] ?? (x => x);
return custom[type] ?? baseTransformers[type] ?? (x => x);
};

const transformAST = <T = unknown>(
root: ESTree.Node,
state: T = null,
customTs = customTransformers,
) => (function t(
node: ESTree.Node,
st: T,
) {
const transformer = getTransformer(node) as RecursiveTransformer<T>;
const transformer = getTransformer(node, customTs) as RecursiveTransformer<T>;
return transformer(node, st, t);
})(root, state);

Expand Down
3 changes: 2 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Table } from './modules';
import { Table, Op } from './modules';
import { TableEx } from './extensions';

export {
Table,
TableEx,
Op,
};

69 changes: 26 additions & 43 deletions src/modules/Aggregator.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,50 @@
import { AggregateType, FieldDescription, DataType, AggregateDescription } from 'Typings';
import { AggregateType, DataType } from 'Typings';

export abstract class Aggregator {
readonly field: FieldDescription;
readonly name: string;

type: AggregateType;
dataType = DataType.Null;

constructor(targetField: FieldDescription, name?: string) {
this.field = targetField;
this.name = name;
}

abstract addUp(value: unknown): Aggregator;
abstract get value(): unknown;
abstract addUp(getter: () => unknown): void;
abstract value: number;

isAnonymous() {
return name === undefined;
}
clone(): Aggregator {
const Ctor = this.constructor as any;
return new Ctor(this.field, this, name);
return new Ctor();
}
}

class SumAggregator extends Aggregator {
type = AggregateType.Sum;
dataType = DataType.Number;

private sum: number = null;

addUp(value: number) {
addUp(getter) {
const value = getter();
if (value == null) return this;

this.sum += +value; // @TODO: handle type coerce
return this;
}

get value() {
return this.sum;
}
}
class MinAggregator extends Aggregator {
type = AggregateType.Min;
dataType = DataType.Number;

private min: number = null;

addUp(value: number) {
addUp(getter) {
const value = getter();
if (value == null) return this;

if (this.min == null) {
this.min = value;
} else {
this.min = Math.min(this.min, +value); // @TODO: handle type coerce
}

return this;
}
get value() {
return this.min;
Expand All @@ -62,18 +53,18 @@ class MinAggregator extends Aggregator {
class MaxAggregator extends Aggregator {
type = AggregateType.Max;
dataType = DataType.Number;

private max: number = null;

addUp(value: number) {
addUp(getter) {
const value = getter();
if (value == null) return this;

if (this.max == null) {
this.max = value;
} else {
this.max = Math.max(this.max, +value); // @TODO: handle type coerce
}

return this;
}
get value() {
return this.max;
Expand All @@ -82,6 +73,7 @@ class MaxAggregator extends Aggregator {
class CountAggregator extends Aggregator {
type = AggregateType.Count;
dataType = DataType.Number;

private count = 0;

addUp() {
Expand All @@ -95,14 +87,14 @@ class CountAggregator extends Aggregator {
class AvgAggregator extends Aggregator {
type = AggregateType.Avg;
dataType = DataType.Number;
private sumAgg = new SumAggregator(this.field, this.name);
private cntAgg = new CountAggregator(this.field, this.name);

addUp(value: number) {
private sumAgg = new SumAggregator();
private cntAgg = new CountAggregator();

addUp(getter) {
const value = getter();
this.sumAgg.addUp(value);
this.cntAgg.addUp();

return this;
}
get value() {
const sum = this.sumAgg.value;
Expand All @@ -115,19 +107,10 @@ class AvgAggregator extends Aggregator {
}
}

export const getAggregatorByDescription = (aggDesc: AggregateDescription): Aggregator => {
switch (aggDesc.type) {
case AggregateType.Sum:
return new SumAggregator(aggDesc.field, aggDesc.name);
case AggregateType.Min:
return new MinAggregator(aggDesc.field, aggDesc.name);
case AggregateType.Max:
return new MaxAggregator(aggDesc.field, aggDesc.name);
case AggregateType.Count:
return new CountAggregator(aggDesc.field, aggDesc.name);
case AggregateType.Avg:
return new AvgAggregator(aggDesc.field, aggDesc.name);
default:
throw Error(`Not Supported Aggregate Type: ${aggDesc.type}`);
}
export const aggMapping = {
[AggregateType.Sum]: SumAggregator,
[AggregateType.Avg]: AvgAggregator,
[AggregateType.Count]: CountAggregator,
[AggregateType.Max]: MaxAggregator,
[AggregateType.Min]: MinAggregator,
};
67 changes: 44 additions & 23 deletions src/modules/aggregate.ts
Original file line number Diff line number Diff line change
@@ -1,23 +1,34 @@
import { ITable, AggregateDescription } from 'Typings';
import { range } from 'PureUtils';
import { ITable, FieldDescription } from 'Typings';
import { range, mapRecord } from 'PureUtils';
import { TableEx } from 'Extensions';

import { extractGroupedColumns } from './/group';
import { Aggregator, getAggregatorByDescription } from './Aggregator';
import { extractGroupedColumns } from './group';
import { Aggregator, aggMapping } from './Aggregator';

export type AggregatorDescription = {
name?: string,
agg: Aggregator,
getterFn: (rowIdx, table) => () => unknown,
}

const aggregateGroupedTable = (
aggs: Aggregator[],
descs: AggregatorDescription[],
table: ITable,
) => {
const { size, keys, map, names } = table.groups;

const groups = range(0, size);
const aggss = aggs.map(agg => groups.map(() => agg.clone()));
const descss: AggregatorDescription[][] = descs
.map(desc => groups
.map(() => ({
...desc,
agg: desc.agg.clone(),
})));

table.traverse((rowIdx) => {
aggss.forEach(aggs => {
const agg = aggs[keys[rowIdx]];
agg.addUp(table.getCell(agg.field.name, rowIdx));
descss.forEach(descs => {
const desc = descs[keys[rowIdx]];
desc.agg.addUp(desc.getterFn(rowIdx, table));
});
});

Expand All @@ -28,40 +39,50 @@ const aggregateGroupedTable = (
data[names[idx]] = column;
});
// aggregated values data
aggs.forEach((agg, idx) => {
data[agg.name] = aggss[idx].map(agg => agg.value);
descs.forEach((desc, idx) => {
data[desc.name] = descss[idx].map(({ agg }) => agg.value);
});

return TableEx.fromColumns(data);
};

const aggregateFlatTable = (
aggs: Aggregator[],
descs: AggregatorDescription[],
table: ITable,
) => {
table.traverse((rowIdx) => {
aggs.forEach(agg => {
agg.addUp(table.getCell(agg.field.name, rowIdx));
descs.forEach(desc => {
desc.agg.addUp(desc.getterFn(rowIdx, table));
});
});

const data = aggs
.reduce((acc, agg) => {
acc[agg.name] = [agg.value];
const data = descs
.reduce((acc, { name, agg }) => {
acc[name] = [agg.value];
return acc;
}, {});

return TableEx.fromColumns(data);
};

export const getAggregatedTable = (
aggDescs: AggregateDescription[],
aggDescs: AggregatorDescription[],
table: ITable,
) => {
const aggs = aggDescs.map(getAggregatorByDescription);

if (table.isGrouped) {
return aggregateGroupedTable(aggs, table);
return aggregateGroupedTable(aggDescs, table);
}
return aggregateFlatTable(aggs, table);
};
return aggregateFlatTable(aggDescs, table);
};

const fields2GetterFn = (fields: FieldDescription[]) =>
(rowIdx: number, table: ITable) =>
() => (fields.length === 1
? table.getCell(fields[0].name, rowIdx)
: fields.map(({ name }) => table.getCell(name, rowIdx)));

export const aggOps = mapRecord((Agg) =>
(...fields: FieldDescription[]): AggregatorDescription => ({
agg: new Agg(),
getterFn: fields2GetterFn(fields),
}), aggMapping);
3 changes: 3 additions & 0 deletions src/modules/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import Table from './table';

export * from './column';
import * as Op from './operators';

export {
Table,
Op
};
19 changes: 0 additions & 19 deletions src/modules/operators/Operator.ts

This file was deleted.

Loading

0 comments on commit c4a5707

Please sign in to comment.