Skip to content

Commit 0af529d

Browse files
committed
refactor: 💡 use abstract class in capacity estimator
1 parent 30dde7a commit 0af529d

File tree

2 files changed

+88
-103
lines changed

2 files changed

+88
-103
lines changed

src/codegen/capacity/CapacityEstimatorCodegen.ts

Lines changed: 80 additions & 103 deletions
Original file line numberDiff line numberDiff line change
@@ -3,22 +3,24 @@ import {JsExpression} from '@jsonjoy.com/codegen/lib/util/JsExpression';
33
import {normalizeAccessor} from '@jsonjoy.com/codegen/lib/util/normalizeAccessor';
44
import {MaxEncodingOverhead, maxEncodingCapacity} from '@jsonjoy.com/util/lib/json-size';
55
import {BoolType, ConType, NumType, KeyOptType} from '../../type';
6-
import type {ArrType, MapType, KeyType, ObjType, OrType, RefType, Type} from '../../type';
6+
import type {AnyType, ArrType, BinType, MapType, KeyType, ObjType, OrType, RefType, StrType, Type} from '../../type';
77
import {DiscriminatorCodegen} from '../discriminator';
88
import {lazyKeyedFactory} from '../util';
9+
import {AbstractCodegen} from '../AbstractCodege';
10+
import type {SchemaPath} from '../types';
911

1012
export type CompiledCapacityEstimator = (value: unknown) => number;
1113

1214
class IncrementSizeStep {
1315
constructor(public readonly inc: number) {}
1416
}
1517

16-
export class CapacityEstimatorCodegen {
18+
export class CapacityEstimatorCodegen extends AbstractCodegen<CompiledCapacityEstimator> {
1719
public static readonly get = lazyKeyedFactory((type: Type, name?: string) => {
1820
const codegen = new CapacityEstimatorCodegen(type, name);
1921
const r = codegen.codegen.options.args[0];
2022
const expression = new JsExpression(() => r);
21-
codegen.onNode(expression, type);
23+
codegen.onNode([], expression, type);
2224
return codegen.compile();
2325
});
2426

@@ -28,6 +30,7 @@ export class CapacityEstimatorCodegen {
2830
public readonly type: Type,
2931
name?: string,
3032
) {
33+
super();
3134
this.codegen = new Codegen({
3235
name: 'approxSize' + (name ? '_' + name : ''),
3336
args: ['r0'],
@@ -48,94 +51,118 @@ export class CapacityEstimatorCodegen {
4851
this.codegen.linkDependency(maxEncodingCapacity, 'maxEncodingCapacity');
4952
}
5053

51-
public inc(inc: number): void {
52-
this.codegen.step(new IncrementSizeStep(inc));
54+
private inc(amount: number): void {
55+
this.codegen.js(/* js */ `size += ${amount};`);
5356
}
5457

55-
public compile(): CompiledCapacityEstimator {
56-
return this.codegen.compile();
58+
protected onAny(path: SchemaPath, r: JsExpression, type: AnyType): void {
59+
const codegen = this.codegen;
60+
const rv = codegen.var(r.use());
61+
codegen.js(/* js */ `size += maxEncodingCapacity(${rv});`);
5762
}
5863

59-
protected genAny(value: JsExpression): void {
60-
const codegen = this.codegen;
61-
const r = codegen.var(value.use());
62-
codegen.js(/* js */ `size += maxEncodingCapacity(${r});`);
64+
protected onCon(path: SchemaPath, r: JsExpression, type: ConType): void {
65+
this.inc(maxEncodingCapacity(type.literal()));
6366
}
6467

65-
protected genArr(value: JsExpression, type: ArrType<any, any, any>): void {
68+
protected onBool(path: SchemaPath, r: JsExpression, type: BoolType): void {
69+
this.inc(MaxEncodingOverhead.Boolean);
70+
}
71+
72+
protected onNum(path: SchemaPath, r: JsExpression, type: NumType): void {
73+
this.inc(MaxEncodingOverhead.Number);
74+
}
75+
76+
protected onStr(path: SchemaPath, r: JsExpression, type: StrType): void {
77+
this.inc(MaxEncodingOverhead.String);
78+
this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.StringLengthMultiplier} * ${r.use()}.length;`);
79+
}
80+
81+
protected onBin(path: SchemaPath, r: JsExpression, type: BinType): void {
82+
this.inc(MaxEncodingOverhead.Binary);
83+
this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.BinaryLengthMultiplier} * ${r.use()}.length;`);
84+
}
85+
86+
protected onArr(path: SchemaPath, r: JsExpression, type: ArrType): void {
6687
const codegen = this.codegen;
6788
this.inc(MaxEncodingOverhead.Array);
68-
const rLen = codegen.var(/* js */ `${value.use()}.length`);
89+
const rLen = codegen.var(/* js */ `${r.use()}.length`);
6990
codegen.js(/* js */ `size += ${MaxEncodingOverhead.ArrayElement} * ${rLen}`);
70-
const itemType = type._type as Type | undefined;
71-
const headType = type._head as Type[] | undefined;
72-
const tailType = type._tail as Type[] | undefined;
73-
const headLength = headType ? headType.length : 0;
74-
const tailLength = tailType ? tailType.length : 0;
75-
if (itemType) {
76-
const isConstantSizeType = type instanceof ConType || type instanceof BoolType || type instanceof NumType;
91+
const {_head = [], _type, _tail = []} = type;
92+
const headLength = _head.length;
93+
const tailLength = _tail.length;
94+
// const tupleLength = headLength + tailLength;
95+
// if (tupleLength) {
96+
// codegen.if(/* js */ `${rLen} < ${tupleLength}`, () => {
97+
// codegen.js(/* js */ `throw new Error('INV_LEN');`);
98+
// });
99+
// }
100+
if (_type) {
101+
const isConstantSizeType = _type instanceof ConType || _type instanceof BoolType || _type instanceof NumType;
77102
if (isConstantSizeType) {
78103
const elementSize =
79-
type instanceof ConType
80-
? maxEncodingCapacity(type.literal())
81-
: type instanceof BoolType
104+
_type instanceof ConType
105+
? maxEncodingCapacity(_type.literal())
106+
: _type instanceof BoolType
82107
? MaxEncodingOverhead.Boolean
83108
: MaxEncodingOverhead.Number;
84109
codegen.js(/* js */ `size += ${rLen} * ${elementSize};`);
85110
} else {
86-
const r = codegen.var(value.use());
111+
const rv = codegen.var(r.use());
87112
const ri = codegen.getRegister();
88113
codegen.js(/* js */ `for(var ${ri} = ${headLength}; ${ri} < ${rLen} - ${tailLength}; ${ri}++) {`);
89-
this.onNode(new JsExpression(() => /* js */ `${r}[${ri}]`), itemType);
114+
this.onNode([...path, {r: ri}], new JsExpression(() => /* js */ `${rv}[${ri}]`), _type);
90115
codegen.js(/* js */ `}`);
91116
}
92117
}
93118
if (headLength > 0) {
94-
const r = codegen.var(value.use());
95-
for (let i = 0; i < headLength; i++) this.onNode(new JsExpression(() => /* js */ `${r}[${i}]`), headType![i]);
119+
const rr = codegen.var(r.use());
120+
for (let i = 0; i < headLength; i++) this.onNode([...path, i], new JsExpression(() => /* js */ `${rr}[${i}]`), _head![i]);
96121
}
97122
if (tailLength > 0) {
98-
const r = codegen.var(value.use());
123+
const rr = codegen.var(r.use());
99124
for (let i = 0; i < tailLength; i++)
100-
this.onNode(new JsExpression(() => /* js */ `${r}[${rLen} - ${i + 1}]`), tailType![i]);
125+
this.onNode([...path, {r: `${rLen} - ${tailLength - i}`}], new JsExpression(() => /* js */ `${rr}[${rLen} - ${i + 1}]`), _tail![i]);
101126
}
102127
}
103128

104-
protected genObj(value: JsExpression, type: Type): void {
129+
protected onObj(path: SchemaPath, r: JsExpression, type: ObjType): void {
105130
const codegen = this.codegen;
106-
const r = codegen.var(value.use());
107-
const objectType = type as ObjType;
108-
const encodeUnknownFields = !!objectType.schema.encodeUnknownKeys;
131+
const rv = codegen.var(r.use());
132+
const encodeUnknownFields = !!type.schema.encodeUnknownKeys;
109133
if (encodeUnknownFields) {
110-
codegen.js(/* js */ `size += maxEncodingCapacity(${r});`);
134+
codegen.js(/* js */ `size += maxEncodingCapacity(${rv});`);
111135
return;
112136
}
113137
this.inc(MaxEncodingOverhead.Object);
114-
const fields = objectType.keys;
115-
for (const f of fields) {
116-
const field = f as KeyType<any, any>;
138+
const fields = type.keys;
139+
for (const field of fields) {
117140
const accessor = normalizeAccessor(field.key);
118-
const fieldExpression = new JsExpression(() => `${r}${accessor}`);
141+
const fieldExpression = new JsExpression(() => `${rv}${accessor}`);
119142
const isOptional = field instanceof KeyOptType;
120143
if (isOptional) {
121-
codegen.if(/* js */ `${JSON.stringify(field.key)} in ${r}`, () => {
144+
codegen.if(/* js */ `${JSON.stringify(field.key)} in ${rv}`, () => {
122145
this.inc(MaxEncodingOverhead.ObjectElement);
123146
this.inc(maxEncodingCapacity(field.key));
124-
this.onNode(fieldExpression, field.val);
147+
this.onNode([...path, field.key], fieldExpression, field.val);
125148
});
126149
} else {
127150
this.inc(MaxEncodingOverhead.ObjectElement);
128151
this.inc(maxEncodingCapacity(field.key));
129-
this.onNode(fieldExpression, field.val);
152+
this.onNode([...path, field.key], fieldExpression, field.val);
130153
}
131154
}
132155
}
133156

134-
protected genMap(value: JsExpression, type: MapType<any>): void {
157+
protected onKey(path: SchemaPath, r: JsExpression, type: KeyType<any, any>): void {
158+
this.onNode([...path, type.key], r, type.val);
159+
}
160+
161+
protected onMap(path: SchemaPath, r: JsExpression, type: MapType): void {
135162
const codegen = this.codegen;
136163
this.inc(MaxEncodingOverhead.Object);
137-
const r = codegen.var(value.use());
138-
const rKeys = codegen.var(/* js */ `Object.keys(${r})`);
164+
const rv = codegen.var(r.use());
165+
const rKeys = codegen.var(/* js */ `Object.keys(${rv})`);
139166
const rKey = codegen.var();
140167
const rLen = codegen.var(/* js */ `${rKeys}.length`);
141168
codegen.js(/* js */ `size += ${MaxEncodingOverhead.ObjectElement} * ${rLen}`);
@@ -146,81 +173,31 @@ export class CapacityEstimatorCodegen {
146173
codegen.js(
147174
/* js */ `size += ${MaxEncodingOverhead.String} + ${MaxEncodingOverhead.StringLengthMultiplier} * ${rKey}.length;`,
148175
);
149-
this.onNode(new JsExpression(() => /* js */ `${r}[${rKey}]`), valueType);
176+
this.onNode([...path, {r: rKey}], new JsExpression(() => /* js */ `${rv}[${rKey}]`), valueType);
150177
codegen.js(/* js */ `}`);
151178
}
152179

153-
protected genRef(value: JsExpression, type: RefType<any>): void {
154-
const system = type.system;
155-
if (!system) throw new Error('NO_SYSTEM');
156-
const estimator = CapacityEstimatorCodegen.get(system.resolve(type.ref()).type);
180+
protected onRef(path: SchemaPath, r: JsExpression, type: RefType): void {
181+
const system = type.getSystem();
182+
const alias = system.resolve(type.ref());
183+
const estimator = CapacityEstimatorCodegen.get(alias.type);
157184
const d = this.codegen.linkDependency(estimator);
158-
this.codegen.js(/* js */ `size += ${d}(${value.use()});`);
185+
this.codegen.js(/* js */ `size += ${d}(${r.use()});`);
159186
}
160187

161-
protected genOr(value: JsExpression, type: OrType<any>): void {
188+
protected onOr(path: SchemaPath, r: JsExpression, type: OrType): void {
162189
const codegen = this.codegen;
163190
const discriminator = DiscriminatorCodegen.get(type);
164191
const d = codegen.linkDependency(discriminator);
165192
const types = type.types;
166193
codegen.switch(
167-
/* js */ `${d}(${value.use()})`,
194+
/* js */ `${d}(${r.use()})`,
168195
types.map((childType: Type, index: number) => [
169196
index,
170197
() => {
171-
this.onNode(value, childType);
198+
this.onNode(path, r, childType);
172199
},
173200
]),
174201
);
175202
}
176-
177-
protected genKey(r: JsExpression, type: KeyType<any, any>): void {
178-
this.onNode(r, type.val);
179-
}
180-
181-
protected onNode(value: JsExpression, type: Type): void {
182-
const kind = type.kind();
183-
switch (kind) {
184-
case 'any':
185-
this.genAny(value);
186-
break;
187-
case 'bool':
188-
this.inc(MaxEncodingOverhead.Boolean);
189-
break;
190-
case 'num':
191-
this.inc(MaxEncodingOverhead.Number);
192-
break;
193-
case 'str':
194-
this.inc(MaxEncodingOverhead.String);
195-
this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.StringLengthMultiplier} * ${value.use()}.length;`);
196-
break;
197-
case 'bin':
198-
this.inc(MaxEncodingOverhead.Binary);
199-
this.codegen.js(/* js */ `size += ${MaxEncodingOverhead.BinaryLengthMultiplier} * ${value.use()}.length;`);
200-
break;
201-
case 'con':
202-
this.inc(maxEncodingCapacity((type as ConType).literal()));
203-
break;
204-
case 'arr':
205-
this.genArr(value, type as ArrType<any, any, any>);
206-
break;
207-
case 'obj':
208-
this.genObj(value, type);
209-
break;
210-
case 'key':
211-
this.genKey(value, type as KeyType<any, any>);
212-
break;
213-
case 'map':
214-
this.genMap(value, type as MapType<any>);
215-
break;
216-
case 'ref':
217-
this.genRef(value, type as RefType<any>);
218-
break;
219-
case 'or':
220-
this.genOr(value, type as OrType<any>);
221-
break;
222-
default:
223-
throw new Error(`"${kind}" type capacity estimation not implemented`);
224-
}
225-
}
226203
}

src/codegen/capacity/__tests__/CapacityEstimatorCodegenContext.spec.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,14 @@ describe('"arr" type', () => {
8787
expect(estimator([])).toBe(maxEncodingCapacity([]));
8888
});
8989

90+
test('"con" elements', () => {
91+
const type = t.Array(t.con('abc'));
92+
const estimator = CapacityEstimatorCodegen.get(type);
93+
expect(estimator([])).toBe(maxEncodingCapacity([]));
94+
expect(estimator(['abc'])).toBe(maxEncodingCapacity(['abc']));
95+
expect(estimator(['abc', 'abc'])).toBe(maxEncodingCapacity(['abc', 'abc']));
96+
});
97+
9098
test('simple elements', () => {
9199
const system = new ModuleType();
92100
const type = system.t.arr;

0 commit comments

Comments
 (0)