@jsonjoy.com/json-type
Version:
High-performance JSON Pointer implementation
230 lines (229 loc) • 8.52 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.codegen = exports.generate = exports.or = exports.ref = exports.map = exports.obj = exports.tup = exports.arr = exports.const_ = exports.bin = exports.str = exports.num = exports.bool = exports.any = void 0;
const JsExpression_1 = require("@jsonjoy.com/util/lib/codegen/util/JsExpression");
const json_size_1 = require("@jsonjoy.com/util/lib/json-size");
const CapacityEstimatorCodegenContext_1 = require("./CapacityEstimatorCodegenContext");
const normalizeAccessor = (key) => {
// Simple property access for valid identifiers, bracket notation otherwise
if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
return `.${key}`;
}
return `[${JSON.stringify(key)}]`;
};
const any = (ctx, value, type) => {
const codegen = ctx.codegen;
codegen.link('Value');
const r = codegen.var(value.use());
codegen.if(`${r} instanceof Value`, () => {
codegen.if(`${r}.type`, () => {
ctx.codegen.js(`size += ${r}.type.capacityEstimator()(${r}.data);`);
}, () => {
ctx.codegen.js(`size += maxEncodingCapacity(${r}.data);`);
});
}, () => {
ctx.codegen.js(`size += maxEncodingCapacity(${r});`);
});
};
exports.any = any;
const bool = (ctx, value) => {
ctx.inc(5 /* MaxEncodingOverhead.Boolean */);
};
exports.bool = bool;
const num = (ctx, value) => {
ctx.inc(22 /* MaxEncodingOverhead.Number */);
};
exports.num = num;
const str = (ctx, value) => {
ctx.inc(5 /* MaxEncodingOverhead.String */);
ctx.codegen.js(`size += ${5 /* MaxEncodingOverhead.StringLengthMultiplier */} * ${value.use()}.length;`);
};
exports.str = str;
const bin = (ctx, value) => {
ctx.inc(41 /* MaxEncodingOverhead.Binary */);
ctx.codegen.js(`size += ${2 /* MaxEncodingOverhead.BinaryLengthMultiplier */} * ${value.use()}.length;`);
};
exports.bin = bin;
const const_ = (ctx, value, type) => {
const constType = type;
ctx.inc((0, json_size_1.maxEncodingCapacity)(constType.value()));
};
exports.const_ = const_;
const arr = (ctx, value, type) => {
const codegen = ctx.codegen;
ctx.inc(5 /* MaxEncodingOverhead.Array */);
const rLen = codegen.var(`${value.use()}.length`);
const arrayType = type; // ArrType
const elementType = arrayType.type;
codegen.js(`size += ${1 /* MaxEncodingOverhead.ArrayElement */} * ${rLen}`);
const fn = elementType.compileCapacityEstimator({
system: ctx.options.system,
name: ctx.options.name,
});
const isConstantSizeType = ['con', 'bool', 'num'].includes(elementType.getTypeName());
if (isConstantSizeType) {
const rFn = codegen.linkDependency(fn);
codegen.js(`size += ${rLen} * ${rFn}(${elementType.random()});`);
}
else {
const rFn = codegen.linkDependency(fn);
const ri = codegen.var('0');
codegen.js(`for (; ${ri} < ${rLen}; ${ri}++) {`);
codegen.js(`size += ${rFn}(${value.use()}[${ri}]);`);
codegen.js(`}`);
}
};
exports.arr = arr;
const tup = (ctx, value, type) => {
const codegen = ctx.codegen;
const r = codegen.var(value.use());
const tupleType = type; // TupType
const types = tupleType.types;
const overhead = 5 /* MaxEncodingOverhead.Array */ + 1 /* MaxEncodingOverhead.ArrayElement */ * types.length;
ctx.inc(overhead);
for (let i = 0; i < types.length; i++) {
const elementType = types[i];
const fn = elementType.compileCapacityEstimator({
system: ctx.options.system,
name: ctx.options.name,
});
const rFn = codegen.linkDependency(fn);
codegen.js(`size += ${rFn}(${r}[${i}]);`);
}
};
exports.tup = tup;
const obj = (ctx, value, type, estimateCapacityFn) => {
const codegen = ctx.codegen;
const r = codegen.var(value.use());
const objectType = type; // ObjType
const encodeUnknownFields = !!objectType.schema.encodeUnknownFields;
if (encodeUnknownFields) {
codegen.js(`size += maxEncodingCapacity(${r});`);
return;
}
const fields = objectType.fields;
const overhead = 5 /* MaxEncodingOverhead.Object */ + fields.length * 2 /* MaxEncodingOverhead.ObjectElement */;
ctx.inc(overhead);
for (const field of fields) {
ctx.inc((0, json_size_1.maxEncodingCapacity)(field.key));
const accessor = normalizeAccessor(field.key);
const isOptional = field.optional || field.constructor?.name === 'ObjectOptionalFieldType';
const block = () => estimateCapacityFn(ctx, new JsExpression_1.JsExpression(() => `${r}${accessor}`), field.value);
if (isOptional) {
codegen.if(`${r}${accessor} !== undefined`, block);
}
else
block();
}
};
exports.obj = obj;
const map = (ctx, value, type) => {
const codegen = ctx.codegen;
ctx.inc(5 /* MaxEncodingOverhead.Object */);
const r = codegen.var(value.use());
const rKeys = codegen.var(`Object.keys(${r})`);
const rKey = codegen.var();
const rLen = codegen.var(`${rKeys}.length`);
codegen.js(`size += ${2 /* MaxEncodingOverhead.ObjectElement */} * ${rLen}`);
const mapType = type; // MapType
const valueType = mapType.valueType;
const fn = valueType.compileCapacityEstimator({
system: ctx.options.system,
name: ctx.options.name,
});
const rFn = codegen.linkDependency(fn);
const ri = codegen.var('0');
codegen.js(`for (; ${ri} < ${rLen}; ${ri}++) {`);
codegen.js(`${rKey} = ${rKeys}[${ri}];`);
codegen.js(`size += maxEncodingCapacity(${rKey}) + ${rFn}(${r}[${rKey}]);`);
codegen.js(`}`);
};
exports.map = map;
const ref = (ctx, value, type) => {
const refType = type; // RefType
const system = ctx.options.system || refType.system;
if (!system)
throw new Error('NO_SYSTEM');
const estimator = system.resolve(refType.schema.ref).type.capacityEstimator();
const d = ctx.codegen.linkDependency(estimator);
ctx.codegen.js(`size += ${d}(${value.use()});`);
};
exports.ref = ref;
const or = (ctx, value, type, estimateCapacityFn) => {
const codegen = ctx.codegen;
const orType = type; // OrType
const discriminator = orType.discriminator();
const d = codegen.linkDependency(discriminator);
const types = orType.types;
codegen.switch(`${d}(${value.use()})`, types.map((childType, index) => [
index,
() => {
estimateCapacityFn(ctx, value, childType);
},
]));
};
exports.or = or;
/**
* Main router function that dispatches capacity estimation to the appropriate
* estimator function based on the type's kind.
*/
const generate = (ctx, value, type) => {
const kind = type.getTypeName();
switch (kind) {
case 'any':
(0, exports.any)(ctx, value, type);
break;
case 'bool':
(0, exports.bool)(ctx, value);
break;
case 'num':
(0, exports.num)(ctx, value);
break;
case 'str':
(0, exports.str)(ctx, value);
break;
case 'bin':
(0, exports.bin)(ctx, value);
break;
case 'con':
(0, exports.const_)(ctx, value, type);
break;
case 'arr':
(0, exports.arr)(ctx, value, type);
break;
case 'tup':
(0, exports.tup)(ctx, value, type);
break;
case 'obj':
(0, exports.obj)(ctx, value, type, exports.generate);
break;
case 'map':
(0, exports.map)(ctx, value, type);
break;
case 'ref':
(0, exports.ref)(ctx, value, type);
break;
case 'or':
(0, exports.or)(ctx, value, type, exports.generate);
break;
default:
throw new Error(`${kind} type capacity estimation not implemented`);
}
};
exports.generate = generate;
/**
* Standalone function to generate a capacity estimator for a given type.
*/
const codegen = (type, options) => {
const ctx = new CapacityEstimatorCodegenContext_1.CapacityEstimatorCodegenContext({
system: type.system,
...options,
type: type,
});
const r = ctx.codegen.options.args[0];
const value = new JsExpression_1.JsExpression(() => r);
// Use the centralized router instead of the abstract method
(0, exports.generate)(ctx, value, type);
return ctx.compile();
};
exports.codegen = codegen;