Skip to content

Commit

Permalink
Merge pull request #74 from UCNot/map-estra-serializer
Browse files Browse the repository at this point in the history
Serialize extra entries of the map
  • Loading branch information
surol authored Aug 21, 2023
2 parents 7762617 + e23ff9c commit 25c5bbb
Show file tree
Hide file tree
Showing 21 changed files with 1,002 additions and 268 deletions.
145 changes: 34 additions & 111 deletions src/compiler/serialization/impl/json/ucs-format-json-map.ts
Original file line number Diff line number Diff line change
@@ -1,132 +1,55 @@
import { EsSnippet, EsVarSymbol, esEscapeString, esMemberAccessor, esline } from 'esgen';
import { EsSnippet, esline } from 'esgen';
import { UcMap } from '../../../../schema/map/uc-map.js';
import { ucModelName } from '../../../../schema/uc-model-name.js';
import { ucNullable } from '../../../../schema/uc-nullable.js';
import { ucOptional } from '../../../../schema/uc-optional.js';
import { UcModel, UcSchema } from '../../../../schema/uc-schema.js';
import { UnsupportedUcSchemaError } from '../../../common/unsupported-uc-schema.error.js';
import { UcModel } from '../../../../schema/uc-schema.js';
import { UC_MODULE_SERIALIZER } from '../../../impl/uc-modules.js';
import { UcsFormatter, UcsFormatterContext, UcsFormatterSignature } from '../../ucs-formatter.js';
import { UcsLib } from '../../ucs-lib.js';
import { ucsCheckJSON, ucsFormatJSON, ucsWriteJSONNull } from './ucs-format-json.js';
import { UcsMapSerializer } from '../map/ucs-map.serializer.js';
import { ucsFormatJSON } from './ucs-format-json.js';

export function ucsFormatJSONMap<
TEntriesModel extends UcMap.EntriesModel,
TExtraModel extends UcModel | false,
>(): UcsFormatter<UcMap.Infer<TEntriesModel, TExtraModel>> {
return ucsFormatJSON(function ucsWriteJSONMap(
{ writer, value }: UcsFormatterSignature.AllValues,
{ entries }: UcMap.Schema<TEntriesModel, TExtraModel>,
args: UcsFormatterSignature.AllValues,
schema: UcMap.Schema<TEntriesModel, TExtraModel>,
context: UcsFormatterContext,
): EsSnippet {
return (code, scope) => {
const lib = scope.get(UcsLib);

const entryValue = new EsVarSymbol(`entryValue`);
const entryIdx = new EsVarSymbol(`entryIdx`);
let isFirst = true;
let mayBeFirst = true;

function writeEntry(entryKey: string, entrySchema: UcSchema): EsSnippet {
let writeKey: EsSnippet;

if (isFirst) {
isFirst = false;

const prefix = lib.binConst(`{${JSON.stringify(entryKey)}:`);

writeKey = code => {
code.write(
esline`await ${writer}.ready;`,
esline`${writer}.write(${prefix});`,
esline`++${entryIdx};`,
);
};

mayBeFirst = !!entrySchema.optional;
} else {
const prefix = lib.binConst(`,${JSON.stringify(entryKey)}:`);

if (mayBeFirst) {
const firstPrefix = lib.binConst(`{${JSON.stringify(entryKey)}:`);

writeKey = code => {
code
.write(esline`await ${writer}.ready;`)
.write(esline`${writer}.write(${entryIdx}++ ? ${prefix} : ${firstPrefix});`);
};
return new UcsMapJSONSerializer(args, schema, context).write();
});
}

mayBeFirst = !!entrySchema.optional;
} else {
writeKey = code => {
code.write(
esline`await ${writer}.ready;`,
esline`${writer}.write(${prefix});`,
esline`++${entryIdx};`,
);
};
}
}
class UcsMapJSONSerializer<
TEntriesModel extends UcMap.EntriesModel,
TExtraModel extends UcModel | false,
> extends UcsMapSerializer<TEntriesModel, TExtraModel> {

return code => {
if (entrySchema.optional) {
if (entrySchema.nullable) {
code
.write(esline`if (${entryValue} != null) {`)
.indent(writeKey, writeValue())
.write(esline`} else if (${entryValue} === null) {`)
.indent(writeKey, ucsWriteJSONNull(writer))
.write('}');
} else {
code
.write(esline`if (${entryValue} != null) {`)
.indent(writeKey, writeValue())
.write('}');
}
} else {
code.write(writeKey, ucsCheckJSON({ writer, value }, entrySchema, writeValue()));
}
};
protected override firstEntryPrefix(entryKey: string): EsSnippet {
return this.lib.binConst(`{${JSON.stringify(entryKey)}:`);
}

function writeValue(): EsSnippet {
return context.format(
ucOptional(ucNullable(entrySchema, false), false),
{
writer,
value: entryValue,
asItem: '0',
},
(schema, context) => {
throw new UnsupportedUcSchemaError(
schema,
`${context}: Can not serialize entry "${esEscapeString(
entryKey,
)}" of type "${ucModelName(schema)}"`,
);
},
);
}
}
protected override nextEntryPrefix(entryKey: string): EsSnippet {
return this.lib.binConst(`,${JSON.stringify(entryKey)}:`);
}

code.write(entryValue.let(), entryIdx.let({ value: () => '0' }));
protected override firstExtraPrefix(extraKey: EsSnippet): EsSnippet {
return esline`\`{\${JSON.stringify(${extraKey})}:\``;
}

for (const [entryKey, entrySchema] of Object.entries<UcSchema>(entries)) {
code.write(esline`${entryValue} = ${value}${esMemberAccessor(entryKey).accessor};`);
code.write(writeEntry(entryKey, entrySchema));
}
protected override nextExtraPrefix(extraKey: EsSnippet): EsSnippet {
return esline`\`,\${JSON.stringify(${extraKey})}:\``;
}

// TODO serialize extra entries
protected override null(): EsSnippet {
return UC_MODULE_SERIALIZER.import('UCS_JSON_NULL');
}

const closingBrace = UC_MODULE_SERIALIZER.import('UCS_CLOSING_BRACE');
protected override endOfMap(): EsSnippet {
return UC_MODULE_SERIALIZER.import('UCS_CLOSING_BRACE');
}

code.write(esline`await ${writer}.ready;`);
if (mayBeFirst) {
const emptyObject = UC_MODULE_SERIALIZER.import('UCS_JSON_EMPTY_OBJECT');
protected override emptyMap(): EsSnippet {
return UC_MODULE_SERIALIZER.import('UCS_JSON_EMPTY_OBJECT');
}

code.write(esline`${writer}.write(${entryIdx} ? ${closingBrace} : ${emptyObject});`);
} else {
code.write(esline`${writer}.write(${closingBrace});`);
}
};
});
}
45 changes: 45 additions & 0 deletions src/compiler/serialization/impl/map/ucs-map-entry-idx.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { EsCode, EsSnippet, EsVarSymbol } from 'esgen';

export class UcsMapEntryIdx {

readonly #declaration = new EsCode();
readonly #postIncrement = new EsCode();
readonly #increment = new EsCode();
#symbol?: EsVarSymbol;

requireIf(condition: boolean): this {
return condition ? this.require() : this;
}

require(): this {
if (!this.#symbol) {
this.#symbol = new EsVarSymbol('entryIdx');
this.#declaration.write(this.#symbol.let({ value: () => '0' }));
this.#postIncrement.line(this.#symbol, '++');
this.#increment.line(this.#postIncrement, ';');
}

return this;
}

increment(): EsSnippet {
return this.#increment;
}

postIncrement(): EsSnippet {
this.require();

return this.#postIncrement;
}

get(): EsSnippet {
this.require();

return this.#symbol!;
}

declare(): EsSnippet {
return this.#declaration;
}

}
61 changes: 61 additions & 0 deletions src/compiler/serialization/impl/map/ucs-map-keys.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { EsVarKind, EsVarSymbol, esMemberAccessor, esline } from 'esgen';
import { UcMap } from '../../../../schema/map/uc-map.js';
import { UcModel, UcSchema } from '../../../../schema/uc-schema.js';
import { ucSchemaTypeSymbol } from '../../../impl/uc-schema-symbol.js';
import { UcsFunction } from '../../ucs-function.js';
import { UcsLib } from '../../ucs-lib.js';

export function ucsMapKeys<
TEntriesModel extends UcMap.EntriesModel,
TExtraModel extends UcModel | false,
>(lib: UcsLib, schema: UcMap.Schema<TEntriesModel, TExtraModel>): EsVarSymbol | null {
return lib
.serializerFor({
// Associate with schema with entries only.
type: 'map',
entries: Object.fromEntries(
Object.keys(schema.entries).map(
entryKey => [entryKey, { type: 'none' } satisfies UcSchema] as const,
),
),
extra: false,
} satisfies UcMap.Schema)
.associate(ucsAssociateMapKeys);
}

function ucsAssociateMapKeys<
TEntriesModel extends UcMap.EntriesModel,
TExtraModel extends UcModel | false,
>(
target: UcsFunction<
UcMap.Infer<TEntriesModel, TExtraModel>,
UcMap.Schema<TEntriesModel, TExtraModel>
>,
): EsVarSymbol | null {
const { schema } = target;
const { entries } = schema;
const keys = Object.keys(entries);

if (!keys.length) {
return null;
}

return new EsVarSymbol(ucSchemaTypeSymbol(schema) + '$keys', {
declare: {
at: 'bundle',
as: EsVarKind.Const,
value: () => code => {
code.multiLine(code => {
code
.write('{')
.indent(code => {
for (const key of keys) {
code.write(esline`${esMemberAccessor(key).key}: 1,`);
}
})
.write('}');
});
},
},
});
}
Loading

0 comments on commit 25c5bbb

Please sign in to comment.