@jsonjoy.com/json-type
Version:
High-performance JSON Pointer implementation
279 lines • 11.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.JsonTextCodegen = void 0;
const toBase64_1 = require("@jsonjoy.com/base64/lib/toBase64");
const codegen_1 = require("@jsonjoy.com/codegen");
const JsExpression_1 = require("@jsonjoy.com/codegen/lib/util/JsExpression");
const normalizeAccessor_1 = require("@jsonjoy.com/codegen/lib/util/normalizeAccessor");
const codec_1 = require("@jsonjoy.com/json-pack/lib/json-binary/codec");
const asString_1 = require("@jsonjoy.com/util/lib/strings/asString");
const type_1 = require("../../type");
const discriminator_1 = require("../discriminator");
const util_1 = require("../util");
const value_1 = require("../../value");
class WriteTextStep {
constructor(str) {
this.str = str;
}
}
class JsonTextCodegen {
constructor(type, name) {
this.type = type;
this.codegen = new codegen_1.Codegen({
name: 'toJson' + (name ? '_' + name : ''),
prologue: `var s = '';`,
epilogue: `return s;`,
linkable: {
toBase64: toBase64_1.toBase64,
Value: value_1.Value,
getEncoder: JsonTextCodegen.get,
},
processSteps: (steps) => {
const stepsJoined = [];
for (let i = 0; i < steps.length; i++) {
const step = steps[i];
if (step instanceof codegen_1.CodegenStepExecJs)
stepsJoined.push(step);
else if (step instanceof WriteTextStep) {
const last = stepsJoined[stepsJoined.length - 1];
if (last instanceof WriteTextStep)
last.str += step.str;
else
stepsJoined.push(step);
}
}
const execSteps = [];
for (const step of stepsJoined) {
if (step instanceof codegen_1.CodegenStepExecJs) {
execSteps.push(step);
}
else if (step instanceof WriteTextStep) {
const js = /* js */ `s += ${JSON.stringify(step.str)};`;
execSteps.push(new codegen_1.CodegenStepExecJs(js));
}
}
return execSteps;
},
});
this.codegen.linkDependency(asString_1.asString, 'asString');
this.codegen.linkDependency(codec_1.stringify, 'stringify');
}
js(js) {
this.codegen.js(js);
}
writeText(str) {
this.codegen.step(new WriteTextStep(str));
}
compile() {
return this.codegen.compile();
}
onArr(value, type) {
this.writeText('[');
const codegen = this.codegen;
const r = codegen.getRegister(); // array
const rl = codegen.getRegister(); // array.length
const rll = codegen.getRegister(); // last
const ri = codegen.getRegister(); // index
this.js(/* js */ `var ${r} = ${value.use()}, ${rl} = ${r}.length, ${rll} = ${rl} - 1, ${ri} = 0;`);
this.js(/* js */ `for(; ${ri} < ${rll}; ${ri}++) ` + '{');
this.onNode(new JsExpression_1.JsExpression(() => `${r}[${ri}]`), type._type);
this.js(/* js */ `s += ',';`);
this.js(/* js */ `}`);
this.js(/* js */ `if (${rl}) {`);
this.onNode(new JsExpression_1.JsExpression(() => `${r}[${rll}]`), type._type);
this.js(/* js */ `}`);
this.writeText(']');
}
onObj(value, objType) {
const { keys: fields } = objType;
const schema = objType.getOptions();
const codegen = this.codegen;
const r = codegen.getRegister();
this.js(/* js */ `var ${r} = ${value.use()};`);
const rKeys = this.codegen.getRegister();
if (schema.encodeUnknownKeys) {
this.js(/* js */ `var ${rKeys} = new Set(Object.keys(${r}));`);
}
const requiredFields = fields.filter((field) => !(field instanceof type_1.KeyOptType));
const optionalFields = fields.filter((field) => field instanceof type_1.KeyOptType);
this.writeText('{');
for (let i = 0; i < requiredFields.length; i++) {
const field = requiredFields[i];
if (i)
this.writeText(',');
this.writeText(JSON.stringify(field.key) + ':');
const accessor = (0, normalizeAccessor_1.normalizeAccessor)(field.key);
const valueExpression = new JsExpression_1.JsExpression(() => `${r}${accessor}`);
if (schema.encodeUnknownKeys)
this.js(/* js */ `${rKeys}.delete(${JSON.stringify(field.key)});`);
this.onNode(valueExpression, field.val);
}
const rHasFields = codegen.getRegister();
if (!requiredFields.length)
this.js(/* js */ `var ${rHasFields} = false;`);
for (let i = 0; i < optionalFields.length; i++) {
const field = optionalFields[i];
const accessor = (0, normalizeAccessor_1.normalizeAccessor)(field.key);
const rValue = codegen.getRegister();
if (schema.encodeUnknownKeys)
this.js(/* js */ `${rKeys}.delete(${JSON.stringify(field.key)});`);
this.js(/* js */ `var ${rValue} = ${r}${accessor};`);
this.js(`if (${rValue} !== undefined) {`);
if (requiredFields.length) {
this.writeText(',');
}
else {
this.js(`if (${rHasFields}) s += ',';`);
this.js(/* js */ `${rHasFields} = true;`);
}
this.writeText(JSON.stringify(field.key) + ':');
const valueExpression = new JsExpression_1.JsExpression(() => `${rValue}`);
this.onNode(valueExpression, field.val);
this.js(`}`);
}
if (schema.encodeUnknownKeys) {
const [rList, ri, rLength, rk] = [codegen.r(), codegen.r(), codegen.r(), codegen.r()];
this.js(`var ${rLength} = ${rKeys}.size;
if (${rLength}) {
var ${rk}, ${rList} = Array.from(${rKeys}.values());
for (var ${ri} = 0; ${ri} < ${rLength}; ${ri}++) {
${rk} = ${rList}[${ri}];
s += ',' + asString(${rk}) + ':' + stringify(${r}[${rk}]);
}
}`);
}
this.writeText('}');
}
onMap(value, type) {
this.writeText('{');
const r = this.codegen.var(value.use());
const rKeys = this.codegen.var(/* js */ `Object.keys(${r})`);
const rLength = this.codegen.var(/* js */ `${rKeys}.length`);
const rKey = this.codegen.var();
this.codegen.if(/* js */ `${rLength}`, () => {
this.js(/* js */ `${rKey} = ${rKeys}[0];`);
this.js(/* js */ `s += asString(${rKey}) + ':';`);
const innerValue = new JsExpression_1.JsExpression(() => /* js */ `${r}[${rKey}]`);
this.onNode(innerValue, type._value);
});
this.js(/* js */ `for (var i = 1; i < ${rLength}; i++) {`);
this.js(/* js */ `${rKey} = ${rKeys}[i];`);
this.js(/* js */ `s += ',' + asString(${rKey}) + ':';`);
const innerValue = new JsExpression_1.JsExpression(() => /* js */ `${r}[${rKey}]`);
this.onNode(innerValue, type._value);
this.js(/* js */ `}`);
this.writeText('}');
}
onRef(value, ref) {
const system = ref.system;
if (!system)
throw new Error('NO_SYSTEM');
const alias = system.resolve(ref.ref());
const fn = JsonTextCodegen.get(alias.type, alias.id);
const d = this.codegen.linkDependency(fn);
this.js(/* js */ `s += ${d}(${value.use()});`);
}
onOr(value, type) {
const codegen = this.codegen;
const discriminator = discriminator_1.DiscriminatorCodegen.get(type);
const d = codegen.linkDependency(discriminator);
const types = type.types;
codegen.switch(`${d}(${value.use()})`, types.map((childType, index) => [
index,
() => {
this.onNode(value, childType);
},
]));
}
onNode(value, type) {
const kind = type.kind();
const codegen = this.codegen;
switch (kind) {
case 'any': {
const r = codegen.var(value.use());
codegen.link('Value');
codegen.link('getEncoder');
codegen.if(
/* js */ `${r} instanceof Value`, () => {
const rType = codegen.var(/* js */ `${r}.type`);
const rData = codegen.var(/* js */ `${r}.data`);
codegen.if(
/* js */ `${rType}`, () => {
codegen.js(/* js */ `s += getEncoder(${rType})(${rData});`);
}, () => {
codegen.js(`s += stringify(${rData});`);
});
}, () => {
codegen.js(`s += stringify(${r});`);
});
break;
}
case 'bool': {
this.js(/* js */ `s += ${value.use()} ? 'true' : 'false';`);
break;
}
case 'num': {
this.js(/* js */ `s += '' + ${value.use()};`);
break;
}
case 'str': {
const strType = type;
if (strType.getSchema().noJsonEscape) {
this.writeText('"');
this.js(/* js */ `s += ${value.use()};`);
this.writeText('"');
}
else {
this.js(/* js */ `s += asString(${value.use()});`);
}
break;
}
case 'bin': {
this.codegen.link('toBase64');
this.writeText('"data:application/octet-stream;base64,');
this.js(/* js */ `s += toBase64(${value.use()});`);
this.writeText('"');
break;
}
case 'con': {
const constType = type;
this.js(/* js */ `s += ${JSON.stringify((0, codec_1.stringify)(constType.literal()))}`);
break;
}
case 'arr': {
this.onArr(value, type);
break;
}
// case 'tup':
// tup(ctx, value, type, generate);
// break;
case 'obj': {
this.onObj(value, type);
break;
}
case 'map': {
this.onMap(value, type);
break;
}
case 'ref': {
this.onRef(value, type);
break;
}
case 'or': {
this.onOr(value, type);
break;
}
default:
throw new Error(`${kind} type JSON text encoding not implemented`);
}
}
}
exports.JsonTextCodegen = JsonTextCodegen;
JsonTextCodegen.get = (0, util_1.lazyKeyedFactory)((type, name) => {
const codegen = new JsonTextCodegen(type, name);
const r = codegen.codegen.options.args[0];
const expression = new JsExpression_1.JsExpression(() => r);
codegen.onNode(expression, type);
return codegen.compile();
});
//# sourceMappingURL=JsonTextCodegen.js.map