From d780ac0ba2acb9ccbea173b1be2077db830b1f26 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 27 Jul 2023 08:48:22 +0700 Subject: [PATCH 01/36] Permit deserializer model options --- src/compiler/serialization/mod.ts | 1 + .../serialization/ucs-compiler.spec.ts | 30 ++--- src/compiler/serialization/ucs-compiler.ts | 14 +-- src/compiler/serialization/ucs-lib.ts | 32 ++++-- src/compiler/serialization/ucs-models.ts | 18 +++ .../boolean/uc-boolean.serializer.spec.ts | 11 +- src/schema/list/uc-list.serializer.spec.ts | 25 +++-- .../list/uc-multi-value.serializer.spec.ts | 4 +- src/schema/map/uc-map.serializer.spec.ts | 104 +++++++++++------- .../numeric/uc-bigint.serializer.spec.ts | 12 +- .../numeric/uc-integer.serializer.spec.ts | 4 +- .../numeric/uc-number.serializer.spec.ts | 4 +- .../string/uc-string.serializer.spec.ts | 8 +- .../unknown/uc-unknown.serializer.spec.ts | 5 +- 14 files changed, 158 insertions(+), 114 deletions(-) create mode 100644 src/compiler/serialization/ucs-models.ts diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 35c948e1..61abd481 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -3,6 +3,7 @@ export * from './ucs-export.signature.js'; export * from './ucs-function.js'; export * from './ucs-generator.js'; export * from './ucs-lib.js'; +export * from './ucs-models.js'; export * from './ucs-support-bigint.js'; export * from './ucs-support-boolean.js'; export * from './ucs-support-defaults.js'; diff --git a/src/compiler/serialization/ucs-compiler.spec.ts b/src/compiler/serialization/ucs-compiler.spec.ts index 1cedff2f..a8d7c946 100644 --- a/src/compiler/serialization/ucs-compiler.spec.ts +++ b/src/compiler/serialization/ucs-compiler.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { SPEC_MODULE } from '../../impl/module-names.js'; -import { UcModel, UcSchema } from '../../schema/uc-schema.js'; +import { UcSchema } from '../../schema/uc-schema.js'; import { TextOutStream } from '../../spec/text-out-stream.js'; import { UcsSupportNumberWithRadix, @@ -10,8 +10,8 @@ import { UcsCompiler } from './ucs-compiler.js'; describe('UcsCompiler', () => { it('respects custom serializer', async () => { - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: Number }, + const compiler = new UcsCompiler({ + models: { writeValue: { model: Number } }, features: UcsSupportNumberWithRadix, }); @@ -25,8 +25,8 @@ describe('UcsCompiler', () => { describe('generate', () => { it('generates module', async () => { - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: Number }, + const compiler = new UcsCompiler({ + models: { writeValue: { model: Number } }, }); const module = await compiler.generate(); @@ -38,8 +38,8 @@ export async function writeValue(stream, value, options) { ); }); it('fails to serialize unknown schema', async () => { - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: { type: 'test-type' } }, + const compiler = new UcsCompiler({ + models: { writeValue: { model: { type: 'test-type' } } }, features: UcsSupportRadixNumber, }); @@ -61,8 +61,8 @@ export async function writeValue(stream, value, options) { }, }; - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: schema }, + const compiler = new UcsCompiler({ + models: { writeValue: { model: schema } }, }); const { writeValue } = await compiler.evaluate(); @@ -79,8 +79,8 @@ export async function writeValue(stream, value, options) { }, }; - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: schema }, + const compiler = new UcsCompiler({ + models: { writeValue: { model: schema } }, }); const { writeValue } = await compiler.evaluate(); @@ -98,8 +98,8 @@ export async function writeValue(stream, value, options) { }; await expect( - new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: schema }, + new UcsCompiler({ + models: { writeValue: { model: schema } }, }).generate(), ).rejects.toThrow( new ReferenceError( @@ -119,8 +119,8 @@ export async function writeValue(stream, value, options) { }; await expect( - new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: schema }, + new UcsCompiler({ + models: { writeValue: { model: schema } }, }).generate(), ).rejects.toThrow( new ReferenceError( diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index b819769e..9bf0c216 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -7,8 +7,7 @@ import { esEvaluate, esGenerate, } from 'esgen'; -import { UcDataType, UcInfer, UcModel, UcSchema } from '../../schema/uc-schema.js'; -import { UcSerializer } from '../../schema/uc-serializer.js'; +import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; @@ -16,6 +15,7 @@ import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UcsFunction } from './ucs-function.js'; import { UcsGenerator } from './ucs-generator.js'; import { UcsLib } from './ucs-lib.js'; +import { UcsExports, UcsModels } from './ucs-models.js'; import { ucsSupportDefaults } from './ucs-support-defaults.js'; /** @@ -40,7 +40,7 @@ export class UcsCompiler extends UccProce super({ processors: 'serializer', - models: Object.values(models), + models: Object.values(models).map(({ model }) => model), features, }); @@ -193,14 +193,6 @@ export namespace UcsCompiler { } } -export interface UcsModels { - readonly [writer: string]: UcModel; -} - -export type UcsExports = { - readonly [writer in keyof TModels]: UcSerializer>; -}; - class UcsTypeEntry = UcSchema> { readonly #schemaIndex: UccSchemaIndex; diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index 011627b5..921ed050 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -8,12 +8,12 @@ import { esStringLiteral, esline, } from 'esgen'; -import { UcSchema, ucSchema } from '../../schema/uc-schema.js'; +import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; -import { UcsModels } from './ucs-compiler.js'; import { UcsExportSignature } from './ucs-export.signature.js'; import { UcsFunction } from './ucs-function.js'; import { UcsGenerator } from './ucs-generator.js'; +import { UcsModels } from './ucs-models.js'; /** * Serializer library allocated by {@link UcsCompiler#bootstrap compiler}. @@ -34,9 +34,7 @@ export class UcsLib { readonly #options: UcsLib.Options; readonly #schemaIndex: UccSchemaIndex; - readonly #models: { - readonly [externalName in keyof TModels]: UcSchema.Of; - }; + readonly #models: UcsSchemaConfigs; readonly #createSerializer: Exclude['createSerializer'], undefined>; readonly #serializers = new Map(); @@ -52,14 +50,18 @@ export class UcsLib { this.#schemaIndex = schemaIndex; this.#models = Object.fromEntries( - Object.entries(models).map(([externalName, model]) => [externalName, ucSchema(model)]), - ) as { - readonly [externalName in keyof TModels]: UcSchema.Of; - }; + Object.entries(models).map(([externalName, entry]) => [ + externalName, + { + ...entry, + model: ucSchema(entry.model), + }, + ]), + ) as UcsSchemaConfigs; this.#createSerializer = createSerializer; - for (const [externalName, schema] of Object.entries(this.#models)) { - const fn = this.serializerFor(schema); + for (const [externalName, { model }] of Object.entries(this.#models)) { + const fn = this.serializerFor(model); ns.refer(fn.exportFn(externalName, UcsExportSignature)); } @@ -128,3 +130,11 @@ export namespace UcsLib { ): UcsFunction; } } + +type UcsSchemaConfigs = { + readonly [externalName in keyof TModels]: UcsSchemaConfig< + UcsModels.ModelOf + >; +}; + +type UcsSchemaConfig = UcsModels.Entry>; diff --git a/src/compiler/serialization/ucs-models.ts b/src/compiler/serialization/ucs-models.ts new file mode 100644 index 00000000..a7a267f6 --- /dev/null +++ b/src/compiler/serialization/ucs-models.ts @@ -0,0 +1,18 @@ +import { UcInfer, UcModel } from '../../schema/uc-schema.js'; +import { UcSerializer } from '../../schema/uc-serializer.js'; + +export interface UcsModels { + readonly [writer: string]: UcsModels.Entry; +} + +export namespace UcsModels { + export interface Entry { + readonly model: TModel; + } + + export type ModelOf = TEntry extends Entry ? TModel : never; +} + +export type UcsExports = { + readonly [writer in keyof TModels]: UcSerializer>>; +}; diff --git a/src/schema/boolean/uc-boolean.serializer.spec.ts b/src/schema/boolean/uc-boolean.serializer.spec.ts index 014fcc48..2d0c1147 100644 --- a/src/schema/boolean/uc-boolean.serializer.spec.ts +++ b/src/schema/boolean/uc-boolean.serializer.spec.ts @@ -3,16 +3,15 @@ import { UcsCompiler } from '../../compiler/serialization/ucs-compiler.js'; import { TextOutStream } from '../../spec/text-out-stream.js'; import { ucNullable } from '../uc-nullable.js'; import { ucOptional } from '../uc-optional.js'; -import { UcModel } from '../uc-schema.js'; import { UcSerializer } from '../uc-serializer.js'; describe('UcBoolean serializer', () => { let writeValue: UcSerializer; beforeAll(async () => { - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ + const compiler = new UcsCompiler({ models: { - writeValue: Boolean, + writeValue: { model: Boolean }, }, }); @@ -26,7 +25,7 @@ describe('UcBoolean serializer', () => { it('serializes optional boolean', async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucOptional(Boolean), + writeValue: { model: ucOptional(Boolean) }, }, }); const { writeValue } = await compiler.evaluate(); @@ -38,7 +37,7 @@ describe('UcBoolean serializer', () => { it('serializes nullable boolean', async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucNullable(Boolean), + writeValue: { model: ucNullable(Boolean) }, }, }); const { writeValue } = await compiler.evaluate(); @@ -50,7 +49,7 @@ describe('UcBoolean serializer', () => { it('serializes optional nullable boolean', async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucOptional(ucNullable(Boolean)), + writeValue: { model: ucOptional(ucNullable(Boolean)) }, }, }); const { writeValue } = await compiler.evaluate(); diff --git a/src/schema/list/uc-list.serializer.spec.ts b/src/schema/list/uc-list.serializer.spec.ts index 687a1ec3..f209941e 100644 --- a/src/schema/list/uc-list.serializer.spec.ts +++ b/src/schema/list/uc-list.serializer.spec.ts @@ -1,6 +1,7 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { UnsupportedUcSchemaError } from '../../compiler/common/unsupported-uc-schema.error.js'; import { UcsCompiler } from '../../compiler/serialization/ucs-compiler.js'; +import { UcsModels } from '../../compiler/serialization/ucs-models.js'; import { TextOutStream } from '../../spec/text-out-stream.js'; import { ucMap } from '../map/uc-map.js'; import { UcString, ucString } from '../string/uc-string.js'; @@ -14,7 +15,7 @@ describe('UcList serializer', () => { it('serializes list', async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(Number), + writeList: { model: ucList(Number) }, }, }); @@ -27,7 +28,7 @@ describe('UcList serializer', () => { it('serializes empty list', async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(Number, { single: 'accept' }), + writeList: { model: ucList(Number, { single: 'accept' }) }, }, }); @@ -38,7 +39,7 @@ describe('UcList serializer', () => { it('serializes nulls', async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(ucNullable(Number)), + writeList: { model: ucList(ucNullable(Number)) }, }, }); @@ -51,7 +52,7 @@ describe('UcList serializer', () => { it('serializes missing items as nulls', async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(ucOptional(Number)), + writeList: { model: ucList(ucOptional(Number)) }, }, }); @@ -68,7 +69,7 @@ describe('UcList serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(String), + writeList: { model: ucList(String) }, }, }); @@ -88,7 +89,7 @@ describe('UcList serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(ucString({ raw: 'asString' })), + writeList: { model: ucList(ucString({ raw: 'asString' })) }, }, }); @@ -108,7 +109,7 @@ describe('UcList serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList(ucNullable(ucString({ raw: 'asString' }))), + writeList: { model: ucList(ucNullable(ucString({ raw: 'asString' }))) }, }, }); @@ -128,7 +129,9 @@ describe('UcList serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList<{ foo: string }>(ucMap<{ foo: UcModel }>({ foo: String })), + writeList: { + model: ucList<{ foo: string }>(ucMap<{ foo: UcModel }>({ foo: String })), + }, }, }); @@ -143,12 +146,12 @@ describe('UcList serializer', () => { }); describe('nested list', () => { - let compiler: UcsCompiler<{ writeList: UcList.Schema }>; + let compiler: UcsCompiler<{ writeList: UcsModels.Entry> }>; beforeAll(() => { compiler = new UcsCompiler({ models: { - writeList: ucList(ucList(Number)), + writeList: { model: ucList(ucList(Number)) }, }, }); }); @@ -182,7 +185,7 @@ describe('UcList serializer', () => { it('does not serialize unrecognized schema', async () => { const compiler = new UcsCompiler({ models: { - writeList: ucList({ type: 'test-type' }), + writeList: { model: ucList({ type: 'test-type' }) }, }, }); diff --git a/src/schema/list/uc-multi-value.serializer.spec.ts b/src/schema/list/uc-multi-value.serializer.spec.ts index 7b49a54c..0032ae67 100644 --- a/src/schema/list/uc-multi-value.serializer.spec.ts +++ b/src/schema/list/uc-multi-value.serializer.spec.ts @@ -11,7 +11,7 @@ describe('UcMultiValue serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeList: ucMultiValue(Number), + writeList: { model: ucMultiValue(Number) }, }, }); @@ -40,7 +40,7 @@ describe('UcMultiValue serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeList: ucMultiValue(Number, { single: 'prefer' }), + writeList: { model: ucMultiValue(Number, { single: 'prefer' }) }, }, }); diff --git a/src/schema/map/uc-map.serializer.spec.ts b/src/schema/map/uc-map.serializer.spec.ts index 8da54cb8..22c54055 100644 --- a/src/schema/map/uc-map.serializer.spec.ts +++ b/src/schema/map/uc-map.serializer.spec.ts @@ -11,10 +11,12 @@ describe('UcMap serializer', () => { it('serializes map', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - foo: String, - bar: Number, - }), + writeMap: { + model: ucMap({ + foo: String, + bar: Number, + }), + }, }, }); @@ -27,14 +29,16 @@ describe('UcMap serializer', () => { it('serializes nested map', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - foo: ucMap({ - test1: Number, - }), - bar: ucMap({ - test2: Number, + writeMap: { + model: ucMap({ + foo: ucMap({ + test1: Number, + }), + bar: ucMap({ + test2: Number, + }), }), - }), + }, }, }); @@ -49,10 +53,12 @@ describe('UcMap serializer', () => { it('serializes list entry', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - foo: ucList(Number), - bar: ucList(ucList(Number)), - }), + writeMap: { + model: ucMap({ + foo: ucList(Number), + bar: ucList(ucList(Number)), + }), + }, }, }); @@ -65,9 +71,11 @@ describe('UcMap serializer', () => { it('serializes entry with empty key', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - '': String, - }), + writeMap: { + model: ucMap({ + '': String, + }), + }, }, }); @@ -81,13 +89,15 @@ describe('UcMap serializer', () => { const specialKey = '(%)\r\n\t\uD83D\uDFB1 ' as const; const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - "'": String, - '!': String, - $: String, - '\\': Number, - [specialKey]: String, - }), + writeMap: { + model: ucMap({ + "'": String, + '!': String, + $: String, + '\\': Number, + [specialKey]: String, + }), + }, }, }); @@ -108,9 +118,11 @@ describe('UcMap serializer', () => { it('serializes nullable entry', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - test: ucNullable(String), - }), + writeMap: { + model: ucMap({ + test: ucNullable(String), + }), + }, }, }); @@ -126,9 +138,11 @@ describe('UcMap serializer', () => { it('serializes optional nullable entry', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - test: ucOptional(ucNullable(String)), - }), + writeMap: { + model: ucMap({ + test: ucOptional(ucNullable(String)), + }), + }, }, }); @@ -145,10 +159,12 @@ describe('UcMap serializer', () => { it('serializes second entry with empty key', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - first: Number, - '': String, - }), + writeMap: { + model: ucMap({ + first: Number, + '': String, + }), + }, }, }); @@ -161,10 +177,12 @@ describe('UcMap serializer', () => { it('serializes second entry with empty key when first one is optional', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - first: ucOptional(Number), - '': String, - }), + writeMap: { + model: ucMap({ + first: ucOptional(Number), + '': String, + }), + }, }, }); @@ -180,9 +198,11 @@ describe('UcMap serializer', () => { it('does not serialize unrecognized schema', async () => { const compiler = new UcsCompiler({ models: { - writeMap: ucMap({ - test: { type: 'test-type' }, - }), + writeMap: { + model: ucMap({ + test: { type: 'test-type' }, + }), + }, }, }); diff --git a/src/schema/numeric/uc-bigint.serializer.spec.ts b/src/schema/numeric/uc-bigint.serializer.spec.ts index 2b86c590..300555f3 100644 --- a/src/schema/numeric/uc-bigint.serializer.spec.ts +++ b/src/schema/numeric/uc-bigint.serializer.spec.ts @@ -11,7 +11,7 @@ describe('UcBigInt serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: BigInt, + writeValue: { model: BigInt }, }, }); @@ -32,7 +32,7 @@ describe('UcBigInt serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucBigInt({ string: 'serialize' }), + writeValue: { model: ucBigInt({ string: 'serialize' }) }, }, }); @@ -59,7 +59,7 @@ describe('UcBigInt serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucBigInt({ number: 'serialize' }), + writeValue: { model: ucBigInt({ number: 'serialize' }) }, }, }); @@ -84,7 +84,7 @@ describe('UcBigInt serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucBigInt({ string: 'serialize', number: 'serialize' }), + writeValue: { model: ucBigInt({ string: 'serialize', number: 'serialize' }) }, }, }); @@ -115,7 +115,7 @@ describe('UcBigInt serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucBigInt({ number: 'auto' }), + writeValue: { model: ucBigInt({ number: 'auto' }) }, }, }); @@ -150,7 +150,7 @@ describe('UcBigInt serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucBigInt({ string: 'serialize', number: 'auto' }), + writeValue: { model: ucBigInt({ string: 'serialize', number: 'auto' }) }, }, }); diff --git a/src/schema/numeric/uc-integer.serializer.spec.ts b/src/schema/numeric/uc-integer.serializer.spec.ts index 0d461bff..7dd588f2 100644 --- a/src/schema/numeric/uc-integer.serializer.spec.ts +++ b/src/schema/numeric/uc-integer.serializer.spec.ts @@ -11,7 +11,7 @@ describe('UcInteger serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucInteger(), + writeValue: { model: ucInteger() }, }, }); @@ -30,7 +30,7 @@ describe('UcInteger serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucInteger({ string: 'serialize' }), + writeValue: { model: ucInteger({ string: 'serialize' }) }, }, }); diff --git a/src/schema/numeric/uc-number.serializer.spec.ts b/src/schema/numeric/uc-number.serializer.spec.ts index 6dfb680e..c22bde89 100644 --- a/src/schema/numeric/uc-number.serializer.spec.ts +++ b/src/schema/numeric/uc-number.serializer.spec.ts @@ -11,7 +11,7 @@ describe('UcNumber serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: Number, + writeValue: { model: Number }, }, }); @@ -41,7 +41,7 @@ describe('UcNumber serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucNumber({ string: 'serialize' }), + writeValue: { model: ucNumber({ string: 'serialize' }) }, }, }); diff --git a/src/schema/string/uc-string.serializer.spec.ts b/src/schema/string/uc-string.serializer.spec.ts index a01b5fdd..a50c2d0b 100644 --- a/src/schema/string/uc-string.serializer.spec.ts +++ b/src/schema/string/uc-string.serializer.spec.ts @@ -16,7 +16,7 @@ describe('UcString serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: String, + writeValue: { model: String }, }, }); @@ -63,7 +63,7 @@ describe('UcString serializer', () => { it('writes multiple chunks', async () => { const compiler = new UcsCompiler({ models: { - writeValue: String, + writeValue: { model: String }, }, createSerializer>(options: UcsFunction.Options) { return new UcsFunction({ @@ -94,7 +94,7 @@ describe('UcString serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucString({ raw: 'asString' }), + writeValue: { model: ucString({ raw: 'asString' }) }, }, }); @@ -128,7 +128,7 @@ describe('UcString serializer', () => { beforeAll(async () => { const compiler = new UcsCompiler({ models: { - writeValue: ucNullable(ucString({ raw: 'asString' })), + writeValue: { model: ucNullable(ucString({ raw: 'asString' })) }, }, }); diff --git a/src/schema/unknown/uc-unknown.serializer.spec.ts b/src/schema/unknown/uc-unknown.serializer.spec.ts index 5110c430..72feb5c2 100644 --- a/src/schema/unknown/uc-unknown.serializer.spec.ts +++ b/src/schema/unknown/uc-unknown.serializer.spec.ts @@ -1,5 +1,6 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { UcsCompiler } from '../../compiler/serialization/ucs-compiler.js'; +import { UcsModels } from '../../compiler/serialization/ucs-models.js'; import { TextOutStream } from '../../spec/text-out-stream.js'; import { UcModel } from '../uc-schema.js'; import { UcSerializer } from '../uc-serializer.js'; @@ -9,8 +10,8 @@ describe('UcUnknown serializer', () => { let writeValue: UcSerializer; beforeAll(async () => { - const compiler = new UcsCompiler<{ writeValue: UcModel }>({ - models: { writeValue: ucUnknown() }, + const compiler = new UcsCompiler<{ writeValue: UcsModels.Entry }>({ + models: { writeValue: { model: ucUnknown() } }, }); ({ writeValue } = await compiler.evaluate()); From 86bd3cd70bfeb8482801a3db808f7a2a7942e8fa Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 27 Jul 2023 10:07:12 +0700 Subject: [PATCH 02/36] Rename `UcsFormatter` --- src/compiler/serialization/mod.ts | 3 +- src/compiler/serialization/ucs-compiler.ts | 44 +++++++++---------- src/compiler/serialization/ucs-formatter.ts | 40 +++++++++++++++++ src/compiler/serialization/ucs-function.ts | 12 ++--- src/compiler/serialization/ucs-generator.ts | 19 -------- src/compiler/serialization/ucs-lib.ts | 12 ++--- .../serialization/ucs-support-bigint.ts | 2 +- .../serialization/ucs-support-boolean.ts | 12 ++--- .../serialization/ucs-support-integer.ts | 2 +- .../serialization/ucs-support-list.ts | 30 ++++++------- src/compiler/serialization/ucs-support-map.ts | 8 ++-- .../serialization/ucs-support-number.ts | 2 +- .../serialization/ucs-support-string.ts | 2 +- .../serialization/ucs-support-unknown.ts | 2 +- src/compiler/serialization/ucs.signature.ts | 19 -------- src/spec/write-uc-radix-number.ts | 6 +-- 16 files changed, 105 insertions(+), 110 deletions(-) create mode 100644 src/compiler/serialization/ucs-formatter.ts delete mode 100644 src/compiler/serialization/ucs-generator.ts delete mode 100644 src/compiler/serialization/ucs.signature.ts diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 61abd481..9829af3a 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -1,7 +1,7 @@ export * from './ucs-compiler.js'; export * from './ucs-export.signature.js'; +export * from './ucs-formatter.js'; export * from './ucs-function.js'; -export * from './ucs-generator.js'; export * from './ucs-lib.js'; export * from './ucs-models.js'; export * from './ucs-support-bigint.js'; @@ -15,4 +15,3 @@ export * from './ucs-support-primitives.js'; export * from './ucs-support-string.js'; export * from './ucs-support-unknown.js'; export * from './ucs-writer.class.js'; -export * from './ucs.signature.js'; diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 9bf0c216..57769919 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -12,8 +12,8 @@ import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; +import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; -import { UcsGenerator } from './ucs-generator.js'; import { UcsLib } from './ucs-lib.js'; import { UcsExports, UcsModels } from './ucs-models.js'; import { ucsSupportDefaults } from './ucs-support-defaults.js'; @@ -48,31 +48,31 @@ export class UcsCompiler extends UccProce } /** - * Assigns serialization code generator to use for `target` value type or schema. + * Assigns formatter to use for `target` value type or schema. * - * Generator provided for particular schema takes precedence over the one provided for the type. + * Formatter provided for particular schema takes precedence over the one provided for the type. * * @typeParam T - Implied data type. * @typeParam TSchema - Schema type. * @param target - Name or class of target value type, or target schema instance. - * @param generator - Assigned generator. + * @param formatter - Assigned formatter. * * @returns `this` instance. */ - useUcsGenerator = UcSchema>( + formatWith = UcSchema>( target: TSchema['type'] | TSchema, - generator: UcsGenerator, + formatter: UcsFormatter, ): this { - const fullGenerator: UcsGenerator = (fn, schema, args) => { - const onValue = generator(fn, schema, args); + const fullGenerator: UcsFormatter = (args, schema, fn) => { + const onValue = formatter(args, schema, fn); return onValue && ucsCheckConstraints(fn, schema, args.value, onValue); }; if (typeof target === 'object') { - this.#typeEntryFor(target.type).useGeneratorFor(target, fullGenerator); + this.#typeEntryFor(target.type).formatSchemaWith(target, fullGenerator); } else { - this.#typeEntryFor(target).useGenerator(fullGenerator); + this.#typeEntryFor(target).formatTypeWith(fullGenerator); } return this; @@ -150,7 +150,7 @@ export class UcsCompiler extends UccProce return { ...this.#options, schemaIndex: this.schemaIndex, - generatorFor: this.#generatorFor.bind(this), + formatterFor: this.#formatterFor.bind(this), createSerializer, }; } @@ -173,10 +173,10 @@ export class UcsCompiler extends UccProce } } - #generatorFor>( + #formatterFor>( schema: TSchema, - ): UcsGenerator | undefined { - return this.#typeEntryFor(schema.type).generatorFor(schema); + ): UcsFormatter | undefined { + return this.#typeEntryFor(schema.type).formatterFor(schema); } } @@ -196,27 +196,27 @@ export namespace UcsCompiler { class UcsTypeEntry = UcSchema> { readonly #schemaIndex: UccSchemaIndex; - readonly #perSchema = new Map>(); - #generator: UcsGenerator | undefined; + readonly #perSchema = new Map>(); + #formatter: UcsFormatter | undefined; constructor(schemaIndex: UccSchemaIndex) { this.#schemaIndex = schemaIndex; } - useGenerator(generator: UcsGenerator): void { - this.#generator = generator; + formatTypeWith(formatter: UcsFormatter): void { + this.#formatter = formatter; } - useGeneratorFor(schema: TSchema, generator: UcsGenerator): void { + formatSchemaWith(schema: TSchema, formatter: UcsFormatter): void { const schemaId = this.#schemaIndex.schemaId(schema); - this.#perSchema.set(schemaId, generator); + this.#perSchema.set(schemaId, formatter); } - generatorFor(schema: TSchema): UcsGenerator | undefined { + formatterFor(schema: TSchema): UcsFormatter | undefined { const schemaId = this.#schemaIndex.schemaId(schema); - return this.#perSchema.get(schemaId) ?? this.#generator; + return this.#perSchema.get(schemaId) ?? this.#formatter; } } diff --git a/src/compiler/serialization/ucs-formatter.ts b/src/compiler/serialization/ucs-formatter.ts new file mode 100644 index 00000000..02730e3c --- /dev/null +++ b/src/compiler/serialization/ucs-formatter.ts @@ -0,0 +1,40 @@ +import { EsArg, EsSignature, EsSnippet } from 'esgen'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UcsFunction } from './ucs-function.js'; + +/** + * Type formatter generates code for type instance formatting. + * + * @typeParam T - Implied data type. + * @typeParam TSchema - Schema type. + * @param args - Serializer argument values. + * @param schema - Schema of serialized value. + * @param fn - Enclosing serializer function. Not necessarily for the target value. + * + * @returns Serializer code snippet, or `undefined` if the value serializer can not be generated. + */ +export type UcsFormatter = UcSchema> = { + format( + args: UcsFormatterSignature.AllValues, + schema: TSchema, + fn: UcsFunction, + ): EsSnippet | undefined; +}['format']; + +export const UcsFormatterSignature: UcsFormatterSignature = /*#__PURE__*/ new EsSignature({ + writer: {}, + value: {}, + 'asItem?': {}, +}); + +export type UcsFormatterSignature = EsSignature; + +export namespace UcsFormatterSignature { + export type Args = { + readonly writer: EsArg; + readonly value: EsArg; + readonly ['asItem?']: EsArg; + }; + export type Values = EsSignature.ValuesOf; + export type AllValues = { readonly [key in keyof Values]-?: Exclude }; +} diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index 2cc58c0c..488b0111 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -5,14 +5,14 @@ import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error. import { ucSchemaSymbol } from '../impl/uc-schema-symbol.js'; import { ucSchemaVariant } from '../impl/uc-schema-variant.js'; import { UcsExportSignature } from './ucs-export.signature.js'; +import { UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js'; -import { UcsSignature } from './ucs.signature.js'; export class UcsFunction = UcSchema> { readonly #schema: TSchema; - readonly #fn: EsFunction; + readonly #fn: EsFunction; readonly #createWriter: Exclude['createWriter'], undefined>; constructor(options: UcsFunction.Options); @@ -24,7 +24,7 @@ export class UcsFunction = UcSc this.#createWriter = createWriter; this.#fn = new EsFunction( `${ucSchemaSymbol(this.schema)}$serialize${ucSchemaVariant(this.schema)}`, - UcsSignature, + UcsFormatterSignature, { declare: { at: 'bundle', @@ -39,18 +39,18 @@ export class UcsFunction = UcSc return this.#schema; } - get fn(): EsFunction { + get fn(): EsFunction { return this.#fn; } serialize( schema: UcSchema, - args: UcsSignature.AllValues, + args: UcsFormatterSignature.AllValues, onUnknownSchema: (schema: UcSchema, fn: UcsFunction) => never = UcsFunction$onUnknownSchema, ): EsSnippet { return (code, scope) => { const lib = scope.get(UcsLib); - const serializer = lib.generatorFor(schema)?.(this, schema, args); + const serializer = lib.formatterFor(schema)?.(args, schema, this); if (serializer == null) { onUnknownSchema(schema, this); diff --git a/src/compiler/serialization/ucs-generator.ts b/src/compiler/serialization/ucs-generator.ts deleted file mode 100644 index 0459e8b1..00000000 --- a/src/compiler/serialization/ucs-generator.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { EsSnippet } from 'esgen'; -import { UcSchema } from '../../schema/uc-schema.js'; -import { UcsFunction } from './ucs-function.js'; -import { UcsSignature } from './ucs.signature.js'; - -/** - * Generates code for type instance serialization. - * - * @typeParam T - Implied data type. - * @typeParam TSchema - Schema type. - * @param serializer - Enclosing serializer function. Not necessarily for the target value. - * @param schema - Schema of serialized value. - * @param args - Serializer argument values. - * - * @returns Serializer code snippet, or `undefined` if the value serializer can not be generated. - */ -export type UcsGenerator = UcSchema> = { - serialize(fn: UcsFunction, schema: TSchema, args: UcsSignature.AllValues): EsSnippet | undefined; -}['serialize']; diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index 921ed050..85a2c234 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -11,8 +11,8 @@ import { import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UcsExportSignature } from './ucs-export.signature.js'; +import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; -import { UcsGenerator } from './ucs-generator.js'; import { UcsModels } from './ucs-models.js'; /** @@ -83,10 +83,10 @@ export class UcsLib { return serializer; } - generatorFor = UcSchema>( + formatterFor = UcSchema>( schema: TSchema, - ): UcsGenerator | undefined { - return this.#options.generatorFor?.(schema); + ): UcsFormatter | undefined { + return this.#options.formatterFor?.(schema); } binConst(value: string): EsSymbol { @@ -119,10 +119,10 @@ export namespace UcsLib { readonly schemaIndex: UccSchemaIndex; readonly models: TModels; - generatorFor?>( + formatterFor?>( this: void, schema: TSchema, - ): UcsGenerator | undefined; + ): UcsFormatter | undefined; createSerializer>( this: void, diff --git a/src/compiler/serialization/ucs-support-bigint.ts b/src/compiler/serialization/ucs-support-bigint.ts index 2dc584a1..868e6092 100644 --- a/src/compiler/serialization/ucs-support-bigint.ts +++ b/src/compiler/serialization/ucs-support-bigint.ts @@ -11,7 +11,7 @@ export function ucsSupportBigInt( ): UccConfig { return { configure({ string = 'parse', number = 'parse' } = {}) { - compiler.useUcsGenerator(target, (_fn, _schema, { writer, value }) => code => { + compiler.formatWith(target, ({ writer, value }) => code => { if (string === 'serialize') { const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); diff --git a/src/compiler/serialization/ucs-support-boolean.ts b/src/compiler/serialization/ucs-support-boolean.ts index 6c77638a..3fb684c2 100644 --- a/src/compiler/serialization/ucs-support-boolean.ts +++ b/src/compiler/serialization/ucs-support-boolean.ts @@ -1,24 +1,18 @@ import { EsSnippet, esline } from 'esgen'; -import { UcBoolean } from '../../schema/boolean/uc-boolean.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsCompiler } from './ucs-compiler.js'; -import { UcsFunction } from './ucs-function.js'; -import { UcsSignature } from './ucs.signature.js'; +import { UcsFormatterSignature } from './ucs-formatter.js'; export function ucsSupportBoolean(compiler: UcsCompiler): UccConfig { return { configure() { - compiler.useUcsGenerator(Boolean, ucsWriteBoolean); + compiler.formatWith(Boolean, ucsWriteBoolean); }, }; } -function ucsWriteBoolean( - _fn: UcsFunction, - _schema: UcBoolean.Schema, - { writer, value }: UcsSignature.AllValues, -): EsSnippet { +function ucsWriteBoolean({ writer, value }: UcsFormatterSignature.AllValues): EsSnippet { return code => { const ucsTrue = UC_MODULE_SERIALIZER.import('UCS_TRUE'); const ucsFalse = UC_MODULE_SERIALIZER.import('UCS_FALSE'); diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-support-integer.ts index eb130755..82dd78dc 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-support-integer.ts @@ -10,7 +10,7 @@ export function ucsSupportInteger( ): UccConfig { return { configure({ string = 'parse' } = {}) { - compiler.useUcsGenerator(target, (_fn, _schema, { writer, value }) => code => { + compiler.formatWith(target, ({ writer, value }) => code => { const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); if (string === 'serialize') { diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-support-list.ts index 4f3acff6..b733c99b 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-support-list.ts @@ -9,8 +9,8 @@ import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error. import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsCompiler } from './ucs-compiler.js'; +import { UcsFormatterSignature } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; -import { UcsSignature } from './ucs.signature.js'; export function ucsSupportList( compiler: UcsCompiler, @@ -20,15 +20,15 @@ export function ucsSupportList( configure(options) { compiler .processModel(schema.item) - .useUcsGenerator(schema, (fn, schema, args) => ucsWriteList(fn, schema, args, options)); + .formatWith(schema, (args, schema, fn) => ucsWriteList(args, schema, fn, options)); }, }; } function ucsWriteList>( - fn: UcsFunction, + args: UcsFormatterSignature.AllValues, schema: UcList.Schema, - args: UcsSignature.AllValues, + fn: UcsFunction, { single }: UccListOptions, ): EsSnippet { const { writer, value } = args; @@ -41,33 +41,33 @@ function ucsWriteList>( return code => { code .write(esline`if (!Array.isArray(${value})) {`) - .indent(ucsWriteItem(fn, itemSchema, { writer, value })) + .indent(ucsWriteItem({ writer, value }, itemSchema, fn)) .write(esline`} else if (${value}.length === 1) {`) - .indent(ucsWriteItem(fn, itemSchema, { writer, value: esline`${value}[0]` })) + .indent(ucsWriteItem({ writer, value: esline`${value}[0]` }, itemSchema, fn)) .write('} else {') - .indent(ucsWriteListItems(fn, schema, args)) + .indent(ucsWriteListItems(args, schema, fn)) .write('}'); }; case 'as-is': return code => { code .write(esline`if (Array.isArray(${value})) {`) - .indent(ucsWriteListItems(fn, schema, args)) + .indent(ucsWriteListItems(args, schema, fn)) .write(esline`} else {`) - .indent(ucsWriteItem(fn, itemSchema, { writer, value })) + .indent(ucsWriteItem({ writer, value }, itemSchema, fn)) .write('}'); }; case 'accept': case 'reject': // Always an array. - return ucsWriteListItems(fn, schema, args); + return ucsWriteListItems(args, schema, fn); } } function ucsWriteListItems>( - fn: UcsFunction, + { writer, value, asItem }: UcsFormatterSignature.AllValues, schema: UcList.Schema, - { writer, value, asItem }: UcsSignature.AllValues, + fn: UcsFunction, ): EsSnippet { const openingParenthesis = UC_MODULE_SERIALIZER.import('UCS_OPENING_PARENTHESIS'); const closingParenthesis = UC_MODULE_SERIALIZER.import('UCS_CLOSING_PARENTHESIS'); @@ -88,7 +88,7 @@ function ucsWriteListItems>( esline`await ${writer}.ready;`, esline`${writer}.write(${itemWritten} || !${asItem} ? ${comma} : ${openingParenthesis});`, esline`${itemWritten} = true;`, - ucsWriteItem(fn, itemSchema, { writer, value: itemValue }), + ucsWriteItem({ writer, value: itemValue }, itemSchema, fn), ) .write(`}`) .write(esline`if (${asItem}) {`) @@ -103,9 +103,9 @@ function ucsWriteListItems>( } function ucsWriteItem( - fn: UcsFunction, + args: Omit, itemSchema: UcSchema, - args: Omit, + fn: UcsFunction, ): EsSnippet { return fn.serialize(itemSchema, { ...args, asItem: '1' }, (schema, fn) => { throw new UnsupportedUcSchemaError( diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index 1b57bf04..3a08fbb8 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -18,15 +18,15 @@ import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsCompiler } from './ucs-compiler.js'; +import { UcsFormatterSignature } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsLib } from './ucs-lib.js'; -import { UcsSignature } from './ucs.signature.js'; export function ucsSupportMap(compiler: UcsCompiler, schema: UcMap.Schema): UccConfig; export function ucsSupportMap(compiler: UcsCompiler, { entries, extra }: UcMap.Schema): UccConfig { return { configure() { - compiler.useUcsGenerator('map', ucsWriteMap); + compiler.formatWith('map', ucsWriteMap); Object.values(entries).forEach(entrySchema => compiler.processModel(entrySchema)); // istanbul ignore next if (extra) { @@ -38,9 +38,9 @@ export function ucsSupportMap(compiler: UcsCompiler, { entries, extra }: UcMap.S } function ucsWriteMap( - fn: UcsFunction, + { writer, value }: UcsFormatterSignature.AllValues, schema: UcMap.Schema, - { writer, value }: UcsSignature.AllValues, + fn: UcsFunction, ): EsSnippet { return (code, scope) => { const lib = scope.get(UcsLib); diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-support-number.ts index 2af4a8fd..9235b1b1 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-support-number.ts @@ -11,7 +11,7 @@ export function ucsSupportNumber( ): UccConfig { return { configure({ string = 'parse' } = {}) { - compiler.useUcsGenerator(target, (_fn, _schema, { writer, value }) => { + compiler.formatWith(target, ({ writer, value }) => { const writeNumber = UC_MODULE_SERIALIZER.import( string === 'serialize' ? 'ucsWriteNumberAsString' : 'ucsWriteNumber', ); diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-support-string.ts index 0d3684d4..6d967d06 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-support-string.ts @@ -11,7 +11,7 @@ export function ucsSupportString( ): UccConfig { return { configure({ raw = 'escape' } = {}) { - compiler.useUcsGenerator(target, (_fn, schema, { writer, value, asItem }) => { + compiler.formatWith(target, ({ writer, value, asItem }, schema) => { const writeString = UC_MODULE_SERIALIZER.import( raw === 'escape' ? 'ucsWriteString' diff --git a/src/compiler/serialization/ucs-support-unknown.ts b/src/compiler/serialization/ucs-support-unknown.ts index 091d74d8..7e5bd04c 100644 --- a/src/compiler/serialization/ucs-support-unknown.ts +++ b/src/compiler/serialization/ucs-support-unknown.ts @@ -6,7 +6,7 @@ import { UcsCompiler } from './ucs-compiler.js'; export function ucsSupportUnknown(compiler: UcsCompiler): UccConfig { return { configure() { - compiler.useUcsGenerator('unknown', (_fn, _schema, { writer, value, asItem }) => { + compiler.formatWith('unknown', ({ writer, value, asItem }) => { const chargeURI = UC_MODULE_CHURI.import('chargeURI'); const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); diff --git a/src/compiler/serialization/ucs.signature.ts b/src/compiler/serialization/ucs.signature.ts deleted file mode 100644 index 75e63103..00000000 --- a/src/compiler/serialization/ucs.signature.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { EsArg, EsSignature } from 'esgen'; - -export const UcsSignature: UcsSignature = /*#__PURE__*/ new EsSignature({ - writer: {}, - value: {}, - 'asItem?': {}, -}); - -export type UcsSignature = EsSignature; - -export namespace UcsSignature { - export type Args = { - readonly writer: EsArg; - readonly value: EsArg; - readonly ['asItem?']: EsArg; - }; - export type Values = EsSignature.ValuesOf; - export type AllValues = { readonly [key in keyof Values]-?: Exclude }; -} diff --git a/src/spec/write-uc-radix-number.ts b/src/spec/write-uc-radix-number.ts index 6f0c1c5c..7dbb85e0 100644 --- a/src/spec/write-uc-radix-number.ts +++ b/src/spec/write-uc-radix-number.ts @@ -17,7 +17,7 @@ export const UcsSupportNumberWithRadix: UccFeature.Object = { uccProcess(compiler) { return { configure() { - compiler.useUcsGenerator(Number, (_fn, _schema, { writer, value }) => { + compiler.formatWith(Number, ({ writer, value }) => { const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); return esline`await ${write}(${writer}, ${value});`; @@ -31,7 +31,7 @@ export const UcsSupportRadixNumber: UccFeature.Object = { uccProcess(compiler) { return { configure() { - compiler.useUcsGenerator('radixNumber', (_fn, _schema, { writer, value }) => { + compiler.formatWith('radixNumber', ({ writer, value }) => { const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); return esline`await ${write}(${writer}, ${value});`; @@ -45,7 +45,7 @@ export const UcsSupportRadixNumberSchema: UccSchemaFeature.Object = uccProcessSchema(compiler, schema: UcSchema) { return { configure() { - compiler.useUcsGenerator(schema.type, (_fn, _schema, { writer, value }) => { + compiler.formatWith(schema.type, ({ writer, value }) => { const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); return esline`await ${write}(${writer}, ${value});`; From 2ef5f7e85197c042869772b92acafb604c4a5591 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 27 Jul 2023 10:28:10 +0700 Subject: [PATCH 03/36] Formatter context --- src/compiler/impl/ucs-check-constraints.ts | 8 +---- src/compiler/serialization/ucs-compiler.ts | 20 ++++++------- src/compiler/serialization/ucs-formatter.ts | 14 +++++++-- src/compiler/serialization/ucs-function.ts | 26 ++++++++--------- .../serialization/ucs-support-list.ts | 29 +++++++++---------- src/compiler/serialization/ucs-support-map.ts | 14 ++++----- 6 files changed, 55 insertions(+), 56 deletions(-) diff --git a/src/compiler/impl/ucs-check-constraints.ts b/src/compiler/impl/ucs-check-constraints.ts index b5d6eaec..bd4982a9 100644 --- a/src/compiler/impl/ucs-check-constraints.ts +++ b/src/compiler/impl/ucs-check-constraints.ts @@ -1,16 +1,10 @@ import { EsCode, EsSnippet, esline } from 'esgen'; import { UcSchema } from '../../schema/uc-schema.js'; -import { UcsFunction } from '../serialization/ucs-function.js'; import { UC_MODULE_SERIALIZER } from './uc-modules.js'; export function ucsCheckConstraints( - { - fn: { - args: { writer }, - }, - }: UcsFunction, + { writer, value }: { readonly writer: EsSnippet; readonly value: EsSnippet }, schema: UcSchema, - value: EsSnippet, onValue: EsSnippet, { onNull = code => { diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 57769919..7512c9e0 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -63,16 +63,16 @@ export class UcsCompiler extends UccProce target: TSchema['type'] | TSchema, formatter: UcsFormatter, ): this { - const fullGenerator: UcsFormatter = (args, schema, fn) => { - const onValue = formatter(args, schema, fn); + const fullFormatter: UcsFormatter = (args, schema, context) => { + const onValue = formatter(args, schema, context); - return onValue && ucsCheckConstraints(fn, schema, args.value, onValue); + return onValue && ucsCheckConstraints(args, schema, onValue); }; if (typeof target === 'object') { - this.#typeEntryFor(target.type).formatSchemaWith(target, fullGenerator); + this.#typeEntryFor(target.type).formatSchemaWith(target, fullFormatter); } else { - this.#typeEntryFor(target).formatTypeWith(fullGenerator); + this.#typeEntryFor(target).formatTypeWith(fullFormatter); } return this; @@ -196,27 +196,27 @@ export namespace UcsCompiler { class UcsTypeEntry = UcSchema> { readonly #schemaIndex: UccSchemaIndex; - readonly #perSchema = new Map>(); - #formatter: UcsFormatter | undefined; + readonly #schemaFormatters = new Map>(); + #typeFormatter: UcsFormatter | undefined; constructor(schemaIndex: UccSchemaIndex) { this.#schemaIndex = schemaIndex; } formatTypeWith(formatter: UcsFormatter): void { - this.#formatter = formatter; + this.#typeFormatter = formatter; } formatSchemaWith(schema: TSchema, formatter: UcsFormatter): void { const schemaId = this.#schemaIndex.schemaId(schema); - this.#perSchema.set(schemaId, formatter); + this.#schemaFormatters.set(schemaId, formatter); } formatterFor(schema: TSchema): UcsFormatter | undefined { const schemaId = this.#schemaIndex.schemaId(schema); - return this.#perSchema.get(schemaId) ?? this.#formatter; + return this.#schemaFormatters.get(schemaId) ?? this.#typeFormatter; } } diff --git a/src/compiler/serialization/ucs-formatter.ts b/src/compiler/serialization/ucs-formatter.ts index 02730e3c..9f7a1fbe 100644 --- a/src/compiler/serialization/ucs-formatter.ts +++ b/src/compiler/serialization/ucs-formatter.ts @@ -1,6 +1,5 @@ import { EsArg, EsSignature, EsSnippet } from 'esgen'; import { UcSchema } from '../../schema/uc-schema.js'; -import { UcsFunction } from './ucs-function.js'; /** * Type formatter generates code for type instance formatting. @@ -9,7 +8,7 @@ import { UcsFunction } from './ucs-function.js'; * @typeParam TSchema - Schema type. * @param args - Serializer argument values. * @param schema - Schema of serialized value. - * @param fn - Enclosing serializer function. Not necessarily for the target value. + * @param context - Formatter context. * * @returns Serializer code snippet, or `undefined` if the value serializer can not be generated. */ @@ -17,10 +16,19 @@ export type UcsFormatter = UcSc format( args: UcsFormatterSignature.AllValues, schema: TSchema, - fn: UcsFunction, + context: UcsFormatterContext, ): EsSnippet | undefined; }['format']; +export interface UcsFormatterContext { + format( + schema: UcSchema, + args: UcsFormatterSignature.AllValues, + onUnknownSchema?: (schema: UcSchema, context: UcsFormatterContext) => never, + ): EsSnippet; + toString(): string; +} + export const UcsFormatterSignature: UcsFormatterSignature = /*#__PURE__*/ new EsSignature({ writer: {}, value: {}, diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index 488b0111..a970a2a0 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -5,11 +5,12 @@ import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error. import { ucSchemaSymbol } from '../impl/uc-schema-symbol.js'; import { ucSchemaVariant } from '../impl/uc-schema-variant.js'; import { UcsExportSignature } from './ucs-export.signature.js'; -import { UcsFormatterSignature } from './ucs-formatter.js'; +import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js'; -export class UcsFunction = UcSchema> { +export class UcsFunction = UcSchema> + implements UcsFormatterContext { readonly #schema: TSchema; readonly #fn: EsFunction; @@ -29,7 +30,7 @@ export class UcsFunction = UcSc declare: { at: 'bundle', async: true, - body: ({ args }) => this.serialize(this.schema, args), + body: ({ args }) => this.format(this.schema, args), }, }, ); @@ -39,14 +40,13 @@ export class UcsFunction = UcSc return this.#schema; } - get fn(): EsFunction { - return this.#fn; - } - - serialize( + format( schema: UcSchema, args: UcsFormatterSignature.AllValues, - onUnknownSchema: (schema: UcSchema, fn: UcsFunction) => never = UcsFunction$onUnknownSchema, + onUnknownSchema: ( + schema: UcSchema, + context: UcsFormatterContext, + ) => never = UcsFunction$onUnknownSchema, ): EsSnippet { return (code, scope) => { const lib = scope.get(UcsLib); @@ -79,7 +79,7 @@ export class UcsFunction = UcSc }), ) .write(`try {`) - .indent(esline`await ${this.fn.call({ writer, value })};`) + .indent(esline`await ${this.#fn.call({ writer, value })};`) .write(`} finally {`) .indent(esline`await ${writer}.done();`) .write(`}`); @@ -89,7 +89,7 @@ export class UcsFunction = UcSc } toString(): string { - return this.fn.toString(); + return this.#fn.toString(); } } @@ -102,10 +102,10 @@ function UcsFunction$createWriter(args: UcsWriterSignature.Values): EsSnippet { }; } -function UcsFunction$onUnknownSchema(schema: UcSchema, fn: UcsFunction): never { +function UcsFunction$onUnknownSchema(schema: UcSchema, context: UcsFormatterContext): never { throw new UnsupportedUcSchemaError( schema, - `${fn}: Can not serialize type "${ucModelName(schema)}"`, + `${context}: Can not serialize type "${ucModelName(schema)}"`, ); } diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-support-list.ts index b733c99b..eeba8e29 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-support-list.ts @@ -9,8 +9,7 @@ import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error. import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsCompiler } from './ucs-compiler.js'; -import { UcsFormatterSignature } from './ucs-formatter.js'; -import { UcsFunction } from './ucs-function.js'; +import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; export function ucsSupportList( compiler: UcsCompiler, @@ -20,7 +19,7 @@ export function ucsSupportList( configure(options) { compiler .processModel(schema.item) - .formatWith(schema, (args, schema, fn) => ucsWriteList(args, schema, fn, options)); + .formatWith(schema, (args, schema, context) => ucsWriteList(args, schema, context, options)); }, }; } @@ -28,7 +27,7 @@ export function ucsSupportList( function ucsWriteList>( args: UcsFormatterSignature.AllValues, schema: UcList.Schema, - fn: UcsFunction, + context: UcsFormatterContext, { single }: UccListOptions, ): EsSnippet { const { writer, value } = args; @@ -41,33 +40,33 @@ function ucsWriteList>( return code => { code .write(esline`if (!Array.isArray(${value})) {`) - .indent(ucsWriteItem({ writer, value }, itemSchema, fn)) + .indent(ucsWriteItem({ writer, value }, itemSchema, context)) .write(esline`} else if (${value}.length === 1) {`) - .indent(ucsWriteItem({ writer, value: esline`${value}[0]` }, itemSchema, fn)) + .indent(ucsWriteItem({ writer, value: esline`${value}[0]` }, itemSchema, context)) .write('} else {') - .indent(ucsWriteListItems(args, schema, fn)) + .indent(ucsWriteListItems(args, schema, context)) .write('}'); }; case 'as-is': return code => { code .write(esline`if (Array.isArray(${value})) {`) - .indent(ucsWriteListItems(args, schema, fn)) + .indent(ucsWriteListItems(args, schema, context)) .write(esline`} else {`) - .indent(ucsWriteItem({ writer, value }, itemSchema, fn)) + .indent(ucsWriteItem({ writer, value }, itemSchema, context)) .write('}'); }; case 'accept': case 'reject': // Always an array. - return ucsWriteListItems(args, schema, fn); + return ucsWriteListItems(args, schema, context); } } function ucsWriteListItems>( { writer, value, asItem }: UcsFormatterSignature.AllValues, schema: UcList.Schema, - fn: UcsFunction, + context: UcsFormatterContext, ): EsSnippet { const openingParenthesis = UC_MODULE_SERIALIZER.import('UCS_OPENING_PARENTHESIS'); const closingParenthesis = UC_MODULE_SERIALIZER.import('UCS_CLOSING_PARENTHESIS'); @@ -88,7 +87,7 @@ function ucsWriteListItems>( esline`await ${writer}.ready;`, esline`${writer}.write(${itemWritten} || !${asItem} ? ${comma} : ${openingParenthesis});`, esline`${itemWritten} = true;`, - ucsWriteItem({ writer, value: itemValue }, itemSchema, fn), + ucsWriteItem({ writer, value: itemValue }, itemSchema, context), ) .write(`}`) .write(esline`if (${asItem}) {`) @@ -105,12 +104,12 @@ function ucsWriteListItems>( function ucsWriteItem( args: Omit, itemSchema: UcSchema, - fn: UcsFunction, + context: UcsFormatterContext, ): EsSnippet { - return fn.serialize(itemSchema, { ...args, asItem: '1' }, (schema, fn) => { + return context.format(itemSchema, { ...args, asItem: '1' }, (schema, context) => { throw new UnsupportedUcSchemaError( schema, - `${fn}: Can not serialize list item of type "${ucModelName(schema)}"`, + `${context}: Can not serialize list item of type "${ucModelName(schema)}"`, ); }); } diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index 3a08fbb8..7c4c0af0 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -18,8 +18,7 @@ import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsCompiler } from './ucs-compiler.js'; -import { UcsFormatterSignature } from './ucs-formatter.js'; -import { UcsFunction } from './ucs-function.js'; +import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; export function ucsSupportMap(compiler: UcsCompiler, schema: UcMap.Schema): UccConfig; @@ -40,7 +39,7 @@ export function ucsSupportMap(compiler: UcsCompiler, { entries, extra }: UcMap.S function ucsWriteMap( { writer, value }: UcsFormatterSignature.AllValues, schema: UcMap.Schema, - fn: UcsFunction, + context: UcsFormatterContext, ): EsSnippet { return (code, scope) => { const lib = scope.get(UcsLib); @@ -90,26 +89,25 @@ function ucsWriteMap( code.write( esline`${entryValue} = ${value}${esMemberAccessor(key).accessor};`, ucsCheckConstraints( - fn, + { writer, value: entryValue }, entrySchema, - entryValue, code => { const closingParenthesis = UC_MODULE_SERIALIZER.import('UCS_CLOSING_PARENTHESIS'); code .write(writeEntryPrefix(key)) .write( - fn.serialize( + context.format( ucOptional(ucNullable(entrySchema, false), false), { writer, value: entryValue, asItem: '0', }, - (schema, fn) => { + (schema, context) => { throw new UnsupportedUcSchemaError( schema, - `${fn}: Can not serialize entry "${esEscapeString( + `${context}: Can not serialize entry "${esEscapeString( key, )}" of type "${ucModelName(schema)}"`, ); From 05f513a56eaa27c652f0c3f11debaa5dc14140fd Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 27 Jul 2023 12:50:35 +0700 Subject: [PATCH 04/36] Require serialization format --- .../serialization/ucs-compiler.spec.ts | 2 +- src/compiler/serialization/ucs-compiler.ts | 28 ++++-- src/compiler/serialization/ucs-formatter.ts | 4 + src/compiler/serialization/ucs-function.ts | 98 ++++++++++++++----- src/compiler/serialization/ucs-lib.ts | 12 ++- src/compiler/serialization/ucs-models.ts | 2 + .../serialization/ucs-support-bigint.ts | 2 +- .../serialization/ucs-support-boolean.ts | 2 +- .../serialization/ucs-support-integer.ts | 2 +- .../serialization/ucs-support-list.ts | 2 +- src/compiler/serialization/ucs-support-map.ts | 2 +- .../serialization/ucs-support-number.ts | 2 +- .../serialization/ucs-support-string.ts | 2 +- .../serialization/ucs-support-unknown.ts | 2 +- src/schema/list/uc-list.serializer.spec.ts | 2 +- src/schema/map/uc-map.serializer.spec.ts | 2 +- src/schema/uc-serializer.ts | 8 ++ src/spec/write-uc-radix-number.ts | 6 +- 18 files changed, 129 insertions(+), 51 deletions(-) diff --git a/src/compiler/serialization/ucs-compiler.spec.ts b/src/compiler/serialization/ucs-compiler.spec.ts index a8d7c946..705161c9 100644 --- a/src/compiler/serialization/ucs-compiler.spec.ts +++ b/src/compiler/serialization/ucs-compiler.spec.ts @@ -44,7 +44,7 @@ export async function writeValue(stream, value, options) { }); await expect(compiler.generate()).rejects.toThrow( - `test_x2D_type$serialize(writer, value, asItem?): Can not serialize type "test-type"`, + `test_x2D_type$charge(writer, value, asItem?): Can not serialize type "test-type"`, ); }); }); diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 7512c9e0..6eac7ba1 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -7,6 +7,7 @@ import { esEvaluate, esGenerate, } from 'esgen'; +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccFeature } from '../processor/ucc-feature.js'; @@ -26,7 +27,7 @@ import { ucsSupportDefaults } from './ucs-support-defaults.js'; export class UcsCompiler extends UccProcessor { readonly #options: UcsCompiler.Options; - readonly #perType = new Map(); + readonly #formats = new Map>(); #bootstrapped = false; @@ -54,12 +55,14 @@ export class UcsCompiler extends UccProce * * @typeParam T - Implied data type. * @typeParam TSchema - Schema type. + * @typeParam format - Name of target format. * @param target - Name or class of target value type, or target schema instance. * @param formatter - Assigned formatter. * * @returns `this` instance. */ formatWith = UcSchema>( + format: UcFormatName, target: TSchema['type'] | TSchema, formatter: UcsFormatter, ): this { @@ -70,20 +73,30 @@ export class UcsCompiler extends UccProce }; if (typeof target === 'object') { - this.#typeEntryFor(target.type).formatSchemaWith(target, fullFormatter); + this.#typeEntryFor(format, target.type).formatSchemaWith(target, fullFormatter); } else { - this.#typeEntryFor(target).formatTypeWith(fullFormatter); + this.#typeEntryFor(format, target).formatTypeWith(fullFormatter); } return this; } - #typeEntryFor>(type: TSchema['type']): UcsTypeEntry { - let typeEntry = this.#perType.get(type) as UcsTypeEntry | undefined; + #typeEntryFor>( + format: UcFormatName, + type: TSchema['type'], + ): UcsTypeEntry { + let perType = this.#formats.get(format); + + if (!perType) { + perType = new Map(); + this.#formats.set(format, perType); + } + + let typeEntry = perType.get(type) as UcsTypeEntry | undefined; if (!typeEntry) { typeEntry = new UcsTypeEntry(this.schemaIndex); - this.#perType.set(type, typeEntry); + perType.set(type, typeEntry); } return typeEntry; @@ -174,9 +187,10 @@ export class UcsCompiler extends UccProce } #formatterFor>( + format: UcFormatName, schema: TSchema, ): UcsFormatter | undefined { - return this.#typeEntryFor(schema.type).formatterFor(schema); + return this.#typeEntryFor(format, schema.type).formatterFor(schema); } } diff --git a/src/compiler/serialization/ucs-formatter.ts b/src/compiler/serialization/ucs-formatter.ts index 9f7a1fbe..6cb83816 100644 --- a/src/compiler/serialization/ucs-formatter.ts +++ b/src/compiler/serialization/ucs-formatter.ts @@ -1,4 +1,5 @@ import { EsArg, EsSignature, EsSnippet } from 'esgen'; +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; /** @@ -21,11 +22,14 @@ export type UcsFormatter = UcSc }['format']; export interface UcsFormatterContext { + readonly formatName: UcFormatName; + format( schema: UcSchema, args: UcsFormatterSignature.AllValues, onUnknownSchema?: (schema: UcSchema, context: UcsFormatterContext) => never, ): EsSnippet; + toString(): string; } diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index a970a2a0..08ebc129 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -1,5 +1,6 @@ import { EsFunction, EsSnippet, EsVarSymbol, esline } from 'esgen'; import { ucModelName } from '../../schema/uc-model-name.js'; +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { ucSchemaSymbol } from '../impl/uc-schema-symbol.js'; @@ -7,14 +8,14 @@ import { ucSchemaVariant } from '../impl/uc-schema-variant.js'; import { UcsExportSignature } from './ucs-export.signature.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; +import { UcsModels } from './ucs-models.js'; import { UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js'; -export class UcsFunction = UcSchema> - implements UcsFormatterContext { +export class UcsFunction = UcSchema> { readonly #schema: TSchema; - readonly #fn: EsFunction; readonly #createWriter: Exclude['createWriter'], undefined>; + readonly #formats = new Map(); constructor(options: UcsFunction.Options); constructor({ @@ -23,17 +24,6 @@ export class UcsFunction = UcSc }: UcsFunction.Options) { this.#schema = schema; this.#createWriter = createWriter; - this.#fn = new EsFunction( - `${ucSchemaSymbol(this.schema)}$serialize${ucSchemaVariant(this.schema)}`, - UcsFormatterSignature, - { - declare: { - at: 'bundle', - async: true, - body: ({ args }) => this.format(this.schema, args), - }, - }, - ); } get schema(): TSchema { @@ -41,6 +31,7 @@ export class UcsFunction = UcSc } format( + format: UcFormatName, schema: UcSchema, args: UcsFormatterSignature.AllValues, onUnknownSchema: ( @@ -49,11 +40,12 @@ export class UcsFunction = UcSc ) => never = UcsFunction$onUnknownSchema, ): EsSnippet { return (code, scope) => { + const context = this.#contextFor(format); const lib = scope.get(UcsLib); - const serializer = lib.formatterFor(schema)?.(args, schema, this); + const serializer = lib.formatterFor(format, schema)?.(args, schema, context); if (serializer == null) { - onUnknownSchema(schema, this); + onUnknownSchema(schema, context); } code.write(serializer); @@ -62,9 +54,9 @@ export class UcsFunction = UcSc exportFn( externalName: string, - signature: UcsExportSignature, + { format = 'charge' }: UcsModels.Entry, ): EsFunction { - return new EsFunction(externalName, signature, { + return new EsFunction(externalName, UcsExportSignature, { declare: { at: 'exports', async: true, @@ -79,7 +71,7 @@ export class UcsFunction = UcSc }), ) .write(`try {`) - .indent(esline`await ${this.#fn.call({ writer, value })};`) + .indent(esline`await ${this.#contextFor(format).fn.call({ writer, value })};`) .write(`} finally {`) .indent(esline`await ${writer}.done();`) .write(`}`); @@ -88,12 +80,27 @@ export class UcsFunction = UcSc }); } - toString(): string { - return this.#fn.toString(); + #contextFor(format: UcFormatName): UcsFunction$Context { + let context = this.#formats.get(format); + + if (!context) { + context = new UcsFunction$Context(this, format); + this.#formats.set(format, context); + } + + return context; } } +export namespace UcsFunction { + export interface Options> { + readonly schema: TSchema; + + createWriter?(this: void, args: UcsWriterSignature.Values, serializer: UcsFunction): EsSnippet; + } +} + function UcsFunction$createWriter(args: UcsWriterSignature.Values): EsSnippet { return async (code, { ns }) => { const naming = await ns.refer(UcsWriterClass).whenNamed(); @@ -109,10 +116,51 @@ function UcsFunction$onUnknownSchema(schema: UcSchema, context: UcsFormatterCont ); } -export namespace UcsFunction { - export interface Options> { - readonly schema: TSchema; +class UcsFunction$Context implements UcsFormatterContext { - createWriter?(this: void, args: UcsWriterSignature.Values, serializer: UcsFunction): EsSnippet; + readonly #serializer: UcsFunction>; + readonly #format: UcFormatName; + readonly #fn: EsFunction; + + constructor(serializer: UcsFunction, format: UcFormatName) { + this.#serializer = serializer; + this.#format = format; + + const { schema } = serializer; + + this.#fn = new EsFunction( + `${ucSchemaSymbol(serializer.schema)}$${format}${ucSchemaVariant(schema)}`, + UcsFormatterSignature, + { + declare: { + at: 'bundle', + async: true, + body: ({ args }) => this.format(schema, args), + }, + }, + ); + } + + get formatName(): UcFormatName { + return this.#format; + } + + get fn(): EsFunction { + return this.#fn; } + + format( + schema: UcSchema, + args: UcsFormatterSignature.AllValues, + onUnknownSchema?: + | ((schema: UcSchema, context: UcsFormatterContext) => never) + | undefined, + ): EsSnippet { + return this.#serializer.format(this.formatName, schema, args, onUnknownSchema); + } + + toString(): string { + return this.#fn.toString(); + } + } diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index 85a2c234..2d9b98e4 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -8,9 +8,9 @@ import { esStringLiteral, esline, } from 'esgen'; +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; -import { UcsExportSignature } from './ucs-export.signature.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsModels } from './ucs-models.js'; @@ -60,10 +60,10 @@ export class UcsLib { ) as UcsSchemaConfigs; this.#createSerializer = createSerializer; - for (const [externalName, { model }] of Object.entries(this.#models)) { - const fn = this.serializerFor(model); + for (const [externalName, entry] of Object.entries(this.#models)) { + const fn = this.serializerFor(entry.model); - ns.refer(fn.exportFn(externalName, UcsExportSignature)); + ns.refer(fn.exportFn(externalName, entry)); } return this; @@ -84,9 +84,10 @@ export class UcsLib { } formatterFor = UcSchema>( + format: UcFormatName, schema: TSchema, ): UcsFormatter | undefined { - return this.#options.formatterFor?.(schema); + return this.#options.formatterFor?.(format, schema); } binConst(value: string): EsSymbol { @@ -121,6 +122,7 @@ export namespace UcsLib { formatterFor?>( this: void, + format: UcFormatName, schema: TSchema, ): UcsFormatter | undefined; diff --git a/src/compiler/serialization/ucs-models.ts b/src/compiler/serialization/ucs-models.ts index a7a267f6..ffd8344d 100644 --- a/src/compiler/serialization/ucs-models.ts +++ b/src/compiler/serialization/ucs-models.ts @@ -1,3 +1,4 @@ +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcInfer, UcModel } from '../../schema/uc-schema.js'; import { UcSerializer } from '../../schema/uc-serializer.js'; @@ -8,6 +9,7 @@ export interface UcsModels { export namespace UcsModels { export interface Entry { readonly model: TModel; + readonly format?: UcFormatName | undefined; } export type ModelOf = TEntry extends Entry ? TModel : never; diff --git a/src/compiler/serialization/ucs-support-bigint.ts b/src/compiler/serialization/ucs-support-bigint.ts index 868e6092..80c56320 100644 --- a/src/compiler/serialization/ucs-support-bigint.ts +++ b/src/compiler/serialization/ucs-support-bigint.ts @@ -11,7 +11,7 @@ export function ucsSupportBigInt( ): UccConfig { return { configure({ string = 'parse', number = 'parse' } = {}) { - compiler.formatWith(target, ({ writer, value }) => code => { + compiler.formatWith('charge', target, ({ writer, value }) => code => { if (string === 'serialize') { const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); diff --git a/src/compiler/serialization/ucs-support-boolean.ts b/src/compiler/serialization/ucs-support-boolean.ts index 3fb684c2..20398670 100644 --- a/src/compiler/serialization/ucs-support-boolean.ts +++ b/src/compiler/serialization/ucs-support-boolean.ts @@ -7,7 +7,7 @@ import { UcsFormatterSignature } from './ucs-formatter.js'; export function ucsSupportBoolean(compiler: UcsCompiler): UccConfig { return { configure() { - compiler.formatWith(Boolean, ucsWriteBoolean); + compiler.formatWith('charge', Boolean, ucsWriteBoolean); }, }; } diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-support-integer.ts index 82dd78dc..4902e7da 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-support-integer.ts @@ -10,7 +10,7 @@ export function ucsSupportInteger( ): UccConfig { return { configure({ string = 'parse' } = {}) { - compiler.formatWith(target, ({ writer, value }) => code => { + compiler.formatWith('charge', target, ({ writer, value }) => code => { const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); if (string === 'serialize') { diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-support-list.ts index eeba8e29..9ea901a1 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-support-list.ts @@ -19,7 +19,7 @@ export function ucsSupportList( configure(options) { compiler .processModel(schema.item) - .formatWith(schema, (args, schema, context) => ucsWriteList(args, schema, context, options)); + .formatWith('charge', schema, (args, schema, context) => ucsWriteList(args, schema, context, options)); }, }; } diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index 7c4c0af0..bf96ca9b 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -25,7 +25,7 @@ export function ucsSupportMap(compiler: UcsCompiler, schema: UcMap.Schema): UccC export function ucsSupportMap(compiler: UcsCompiler, { entries, extra }: UcMap.Schema): UccConfig { return { configure() { - compiler.formatWith('map', ucsWriteMap); + compiler.formatWith('charge', 'map', ucsWriteMap); Object.values(entries).forEach(entrySchema => compiler.processModel(entrySchema)); // istanbul ignore next if (extra) { diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-support-number.ts index 9235b1b1..7cfb44d4 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-support-number.ts @@ -11,7 +11,7 @@ export function ucsSupportNumber( ): UccConfig { return { configure({ string = 'parse' } = {}) { - compiler.formatWith(target, ({ writer, value }) => { + compiler.formatWith('charge', target, ({ writer, value }) => { const writeNumber = UC_MODULE_SERIALIZER.import( string === 'serialize' ? 'ucsWriteNumberAsString' : 'ucsWriteNumber', ); diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-support-string.ts index 6d967d06..6d6cbc10 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-support-string.ts @@ -11,7 +11,7 @@ export function ucsSupportString( ): UccConfig { return { configure({ raw = 'escape' } = {}) { - compiler.formatWith(target, ({ writer, value, asItem }, schema) => { + compiler.formatWith('charge', target, ({ writer, value, asItem }, schema) => { const writeString = UC_MODULE_SERIALIZER.import( raw === 'escape' ? 'ucsWriteString' diff --git a/src/compiler/serialization/ucs-support-unknown.ts b/src/compiler/serialization/ucs-support-unknown.ts index 7e5bd04c..31ae9ddb 100644 --- a/src/compiler/serialization/ucs-support-unknown.ts +++ b/src/compiler/serialization/ucs-support-unknown.ts @@ -6,7 +6,7 @@ import { UcsCompiler } from './ucs-compiler.js'; export function ucsSupportUnknown(compiler: UcsCompiler): UccConfig { return { configure() { - compiler.formatWith('unknown', ({ writer, value, asItem }) => { + compiler.formatWith('charge', 'unknown', ({ writer, value, asItem }) => { const chargeURI = UC_MODULE_CHURI.import('chargeURI'); const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); diff --git a/src/schema/list/uc-list.serializer.spec.ts b/src/schema/list/uc-list.serializer.spec.ts index f209941e..42d17848 100644 --- a/src/schema/list/uc-list.serializer.spec.ts +++ b/src/schema/list/uc-list.serializer.spec.ts @@ -200,7 +200,7 @@ describe('UcList serializer', () => { expect(error).toBeInstanceOf(UnsupportedUcSchemaError); expect(error?.schema.type).toBe('test-type'); expect(error?.message).toBe( - 'list$serialize(writer, value, asItem?): Can not serialize list item of type "test-type"', + 'list$charge(writer, value, asItem?): Can not serialize list item of type "test-type"', ); }); }); diff --git a/src/schema/map/uc-map.serializer.spec.ts b/src/schema/map/uc-map.serializer.spec.ts index 22c54055..b60abf5b 100644 --- a/src/schema/map/uc-map.serializer.spec.ts +++ b/src/schema/map/uc-map.serializer.spec.ts @@ -217,7 +217,7 @@ describe('UcMap serializer', () => { expect(error).toBeInstanceOf(UnsupportedUcSchemaError); expect(error?.schema.type).toBe('test-type'); expect(error?.message).toBe( - 'map$serialize(writer, value, asItem?): Can not serialize entry "test" of type "test-type"', + 'map$charge(writer, value, asItem?): Can not serialize entry "test" of type "test-type"', ); }); }); diff --git a/src/schema/uc-serializer.ts b/src/schema/uc-serializer.ts index f2c1aa66..a59c107c 100644 --- a/src/schema/uc-serializer.ts +++ b/src/schema/uc-serializer.ts @@ -1,5 +1,6 @@ import { UcBundle } from './uc-bundle.js'; import { ucModelName } from './uc-model-name.js'; +import { UcFormatName } from './uc-presentations.js'; import { UcModel } from './uc-schema.js'; /** @@ -45,6 +46,13 @@ export namespace UcSerializer { * Default bundle will be used when omitted. */ readonly bundle?: UcBundle | undefined; + + /** + * Name of serialization format. + * + * @defaultValue `'charge'` + */ + readonly to?: UcFormatName | undefined; } } diff --git a/src/spec/write-uc-radix-number.ts b/src/spec/write-uc-radix-number.ts index 7dbb85e0..6b37155c 100644 --- a/src/spec/write-uc-radix-number.ts +++ b/src/spec/write-uc-radix-number.ts @@ -17,7 +17,7 @@ export const UcsSupportNumberWithRadix: UccFeature.Object = { uccProcess(compiler) { return { configure() { - compiler.formatWith(Number, ({ writer, value }) => { + compiler.formatWith('charge', Number, ({ writer, value }) => { const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); return esline`await ${write}(${writer}, ${value});`; @@ -31,7 +31,7 @@ export const UcsSupportRadixNumber: UccFeature.Object = { uccProcess(compiler) { return { configure() { - compiler.formatWith('radixNumber', ({ writer, value }) => { + compiler.formatWith('charge', 'radixNumber', ({ writer, value }) => { const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); return esline`await ${write}(${writer}, ${value});`; @@ -45,7 +45,7 @@ export const UcsSupportRadixNumberSchema: UccSchemaFeature.Object = uccProcessSchema(compiler, schema: UcSchema) { return { configure() { - compiler.formatWith(schema.type, ({ writer, value }) => { + compiler.formatWith('charge', schema.type, ({ writer, value }) => { const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); return esline`await ${write}(${writer}, ${value});`; From 5d6773a04a76cb7d4318a2051bc41bec002fa11b Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 27 Jul 2023 15:00:46 +0700 Subject: [PATCH 05/36] Schema processing setup interfaces --- .../deserialization/bigint.ucrx.class.ts | 20 +-- .../deserialization/boolean.ucrx.class.ts | 8 +- .../deserialization/integer.ucrx.class.ts | 9 +- .../deserialization/list.ucrx.class.ts | 9 +- .../deserialization/map.ucrx.class.ts | 13 +- src/compiler/deserialization/mod.ts | 1 + .../deserialization/number.ucrx.class.ts | 20 +-- .../deserialization/string.ucrx.class.ts | 17 +- src/compiler/deserialization/ucd-compiler.ts | 97 +++-------- src/compiler/deserialization/ucd-setup.ts | 104 ++++++++++++ .../deserialization/ucd-support-defaults.ts | 6 +- .../deserialization/ucd-support-inset.ts | 9 +- .../deserialization/ucd-support-non-finite.ts | 6 +- .../deserialization/ucd-support-primitives.ts | 6 +- .../deserialization/unknown.ucrx.class.ts | 6 +- src/compiler/processor/mod.ts | 1 + src/compiler/processor/ucc-feature.ts | 28 +-- src/compiler/processor/ucc-processor.ts | 160 +++++++++--------- src/compiler/processor/ucc-schema-feature.ts | 26 +-- src/compiler/processor/ucc-setup.ts | 42 +++++ src/compiler/rx/mod.ts | 1 + src/compiler/rx/ucrx-processor.ts | 57 +------ src/compiler/rx/ucrx-setup.ts | 57 +++++++ src/compiler/serialization/mod.ts | 1 + src/compiler/serialization/ucs-compiler.ts | 37 ++-- src/compiler/serialization/ucs-setup.ts | 27 +++ .../serialization/ucs-support-bigint.ts | 6 +- .../serialization/ucs-support-boolean.ts | 6 +- .../serialization/ucs-support-defaults.ts | 6 +- .../serialization/ucs-support-integer.ts | 6 +- .../serialization/ucs-support-list.ts | 19 ++- src/compiler/serialization/ucs-support-map.ts | 12 +- .../serialization/ucs-support-number.ts | 6 +- .../serialization/ucs-support-primitives.ts | 6 +- .../serialization/ucs-support-string.ts | 6 +- .../serialization/ucs-support-unknown.ts | 6 +- .../validation/ucv-support-numeric-range.ts | 6 +- .../validation/ucv-support-string-length.ts | 6 +- .../validation/ucv-support-string-pattern.ts | 6 +- src/spec/meta-map.entity.ts | 6 +- src/spec/plain.format.ts | 6 +- src/spec/timestamp.format.ts | 29 ++-- src/spec/write-uc-radix-number.ts | 8 +- 43 files changed, 521 insertions(+), 393 deletions(-) create mode 100644 src/compiler/deserialization/ucd-setup.ts create mode 100644 src/compiler/processor/ucc-setup.ts create mode 100644 src/compiler/rx/ucrx-setup.ts create mode 100644 src/compiler/serialization/ucs-setup.ts diff --git a/src/compiler/deserialization/bigint.ucrx.class.ts b/src/compiler/deserialization/bigint.ucrx.class.ts index 42b8a1c9..56bfb0fe 100644 --- a/src/compiler/deserialization/bigint.ucrx.class.ts +++ b/src/compiler/deserialization/bigint.ucrx.class.ts @@ -4,30 +4,26 @@ import { UC_MODULE_DESERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class BigIntUcrxClass extends UcrxClass { - static uccProcess(compiler: UcdCompiler.Any): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { - compiler.useUcrxClass( - BigInt, - (lib, schema) => new this(lib, schema), - ); + setup.useUcrxClass(BigInt, (lib, schema: UcBigInt.Schema) => new this(lib, schema)); }, }; } - static uccProcessSchema( - processor: UcrxProcessor.Any, - schema: UcBigInt.Schema, - ): UccConfig { + static uccProcessSchema(setup: UcrxSetup, schema: UcBigInt.Schema): UccConfig { return { configure: variant => { - processor.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant)); + setup.useUcrxClass( + schema, + (lib, schema) => new this(lib, schema as UcBigInt.Schema, variant), + ); }, }; } diff --git a/src/compiler/deserialization/boolean.ucrx.class.ts b/src/compiler/deserialization/boolean.ucrx.class.ts index e0380dba..7ae05b33 100644 --- a/src/compiler/deserialization/boolean.ucrx.class.ts +++ b/src/compiler/deserialization/boolean.ucrx.class.ts @@ -3,17 +3,17 @@ import { UcBoolean } from '../../schema/boolean/uc-boolean.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class BooleanUcrxClass extends UcrxClass { - static uccProcess(processor: UcdCompiler.Any): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { - processor.useUcrxClass( + setup.useUcrxClass( Boolean, - (lib, schema) => new this(lib, schema), + (lib, schema: UcBoolean.Schema) => new this(lib, schema), ); }, }; diff --git a/src/compiler/deserialization/integer.ucrx.class.ts b/src/compiler/deserialization/integer.ucrx.class.ts index 596b24b4..2e94b7d7 100644 --- a/src/compiler/deserialization/integer.ucrx.class.ts +++ b/src/compiler/deserialization/integer.ucrx.class.ts @@ -4,18 +4,21 @@ import { UC_MODULE_CHURI, UC_MODULE_DESERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; export class IntegerUcrxClass extends UcrxClass { static uccProcessSchema( - processor: UcrxProcessor.Any, + setup: UcrxSetup, schema: UcInteger.Schema, ): UccConfig { return { configure: variant => { - processor.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant)); + setup.useUcrxClass( + schema, + (lib, schema: UcInteger.Schema) => new this(lib, schema, variant), + ); }, }; } diff --git a/src/compiler/deserialization/list.ucrx.class.ts b/src/compiler/deserialization/list.ucrx.class.ts index 0a01bd5d..d9792fd3 100644 --- a/src/compiler/deserialization/list.ucrx.class.ts +++ b/src/compiler/deserialization/list.ucrx.class.ts @@ -23,21 +23,18 @@ import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; import { UcrxBeforeMod, UcrxMethod } from '../rx/ucrx-method.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class ListUcrxClass< TItem = unknown, TItemModel extends UcModel = UcModel, > extends UcrxClass> { - static uccProcessSchema( - compiler: UcdCompiler.Any, - schema: UcList.Schema, - ): UccConfig { + static uccProcessSchema(setup: UcrxSetup, schema: UcList.Schema): UccConfig { return { configure: options => { - compiler + setup .processModel(schema.item) .useUcrxClass(schema, (lib, schema: UcList.Schema) => new this(lib, schema, options)); }, diff --git a/src/compiler/deserialization/map.ucrx.class.ts b/src/compiler/deserialization/map.ucrx.class.ts index 3a096ddb..3f1b86e5 100644 --- a/src/compiler/deserialization/map.ucrx.class.ts +++ b/src/compiler/deserialization/map.ucrx.class.ts @@ -19,9 +19,9 @@ import { ucSchemaVariant } from '../impl/uc-schema-variant.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; import { MapUcrxEntry } from './map.ucrx-entry.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class MapUcrxClass< in out TEntriesModel extends UcMap.EntriesModel = UcMap.EntriesModel, @@ -32,20 +32,17 @@ export class MapUcrxClass< UcMap.Schema > { - static uccProcessSchema( - compiler: UcdCompiler.Any, - schema: UcMap.Schema, - ): UccConfig { + static uccProcessSchema(setup: UcrxSetup, schema: UcMap.Schema): UccConfig { const { entries, extra } = schema; return { configure: variant => { - compiler.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant)); + setup.useUcrxClass(schema, (lib, schema: UcMap.Schema) => new this(lib, schema, variant)); for (const entrySchema of Object.values(entries)) { - compiler.processModel(entrySchema); + setup.processModel(entrySchema); } if (extra) { - compiler.processModel(extra); + setup.processModel(extra); } }, }; diff --git a/src/compiler/deserialization/mod.ts b/src/compiler/deserialization/mod.ts index 19e486bb..3077e3f3 100644 --- a/src/compiler/deserialization/mod.ts +++ b/src/compiler/deserialization/mod.ts @@ -12,6 +12,7 @@ export * from './ucd-function.js'; export * from './ucd-handler-feature.js'; export * from './ucd-lib.js'; export * from './ucd-models.js'; +export * from './ucd-setup.js'; export * from './ucd-support-defaults.js'; export * from './ucd-support-inset.js'; export * from './ucd-support-non-finite.js'; diff --git a/src/compiler/deserialization/number.ucrx.class.ts b/src/compiler/deserialization/number.ucrx.class.ts index 7da34bd8..6cd01951 100644 --- a/src/compiler/deserialization/number.ucrx.class.ts +++ b/src/compiler/deserialization/number.ucrx.class.ts @@ -4,30 +4,26 @@ import { UC_MODULE_CHURI } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class NumberUcrxClass extends UcrxClass { - static uccProcess(compiler: UcdCompiler.Any): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { - compiler.useUcrxClass( - Number, - (lib, schema) => new this(lib, schema), - ); + setup.useUcrxClass(Number, (lib, schema: UcNumber.Schema) => new this(lib, schema)); }, }; } - static uccProcessSchema( - processor: UcrxProcessor.Any, - schema: UcNumber.Schema, - ): UccConfig { + static uccProcessSchema(setup: UcrxSetup, schema: UcNumber.Schema): UccConfig { return { configure: variant => { - processor.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant)); + setup.useUcrxClass( + schema, + (lib, schema: UcNumber.Schema) => new this(lib, schema, variant), + ); }, }; } diff --git a/src/compiler/deserialization/string.ucrx.class.ts b/src/compiler/deserialization/string.ucrx.class.ts index 44c87c66..63d2a4e3 100644 --- a/src/compiler/deserialization/string.ucrx.class.ts +++ b/src/compiler/deserialization/string.ucrx.class.ts @@ -3,30 +3,29 @@ import { UcString } from '../../schema/string/uc-string.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class StringUcrxClass extends UcrxClass { - static uccProcess(compiler: UcdCompiler.Any): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { - compiler.useUcrxClass( - String, - (lib, schema) => new this(lib, schema), - ); + setup.useUcrxClass(String, (lib, schema: UcString.Schema) => new this(lib, schema)); }, }; } static uccProcessSchema( - processor: UcrxProcessor.Any, + processor: UcrxSetup, schema: UcString.Schema, ): UccConfig { return { configure: variant => { - processor.useUcrxClass(schema, (lib, schema) => new this(lib, schema, variant)); + processor.useUcrxClass( + schema, + (lib, schema: UcString.Schema) => new this(lib, schema, variant), + ); }, }; } diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index d92b50b2..039d6de5 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -26,6 +26,7 @@ import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; import { UcdHandlerFeature } from './ucd-handler-feature.js'; import { UcdLib } from './ucd-lib.js'; import { UcdExports, UcdModels } from './ucd-models.js'; +import { UcdSetup } from './ucd-setup.js'; import { ucdSupportDefaults } from './ucd-support-defaults.js'; /** @@ -33,9 +34,9 @@ import { ucdSupportDefaults } from './ucd-support-defaults.js'; * * @typeParam TModels - Compiled models record type. */ -export class UcdCompiler< - out TModels extends UcdModels = UcdModels, -> extends UcrxProcessor { +export class UcdCompiler + extends UcrxProcessor + implements UcdSetup { readonly #options: UcdCompiler.Options; @@ -68,14 +69,24 @@ export class UcdCompiler< this.#meta = new UcdHandlerRegistry('defaultMeta'); } + protected override createSetup(): UcdSetup { + return this; + } + + protected override createSchemaSetup(schema: UcSchema): UcdSetup; + protected override createSchemaSetup(_schema: UcSchema): UcdSetup { + return this; + } + protected override createConfig( - feature: UccFeature, + setup: UcdSetup, + feature: UccFeature, ): UccConfig { if (feature === ucdSupportDefaults) { return this.#enableDefault() as UccConfig; } - return super.createConfig(feature); + return super.createConfig(setup, feature); } #enableDefault(): UccConfig { @@ -100,24 +111,13 @@ export class UcdCompiler< this.#meta.makeDefault(); } - /** - * Requests the given `schema` to be compiled. - * - * Once compiled, an {@link UcrxClass} will be reported to the given `whenCompiled` callback. It can be used - * to generated parser code for the input matching the schema. - * - * @param schema - Schema to compile. - * @param whenCompiled - Callback function to call when schema compiled. - * - * @returns `this` instance. - */ - compileSchema = UcSchema>( - schema: TSchema, + compileSchema( + schema: UcSchema, whenCompiled: ( /** * Compiled charge receiver class. */ - ucrxClass: UcrxClass, + ucrxClass: UcrxClass, ) => void, ): this { this.#internalModels.push({ schema, whenCompiled }); @@ -125,86 +125,34 @@ export class UcdCompiler< return this; } - /** - * Configures entity handler. - * - * @param entity - Matching entity name. - * @param feature - Entity support feature. - * - * @returns `this` instance. - */ handleEntity(entity: string, feature: UcdHandlerFeature): this { this.#entities.addHandler(entity, feature); return this; } - /** - * Configures data format handler. - * - * @param format - Matching format name. - * @param feature - Format support feature. - * - * @returns `this` instance. - */ handleFormat(format: string, feature: UcdHandlerFeature): this { this.#formats.addHandler(format, feature); return this; } - /** - * Configures metadata attribute handler. - * - * @param attribute - Matching metadata attribute name. - * @param feature - Metadata support feature. - * - * @returns `this` instance. - */ handleMeta(attribute: string, feature: UcdHandlerFeature): this { this.#meta.addHandler(attribute, feature); return this; } - /** - * Requests the given `attribute` value to be parsed with the given `schema`. - * - * @param attribute - Target attribute. - * @param schema - Attribute value schema. - * @param set - Emits code for attribute value assignment. - * - * By default, attribute will be added to metadata. - * - * @returns `this` instance. - */ parseMetaValue = UcSchema>( attribute: string, schema: TSchema, set: ( this: void, - /** - * Attribute value assignment arguments. - */ args: { - /** - * Charge processing context. - */ readonly cx: EsSnippet; - - /** - * Charge receiver. - */ readonly rx: EsSnippet; - - /** - * Attribute value. - */ readonly value: EsSnippet; }, - /** - * Declaration context of attribute handler function. - */ context: EsDeclarationContext, ) => EsSnippet = ({ cx, value }) => esline`${cx}.meta.add(${esStringLiteral(attribute)}, ${value});`, ): this { @@ -342,17 +290,12 @@ export class UcdCompiler< } export namespace UcdCompiler { - export type Any = UcdCompiler; - export interface Options extends Omit { readonly models: TModels; readonly presentations?: UcPresentationName | UcPresentationName[] | undefined; readonly validate?: boolean | undefined; - readonly features?: - | UccFeature - | readonly UccFeature[] - | undefined; + readonly features?: UccFeature | readonly UccFeature[] | undefined; readonly exportDefaults?: boolean | undefined; } } diff --git a/src/compiler/deserialization/ucd-setup.ts b/src/compiler/deserialization/ucd-setup.ts new file mode 100644 index 00000000..932b40ad --- /dev/null +++ b/src/compiler/deserialization/ucd-setup.ts @@ -0,0 +1,104 @@ +import { EsDeclarationContext, EsSnippet } from 'esgen'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; +import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; +import { UcdHandlerFeature } from './ucd-handler-feature.js'; + +/** + * Schema {@link UcdCompiler deserializer} setup. + */ +export interface UcdSetup extends UcrxSetup { + /** + * Requests the given `schema` to be compiled. + * + * Once compiled, an {@link UcrxClass} will be reported to the given `whenCompiled` callback. It can be used + * to generated parser code for the input matching the schema. + * + * @typeParam T - Implied data type. + * @param schema - Schema to compile. + * @param whenCompiled - Callback function to call when schema compiled. + * + * @returns `this` instance. + */ + compileSchema( + schema: UcSchema, + whenCompiled: ( + /** + * Compiled charge receiver class. + */ + ucrxClass: UcrxClass, + ) => void, + ): this; + + /** + * Configures entity handler. + * + * @param entity - Matching entity name. + * @param feature - Entity support feature. + * + * @returns `this` instance. + */ + handleEntity(entity: string, feature: UcdHandlerFeature): this; + + /** + * Configures data format handler. + * + * @param format - Matching format name. + * @param feature - Format support feature. + * + * @returns `this` instance. + */ + handleFormat(format: string, feature: UcdHandlerFeature): this; + + /** + * Configures metadata attribute handler. + * + * @param attribute - Matching metadata attribute name. + * @param feature - Metadata support feature. + * + * @returns `this` instance. + */ + handleMeta(attribute: string, feature: UcdHandlerFeature): this; + + /** + * Requests the given `attribute` value to be parsed with the given `schema`. + * + * @param attribute - Target attribute. + * @param schema - Attribute value schema. + * @param set - Emits code for attribute value assignment. + * + * By default, attribute will be added to metadata. + * + * @returns `this` instance. + */ + parseMetaValue = UcSchema>( + attribute: string, + schema: TSchema, + set?: ( + this: void, + /** + * Attribute value assignment arguments. + */ + args: { + /** + * Charge processing context. + */ + readonly cx: EsSnippet; + + /** + * Charge receiver. + */ + readonly rx: EsSnippet; + + /** + * Attribute value. + */ + readonly value: EsSnippet; + }, + /** + * Declaration context of attribute handler function. + */ + context: EsDeclarationContext, + ) => EsSnippet, + ): this; +} diff --git a/src/compiler/deserialization/ucd-support-defaults.ts b/src/compiler/deserialization/ucd-support-defaults.ts index af25baf5..7acf76a4 100644 --- a/src/compiler/deserialization/ucd-support-defaults.ts +++ b/src/compiler/deserialization/ucd-support-defaults.ts @@ -1,12 +1,12 @@ import { UccConfig } from '../processor/ucc-config.js'; -import { UcdCompiler } from './ucd-compiler.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { ucdSupportNonFinite } from './ucd-support-non-finite.js'; import { ucdSupportPrimitives } from './ucd-support-primitives.js'; -export function ucdSupportDefaults(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportDefaults(setup: UcrxSetup): UccConfig { return { configure() { - compiler.enable(ucdSupportPrimitives).enable(ucdSupportNonFinite); + setup.enable(ucdSupportPrimitives).enable(ucdSupportNonFinite); }, }; } diff --git a/src/compiler/deserialization/ucd-support-inset.ts b/src/compiler/deserialization/ucd-support-inset.ts index 3fde1df4..f9ad9048 100644 --- a/src/compiler/deserialization/ucd-support-inset.ts +++ b/src/compiler/deserialization/ucd-support-inset.ts @@ -5,15 +5,12 @@ import { UC_TOKEN_INSET_URI_PARAM } from '../../syntax/uc-token.js'; import { UcrxCore$stubBody } from '../impl/ucrx-core.stub.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; -import { UcdCompiler } from './ucd-compiler.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; -export function ucdSupportInset( - compiler: UcdCompiler.Any, - schema: UcSchema, -): UccConfig { +export function ucdSupportInset(setup: UcrxSetup, schema: UcSchema): UccConfig { return { configure({ lexer, from, method, args }, { within }) { - compiler + setup .modifyUcrxClass(schema, { applyTo(ucrxClass) { if (!ucrxClass.findMember(UcrxCore.ins)?.declared) { diff --git a/src/compiler/deserialization/ucd-support-non-finite.ts b/src/compiler/deserialization/ucd-support-non-finite.ts index d6e0b531..9129b245 100644 --- a/src/compiler/deserialization/ucd-support-non-finite.ts +++ b/src/compiler/deserialization/ucd-support-non-finite.ts @@ -1,12 +1,12 @@ import { UC_MODULE_DESERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcdCompiler } from './ucd-compiler.js'; import { UcdHandlerFeature, UcdHandlerSetup } from './ucd-handler-feature.js'; +import { UcdSetup } from './ucd-setup.js'; -export function ucdSupportNonFinite(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportNonFinite(setup: UcdSetup): UccConfig { return { configure() { - compiler + setup .handleEntity('Infinity', handleUcdNonFinite('ucdInfinity')) .handleEntity('-Infinity', handleUcdNonFinite('ucdNegativeInfinity')) .handleEntity('NaN', handleUcdNonFinite('ucdNaN')); diff --git a/src/compiler/deserialization/ucd-support-primitives.ts b/src/compiler/deserialization/ucd-support-primitives.ts index 5c1ab5ae..321a10c6 100644 --- a/src/compiler/deserialization/ucd-support-primitives.ts +++ b/src/compiler/deserialization/ucd-support-primitives.ts @@ -1,14 +1,14 @@ import { UccConfig } from '../processor/ucc-config.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { BigIntUcrxClass } from './bigint.ucrx.class.js'; import { BooleanUcrxClass } from './boolean.ucrx.class.js'; import { NumberUcrxClass } from './number.ucrx.class.js'; import { StringUcrxClass } from './string.ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; -export function ucdSupportPrimitives(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportPrimitives(setup: UcrxSetup): UccConfig { return { configure() { - compiler + setup .enable(BooleanUcrxClass) .enable(BigIntUcrxClass) .enable(NumberUcrxClass) diff --git a/src/compiler/deserialization/unknown.ucrx.class.ts b/src/compiler/deserialization/unknown.ucrx.class.ts index 6abb85d2..1a722095 100644 --- a/src/compiler/deserialization/unknown.ucrx.class.ts +++ b/src/compiler/deserialization/unknown.ucrx.class.ts @@ -19,15 +19,15 @@ import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; import { UcrxBeforeMod, UcrxMethod } from '../rx/ucrx-method.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; -import { UcdCompiler } from './ucd-compiler.js'; export class UnknownUcrxClass extends UcrxClass { - static uccProcessSchema(compiler: UcdCompiler.Any, schema: UcSchema): UccConfig { + static uccProcessSchema(setup: UcrxSetup, schema: UcSchema): UccConfig { return { configure: () => { - compiler + setup .useUcrxClass('unknown', (lib, schema) => new this(lib, schema)) .processModel(this.listSchemaFor(schema)) .processModel(this.mapSchemaFor(schema)); diff --git a/src/compiler/processor/mod.ts b/src/compiler/processor/mod.ts index bfef576e..b17a9217 100644 --- a/src/compiler/processor/mod.ts +++ b/src/compiler/processor/mod.ts @@ -3,3 +3,4 @@ export * from './ucc-feature.js'; export * from './ucc-processor.js'; export * from './ucc-schema-feature.js'; export * from './ucc-schema-index.js'; +export * from './ucc-setup.js'; diff --git a/src/compiler/processor/ucc-feature.ts b/src/compiler/processor/ucc-feature.ts index a12f7b3d..e9982571 100644 --- a/src/compiler/processor/ucc-feature.ts +++ b/src/compiler/processor/ucc-feature.ts @@ -6,41 +6,41 @@ import { UccProcessor } from './ucc-processor.js'; * * Can be enabled by {@link churi!UcConstraints schema constraints} or {@link UccProcessor#enable explicitly}. * - * @typeParam TProcessor - Supported schema processor type. + * @typeParam TSetup - Schema processing setup type. * @typeParam TOptions - Type of schema processing options. */ -export type UccFeature, TOptions = void> = - | UccFeature.Object - | UccFeature.Function; +export type UccFeature = + | UccFeature.Object + | UccFeature.Function; export namespace UccFeature { /** * Schema processing feature interface. * - * @typeParam TProcessor - Supported schema processor type. + * @typeParam TSetup - Schema processing setup type. * @typeParam TOptions - Type of schema processing options. */ - export interface Object, in TOptions = void> { + export interface Object { /** * Enables this feature in schema `processor` during setup. * - * Called when feature {@link UccProcessor#enable enabled} in processor, at least once per processor. + * Called when feature {@link UccProcessor#enable enabled} in processor, at most once per processor. * - * @param processor - Schema processor to enable. + * @param setup - Schema processing setup. * * @returns Configuration of schema processing. */ - uccProcess(processor: TProcessor): UccConfig; + uccProcess(setup: TSetup): UccConfig; } /** * Schema processing feature signature. * - * @typeParam TProcessor - Supported schema processor type. + * @typeParam TSetup - Schema processing setup type. * @typeParam TOptions - Type of schema processing options. */ - export type Function< - in TProcessor extends UccProcessor, - in TOptions = void, - > = UccFeature.Object['uccProcess']; + export type Function = UccFeature.Object< + TSetup, + TOptions + >['uccProcess']; } diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 795c5f04..8e8943c2 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -11,21 +11,24 @@ import { UccConfig, UccConfigContext } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; import { UccSchemaFeature } from './ucc-schema-feature.js'; import { UccSchemaIndex } from './ucc-schema-index.js'; +import { UccSetup } from './ucc-setup.js'; /** * Abstract schema processor. * * Supports processing {@link UccFeature features}. * - * @typeParam TProcessor - Type of this schema processor. + * @typeParam TSetup - Schema processing setup type. */ -export abstract class UccProcessor> { +export abstract class UccProcessor> + implements UccSetup { readonly #schemaIndex: UccSchemaIndex; readonly #models: readonly UcModel[] | undefined; - readonly #features: readonly UccFeature[] | undefined; - readonly #configs = new Map, () => UccConfig>(); - readonly #uses = new Map>(); + readonly #features: readonly UccFeature[] | undefined; + readonly #configs = new Map, () => UccConfig>(); + readonly #uses = new Map>(); + #setup?: TSetup; #hasPendingInstructions = false; /** @@ -33,8 +36,8 @@ export abstract class UccProcessor); - constructor({ processors, presentations = [], models, features }: UccProcessorInit) { + constructor(init: UccProcessorInit); + constructor({ processors, presentations = [], models, features }: UccProcessorInit) { this.#schemaIndex = new UccSchemaIndex( asArray(processors), asArray(presentations), @@ -47,70 +50,23 @@ export abstract class UccProcessor(feature: UccFeature, options: TOptions): this; - - /** - * Enables the given processing `feature` that does not require options. - * - * @typeParam TOptions - Type of schema processing options. - * @param feature - Feature to enable. - * - * @returns `this` instance. - */ - enable(feature: UccFeature): this; - - /** - * Enables the given processing `feature`. - * - * @typeParam TOptions - Type of schema processing options. - * @param feature - Feature to enable. - * @param options - Processing options. - * - * @returns `this` instance. - */ - enable(feature: UccFeature, options?: TOptions): this { + enable(feature: UccFeature, options?: TOptions): this { let getConfig = this.#configs.get(feature) as (() => UccConfig) | undefined; if (!getConfig) { - getConfig = lazyValue(() => this.createConfig(feature)); + getConfig = lazyValue(() => this.createConfig(this.#getSetup(), feature)); this.#configs.set(feature, getConfig); } - getConfig().configure(options!, {}); + this.configure(getConfig(), options!, {}); return this; } - /** - * Creates schema processing configuration for just {@link enable enabled} `feature`. - * - * @param feature - Enabled feature. - * - * @returns Schema processing configuration. - */ - protected createConfig(feature: UccFeature): UccConfig { - return 'uccProcess' in feature - ? feature.uccProcess(this as unknown as TProcessor) - : feature(this as unknown as TProcessor); + #getSetup(): TSetup { + return (this.#setup ??= this.createSetup()); } - /** - * Applies model processing instructions specified as its {@link churi!UcSchema#where constraints}. - * - * @typeParam T - Implied data type. - * @param model - Target model. - * - * @returns `this` instance. - */ processModel(model: UcModel): this { const schema = ucSchema(model); @@ -138,12 +94,12 @@ export abstract class UccProcessor | undefined; + let use = this.#uses.get(useId) as UccProcessor$FeatureUse | undefined; if (!use) { this.#hasPendingInstructions = true; use = new UccProcessor$FeatureUse(schema, from, feature); - this.#uses.set(useId, use); + this.#uses.set(useId, use as UccProcessor$FeatureUse); } use.configure(options as TOptions, context); @@ -170,7 +126,10 @@ export abstract class UccProcessor await use.enableIn(this as unknown as TProcessor), + async use => await use.enableIn( + schema => this.createSchemaSetup(schema), + (config, options, context) => this.configure(config, options, context), + ), ), ); } @@ -182,14 +141,56 @@ export abstract class UccProcessor( + setup: TSetup, + feature: UccFeature, + ): UccConfig { + return 'uccProcess' in feature ? feature.uccProcess(setup) : feature(setup); + } + + /** + * Configures feature. + * + * @param config - Schema processing configuration. + * @param options - Configuration options. + * @param context - Configuration context. + */ + protected configure( + config: UccConfig, + options: TOptions, + context: UccConfigContext, + ): void { + config.configure(options, context); + } + } /** * Schema {@link UccProcessor processor} initialization options. * - * @typeParam TProcessor - Schema processor type. + * @typeParam TSetup - Schema processing setup type. */ -export interface UccProcessorInit> { +export interface UccProcessorInit> { /** * Processor names within {@link churi!UcConstraints schema constraints}. */ @@ -210,13 +211,10 @@ export interface UccProcessorInit> { /** * Additional schema processing features to enable and use. */ - readonly features?: - | UccFeature - | readonly UccFeature[] - | undefined; + readonly features?: UccFeature | readonly UccFeature[] | undefined; } -class UccProcessor$FeatureUse, TOptions = unknown> { +class UccProcessor$FeatureUse, TOptions = unknown> { readonly #schema: UcSchema; readonly #from: string; @@ -234,7 +232,10 @@ class UccProcessor$FeatureUse, TO this.#options.push([options, context]); } - async enableIn(processor: TProcessor): Promise { + async enableIn( + createSetup: (schema: UcSchema) => TSetup, + configure: (config: UccConfig, options: TOptions, context: UccConfigContext) => void, + ): Promise { if (this.#enabled) { return; } @@ -243,20 +244,22 @@ class UccProcessor$FeatureUse, TO const { [this.#name]: feature, - }: { [name: string]: UccFeature | UccSchemaFeature } = + }: { [name: string]: UccFeature | UccSchemaFeature } = await import(this.#from); if (mayHaveProperties(feature)) { let configured = false; + const setup = createSetup(this.#schema); + if ('uccProcess' in feature) { - for (const options of this.#options) { - processor.enable(feature, options); + for (const [options] of this.#options) { + setup.enable(feature, options); } configured = true; } if ('uccProcessSchema' in feature) { - this.#configure(feature.uccProcessSchema(processor, this.#schema)); + this.#configure(feature.uccProcessSchema(setup, this.#schema), configure); configured = true; } @@ -265,7 +268,7 @@ class UccProcessor$FeatureUse, TO } if (typeof feature === 'function') { - this.#configure(feature(processor, this.#schema)); + this.#configure(feature(setup, this.#schema), configure); return; } @@ -278,9 +281,12 @@ class UccProcessor$FeatureUse, TO throw new ReferenceError(`Not a schema processing feature: ${this}`); } - #configure(config: UccConfig): void { + #configure( + config: UccConfig, + configure: (config: UccConfig, options: TOptions, context: UccConfigContext) => void, + ): void { for (const [options, context] of this.#options) { - config.configure(options, context); + configure(config, options, context); } } diff --git a/src/compiler/processor/ucc-schema-feature.ts b/src/compiler/processor/ucc-schema-feature.ts index 3a0be227..2f0c6512 100644 --- a/src/compiler/processor/ucc-schema-feature.ts +++ b/src/compiler/processor/ucc-schema-feature.ts @@ -1,48 +1,48 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UccConfig } from './ucc-config.js'; -import { UccProcessor } from './ucc-processor.js'; +import { UccSetup } from './ucc-setup.js'; /** * Schema-specific processing feature. * * Can be enabled by {@link churi!UcConstraints schema constraints}. * - * @typeParam TProcessor - Supported schema processor type. + * @typeParam TSetup - Schema processing setup type. * @typeParam TOptions - Type of schema processing options. */ -export type UccSchemaFeature, TOptions = unknown> = - | UccSchemaFeature.Object - | UccSchemaFeature.Function; +export type UccSchemaFeature, TOptions = unknown> = + | UccSchemaFeature.Object + | UccSchemaFeature.Function; export namespace UccSchemaFeature { /** * Schema-specific processing feature interface. * - * @typeParam TProcessor - Supported schema processor type. + * @typeParam TSetup - Schema processing setup type. * @typeParam TOptions - Type of schema processing options. */ - export interface Object, in TOptions = unknown> { + export interface Object, in TOptions = unknown> { /** * Enables particular `schema` processing. * - * Called when feature enabled in processor, at least once per schema per processor. + * Called when feature enabled in processor, at most once per schema per processor. * - * @param processor - Schema processor to enable processing in. + * @param setup - Target schema processing setup. * @param schema - Schema to process. * * @returns Configuration of schema processing. */ - uccProcessSchema(processor: TProcessor, schema: UcSchema): UccConfig; + uccProcessSchema(setup: TSetup, schema: UcSchema): UccConfig; } /** * Schema-specific processing feature signature. * - * @typeParam TProcessor - Supported schema processor type. + * @typeParam TSetup - Schema processing setup type. * @typeParam TOptions - Type of schema processing options. */ export type Function< - TProcessor extends UccProcessor, + TSetup extends UccSetup, TOptions = unknown, - > = UccSchemaFeature.Object['uccProcessSchema']; + > = UccSchemaFeature.Object['uccProcessSchema']; } diff --git a/src/compiler/processor/ucc-setup.ts b/src/compiler/processor/ucc-setup.ts new file mode 100644 index 00000000..b791b651 --- /dev/null +++ b/src/compiler/processor/ucc-setup.ts @@ -0,0 +1,42 @@ +import { UcModel } from '../../schema/uc-schema.js'; +import { UccFeature } from './ucc-feature.js'; + +/** + * Schema {@link UccProcessor processing} setup. + * + * Supports processing {@link UccFeature features}. + * + * @typeParam TSetup - Schema processing setup type. + */ +export interface UccSetup { + /** + * Enables the given processing `feature`. + * + * @typeParam TOptions - Type of schema processing options. + * @param feature - Feature to enable. + * @param options - Processing options. + * + * @returns `this` instance. + */ + enable(feature: UccFeature, options: TOptions): this; + + /** + * Enables the given processing `feature` that does not require options. + * + * @typeParam TOptions - Type of schema processing options. + * @param feature - Feature to enable. + * + * @returns `this` instance. + */ + enable(feature: UccFeature): this; + + /** + * Applies model processing instructions specified as its {@link churi!UcSchema#where constraints}. + * + * @typeParam T - Implied data type. + * @param model - Target model. + * + * @returns `this` instance. + */ + processModel(model: UcModel): this; +} diff --git a/src/compiler/rx/mod.ts b/src/compiler/rx/mod.ts index ce99f109..4f1591a1 100644 --- a/src/compiler/rx/mod.ts +++ b/src/compiler/rx/mod.ts @@ -8,4 +8,5 @@ export * from './ucrx-method.js'; export * from './ucrx-processor.js'; export * from './ucrx-property.js'; export * from './ucrx-setter.js'; +export * from './ucrx-setup.js'; export * from './ucrx.class.js'; diff --git a/src/compiler/rx/ucrx-processor.ts b/src/compiler/rx/ucrx-processor.ts index 37372b3f..89391f53 100644 --- a/src/compiler/rx/ucrx-processor.ts +++ b/src/compiler/rx/ucrx-processor.ts @@ -4,36 +4,22 @@ import { UccProcessor } from '../processor/ucc-processor.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UcrxLib } from './ucrx-lib.js'; import { UcrxBeforeMod, UcrxMethod } from './ucrx-method.js'; +import { UcrxSetup } from './ucrx-setup.js'; import { UcrxClass, UcrxClassMod, UcrxProto, UcrxSignature } from './ucrx.class.js'; /** - * Schema processor utilizing {@link churi!Ucrx charge receiver} code generation. + * Setup of schema {@link UcrxProcessor processing} utilizing {@link churi!Ucrx charge receiver} code generation. * - * @typeParam TProcessor - Type of this schema processor. + * @typeParam TSetup - Schema processing setup type. */ -export abstract class UcrxProcessor< - in TProcessor extends UcrxProcessor, -> extends UccProcessor { +export abstract class UcrxProcessor> + extends UccProcessor + implements UcrxSetup { readonly #perType = new Map(); readonly #methods = new Set>(); - /** - * Assigns {@link churi!Ucrx Ucrx} class to use for `target` value type or schema processing. - * - * The class prototype provided for particular schema takes precedence over the one provided for the type. - * - * @typeParam T - Implied data type. - * @typeParam TSchema - Schema type. - * @param target - Name or class of target value type, or target schema instance. - * @param proto - Ucrx class prototype. - * - * @returns `this` instance. - */ - useUcrxClass = UcSchema>( - target: TSchema['type'] | TSchema, - proto: UcrxProto, - ): this { + useUcrxClass(target: UcSchema['type'] | UcSchema, proto: UcrxProto): this { if (typeof target === 'object') { this.#typeEntryFor(target.type).useProtoFor(target, proto); } else { @@ -43,24 +29,12 @@ export abstract class UcrxProcessor< return this; } - modifyUcrxClass = UcSchema>( - schema: TSchema, - mod: UcrxClassMod, - ): this { + modifyUcrxClass(schema: UcSchema, mod: UcrxClassMod): this { this.#typeEntryFor(schema.type).modifyClass(schema, mod); return this; } - /** - * Declares `method` to present in all {@link churi!Ucrx charge receiver} implementations. - * - * @typeParam TArgs - Type of method arguments definition. - * @typeParam TMod - Type of method modifier. - * @param method - Declaration of method to add to charge receiver template. - * - * @returns `this` instance. - */ declareUcrxMethod>( method: UcrxMethod, ): this { @@ -69,17 +43,6 @@ export abstract class UcrxProcessor< return this; } - /** - * Modifies the `method` of the target `type` receiver. - * - * @typeParam TArgs - Type of method arguments definition. - * @typeParam TMod - Type of method modifier. - * @param schema - Target schema. - * @param method - Method to modify. - * @param mod - Modifier to apply to target `method`. - * - * @returns `this` instance. - */ modifyUcrxMethod>( schema: UcSchema, method: UcrxMethod, @@ -116,10 +79,6 @@ export abstract class UcrxProcessor< } -export namespace UcrxProcessor { - export type Any = UcrxProcessor; -} - class UcrxTypeEntry = UcSchema> { readonly #schemaIndex: UccSchemaIndex; diff --git a/src/compiler/rx/ucrx-setup.ts b/src/compiler/rx/ucrx-setup.ts new file mode 100644 index 00000000..4fbdc5e6 --- /dev/null +++ b/src/compiler/rx/ucrx-setup.ts @@ -0,0 +1,57 @@ +import { EsSignature } from 'esgen'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UccSetup } from '../processor/ucc-setup.js'; +import { UcrxBeforeMod, UcrxMethod } from './ucrx-method.js'; +import { UcrxClassMod, UcrxProto } from './ucrx.class.js'; + +/** + * Schema processor utilizing {@link churi!Ucrx charge receiver} code generation. + * + * @typeParam TProcessor - Type of this schema processor. + */ +export interface UcrxSetup extends UccSetup { + /** + * Assigns {@link churi!Ucrx Ucrx} class to use for `target` value type or schema processing. + * + * The class prototype provided for particular schema takes precedence over the one provided for the type. + * + * @typeParam T - Implied data type. + * @param target - Name or class of target value type, or target schema instance. + * @param proto - Ucrx class prototype. + * + * @returns `this` instance. + */ + useUcrxClass(target: UcSchema['type'] | UcSchema, proto: UcrxProto): this; + + modifyUcrxClass(schema: UcSchema, mod: UcrxClassMod): this; + + /** + * Declares `method` to present in all {@link churi!Ucrx charge receiver} implementations. + * + * @typeParam TArgs - Type of method arguments definition. + * @typeParam TMod - Type of method modifier. + * @param method - Declaration of method to add to charge receiver template. + * + * @returns `this` instance. + */ + declareUcrxMethod>( + method: UcrxMethod, + ): this; + + /** + * Modifies the `method` of the target `type` receiver. + * + * @typeParam TArgs - Type of method arguments definition. + * @typeParam TMod - Type of method modifier. + * @param schema - Target schema. + * @param method - Method to modify. + * @param mod - Modifier to apply to target `method`. + * + * @returns `this` instance. + */ + modifyUcrxMethod>( + schema: UcSchema, + method: UcrxMethod, + mod: TMod, + ): this; +} diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 9829af3a..fef0a719 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -4,6 +4,7 @@ export * from './ucs-formatter.js'; export * from './ucs-function.js'; export * from './ucs-lib.js'; export * from './ucs-models.js'; +export * from './ucs-setup.js'; export * from './ucs-support-bigint.js'; export * from './ucs-support-boolean.js'; export * from './ucs-support-defaults.js'; diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 6eac7ba1..32e73279 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -17,6 +17,7 @@ import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsLib } from './ucs-lib.js'; import { UcsExports, UcsModels } from './ucs-models.js'; +import { UcsSetup } from './ucs-setup.js'; import { ucsSupportDefaults } from './ucs-support-defaults.js'; /** @@ -24,7 +25,9 @@ import { ucsSupportDefaults } from './ucs-support-defaults.js'; * * @typeParam TModels - Compiled models record type. */ -export class UcsCompiler extends UccProcessor { +export class UcsCompiler + extends UccProcessor + implements UcsSetup { readonly #options: UcsCompiler.Options; readonly #formats = new Map>(); @@ -48,25 +51,21 @@ export class UcsCompiler extends UccProce this.#options = options; } - /** - * Assigns formatter to use for `target` value type or schema. - * - * Formatter provided for particular schema takes precedence over the one provided for the type. - * - * @typeParam T - Implied data type. - * @typeParam TSchema - Schema type. - * @typeParam format - Name of target format. - * @param target - Name or class of target value type, or target schema instance. - * @param formatter - Assigned formatter. - * - * @returns `this` instance. - */ - formatWith = UcSchema>( + protected override createSetup(): UcsSetup { + return this; + } + + protected override createSchemaSetup(schema: UcSchema): UcsSetup; + protected override createSchemaSetup(_schema: UcSchema): UcsSetup { + return this; + } + + formatWith( format: UcFormatName, - target: TSchema['type'] | TSchema, - formatter: UcsFormatter, + target: UcSchema['type'] | UcSchema, + formatter: UcsFormatter, ): this { - const fullFormatter: UcsFormatter = (args, schema, context) => { + const fullFormatter: UcsFormatter = (args, schema, context) => { const onValue = formatter(args, schema, context); return onValue && ucsCheckConstraints(args, schema, onValue); @@ -198,7 +197,7 @@ export class UcsCompiler extends UccProce export namespace UcsCompiler { export interface Options { readonly models: TModels; - readonly features?: UccFeature | readonly UccFeature[] | undefined; + readonly features?: UccFeature | readonly UccFeature[] | undefined; createSerializer?>( this: void, diff --git a/src/compiler/serialization/ucs-setup.ts b/src/compiler/serialization/ucs-setup.ts new file mode 100644 index 00000000..57722c5c --- /dev/null +++ b/src/compiler/serialization/ucs-setup.ts @@ -0,0 +1,27 @@ +import { UcFormatName } from '../../schema/uc-presentations.js'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UccSetup } from '../processor/ucc-setup.js'; +import { UcsFormatter } from './ucs-formatter.js'; + +/** + * Schema {@link UcsCompiler serializer} setup. + */ +export interface UcsSetup extends UccSetup { + /** + * Assigns formatter to use for `target` value type or schema. + * + * Formatter provided for particular schema takes precedence over the one provided for the type. + * + * @typeParam T - Implied data type. + * @typeParam format - Name of target format. + * @param target - Name or class of target value type, or target schema instance. + * @param formatter - Assigned formatter. + * + * @returns `this` instance. + */ + formatWith( + format: UcFormatName, + target: UcSchema['type'] | UcSchema, + formatter: UcsFormatter, + ): this; +} diff --git a/src/compiler/serialization/ucs-support-bigint.ts b/src/compiler/serialization/ucs-support-bigint.ts index 80c56320..a8151562 100644 --- a/src/compiler/serialization/ucs-support-bigint.ts +++ b/src/compiler/serialization/ucs-support-bigint.ts @@ -3,15 +3,15 @@ import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; import { UcDataType } from '../../schema/uc-schema.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; export function ucsSupportBigInt( - compiler: UcsCompiler, + setup: UcsSetup, target: UcBigInt.Schema | UcDataType = BigInt, ): UccConfig { return { configure({ string = 'parse', number = 'parse' } = {}) { - compiler.formatWith('charge', target, ({ writer, value }) => code => { + setup.formatWith('charge', target, ({ writer, value }) => code => { if (string === 'serialize') { const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); diff --git a/src/compiler/serialization/ucs-support-boolean.ts b/src/compiler/serialization/ucs-support-boolean.ts index 20398670..de66dc51 100644 --- a/src/compiler/serialization/ucs-support-boolean.ts +++ b/src/compiler/serialization/ucs-support-boolean.ts @@ -1,13 +1,13 @@ import { EsSnippet, esline } from 'esgen'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; import { UcsFormatterSignature } from './ucs-formatter.js'; +import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportBoolean(compiler: UcsCompiler): UccConfig { +export function ucsSupportBoolean(setup: UcsSetup): UccConfig { return { configure() { - compiler.formatWith('charge', Boolean, ucsWriteBoolean); + setup.formatWith('charge', Boolean, ucsWriteBoolean); }, }; } diff --git a/src/compiler/serialization/ucs-support-defaults.ts b/src/compiler/serialization/ucs-support-defaults.ts index 2f485b32..adc69cb6 100644 --- a/src/compiler/serialization/ucs-support-defaults.ts +++ b/src/compiler/serialization/ucs-support-defaults.ts @@ -1,11 +1,11 @@ import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; import { ucsSupportPrimitives } from './ucs-support-primitives.js'; -export function ucsSupportDefaults(compiler: UcsCompiler): UccConfig { +export function ucsSupportDefaults(setup: UcsSetup): UccConfig { return { configure() { - compiler.enable(ucsSupportPrimitives); + setup.enable(ucsSupportPrimitives); }, }; } diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-support-integer.ts index 4902e7da..86a46bf2 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-support-integer.ts @@ -2,15 +2,15 @@ import { esline } from 'esgen'; import { UcInteger } from '../../schema/numeric/uc-integer.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; export function ucsSupportInteger( - compiler: UcsCompiler, + setup: UcsSetup, target: UcInteger.Schema, ): UccConfig { return { configure({ string = 'parse' } = {}) { - compiler.formatWith('charge', target, ({ writer, value }) => code => { + setup.formatWith('charge', target, ({ writer, value }) => code => { const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); if (string === 'serialize') { diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-support-list.ts index 9ea901a1..adc75efd 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-support-list.ts @@ -8,18 +8,23 @@ import { UccListOptions } from '../common/ucc-list-options.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; +import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportList( - compiler: UcsCompiler, - schema: UcList.Schema, -): UccConfig { +export function ucsSupportList(setup: UcsSetup, schema: UcList.Schema): UccConfig { return { configure(options) { - compiler + setup .processModel(schema.item) - .formatWith('charge', schema, (args, schema, context) => ucsWriteList(args, schema, context, options)); + .formatWith( + 'charge', + schema, + ( + args: UcsFormatterSignature.AllValues, + schema: UcList.Schema, + context: UcsFormatterContext, + ) => ucsWriteList(args, schema, context, options), + ); }, }; } diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index bf96ca9b..b1abc97c 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -17,20 +17,20 @@ import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error. import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; +import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportMap(compiler: UcsCompiler, schema: UcMap.Schema): UccConfig; -export function ucsSupportMap(compiler: UcsCompiler, { entries, extra }: UcMap.Schema): UccConfig { +export function ucsSupportMap(setup: UcsSetup, schema: UcMap.Schema): UccConfig; +export function ucsSupportMap(setup: UcsSetup, { entries, extra }: UcMap.Schema): UccConfig { return { configure() { - compiler.formatWith('charge', 'map', ucsWriteMap); - Object.values(entries).forEach(entrySchema => compiler.processModel(entrySchema)); + setup.formatWith('charge', 'map', ucsWriteMap); + Object.values(entries).forEach(entrySchema => setup.processModel(entrySchema)); // istanbul ignore next if (extra) { // TODO Implement extra entries serialization. - compiler.processModel(extra); + setup.processModel(extra); } }, }; diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-support-number.ts index 7cfb44d4..bf86279f 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-support-number.ts @@ -3,15 +3,15 @@ import { UcNumber } from '../../schema/numeric/uc-number.js'; import { UcDataType } from '../../schema/uc-schema.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; export function ucsSupportNumber( - compiler: UcsCompiler, + setup: UcsSetup, target: UcNumber.Schema | UcDataType = Number, ): UccConfig { return { configure({ string = 'parse' } = {}) { - compiler.formatWith('charge', target, ({ writer, value }) => { + setup.formatWith('charge', target, ({ writer, value }) => { const writeNumber = UC_MODULE_SERIALIZER.import( string === 'serialize' ? 'ucsWriteNumberAsString' : 'ucsWriteNumber', ); diff --git a/src/compiler/serialization/ucs-support-primitives.ts b/src/compiler/serialization/ucs-support-primitives.ts index 61c5fc1d..33315bd0 100644 --- a/src/compiler/serialization/ucs-support-primitives.ts +++ b/src/compiler/serialization/ucs-support-primitives.ts @@ -1,14 +1,14 @@ import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; import { ucsSupportBigInt } from './ucs-support-bigint.js'; import { ucsSupportBoolean } from './ucs-support-boolean.js'; import { ucsSupportNumber } from './ucs-support-number.js'; import { ucsSupportString } from './ucs-support-string.js'; -export function ucsSupportPrimitives(compiler: UcsCompiler): UccConfig { +export function ucsSupportPrimitives(setup: UcsSetup): UccConfig { return { configure() { - compiler + setup .enable(ucsSupportBoolean) .enable(ucsSupportBigInt) .enable(ucsSupportNumber) diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-support-string.ts index 6d6cbc10..60ea2538 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-support-string.ts @@ -3,15 +3,15 @@ import { UcString } from '../../schema/string/uc-string.js'; import { UcDataType } from '../../schema/uc-schema.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; export function ucsSupportString( - compiler: UcsCompiler, + setup: UcsSetup, target: UcString.Schema | UcDataType = String, ): UccConfig { return { configure({ raw = 'escape' } = {}) { - compiler.formatWith('charge', target, ({ writer, value, asItem }, schema) => { + setup.formatWith('charge', target, ({ writer, value, asItem }, schema) => { const writeString = UC_MODULE_SERIALIZER.import( raw === 'escape' ? 'ucsWriteString' diff --git a/src/compiler/serialization/ucs-support-unknown.ts b/src/compiler/serialization/ucs-support-unknown.ts index 31ae9ddb..237431d9 100644 --- a/src/compiler/serialization/ucs-support-unknown.ts +++ b/src/compiler/serialization/ucs-support-unknown.ts @@ -1,12 +1,12 @@ import { esline } from 'esgen'; import { UC_MODULE_CHURI, UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsCompiler } from './ucs-compiler.js'; +import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportUnknown(compiler: UcsCompiler): UccConfig { +export function ucsSupportUnknown(setup: UcsSetup): UccConfig { return { configure() { - compiler.formatWith('charge', 'unknown', ({ writer, value, asItem }) => { + setup.formatWith('charge', 'unknown', ({ writer, value, asItem }) => { const chargeURI = UC_MODULE_CHURI.import('chargeURI'); const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); diff --git a/src/compiler/validation/ucv-support-numeric-range.ts b/src/compiler/validation/ucv-support-numeric-range.ts index 43b06738..84d47918 100644 --- a/src/compiler/validation/ucv-support-numeric-range.ts +++ b/src/compiler/validation/ucv-support-numeric-range.ts @@ -3,8 +3,8 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UC_MODULE_VALIDATOR } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; import { UcrxSetter } from '../rx/ucrx-setter.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { ucvValidate } from './ucv-validate.js'; export type UcvNumericRange = [ @@ -14,7 +14,7 @@ export type UcvNumericRange = [ ]; export function ucvSupportNumericRange( - processor: UcrxProcessor.Any, + setup: UcrxSetup, schema: UcSchema, ): UccConfig { return { @@ -32,7 +32,7 @@ export function ucvSupportNumericRange( const message = or != null ? `, ${esStringLiteral(or)}` : ''; - processor.modifyUcrxMethod(schema, setter, { + setup.modifyUcrxMethod(schema, setter, { before({ member: { args } }) { return ucvValidate(args, ({ value, reject }) => code => { const ucvReject = UC_MODULE_VALIDATOR.import(`ucvViolate${constraint}`); diff --git a/src/compiler/validation/ucv-support-string-length.ts b/src/compiler/validation/ucv-support-string-length.ts index 86691816..f46ca428 100644 --- a/src/compiler/validation/ucv-support-string-length.ts +++ b/src/compiler/validation/ucv-support-string-length.ts @@ -3,7 +3,7 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UC_MODULE_VALIDATOR } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { ucvValidate } from './ucv-validate.js'; export type UcvStringLength = [ @@ -13,12 +13,12 @@ export type UcvStringLength = [ ]; export function ucvSupportStringLength( - processor: UcrxProcessor.Any, + setup: UcrxSetup, schema: UcSchema, ): UccConfig { return { configure([constraint, than, or]) { - processor.modifyUcrxMethod(schema, UcrxCore.str, { + setup.modifyUcrxMethod(schema, UcrxCore.str, { before({ member: { args } }) { return ucvValidate(args, ({ value, reject }) => code => { const bound = String(than); diff --git a/src/compiler/validation/ucv-support-string-pattern.ts b/src/compiler/validation/ucv-support-string-pattern.ts index 3ddd0dc7..a35996c0 100644 --- a/src/compiler/validation/ucv-support-string-pattern.ts +++ b/src/compiler/validation/ucv-support-string-pattern.ts @@ -3,18 +3,18 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UC_MODULE_VALIDATOR } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; -import { UcrxProcessor } from '../rx/ucrx-processor.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; import { ucvValidate } from './ucv-validate.js'; export type UcvStringPattern = [match: RegExp, or?: string | undefined]; export function ucvSupportStringPattern( - processor: UcrxProcessor.Any, + setup: UcrxSetup, schema: UcSchema, ): UccConfig { return { configure([match, or]) { - processor.modifyUcrxMethod(schema, UcrxCore.str, { + setup.modifyUcrxMethod(schema, UcrxCore.str, { before({ member: { args } }) { return ucvValidate(args, ({ value, reject }) => code => { const pattern = String(match); diff --git a/src/spec/meta-map.entity.ts b/src/spec/meta-map.entity.ts index bc76b3ac..c3917bdd 100644 --- a/src/spec/meta-map.entity.ts +++ b/src/spec/meta-map.entity.ts @@ -1,4 +1,4 @@ -import { UcdCompiler } from '../compiler/deserialization/ucd-compiler.js'; +import { UcdSetup } from '../compiler/deserialization/ucd-setup.js'; import { UC_MODULE_SPEC } from '../compiler/impl/uc-modules.js'; import { UccConfig } from '../compiler/processor/ucc-config.js'; import { UcrxContext } from '../rx/ucrx-context.js'; @@ -25,10 +25,10 @@ export function readMetaMap(context: UcrxContext, rx: Ucrx): 0 | 1 { return rx.map(context); } -export function ucdSupportMetaMapEntity(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportMetaMapEntity(setup: UcdSetup): UccConfig { return { configure() { - compiler.handleEntity('meta-map', ({ register }) => code => { + setup.handleEntity('meta-map', ({ register }) => code => { code.write(register(UC_MODULE_SPEC.import('readMetaMap'))); }); }, diff --git a/src/spec/plain.format.ts b/src/spec/plain.format.ts index efb1e072..bc6239a5 100644 --- a/src/spec/plain.format.ts +++ b/src/spec/plain.format.ts @@ -1,4 +1,4 @@ -import { UcdCompiler } from '../compiler/deserialization/ucd-compiler.js'; +import { UcdSetup } from '../compiler/deserialization/ucd-setup.js'; import { UC_MODULE_SPEC } from '../compiler/impl/uc-modules.js'; import { UccConfig } from '../compiler/processor/ucc-config.js'; import { UcrxContext } from '../rx/ucrx-context.js'; @@ -15,10 +15,10 @@ export function readPlainFormat( return rx.str(`!${format}'${printUcTokens(data)}`, context); } -export function ucdSupportPlainEntity(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportPlainEntity(setup: UcdSetup): UccConfig { return { configure() { - compiler.handleFormat('plain', ({ register }) => code => { + setup.handleFormat('plain', ({ register }) => code => { code.write(register(UC_MODULE_SPEC.import('readPlainFormat'))); }); }, diff --git a/src/spec/timestamp.format.ts b/src/spec/timestamp.format.ts index 136ad973..01d5babd 100644 --- a/src/spec/timestamp.format.ts +++ b/src/spec/timestamp.format.ts @@ -1,5 +1,5 @@ import { EsFunction, EsVarSymbol, esline } from 'esgen'; -import { UcdCompiler } from '../compiler/deserialization/ucd-compiler.js'; +import { UcdSetup } from '../compiler/deserialization/ucd-setup.js'; import { UC_MODULE_CHURI } from '../compiler/impl/uc-modules.js'; import { UccConfig } from '../compiler/processor/ucc-config.js'; import { UccFeature } from '../compiler/processor/ucc-feature.js'; @@ -23,10 +23,10 @@ export const TimestampUcrxMethod = new UcrxSetter('date', { typeName: 'date', }); -export function ucdSupportTimestampFormat(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportTimestampFormat(setup: UcdSetup): UccConfig { return { configure() { - compiler.declareUcrxMethod(TimestampUcrxMethod).enable(ucdSupportTimestampFormatOnly); + setup.declareUcrxMethod(TimestampUcrxMethod).enable(ucdSupportTimestampFormatOnly); }, }; } @@ -58,10 +58,10 @@ const readTimestampEntityFn = new EsFunction( }, ); -export function ucdSupportTimestampFormatOnly(compiler: UcdCompiler.Any): UccConfig { +export function ucdSupportTimestampFormatOnly(setup: UcdSetup): UccConfig { return { configure() { - compiler.handleFormat('timestamp', ({ register, refer }) => code => { + setup.handleFormat('timestamp', ({ register, refer }) => code => { refer(readTimestampEntityFn); code.write(register(readTimestampEntityFn.symbol)); @@ -70,11 +70,11 @@ export function ucdSupportTimestampFormatOnly(compiler: UcdCompiler.Any): UccCon }; } -export const UcdSupportTimestamp: UccFeature.Object = { - uccProcess(compiler) { +export const UcdSupportTimestamp: UccFeature.Object = { + uccProcess(setup) { return { configure() { - compiler + setup .enable(ucdSupportTimestampFormat) .useUcrxClass('timestamp', (lib, schema) => new TimestampUcrxClass(lib, schema)); }, @@ -82,23 +82,20 @@ export const UcdSupportTimestamp: UccFeature.Object = { }, }; -export const UcdSupportTimestampSchema: UccSchemaFeature.Object = { - uccProcessSchema(compiler, _schema) { +export const UcdSupportTimestampSchema: UccSchemaFeature.Object = { + uccProcessSchema(setup, _schema) { return { configure() { - compiler.enable(UcdSupportTimestamp); + setup.enable(UcdSupportTimestamp); }, }; }, }; -export function ucdSupportTimestampSchema( - compiler: UcdCompiler.Any, - _schema: UcSchema, -): UccConfig { +export function ucdSupportTimestampSchema(setup: UcdSetup, _schema: UcSchema): UccConfig { return { configure() { - compiler.enable(UcdSupportTimestamp); + setup.enable(UcdSupportTimestamp); }, }; } diff --git a/src/spec/write-uc-radix-number.ts b/src/spec/write-uc-radix-number.ts index 6b37155c..fc88825d 100644 --- a/src/spec/write-uc-radix-number.ts +++ b/src/spec/write-uc-radix-number.ts @@ -2,7 +2,7 @@ import { esline } from 'esgen'; import { UC_MODULE_SPEC } from '../compiler/impl/uc-modules.js'; import { UccFeature } from '../compiler/processor/ucc-feature.js'; import { UccSchemaFeature } from '../compiler/processor/ucc-schema-feature.js'; -import { UcsCompiler } from '../compiler/serialization/ucs-compiler.js'; +import { UcsSetup } from '../compiler/serialization/ucs-setup.js'; import { UcSchema } from '../schema/uc-schema.js'; import { ucsWriteAsIs } from '../serializer/ucs-write-asis.js'; import { UcsWriter } from '../serializer/ucs-writer.js'; @@ -13,7 +13,7 @@ export async function writeUcRadixNumber(writer: UcsWriter, value: number): Prom await ucsWriteAsIs(writer, (radix === 16 ? '0x' : '') + value.toString(Number(radix))); } -export const UcsSupportNumberWithRadix: UccFeature.Object = { +export const UcsSupportNumberWithRadix: UccFeature.Object = { uccProcess(compiler) { return { configure() { @@ -27,7 +27,7 @@ export const UcsSupportNumberWithRadix: UccFeature.Object = { }, }; -export const UcsSupportRadixNumber: UccFeature.Object = { +export const UcsSupportRadixNumber: UccFeature.Object = { uccProcess(compiler) { return { configure() { @@ -41,7 +41,7 @@ export const UcsSupportRadixNumber: UccFeature.Object = { }, }; -export const UcsSupportRadixNumberSchema: UccSchemaFeature.Object = { +export const UcsSupportRadixNumberSchema: UccSchemaFeature.Object = { uccProcessSchema(compiler, schema: UcSchema) { return { configure() { From fe6b839491a5a8dc45a6bcc65af8cd9688caf82b Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 27 Jul 2023 17:02:29 +0700 Subject: [PATCH 06/36] Inset formatters --- src/compiler/serialization/ucs-compiler.ts | 64 +++++++++++++++--- src/compiler/serialization/ucs-formatter.ts | 10 ++- src/compiler/serialization/ucs-function.ts | 75 +++++++++++++++++---- src/compiler/serialization/ucs-lib.ts | 15 ++++- 4 files changed, 140 insertions(+), 24 deletions(-) diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 32e73279..e84b8288 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -7,9 +7,10 @@ import { esEvaluate, esGenerate, } from 'esgen'; -import { UcFormatName } from '../../schema/uc-presentations.js'; +import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; +import { UccConfig, UccConfigContext } from '../processor/ucc-config.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; @@ -30,8 +31,9 @@ export class UcsCompiler implements UcsSetup { readonly #options: UcsCompiler.Options; - readonly #formats = new Map>(); + readonly #presentations = new Map>(); + #configContext: UccConfigContext | undefined; #bootstrapped = false; /** @@ -60,11 +62,25 @@ export class UcsCompiler return this; } + protected override configure( + config: UccConfig, + options: TOptions, + context: UccConfigContext, + ): void { + const prevContext = this.#configContext; + + this.#configContext = context; + super.configure(config, options, context); + this.#configContext = prevContext; + } + formatWith( format: UcFormatName, target: UcSchema['type'] | UcSchema, formatter: UcsFormatter, ): this { + const inset = this.#configContext?.within as UcInsetName | undefined; + const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; const fullFormatter: UcsFormatter = (args, schema, context) => { const onValue = formatter(args, schema, context); @@ -72,35 +88,43 @@ export class UcsCompiler }; if (typeof target === 'object') { - this.#typeEntryFor(format, target.type).formatSchemaWith(target, fullFormatter); + this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, fullFormatter); } else { - this.#typeEntryFor(format, target).formatTypeWith(fullFormatter); + this.#typeEntryFor(id, format, target).formatTypeWith(fullFormatter); } return this; } #typeEntryFor>( + id: UcsPresentationId, format: UcFormatName, type: TSchema['type'], ): UcsTypeEntry { - let perType = this.#formats.get(format); + let perType = this.#presentations.get(id); if (!perType) { perType = new Map(); - this.#formats.set(format, perType); + this.#presentations.set(id, perType); } let typeEntry = perType.get(type) as UcsTypeEntry | undefined; if (!typeEntry) { - typeEntry = new UcsTypeEntry(this.schemaIndex); + typeEntry = new UcsTypeEntry(this.schemaIndex, format); perType.set(type, typeEntry); } return typeEntry; } + #findTypeEntryFor>( + id: UcsPresentationId, + type: TSchema['type'], + ): UcsTypeEntry | undefined { + return this.#presentations.get(id)?.get(type) as UcsTypeEntry | undefined; + } + /** * Generates serialization code. * @@ -163,6 +187,7 @@ export class UcsCompiler ...this.#options, schemaIndex: this.schemaIndex, formatterFor: this.#formatterFor.bind(this), + insetFormatterFor: this.#insetFormatterFor.bind(this), createSerializer, }; } @@ -189,7 +214,26 @@ export class UcsCompiler format: UcFormatName, schema: TSchema, ): UcsFormatter | undefined { - return this.#typeEntryFor(format, schema.type).formatterFor(schema); + return this.#findTypeEntryFor(`format:${format}`, schema.type)?.formatterFor( + schema, + ); + } + + #insetFormatterFor>( + inset: UcInsetName, + schema: TSchema, + ): [UcFormatName, UcsFormatter] | undefined { + const typeEntry = this.#findTypeEntryFor(`inset:${inset}`, schema.type); + + if (typeEntry) { + const formatter = typeEntry?.formatterFor(schema); + + if (formatter) { + return [typeEntry.format, formatter]; + } + } + + return; } } @@ -206,13 +250,15 @@ export namespace UcsCompiler { } } +type UcsPresentationId = `format:${UcFormatName}` | `inset:${UcInsetName}`; + class UcsTypeEntry = UcSchema> { readonly #schemaIndex: UccSchemaIndex; readonly #schemaFormatters = new Map>(); #typeFormatter: UcsFormatter | undefined; - constructor(schemaIndex: UccSchemaIndex) { + constructor(schemaIndex: UccSchemaIndex, readonly format: UcFormatName) { this.#schemaIndex = schemaIndex; } diff --git a/src/compiler/serialization/ucs-formatter.ts b/src/compiler/serialization/ucs-formatter.ts index 6cb83816..2980bc33 100644 --- a/src/compiler/serialization/ucs-formatter.ts +++ b/src/compiler/serialization/ucs-formatter.ts @@ -1,5 +1,5 @@ import { EsArg, EsSignature, EsSnippet } from 'esgen'; -import { UcFormatName } from '../../schema/uc-presentations.js'; +import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; /** @@ -30,6 +30,14 @@ export interface UcsFormatterContext { onUnknownSchema?: (schema: UcSchema, context: UcsFormatterContext) => never, ): EsSnippet; + formatInset( + format: UcInsetName, + schema: UcSchema, + args: UcsFormatterSignature.AllValues, + onUnknownInset?: (schema: UcSchema, inset: UcInsetName) => never, + onUnknownSchema?: (schema: UcSchema, context: UcsFormatterContext) => never, + ): EsSnippet; + toString(): string; } diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index 08ebc129..c7f090d6 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -1,12 +1,12 @@ import { EsFunction, EsSnippet, EsVarSymbol, esline } from 'esgen'; import { ucModelName } from '../../schema/uc-model-name.js'; -import { UcFormatName } from '../../schema/uc-presentations.js'; +import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { ucSchemaSymbol } from '../impl/uc-schema-symbol.js'; import { ucSchemaVariant } from '../impl/uc-schema-variant.js'; import { UcsExportSignature } from './ucs-export.signature.js'; -import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; +import { UcsFormatter, UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsModels } from './ucs-models.js'; import { UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js'; @@ -15,7 +15,10 @@ export class UcsFunction = UcSc readonly #schema: TSchema; readonly #createWriter: Exclude['createWriter'], undefined>; - readonly #formats = new Map(); + readonly #formats = new Map< + UcFormatName | `${UcInsetName}(${UcFormatName})`, + UcsFunction$Context + >(); constructor(options: UcsFunction.Options); constructor({ @@ -31,24 +34,52 @@ export class UcsFunction = UcSc } format( - format: UcFormatName, + { + format, + inset, + }: + | { + format: UcFormatName; + inset?: undefined; + } + | { + format?: undefined; + inset: UcInsetName; + }, schema: UcSchema, args: UcsFormatterSignature.AllValues, onUnknownSchema: ( schema: UcSchema, context: UcsFormatterContext, ) => never = UcsFunction$onUnknownSchema, + onUnknownInset: (schema: UcSchema, inset: UcInsetName) => never = UcsFunction$onUnknownInset, ): EsSnippet { return (code, scope) => { - const context = this.#contextFor(format); const lib = scope.get(UcsLib); - const serializer = lib.formatterFor(format, schema)?.(args, schema, context); + let context: UcsFunction$Context; + let formatter: UcsFormatter | undefined; + + if (inset) { + const insetFormatter = lib.insetFormatterFor(inset, schema); + + if (!insetFormatter) { + onUnknownInset(schema, inset); + } + + context = this.#contextFor(insetFormatter[0], inset); + formatter = insetFormatter[1]; + } else { + context = this.#contextFor(format); + formatter = lib.formatterFor(format, schema); + } - if (serializer == null) { + const write = formatter?.(args, schema, context); + + if (write == null) { onUnknownSchema(schema, context); } - code.write(serializer); + code.write(write); }; } @@ -80,12 +111,13 @@ export class UcsFunction = UcSc }); } - #contextFor(format: UcFormatName): UcsFunction$Context { - let context = this.#formats.get(format); + #contextFor(format: UcFormatName, inset?: UcInsetName): UcsFunction$Context { + const id = inset ? (`${inset}(${format})` as const) : format; + let context = this.#formats.get(id); if (!context) { context = new UcsFunction$Context(this, format); - this.#formats.set(format, context); + this.#formats.set(id, context); } return context; @@ -116,6 +148,13 @@ function UcsFunction$onUnknownSchema(schema: UcSchema, context: UcsFormatterCont ); } +function UcsFunction$onUnknownInset(schema: UcSchema, inset: UcInsetName): never { + throw new UnsupportedUcSchemaError( + schema, + `Can not serialize inset "${inset} of type "${ucModelName(schema)}"`, + ); +} + class UcsFunction$Context implements UcsFormatterContext { readonly #serializer: UcsFunction>; @@ -150,13 +189,23 @@ class UcsFunction$Context implements UcsFormatterContext { } format( - schema: UcSchema, + schema: UcSchema, args: UcsFormatterSignature.AllValues, onUnknownSchema?: | ((schema: UcSchema, context: UcsFormatterContext) => never) | undefined, ): EsSnippet { - return this.#serializer.format(this.formatName, schema, args, onUnknownSchema); + return this.#serializer.format({ format: this.formatName }, schema, args, onUnknownSchema); + } + + formatInset( + inset: UcInsetName, + schema: UcSchema, + args: UcsFormatterSignature.AllValues, + onUnknownInset?: (schema: UcSchema, inset: UcInsetName) => never, + onUnknownSchema?: (schema: UcSchema, context: UcsFormatterContext) => never, + ): EsSnippet { + return this.#serializer.format({ inset }, schema, args, onUnknownSchema, onUnknownInset); } toString(): string { diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index 2d9b98e4..73b49dc2 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -8,7 +8,7 @@ import { esStringLiteral, esline, } from 'esgen'; -import { UcFormatName } from '../../schema/uc-presentations.js'; +import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UcsFormatter } from './ucs-formatter.js'; @@ -90,6 +90,13 @@ export class UcsLib { return this.#options.formatterFor?.(format, schema); } + insetFormatterFor = UcSchema>( + inset: UcInsetName, + schema: TSchema, + ): [UcFormatName, UcsFormatter] | undefined { + return this.#options.insetFormatterFor?.(inset, schema); + } + binConst(value: string): EsSymbol { const encoder = (this.#textEncoder ??= esConst('TEXT_ENCODER', 'new TextEncoder()')); @@ -126,6 +133,12 @@ export namespace UcsLib { schema: TSchema, ): UcsFormatter | undefined; + insetFormatterFor?>( + this: void, + inset: UcInsetName, + schema: TSchema, + ): [UcFormatName, UcsFormatter] | undefined; + createSerializer>( this: void, options: UcsFunction.Options, From cc02cf18bce7bd076556bc1e85fd0a67a83f87b5 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sat, 29 Jul 2023 15:15:48 +0700 Subject: [PATCH 07/36] Add schema processing profiles --- .../deserialization/ucd-compiler.spec.ts | 44 +- src/compiler/deserialization/ucd-compiler.ts | 7 +- .../deserialization/ucd-support-inset.ts | 4 +- src/compiler/processor/ucc-config.ts | 15 +- src/compiler/processor/ucc-processor.spec.ts | 319 ++++++++++++++ src/compiler/processor/ucc-processor.ts | 395 ++++++++++++++---- src/compiler/processor/ucc-profile.ts | 142 +++++++ src/compiler/processor/ucc-setup.ts | 24 +- .../serialization/ucs-compiler.spec.ts | 44 +- src/compiler/serialization/ucs-compiler.ts | 21 +- src/spec/mod.ts | 2 +- src/spec/ucc-test-setup.ts | 56 +++ src/spec/wrong.feature.ts | 1 - 13 files changed, 857 insertions(+), 217 deletions(-) create mode 100644 src/compiler/processor/ucc-processor.spec.ts create mode 100644 src/compiler/processor/ucc-profile.ts create mode 100644 src/spec/ucc-test-setup.ts delete mode 100644 src/spec/wrong.feature.ts diff --git a/src/compiler/deserialization/ucd-compiler.spec.ts b/src/compiler/deserialization/ucd-compiler.spec.ts index bcd91fea..c512ecae 100644 --- a/src/compiler/deserialization/ucd-compiler.spec.ts +++ b/src/compiler/deserialization/ucd-compiler.spec.ts @@ -54,7 +54,7 @@ describe('UcdCompiler', () => { }); }); - describe('schema uses', () => { + describe('schema constraints', () => { it('enables deserializer feature', async () => { const schema: UcSchema = { type: 'timestamp', @@ -118,48 +118,6 @@ describe('UcdCompiler', () => { expect(readTimestamp(`!timestamp'${now.toISOString()}`)).toBe(now.getTime()); }); - it('fails to enable missing feature', async () => { - const schema: UcSchema = { - type: 'timestamp', - where: { - deserializer: { - use: 'MissingFeature', - from: SPEC_MODULE, - }, - }, - }; - - await expect( - new UcdCompiler({ - models: { readTimestamp: { model: schema } }, - }).bootstrap(), - ).rejects.toThrow( - new ReferenceError( - `No such schema processing feature: import('${SPEC_MODULE}').MissingFeature`, - ), - ); - }); - it('fails to enable wrong feature', async () => { - const schema: UcSchema = { - type: 'timestamp', - where: { - deserializer: { - use: 'WrongFeature', - from: SPEC_MODULE, - }, - }, - }; - - await expect( - new UcdCompiler({ - models: { readTimestamp: { model: schema } }, - }).bootstrap(), - ).rejects.toThrow( - new ReferenceError( - `Not a schema processing feature: import('${SPEC_MODULE}').WrongFeature`, - ), - ); - }); }); describe('generate', () => { diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 039d6de5..dca13614 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -73,11 +73,6 @@ export class UcdCompiler return this; } - protected override createSchemaSetup(schema: UcSchema): UcdSetup; - protected override createSchemaSetup(_schema: UcSchema): UcdSetup { - return this; - } - protected override createConfig( setup: UcdSetup, feature: UccFeature, @@ -104,7 +99,7 @@ export class UcdCompiler // Stop registering default handlers. // Start registering custom ones. - defaultConfig.configure(undefined, {}); + defaultConfig.configure(undefined); this.#entities.makeDefault(); this.#formats.makeDefault(); diff --git a/src/compiler/deserialization/ucd-support-inset.ts b/src/compiler/deserialization/ucd-support-inset.ts index f9ad9048..6d3e46d4 100644 --- a/src/compiler/deserialization/ucd-support-inset.ts +++ b/src/compiler/deserialization/ucd-support-inset.ts @@ -9,7 +9,9 @@ import { UcrxSetup } from '../rx/ucrx-setup.js'; export function ucdSupportInset(setup: UcrxSetup, schema: UcSchema): UccConfig { return { - configure({ lexer, from, method, args }, { within }) { + configure({ lexer, from, method, args }) { + const within = setup.currentPresentation; + setup .modifyUcrxClass(schema, { applyTo(ucrxClass) { diff --git a/src/compiler/processor/ucc-config.ts b/src/compiler/processor/ucc-config.ts index 0ee38a38..46d68365 100644 --- a/src/compiler/processor/ucc-config.ts +++ b/src/compiler/processor/ucc-config.ts @@ -1,5 +1,3 @@ -import { UcPresentationName } from '../../schema/uc-presentations.js'; - /** * Schema processing configuration. * @@ -14,17 +12,6 @@ export interface UccConfig { * May be called multiple times. * * @param options - Configuration options. - * @param context - Configuration context. */ - configure(options: TOptions, context: UccConfigContext): void; -} - -/** - * Schema processing configuration context. - */ -export interface UccConfigContext { - /** - * Presentation name the feature is applied in. - */ - readonly within?: UcPresentationName | undefined; + configure(options: TOptions): void; } diff --git a/src/compiler/processor/ucc-processor.spec.ts b/src/compiler/processor/ucc-processor.spec.ts new file mode 100644 index 00000000..3701fd55 --- /dev/null +++ b/src/compiler/processor/ucc-processor.spec.ts @@ -0,0 +1,319 @@ +import { describe, expect, it } from '@jest/globals'; +import { SPEC_MODULE } from '../../impl/module-names.js'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { + UccTestSetup, + recordUcTestData, + ucTestRecord, + ucTestSubRecord, + ucTestSupportRecord, +} from '../../spec/ucc-test-setup.js'; +import { UccProcessor } from './ucc-processor.js'; + +describe('UccProcessor', () => { + describe('schema constraints', () => { + it('fails to enable missing feature', async () => { + const schema: UcSchema = { + type: 'timestamp', + where: { + deserializer: { + use: 'MissingFeature', + from: SPEC_MODULE, + }, + }, + }; + + await expect( + new UccTestProcessor({ + processors: ['deserializer'], + models: [schema], + }).bootstrap(), + ).rejects.toThrow( + new ReferenceError( + `No such schema processing feature: import('${SPEC_MODULE}').MissingFeature`, + ), + ); + }); + it('fails to enable wrong feature', async () => { + const schema: UcSchema = { + type: 'timestamp', + where: { + deserializer: { + use: 'WrongFeature', + from: SPEC_MODULE, + }, + }, + }; + + await expect( + new UccTestProcessor({ + processors: ['deserializer'], + models: [schema], + }).bootstrap(), + ).rejects.toThrow( + new ReferenceError( + `Not a schema processing feature: import('${SPEC_MODULE}').WrongFeature`, + ), + ); + }); + it('applies schema constraint', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', where: constraints }; + const processor = new UccTestProcessor({ + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([ + { + processor: 'deserializer', + schema, + constraint: constraints.deserializer, + options: 'test', + }, + ]); + }); + it('applies schema constraint within presentation', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', within: { charge: constraints } }; + const processor = new UccTestProcessor({ + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([ + { + processor: 'deserializer', + schema, + presentation: 'charge', + constraint: constraints.deserializer, + options: 'test', + }, + ]); + }); + it('enables feature', async () => { + const constraints = ucTestSubRecord('test'); + const schema: UcSchema = { type: 'test', within: { charge: constraints } }; + const processor = new UccTestProcessor({ + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([ + { + processor: 'deserializer', + options: 'test', + }, + ]); + }); + }); + + describe('features', () => { + it('enables feature', async () => { + const processor = new UccTestProcessor({ + processors: [], + models: [], + features: setup => ({ + configure(options) { + recordUcTestData(setup, options); + }, + }), + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([{}]); + }); + }); + + describe('profiles', () => { + it('applies constraint at most once', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', where: constraints }; + const processor = new UccTestProcessor({ + profiles: activation => { + activation.onConstraint( + { + processor: 'deserializer', + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + }, + async application => { + application.ignore(); + await application.apply(); + await application.apply(); + application.ignore(); + await application.apply(); + await application.apply(); + }, + ); + }, + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([ + { + processor: 'deserializer', + schema, + constraint: constraints.deserializer, + options: 'test', + }, + ]); + }); + it('allows to ignore constraint', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', where: constraints }; + const processor = new UccTestProcessor({ + profiles: activation => { + activation.onConstraint( + { + processor: 'deserializer', + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + }, + application => { + application.ignore(); + }, + ); + }, + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([]); + }); + it('allows to ignore constraint within any presentation', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', within: { charge: constraints } }; + const processor = new UccTestProcessor({ + profiles: activation => { + activation.onConstraint( + { + processor: 'deserializer', + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + }, + application => { + application.ignore(); + }, + ); + }, + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([]); + }); + it('allows to ignore constraint within concrete presentation', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', within: { charge: constraints } }; + const processor = new UccTestProcessor({ + profiles: activation => { + activation.onConstraint( + { + processor: 'deserializer', + within: 'charge', + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + }, + application => { + application.ignore(); + }, + ); + }, + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([]); + }); + it('allows to apply multiple constraints', async () => { + const constraints = ucTestRecord('test'); + const schema: UcSchema = { type: 'test', within: { charge: constraints } }; + const processor = new UccTestProcessor({ + profiles: activation => { + activation + .onConstraint( + { + processor: 'deserializer', + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + }, + application => { + application.ignore(); + activation.setup.record({ + processor: application.processor, + schema: application.schema, + within: application.within, + constraint: application.constraint, + }); + }, + ) + .onConstraint( + { + processor: 'deserializer', + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + }, + () => { + recordUcTestData(activation.setup, 2); + }, + ); + }, + processors: ['deserializer'], + models: [schema], + }); + + await processor.bootstrap(); + + expect(processor.records).toEqual([ + { + processor: 'deserializer', + schema, + within: 'charge', + constraint: constraints.deserializer, + }, + { + processor: 'deserializer', + schema, + presentation: 'charge', + constraint: constraints.deserializer, + options: 2, + }, + ]); + }); + }); +}); + +class UccTestProcessor extends UccProcessor implements UccTestSetup { + + readonly records: unknown[] = []; + + record(value: unknown): void { + this.records.push(value); + } + + async bootstrap(): Promise { + await this.processInstructions(); + } + + protected override createSetup(): UccTestSetup { + return this; + } + +} diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 8e8943c2..b590c078 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -7,8 +7,9 @@ import { } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; -import { UccConfig, UccConfigContext } from './ucc-config.js'; +import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; +import { UccProfile } from './ucc-profile.js'; import { UccSchemaFeature } from './ucc-schema-feature.js'; import { UccSchemaIndex } from './ucc-schema-index.js'; import { UccSetup } from './ucc-setup.js'; @@ -24,12 +25,17 @@ export abstract class UccProcessor> implements UccSetup { readonly #schemaIndex: UccSchemaIndex; + readonly #profiles: readonly UccProfile[] | undefined; readonly #models: readonly UcModel[] | undefined; readonly #features: readonly UccFeature[] | undefined; + readonly #getSetup = lazyValue(() => this.createSetup()); + readonly #profiler = new UccProcessor$Profiler(this, this.#configureAsync.bind(this)); + readonly #configs = new Map, () => UccConfig>(); readonly #uses = new Map>(); - #setup?: TSetup; + #hasPendingInstructions = false; + #current: UccProcessor$Current = {}; /** * Constructs schema processor. @@ -37,42 +43,93 @@ export abstract class UccProcessor> * @param init - Processor initialization options. */ constructor(init: UccProcessorInit); - constructor({ processors, presentations = [], models, features }: UccProcessorInit) { + constructor({ + processors, + presentations = [], + profiles, + models, + features, + }: UccProcessorInit) { this.#schemaIndex = new UccSchemaIndex( asArray(processors), asArray(presentations), ); + this.#profiles = profiles && asArray(profiles); this.#models = models; this.#features = features && asArray(features); } + get setup(): TSetup { + return this.#getSetup(); + } + get schemaIndex(): UccSchemaIndex { return this.#schemaIndex; } + get currentProcessor(): UcProcessorName | undefined { + return this.#current.processor; + } + + get currentSchema(): UcSchema | undefined { + return this.#current.schema; + } + + get currentPresentation(): UcPresentationName | undefined { + return this.#current.within; + } + + get currentConstraint(): UcFeatureConstraint | undefined { + return this.#current.constraint; + } + enable(feature: UccFeature, options?: TOptions): this { let getConfig = this.#configs.get(feature) as (() => UccConfig) | undefined; if (!getConfig) { - getConfig = lazyValue(() => this.createConfig(this.#getSetup(), feature)); + getConfig = lazyValue(() => this.createConfig(this.setup, feature)); this.#configs.set(feature, getConfig); } - this.configure(getConfig(), options!, {}); + this.#configureSync({}, () => getConfig!().configure(options!)); return this; } - #getSetup(): TSetup { - return (this.#setup ??= this.createSetup()); + #configureSync(current: UccProcessor$Current, action: () => void): void { + const prev = this.#pushCurrent(current); + + try { + action(); + } finally { + this.#current = prev; + } + } + + async #configureAsync(current: UccProcessor$Current, action: () => Promise): Promise { + const prev = this.#pushCurrent(current); + + try { + await action(); + } finally { + this.#current = prev; + } + } + + #pushCurrent(current: UccProcessor$Current): UccProcessor$Current { + const prev = this.#current; + + this.#current = current.processor ? current : { ...current, processor: prev.processor }; + + return prev; } processModel(model: UcModel): this { const schema = ucSchema(model); - this.#applyConstraints(schema, schema.where, {}); + this.#applyConstraints(schema, undefined, schema.where); for (const within of this.schemaIndex.listPresentations(schema.within)) { - this.#applyConstraints(schema, schema.within![within], { within }); + this.#applyConstraints(schema, within, schema.within![within]); } return this; @@ -80,38 +137,49 @@ export abstract class UccProcessor> #applyConstraints( schema: UcSchema, + within: UcPresentationName | undefined, constraints: UcConstraints | undefined, - context: UccConfigContext, ): void { - for (const processorName of this.schemaIndex.processors) { - asArray(constraints?.[processorName]).forEach(feature => this.#useFeature(schema, feature, context)); + for (const processor of this.schemaIndex.processors) { + for (const feature of asArray(constraints?.[processor])) { + this.#useFeature(processor, schema, within, feature); + } } } - #useFeature( + #useFeature( + processor: UcProcessorName, schema: UcSchema, - { use: feature, from, with: options }: UcFeatureConstraint, - context: UccConfigContext, + within: UcPresentationName | undefined, + constraint: UcFeatureConstraint, ): void { + const { use: feature, from } = constraint; const useId = `${this.schemaIndex.schemaId(schema)}::${from}::${feature}`; - let use = this.#uses.get(useId) as UccProcessor$FeatureUse | undefined; + let use = this.#uses.get(useId); if (!use) { this.#hasPendingInstructions = true; - use = new UccProcessor$FeatureUse(schema, from, feature); - this.#uses.set(useId, use as UccProcessor$FeatureUse); + use = new UccProcessor$FeatureUse(this.#profiler, schema); + this.#uses.set(useId, use); } - use.configure(options as TOptions, context); + use.addConfig(processor, within, constraint); } protected async processInstructions(): Promise { + this.#applyProfiles(); this.#collectInstructions(); await this.#processInstructions(); this.#enableExplicitFeatures(); await this.#processInstructions(); // More instructions may be added by explicit features. } + #applyProfiles(): void { + this.#profiles?.forEach(profile => { + profile(new UccProcessor$ProfileActivation(this.#profiler)); + }); + } + #collectInstructions(): void { this.#models?.forEach(model => { this.processModel(model); @@ -124,14 +192,9 @@ export abstract class UccProcessor> async #processInstructions(): Promise { while (this.#hasPendingInstructions) { this.#hasPendingInstructions = false; - await Promise.all( - [...this.#uses.values()].map( - async use => await use.enableIn( - schema => this.createSchemaSetup(schema), - (config, options, context) => this.configure(config, options, context), - ), - ), - ); + for (const use of this.#uses.values()) { + await use.apply(); + } } } @@ -146,13 +209,6 @@ export abstract class UccProcessor> */ protected abstract createSetup(): TSetup; - /** - * Creates processing setup for {@link UccSchemaFeature schema-specific feature}. - * - * @param schema - Target schema. - */ - protected abstract createSchemaSetup(schema: UcSchema): TSetup; - /** * Creates schema processing configuration for just {@link enable enabled} `feature`. * @@ -168,21 +224,6 @@ export abstract class UccProcessor> return 'uccProcess' in feature ? feature.uccProcess(setup) : feature(setup); } - /** - * Configures feature. - * - * @param config - Schema processing configuration. - * @param options - Configuration options. - * @param context - Configuration context. - */ - protected configure( - config: UccConfig, - options: TOptions, - context: UccConfigContext, - ): void { - config.configure(options, context); - } - } /** @@ -190,7 +231,7 @@ export abstract class UccProcessor> * * @typeParam TSetup - Schema processing setup type. */ -export interface UccProcessorInit> { +export interface UccProcessorInit> { /** * Processor names within {@link churi!UcConstraints schema constraints}. */ @@ -203,6 +244,11 @@ export interface UccProcessorInit> { */ readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; + /** + * Processor profiles to enable. + */ + readonly profiles?: UccProfile | readonly UccProfile[] | undefined; + /** * Models with constraints to extract processing instructions from. */ @@ -214,52 +260,168 @@ export interface UccProcessorInit> { readonly features?: UccFeature | readonly UccFeature[] | undefined; } -class UccProcessor$FeatureUse, TOptions = unknown> { +interface UccProcessor$Current { + readonly processor?: UcProcessorName | undefined; + readonly schema?: UcSchema | undefined; + readonly within?: UcPresentationName | undefined; + readonly constraint?: UcFeatureConstraint | undefined; +} + +class UccProcessor$Profiler> { + + readonly #processor: UccProcessor; + readonly #configure: ( + current: UccProcessor$Current, + action: () => Promise, + ) => Promise; + + readonly #handlers = new Map(); + constructor( + processor: UccProcessor, + configure: (current: UccProcessor$Current, action: () => Promise) => Promise, + ) { + this.#processor = processor; + this.#configure = configure; + } + + get setup(): TSetup { + return this.#processor.setup; + } + + onConstraint( + { processor, within, use, from }: UccProfile.ConstraintCriterion, + handler: UccProfile.ConstraintHandler, + ): void { + const handlerId = this.#handlerId(processor, within, use, from); + const prevHandler = this.#handlers.get(handlerId); + + if (prevHandler) { + this.#handlers.set(handlerId, async application => { + await prevHandler(application); + await handler(application); + }); + } else { + this.#handlers.set(handlerId, handler); + } + } + + async applyConstraint( + processor: UcProcessorName, + schema: UcSchema, + within: UcPresentationName | undefined, + constraint: UcFeatureConstraint, + ): Promise { + const application = new UccProcessor$ConstraintApplication( + this, + processor, + schema, + within, + constraint, + ); + + await this.#configure({ processor, schema, within, constraint }, async () => { + await this.#findHandler(processor, within, constraint)?.(application); + if (within) { + // Apply any presentation handler. + await this.#findHandler(processor, undefined, constraint)?.(application); + } + if (!application.isIgnored()) { + await application.apply(); + } + }); + } + + #findHandler( + processor: UcProcessorName, + within: UcPresentationName | undefined, + { use, from }: UcFeatureConstraint, + ): UccProfile.ConstraintHandler | undefined { + return this.#handlers.get(this.#handlerId(processor, within, use, from)); // Match concrete presentations.; + } + + #handlerId( + processor: UcProcessorName, + within: UcPresentationName | undefined, + use: string, + from: string, + ): string { + return `${processor}(${within} ?? '*'):${use}@${from}`; + } + +} + +class UccProcessor$ConstraintApplication> + implements UccProfile.ConstraintApplication { + + readonly #profiler: UccProcessor$Profiler; + readonly #processor: UcProcessorName; readonly #schema: UcSchema; - readonly #from: string; - readonly #name: string; - readonly #options: [TOptions, UccConfigContext][] = []; - #enabled = false; + readonly #within: UcPresentationName | undefined; + readonly #constraint: UcFeatureConstraint; + #applied = 0; - constructor(schema: UcSchema, from: string, name: string) { + constructor( + profiler: UccProcessor$Profiler, + processor: UcProcessorName, + schema: UcSchema, + within: UcPresentationName | undefined, + constraint: UcFeatureConstraint, + ) { + this.#profiler = profiler; + this.#processor = processor; this.#schema = schema; - this.#from = from; - this.#name = name; + this.#within = within; + this.#constraint = constraint; } - configure(options: TOptions, context: UccConfigContext): void { - this.#options.push([options, context]); + get schema(): UcSchema { + return this.#schema; } - async enableIn( - createSetup: (schema: UcSchema) => TSetup, - configure: (config: UccConfig, options: TOptions, context: UccConfigContext) => void, - ): Promise { - if (this.#enabled) { + get processor(): UcProcessorName { + return this.#processor; + } + + get within(): UcPresentationName | undefined { + return this.#within; + } + + get constraint(): UcFeatureConstraint { + return this.#constraint; + } + + isApplied(): boolean { + return this.#applied > 0; + } + + isIgnored(): boolean { + return this.#applied < 0; + } + + async apply(): Promise { + if (this.isApplied()) { return; } - this.#enabled = true; + this.#applied = 1; + + const { use, from, with: options } = this.constraint; const { - [this.#name]: feature, + [use]: constraint, }: { [name: string]: UccFeature | UccSchemaFeature } = - await import(this.#from); + await import(from); - if (mayHaveProperties(feature)) { + if (mayHaveProperties(constraint)) { let configured = false; - const setup = createSetup(this.#schema); - - if ('uccProcess' in feature) { - for (const [options] of this.#options) { - setup.enable(feature, options); - } + if ('uccProcess' in constraint) { + this.#profiler.setup.enable(constraint, options); configured = true; } - if ('uccProcessSchema' in feature) { - this.#configure(feature.uccProcessSchema(setup, this.#schema), configure); + if ('uccProcessSchema' in constraint) { + constraint.uccProcessSchema(this.#profiler.setup, this.schema).configure(options); configured = true; } @@ -267,31 +429,90 @@ class UccProcessor$FeatureUse, TOptions = unk return; } - if (typeof feature === 'function') { - this.#configure(feature(setup, this.#schema), configure); + if (typeof constraint === 'function') { + constraint(this.#profiler.setup, this.schema).configure(options); return; } } - if (feature === undefined) { + if (constraint === undefined) { throw new ReferenceError(`No such schema processing feature: ${this}`); } throw new ReferenceError(`Not a schema processing feature: ${this}`); } - #configure( - config: UccConfig, - configure: (config: UccConfig, options: TOptions, context: UccConfigContext) => void, - ): void { - for (const [options, context] of this.#options) { - configure(config, options, context); + ignore(): void { + if (!this.isApplied()) { + this.#applied = -1; } } toString(): string { - return `import(${esStringLiteral(this.#from)}).${esQuoteKey(this.#name)}`; + const { use, from } = this.#constraint; + + return `import(${esStringLiteral(from)}).${esQuoteKey(use)}`; + } + +} + +class UccProcessor$ProfileActivation> + implements UccProfile.Activation { + + readonly #profiler: UccProcessor$Profiler; + + constructor(profiler: UccProcessor$Profiler) { + this.#profiler = profiler; + } + + get setup(): TSetup { + return this.#profiler.setup; + } + + onConstraint( + criterion: UccProfile.ConstraintCriterion, + handler: UccProfile.ConstraintHandler, + ): this { + this.#profiler.onConstraint(criterion, handler); + + return this; + } + +} + +class UccProcessor$FeatureUse> { + + readonly #schema: UcSchema; + readonly #constraints: [UcProcessorName, UcPresentationName | undefined, UcFeatureConstraint][] = + []; + + #applied = false; + #profiler: UccProcessor$Profiler; + + constructor(profiler: UccProcessor$Profiler, schema: UcSchema) { + this.#profiler = profiler; + this.#schema = schema; + } + + addConfig( + processor: UcProcessorName, + presentation: UcPresentationName | undefined, + constraint: UcFeatureConstraint, + ): void { + this.#constraints.push([processor, presentation, constraint]); + } + + async apply(): Promise { + if (this.#applied) { + return; + } + + this.#applied = true; + + for (const [processor, within, constraint] of this.#constraints) { + await this.#profiler.applyConstraint(processor, this.#schema, within, constraint); + } } } diff --git a/src/compiler/processor/ucc-profile.ts b/src/compiler/processor/ucc-profile.ts new file mode 100644 index 00000000..04fd39f5 --- /dev/null +++ b/src/compiler/processor/ucc-profile.ts @@ -0,0 +1,142 @@ +import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; +import { UcPresentationName } from '../../schema/uc-presentations.js'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UccSetup } from './ucc-setup.js'; + +/** + * Schema {@link UccProcessor processing} profile. + * + * Can be used e.g. to refine {@link churi!UcConstraints schema constraints}, or to enable {@link UccFeature processing + * features}. + * + * @typeParam TSetup - Schema processing setup type. + * @param activation - Activation context. + */ +export type UccProfile> = ( + this: void, + activation: UccProfile.Activation, +) => void; + +export namespace UccProfile { + /** + * Activation context of {@link UccProfile schema processing profile}. + */ + export interface Activation> { + /** + * Schema processing setup. + */ + readonly setup: TSetup; + + /** + * Registers {@link churi!UcFeatureConstraint schema constraint} application handler. + * + * If multiple handlers match the same `criteria`, they all will be applied in order of their registration. + * Handlers matching any presentation always applied after the ones matching concrete one. + * + * @param criterion - Constraint criterion to apply the `handler` to. + * @param handler - Constraint application handler to call each time the matching constraint is about to be + * applied. + */ + onConstraint(criterion: ConstraintCriterion, handler: ConstraintHandler): this; + } + + /** + * Schema constraint {@link Activation#onConstraint application} handler. + * + * The handler is called each time the matching constraint applied. + * + * If the handler neither {@link ConstraintApplication#apply applies}, nor {@link ConstraintApplication#ignore + * ignores} the constraint, the latter will be applied automatically after the the handler ends its work. + * + * @param application - Constraint application context. + * + * @returns Either none if constraint application handled immediately, or promise-like instance resolved when + * constraint application handle asynchronously. + */ + export type ConstraintHandler = ( + this: void, + application: ConstraintApplication, + ) => void | PromiseLike; + + /** + * Schema constraint criterion. + */ + export interface ConstraintCriterion { + /** + * Target schema processor. + */ + readonly processor: UcProcessorName; + + /** + * Target schema instance presentation, or `undefined` to match any presentation. + */ + readonly within?: UcPresentationName | undefined; + + /** + * Target {@link churi!UcFeatureConstraint#use feature name} to use. + */ + readonly use: string; + + /** + * Target {@link churi!UcFeatureConstraint#from ECMAScript module name} to import the feature from. + */ + readonly from: string; + } + + /** + * Schema constraint application context. + */ + export interface ConstraintApplication { + /** + * Schema processor name. + */ + get processor(): UcProcessorName; + + /** + * Target schema the {@link constraint} is applied to. + */ + get schema(): UcSchema; + + /** + * Schema instance presentation the {@link constraint} to be applied within. + */ + get within(): UcPresentationName | undefined; + + /** + * Schema constraint about to be applied. + */ + get constraint(): UcFeatureConstraint; + + /** + * Informs whether the {@link constraint} is {@link apply applied} already. + * + * @returns `true` after {@link apply} method call, or `false` otherwise. + */ + isApplied(): boolean; + + /** + * Informs whether the {@link constraint} is {@link ignore ignored}. + * + * @returns `true` after {@link ignore} method call, or `false` otherwise or after {@link apply} method call. + */ + isIgnored(): boolean; + + /** + * Applies the schema {@link constraint} immediately. + * + * Does nothing if the constraint {@link isApplied applied} already. + * + * Ignores the {@link ignore} instruction. + */ + apply(): Promise; + + /** + * Ignores the {@link constraint}. + * + * Calling this method disables automatic {@link constraint} application. + * + * Does nothing if the constraint {@link isApplied applied} already. + */ + ignore(): void; + } +} diff --git a/src/compiler/processor/ucc-setup.ts b/src/compiler/processor/ucc-setup.ts index b791b651..8082fd7c 100644 --- a/src/compiler/processor/ucc-setup.ts +++ b/src/compiler/processor/ucc-setup.ts @@ -1,4 +1,6 @@ -import { UcModel } from '../../schema/uc-schema.js'; +import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; +import { UcPresentationName } from '../../schema/uc-presentations.js'; +import { UcModel, UcSchema } from '../../schema/uc-schema.js'; import { UccFeature } from './ucc-feature.js'; /** @@ -9,6 +11,26 @@ import { UccFeature } from './ucc-feature.js'; * @typeParam TSetup - Schema processing setup type. */ export interface UccSetup { + /** + * Currently working schema processor name. + */ + get currentProcessor(): UcProcessorName | undefined; + + /** + * Currently processed schema, if any + */ + get currentSchema(): UcSchema | undefined; + + /** + * Current presentation name, if any. + */ + get currentPresentation(): UcPresentationName | undefined; + + /** + * Currently processed schema constraint, if any + */ + get currentConstraint(): UcFeatureConstraint | undefined; + /** * Enables the given processing `feature`. * diff --git a/src/compiler/serialization/ucs-compiler.spec.ts b/src/compiler/serialization/ucs-compiler.spec.ts index 705161c9..b037cc9a 100644 --- a/src/compiler/serialization/ucs-compiler.spec.ts +++ b/src/compiler/serialization/ucs-compiler.spec.ts @@ -49,7 +49,7 @@ export async function writeValue(stream, value, options) { }); }); - describe('schema uses', () => { + describe('schema constraints', () => { it('enables serializer feature', async () => { const schema: UcSchema = { type: 'radixNumber', @@ -86,47 +86,5 @@ export async function writeValue(stream, value, options) { await expect(TextOutStream.read(async to => await writeValue(to, 128))).resolves.toBe('0x80'); }); - it('fails to enable missing feature', async () => { - const schema: UcSchema = { - type: 'hexNumber', - where: { - serializer: { - use: 'MissingFeature', - from: SPEC_MODULE, - }, - }, - }; - - await expect( - new UcsCompiler({ - models: { writeValue: { model: schema } }, - }).generate(), - ).rejects.toThrow( - new ReferenceError( - `No such schema processing feature: import('${SPEC_MODULE}').MissingFeature`, - ), - ); - }); - it('fails to enable wrong feature', async () => { - const schema: UcSchema = { - type: 'hexNumber', - where: { - serializer: { - use: 'WrongFeature', - from: SPEC_MODULE, - }, - }, - }; - - await expect( - new UcsCompiler({ - models: { writeValue: { model: schema } }, - }).generate(), - ).rejects.toThrow( - new ReferenceError( - `Not a schema processing feature: import('${SPEC_MODULE}').WrongFeature`, - ), - ); - }); }); }); diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index e84b8288..40765c21 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -10,7 +10,6 @@ import { import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; -import { UccConfig, UccConfigContext } from '../processor/ucc-config.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; @@ -33,7 +32,6 @@ export class UcsCompiler readonly #options: UcsCompiler.Options; readonly #presentations = new Map>(); - #configContext: UccConfigContext | undefined; #bootstrapped = false; /** @@ -57,29 +55,12 @@ export class UcsCompiler return this; } - protected override createSchemaSetup(schema: UcSchema): UcsSetup; - protected override createSchemaSetup(_schema: UcSchema): UcsSetup { - return this; - } - - protected override configure( - config: UccConfig, - options: TOptions, - context: UccConfigContext, - ): void { - const prevContext = this.#configContext; - - this.#configContext = context; - super.configure(config, options, context); - this.#configContext = prevContext; - } - formatWith( format: UcFormatName, target: UcSchema['type'] | UcSchema, formatter: UcsFormatter, ): this { - const inset = this.#configContext?.within as UcInsetName | undefined; + const inset = this.currentPresentation as UcInsetName | undefined; const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; const fullFormatter: UcsFormatter = (args, schema, context) => { const onValue = formatter(args, schema, context); diff --git a/src/spec/mod.ts b/src/spec/mod.ts index 3171f2ad..33c9ab88 100644 --- a/src/spec/mod.ts +++ b/src/spec/mod.ts @@ -2,5 +2,5 @@ export * from './meta-map.entity.js'; export * from './plain.format.js'; export * from './small-chunk-ucs-writer.js'; export * from './timestamp.format.js'; +export * from './ucc-test-setup.js'; export * from './write-uc-radix-number.js'; -export * from './wrong.feature.js'; diff --git a/src/spec/ucc-test-setup.ts b/src/spec/ucc-test-setup.ts new file mode 100644 index 00000000..679c7f7c --- /dev/null +++ b/src/spec/ucc-test-setup.ts @@ -0,0 +1,56 @@ +import { UccConfig } from '../compiler/processor/ucc-config.js'; +import { UccSetup } from '../compiler/processor/ucc-setup.js'; +import { SPEC_MODULE } from '../impl/module-names.js'; +import { UcOmniConstraints } from '../schema/uc-constraints.js'; + +export interface UccTestSetup extends UccSetup { + record(value: unknown): void; +} + +export const WrongFeature = 'WrongFeature'; + +export function ucTestRecord(options?: unknown): UcOmniConstraints { + return { + deserializer: { + use: ucTestSupportRecord.name, + from: SPEC_MODULE, + with: options, + }, + }; +} + +export function ucTestSupportRecord(setup: UccTestSetup): UccConfig { + return { + configure(options) { + recordUcTestData(setup, options); + }, + }; +} + +export function ucTestSubRecord(options?: unknown): UcOmniConstraints { + return { + deserializer: { + use: ucTestSupportSubRecord.name, + from: SPEC_MODULE, + with: options, + }, + }; +} + +export function ucTestSupportSubRecord(setup: UccTestSetup): UccConfig { + return { + configure(options) { + setup.enable(ucTestSupportRecord, options); + }, + }; +} + +export function recordUcTestData(setup: UccTestSetup, options: unknown): void { + setup.record({ + processor: setup.currentProcessor, + schema: setup.currentSchema, + presentation: setup.currentPresentation, + constraint: setup.currentConstraint, + options, + }); +} diff --git a/src/spec/wrong.feature.ts b/src/spec/wrong.feature.ts deleted file mode 100644 index 816aafb1..00000000 --- a/src/spec/wrong.feature.ts +++ /dev/null @@ -1 +0,0 @@ -export const WrongFeature = 'WrongFeature'; From b550a08b72edaf466d4a856d00a72fd8e109b1a8 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 10:35:54 +0700 Subject: [PATCH 08/36] Move setup to constraint handler --- src/compiler/processor/ucc-processor.spec.ts | 6 ++--- src/compiler/processor/ucc-processor.ts | 20 +++++++++------- src/compiler/processor/ucc-profile.ts | 25 ++++++++++++-------- src/serializer/mod.ts | 2 +- 4 files changed, 31 insertions(+), 22 deletions(-) diff --git a/src/compiler/processor/ucc-processor.spec.ts b/src/compiler/processor/ucc-processor.spec.ts index 3701fd55..0ae8ffa1 100644 --- a/src/compiler/processor/ucc-processor.spec.ts +++ b/src/compiler/processor/ucc-processor.spec.ts @@ -256,7 +256,7 @@ describe('UccProcessor', () => { }, application => { application.ignore(); - activation.setup.record({ + application.setup.record({ processor: application.processor, schema: application.schema, within: application.within, @@ -270,8 +270,8 @@ describe('UccProcessor', () => { use: ucTestSupportRecord.name, from: SPEC_MODULE, }, - () => { - recordUcTestData(activation.setup, 2); + ({ setup }) => { + recordUcTestData(setup, 2); }, ); }, diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index b590c078..9a0b0794 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -275,7 +275,7 @@ class UccProcessor$Profiler> { action: () => Promise, ) => Promise; - readonly #handlers = new Map(); + readonly #handlers = new Map>(); constructor( processor: UccProcessor, @@ -291,7 +291,7 @@ class UccProcessor$Profiler> { onConstraint( { processor, within, use, from }: UccProfile.ConstraintCriterion, - handler: UccProfile.ConstraintHandler, + handler: UccProfile.ConstraintHandler, ): void { const handlerId = this.#handlerId(processor, within, use, from); const prevHandler = this.#handlers.get(handlerId); @@ -336,7 +336,7 @@ class UccProcessor$Profiler> { processor: UcProcessorName, within: UcPresentationName | undefined, { use, from }: UcFeatureConstraint, - ): UccProfile.ConstraintHandler | undefined { + ): UccProfile.ConstraintHandler | undefined { return this.#handlers.get(this.#handlerId(processor, within, use, from)); // Match concrete presentations.; } @@ -352,7 +352,7 @@ class UccProcessor$Profiler> { } class UccProcessor$ConstraintApplication> - implements UccProfile.ConstraintApplication { + implements UccProfile.ConstraintApplication { readonly #profiler: UccProcessor$Profiler; readonly #processor: UcProcessorName; @@ -375,6 +375,10 @@ class UccProcessor$ConstraintApplication> this.#constraint = constraint; } + get setup(): TSetup { + return this.#profiler.setup; + } + get schema(): UcSchema { return this.#schema; } @@ -417,11 +421,11 @@ class UccProcessor$ConstraintApplication> let configured = false; if ('uccProcess' in constraint) { - this.#profiler.setup.enable(constraint, options); + this.setup.enable(constraint, options); configured = true; } if ('uccProcessSchema' in constraint) { - constraint.uccProcessSchema(this.#profiler.setup, this.schema).configure(options); + constraint.uccProcessSchema(this.setup, this.schema).configure(options); configured = true; } @@ -430,7 +434,7 @@ class UccProcessor$ConstraintApplication> } if (typeof constraint === 'function') { - constraint(this.#profiler.setup, this.schema).configure(options); + constraint(this.setup, this.schema).configure(options); return; } @@ -472,7 +476,7 @@ class UccProcessor$ProfileActivation> onConstraint( criterion: UccProfile.ConstraintCriterion, - handler: UccProfile.ConstraintHandler, + handler: UccProfile.ConstraintHandler, ): this { this.#profiler.onConstraint(criterion, handler); diff --git a/src/compiler/processor/ucc-profile.ts b/src/compiler/processor/ucc-profile.ts index 04fd39f5..0abf6c67 100644 --- a/src/compiler/processor/ucc-profile.ts +++ b/src/compiler/processor/ucc-profile.ts @@ -9,7 +9,7 @@ import { UccSetup } from './ucc-setup.js'; * Can be used e.g. to refine {@link churi!UcConstraints schema constraints}, or to enable {@link UccFeature processing * features}. * - * @typeParam TSetup - Schema processing setup type. + * @typeParam TSetup - Type of schema processing setup. * @param activation - Activation context. */ export type UccProfile> = ( @@ -20,13 +20,10 @@ export type UccProfile> = ( export namespace UccProfile { /** * Activation context of {@link UccProfile schema processing profile}. + * + * @typeParam TSetup - Type of schema processing setup. */ export interface Activation> { - /** - * Schema processing setup. - */ - readonly setup: TSetup; - /** * Registers {@link churi!UcFeatureConstraint schema constraint} application handler. * @@ -37,7 +34,7 @@ export namespace UccProfile { * @param handler - Constraint application handler to call each time the matching constraint is about to be * applied. */ - onConstraint(criterion: ConstraintCriterion, handler: ConstraintHandler): this; + onConstraint(criterion: ConstraintCriterion, handler: ConstraintHandler): this; } /** @@ -48,14 +45,15 @@ export namespace UccProfile { * If the handler neither {@link ConstraintApplication#apply applies}, nor {@link ConstraintApplication#ignore * ignores} the constraint, the latter will be applied automatically after the the handler ends its work. * + * @typeParam TSetup - Type of schema processing setup. * @param application - Constraint application context. * * @returns Either none if constraint application handled immediately, or promise-like instance resolved when * constraint application handle asynchronously. */ - export type ConstraintHandler = ( + export type ConstraintHandler> = ( this: void, - application: ConstraintApplication, + application: ConstraintApplication, ) => void | PromiseLike; /** @@ -85,8 +83,15 @@ export namespace UccProfile { /** * Schema constraint application context. + * + * @typeParam TSetup - Type of schema processing setup. */ - export interface ConstraintApplication { + export interface ConstraintApplication> { + /** + * Schema processing setup. + */ + get setup(): TSetup; + /** * Schema processor name. */ diff --git a/src/serializer/mod.ts b/src/serializer/mod.ts index 60201aa8..3a200576 100644 --- a/src/serializer/mod.ts +++ b/src/serializer/mod.ts @@ -8,7 +8,7 @@ export * from './ucs-constants.js'; export * from './ucs-memory.js'; export * from './ucs-write-asis.js'; +export * from './ucs-write-bigint.js'; export * from './ucs-write-number.js'; export * from './ucs-write-string.js'; export * from './ucs-writer.js'; -export * from './ucs-write-bigint.js'; From 175301d8d96a384d3a4350b10e4226e1a7620c6b Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 10:37:28 +0700 Subject: [PATCH 09/36] Remove redundant property --- src/compiler/processor/ucc-processor.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 9a0b0794..1e5beeeb 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -470,10 +470,6 @@ class UccProcessor$ProfileActivation> this.#profiler = profiler; } - get setup(): TSetup { - return this.#profiler.setup; - } - onConstraint( criterion: UccProfile.ConstraintCriterion, handler: UccProfile.ConstraintHandler, From 506b187707b0926341bfc2667ed7bfeee0a0aaeb Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 11:19:29 +0700 Subject: [PATCH 10/36] Reorganize implementations layout --- project.config.js | 4 +- .../impl/uc-value.compiler.ts | 10 ++--- .../impl/ucd-handler-registry.ts | 4 +- .../impl/uri-charge.compiler.ts | 38 +++++++++---------- .../impl/uri-charge.deserializer.stub.ts | 4 +- src/compiler/deserialization/ucd-compiler.ts | 2 +- .../deserialization/ucd-support-inset.ts | 2 +- .../{ => rx}/impl/custom-base.ucrx.class.ts | 2 +- .../{ => rx}/impl/custom-opaque.ucrx.class.ts | 0 src/compiler/{ => rx}/impl/ucrx-core.stub.ts | 0 src/compiler/{ => rx}/impl/void.ucrx.class.ts | 8 ++-- src/compiler/rx/ucrx-attr-setter.ts | 2 +- src/compiler/rx/ucrx-core.ts | 2 +- src/compiler/rx/ucrx-entity-setter.ts | 2 +- src/compiler/rx/ucrx-formatted-setter.ts | 2 +- src/compiler/rx/ucrx-inset-method.ts | 2 +- src/compiler/rx/ucrx-lib.ts | 6 +-- src/compiler/rx/ucrx.class.spec.ts | 2 +- .../impl/ucs-check-constraints.ts | 4 +- .../serialization/impl/ucs-format-bigint.ts | 32 ++++++++++++++++ src/compiler/serialization/ucs-compiler.ts | 2 +- .../serialization/ucs-support-bigint.ts | 28 ++------------ src/compiler/serialization/ucs-support-map.ts | 2 +- src/schema/meta/uc-meta.deserializer.spec.ts | 2 +- .../unknown/uc-unknown.deserializer.spec.ts | 2 +- .../uri-charge/parse-uri-charge.spec.ts | 2 +- tsconfig.json | 4 +- 27 files changed, 91 insertions(+), 79 deletions(-) rename src/compiler/{ => deserialization}/impl/uc-value.compiler.ts (78%) rename src/compiler/{ => deserialization}/impl/ucd-handler-registry.ts (93%) rename src/compiler/{ => deserialization}/impl/uri-charge.compiler.ts (81%) rename src/compiler/{ => deserialization}/impl/uri-charge.deserializer.stub.ts (50%) rename src/compiler/{ => rx}/impl/custom-base.ucrx.class.ts (85%) rename src/compiler/{ => rx}/impl/custom-opaque.ucrx.class.ts (100%) rename src/compiler/{ => rx}/impl/ucrx-core.stub.ts (100%) rename src/compiler/{ => rx}/impl/void.ucrx.class.ts (79%) rename src/compiler/{ => serialization}/impl/ucs-check-constraints.ts (89%) create mode 100644 src/compiler/serialization/impl/ucs-format-bigint.ts diff --git a/project.config.js b/project.config.js index ec7ba96d..1f136301 100644 --- a/project.config.js +++ b/project.config.js @@ -10,7 +10,7 @@ export default new ProjectConfig({ default: './dist/churi.core.js', }, './churi.uc-value.compiler.js': { - source: './src/compiler/impl/uc-value.compiler.ts', + source: './src/compiler/deserialization/impl/uc-value.compiler.ts', default: './dist/churi.uc-value.compiler.js', }, './churi.uri-charge.js': { @@ -18,7 +18,7 @@ export default new ProjectConfig({ default: './dist/churi.uri-charge.js', }, './churi.uri-charge.compiler.js': { - source: './src/compiler/impl/uri-charge.compiler.ts', + source: './src/compiler/deserialization/impl/uri-charge.compiler.ts', default: './dist/churi.uri-charge.compiler.js', }, }, diff --git a/src/compiler/impl/uc-value.compiler.ts b/src/compiler/deserialization/impl/uc-value.compiler.ts similarity index 78% rename from src/compiler/impl/uc-value.compiler.ts rename to src/compiler/deserialization/impl/uc-value.compiler.ts index 50f6c46b..a73156ea 100644 --- a/src/compiler/impl/uc-value.compiler.ts +++ b/src/compiler/deserialization/impl/uc-value.compiler.ts @@ -1,9 +1,9 @@ import { EsFunction, esline } from 'esgen'; -import { UcUnknown, ucUnknown } from '../../schema/unknown/uc-unknown.js'; -import { UcdCompiler } from '../deserialization/ucd-compiler.js'; -import { UcdLib } from '../deserialization/ucd-lib.js'; -import { UcdModels } from '../deserialization/ucd-models.js'; -import { UcrxLib } from '../rx/ucrx-lib.js'; +import { UcUnknown, ucUnknown } from '../../../schema/unknown/uc-unknown.js'; +import { UcrxLib } from '../../rx/ucrx-lib.js'; +import { UcdCompiler } from '../ucd-compiler.js'; +import { UcdLib } from '../ucd-lib.js'; +import { UcdModels } from '../ucd-models.js'; export class UcValueCompiler extends UcdCompiler<{ parseUcValue: UcdModels.SyncEntry; diff --git a/src/compiler/impl/ucd-handler-registry.ts b/src/compiler/deserialization/impl/ucd-handler-registry.ts similarity index 93% rename from src/compiler/impl/ucd-handler-registry.ts rename to src/compiler/deserialization/impl/ucd-handler-registry.ts index 9f14e585..13c45515 100644 --- a/src/compiler/impl/ucd-handler-registry.ts +++ b/src/compiler/deserialization/impl/ucd-handler-registry.ts @@ -6,8 +6,8 @@ import { esMemberAccessor, esline, } from 'esgen'; -import { UcdHandlerFeature } from '../deserialization/ucd-handler-feature.js'; -import { UC_MODULE_DESERIALIZER_DEFAULTS } from './uc-modules.js'; +import { UC_MODULE_DESERIALIZER_DEFAULTS } from '../../impl/uc-modules.js'; +import { UcdHandlerFeature } from '../ucd-handler-feature.js'; export class UcdHandlerRegistry { diff --git a/src/compiler/impl/uri-charge.compiler.ts b/src/compiler/deserialization/impl/uri-charge.compiler.ts similarity index 81% rename from src/compiler/impl/uri-charge.compiler.ts rename to src/compiler/deserialization/impl/uri-charge.compiler.ts index ce49b96b..4addfb5d 100644 --- a/src/compiler/impl/uri-charge.compiler.ts +++ b/src/compiler/deserialization/impl/uri-charge.compiler.ts @@ -1,27 +1,27 @@ import { EsCode, EsSignature, EsSnippet, esStringLiteral, esline } from 'esgen'; -import { COMPILER_MODULE } from '../../impl/module-names.js'; -import { UcList, ucList } from '../../schema/list/uc-list.js'; -import { UcMap, ucMap } from '../../schema/map/uc-map.js'; -import { UcNullable } from '../../schema/uc-nullable.js'; -import { UcSchema } from '../../schema/uc-schema.js'; -import { URICharge } from '../../schema/uri-charge/uri-charge.js'; -import { ListUcrxClass } from '../deserialization/list.ucrx.class.js'; -import { MapUcrxClass, MapUcrxStore } from '../deserialization/map.ucrx.class.js'; -import { UcdCompiler } from '../deserialization/ucd-compiler.js'; -import { UcdLib } from '../deserialization/ucd-lib.js'; -import { UcdModels } from '../deserialization/ucd-models.js'; -import { ucdSupportDefaults } from '../deserialization/ucd-support-defaults.js'; -import { UnknownUcrxClass } from '../deserialization/unknown.ucrx.class.js'; -import { UcrxCore } from '../rx/ucrx-core.js'; -import { UcrxEntitySetterSignature } from '../rx/ucrx-entity-setter.js'; -import { UcrxFormattedSetterSignature } from '../rx/ucrx-formatted-setter.js'; -import { UcrxBeforeMod, UcrxMethod } from '../rx/ucrx-method.js'; -import { UcrxSetter, UcrxSetterSignature, isUcrxSetter } from '../rx/ucrx-setter.js'; +import { COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcList, ucList } from '../../../schema/list/uc-list.js'; +import { UcMap, ucMap } from '../../../schema/map/uc-map.js'; +import { UcNullable } from '../../../schema/uc-nullable.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { URICharge } from '../../../schema/uri-charge/uri-charge.js'; import { UC_MODULE_CHURI, UC_MODULE_UC_VALUE_DESERIALIZER, UC_MODULE_URI_CHARGE, -} from './uc-modules.js'; +} from '../../impl/uc-modules.js'; +import { UcrxCore } from '../../rx/ucrx-core.js'; +import { UcrxEntitySetterSignature } from '../../rx/ucrx-entity-setter.js'; +import { UcrxFormattedSetterSignature } from '../../rx/ucrx-formatted-setter.js'; +import { UcrxBeforeMod, UcrxMethod } from '../../rx/ucrx-method.js'; +import { UcrxSetter, UcrxSetterSignature, isUcrxSetter } from '../../rx/ucrx-setter.js'; +import { ListUcrxClass } from '../list.ucrx.class.js'; +import { MapUcrxClass, MapUcrxStore } from '../map.ucrx.class.js'; +import { UcdCompiler } from '../ucd-compiler.js'; +import { UcdLib } from '../ucd-lib.js'; +import { UcdModels } from '../ucd-models.js'; +import { ucdSupportDefaults } from '../ucd-support-defaults.js'; +import { UnknownUcrxClass } from '../unknown.ucrx.class.js'; export class URIChargeCompiler extends UcdCompiler<{ parseURICharge: UcdModels.SyncEntry>; diff --git a/src/compiler/impl/uri-charge.deserializer.stub.ts b/src/compiler/deserialization/impl/uri-charge.deserializer.stub.ts similarity index 50% rename from src/compiler/impl/uri-charge.deserializer.stub.ts rename to src/compiler/deserialization/impl/uri-charge.deserializer.stub.ts index 265d71e9..9745f06e 100644 --- a/src/compiler/impl/uri-charge.deserializer.stub.ts +++ b/src/compiler/deserialization/impl/uri-charge.deserializer.stub.ts @@ -1,6 +1,6 @@ import { noop } from '@proc7ts/primitives'; -import { UcDeserializer } from '../../schema/uc-deserializer.js'; -import { URICharge } from '../../schema/uri-charge/uri-charge.js'; +import { UcDeserializer } from '../../../schema/uc-deserializer.js'; +import { URICharge } from '../../../schema/uri-charge/uri-charge.js'; // istanbul ignore next export const parseURICharge: UcDeserializer.Sync = noop as any; diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index dca13614..ad46fb7b 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -17,12 +17,12 @@ import { import { capitalize } from 'httongue'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; -import { UcdHandlerRegistry } from '../impl/ucd-handler-registry.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; import { UcrxProcessor } from '../rx/ucrx-processor.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; +import { UcdHandlerRegistry } from './impl/ucd-handler-registry.js'; import { UcdHandlerFeature } from './ucd-handler-feature.js'; import { UcdLib } from './ucd-lib.js'; import { UcdExports, UcdModels } from './ucd-models.js'; diff --git a/src/compiler/deserialization/ucd-support-inset.ts b/src/compiler/deserialization/ucd-support-inset.ts index 6d3e46d4..60e27229 100644 --- a/src/compiler/deserialization/ucd-support-inset.ts +++ b/src/compiler/deserialization/ucd-support-inset.ts @@ -2,8 +2,8 @@ import { esImport, esMemberAccessor, esline } from 'esgen'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UC_TOKEN_INSET_URI_PARAM } from '../../syntax/uc-token.js'; -import { UcrxCore$stubBody } from '../impl/ucrx-core.stub.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { UcrxCore$stubBody } from '../rx/impl/ucrx-core.stub.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxSetup } from '../rx/ucrx-setup.js'; diff --git a/src/compiler/impl/custom-base.ucrx.class.ts b/src/compiler/rx/impl/custom-base.ucrx.class.ts similarity index 85% rename from src/compiler/impl/custom-base.ucrx.class.ts rename to src/compiler/rx/impl/custom-base.ucrx.class.ts index ce416954..dbfe6663 100644 --- a/src/compiler/impl/custom-base.ucrx.class.ts +++ b/src/compiler/rx/impl/custom-base.ucrx.class.ts @@ -1,5 +1,5 @@ import { EsClass } from 'esgen'; -import { UcrxSignature } from '../rx/ucrx.class.js'; +import { UcrxSignature } from '../ucrx.class.js'; import { VoidUcrxClass } from './void.ucrx.class.js'; export class CustomBaseUcrxClass extends EsClass { diff --git a/src/compiler/impl/custom-opaque.ucrx.class.ts b/src/compiler/rx/impl/custom-opaque.ucrx.class.ts similarity index 100% rename from src/compiler/impl/custom-opaque.ucrx.class.ts rename to src/compiler/rx/impl/custom-opaque.ucrx.class.ts diff --git a/src/compiler/impl/ucrx-core.stub.ts b/src/compiler/rx/impl/ucrx-core.stub.ts similarity index 100% rename from src/compiler/impl/ucrx-core.stub.ts rename to src/compiler/rx/impl/ucrx-core.stub.ts diff --git a/src/compiler/impl/void.ucrx.class.ts b/src/compiler/rx/impl/void.ucrx.class.ts similarity index 79% rename from src/compiler/impl/void.ucrx.class.ts rename to src/compiler/rx/impl/void.ucrx.class.ts index 21d8b8d6..c1b9d22d 100644 --- a/src/compiler/impl/void.ucrx.class.ts +++ b/src/compiler/rx/impl/void.ucrx.class.ts @@ -1,8 +1,8 @@ import { EsClass, EsField, esImportClass } from 'esgen'; -import { VoidUcrx } from '../../rx/void.ucrx.js'; -import { UcrxCore } from '../rx/ucrx-core.js'; -import { UcrxSignature } from '../rx/ucrx.class.js'; -import { UC_MODULE_CHURI } from './uc-modules.js'; +import { VoidUcrx } from '../../../rx/void.ucrx.js'; +import { UC_MODULE_CHURI } from '../../impl/uc-modules.js'; +import { UcrxCore } from '../ucrx-core.js'; +import { UcrxSignature } from '../ucrx.class.js'; export class VoidUcrxClass extends EsClass { diff --git a/src/compiler/rx/ucrx-attr-setter.ts b/src/compiler/rx/ucrx-attr-setter.ts index fc20ad36..e53f1a6d 100644 --- a/src/compiler/rx/ucrx-attr-setter.ts +++ b/src/compiler/rx/ucrx-attr-setter.ts @@ -1,5 +1,5 @@ import { EsArg, EsSignature } from 'esgen'; -import { UcrxCore$stub } from '../impl/ucrx-core.stub.js'; +import { UcrxCore$stub } from './impl/ucrx-core.stub.js'; import { UcrxMethod } from './ucrx-method.js'; export class UcrxAttrSetter extends UcrxMethod { diff --git a/src/compiler/rx/ucrx-core.ts b/src/compiler/rx/ucrx-core.ts index c0648cd3..dd9be9c3 100644 --- a/src/compiler/rx/ucrx-core.ts +++ b/src/compiler/rx/ucrx-core.ts @@ -1,5 +1,5 @@ import { EsArg } from 'esgen'; -import { UcrxCore$stub, UcrxCore$stubBody } from '../impl/ucrx-core.stub.js'; +import { UcrxCore$stub, UcrxCore$stubBody } from './impl/ucrx-core.stub.js'; import { UcrxAttrSetter, UcrxAttrSetterSignature } from './ucrx-attr-setter.js'; import { UcrxEntitySetter } from './ucrx-entity-setter.js'; import { UcrxFormattedSetter } from './ucrx-formatted-setter.js'; diff --git a/src/compiler/rx/ucrx-entity-setter.ts b/src/compiler/rx/ucrx-entity-setter.ts index 1ce0fac4..2a92adb1 100644 --- a/src/compiler/rx/ucrx-entity-setter.ts +++ b/src/compiler/rx/ucrx-entity-setter.ts @@ -1,5 +1,5 @@ import { EsArg, EsSignature } from 'esgen'; -import { UcrxCore$stub } from '../impl/ucrx-core.stub.js'; +import { UcrxCore$stub } from './impl/ucrx-core.stub.js'; import { UcrxMethod } from './ucrx-method.js'; export class UcrxEntitySetter extends UcrxMethod { diff --git a/src/compiler/rx/ucrx-formatted-setter.ts b/src/compiler/rx/ucrx-formatted-setter.ts index 493cdab7..6cb40e37 100644 --- a/src/compiler/rx/ucrx-formatted-setter.ts +++ b/src/compiler/rx/ucrx-formatted-setter.ts @@ -1,5 +1,5 @@ import { EsArg, EsSignature } from 'esgen'; -import { UcrxCore$stub } from '../impl/ucrx-core.stub.js'; +import { UcrxCore$stub } from './impl/ucrx-core.stub.js'; import { UcrxMethod } from './ucrx-method.js'; export class UcrxFormattedSetter extends UcrxMethod { diff --git a/src/compiler/rx/ucrx-inset-method.ts b/src/compiler/rx/ucrx-inset-method.ts index b27fcdd0..71517faa 100644 --- a/src/compiler/rx/ucrx-inset-method.ts +++ b/src/compiler/rx/ucrx-inset-method.ts @@ -9,7 +9,7 @@ import { esStringLiteral, esline, } from 'esgen'; -import { UcrxCore$stub } from '../impl/ucrx-core.stub.js'; +import { UcrxCore$stub } from './impl/ucrx-core.stub.js'; import { UcrxBeforeMod, UcrxMethod, UcrxMethodInit } from './ucrx-method.js'; import { UcrxClass } from './ucrx.class.js'; diff --git a/src/compiler/rx/ucrx-lib.ts b/src/compiler/rx/ucrx-lib.ts index 0679e275..b8e7f3c7 100644 --- a/src/compiler/rx/ucrx-lib.ts +++ b/src/compiler/rx/ucrx-lib.ts @@ -1,8 +1,8 @@ import { EsArg, EsClass, EsScope, EsSignature } from 'esgen'; import { UcSchema } from '../../schema/uc-schema.js'; -import { CustomBaseUcrxClass } from '../impl/custom-base.ucrx.class.js'; -import { CustomOpaqueUcrxClass } from '../impl/custom-opaque.ucrx.class.js'; -import { VoidUcrxClass } from '../impl/void.ucrx.class.js'; +import { CustomBaseUcrxClass } from './impl/custom-base.ucrx.class.js'; +import { CustomOpaqueUcrxClass } from './impl/custom-opaque.ucrx.class.js'; +import { VoidUcrxClass } from './impl/void.ucrx.class.js'; import { UcrxMethod } from './ucrx-method.js'; import { UcrxClass, UcrxProto } from './ucrx.class.js'; diff --git a/src/compiler/rx/ucrx.class.spec.ts b/src/compiler/rx/ucrx.class.spec.ts index d800db22..99eec52d 100644 --- a/src/compiler/rx/ucrx.class.spec.ts +++ b/src/compiler/rx/ucrx.class.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import { EsField } from 'esgen'; import { ucMap } from '../../schema/map/uc-map.js'; -import { VoidUcrxClass } from '../impl/void.ucrx.class.js'; +import { VoidUcrxClass } from './impl/void.ucrx.class.js'; import { UcrxClass, UcrxSignature } from './ucrx.class.js'; describe('UcrxClass', () => { diff --git a/src/compiler/impl/ucs-check-constraints.ts b/src/compiler/serialization/impl/ucs-check-constraints.ts similarity index 89% rename from src/compiler/impl/ucs-check-constraints.ts rename to src/compiler/serialization/impl/ucs-check-constraints.ts index bd4982a9..efe024a6 100644 --- a/src/compiler/impl/ucs-check-constraints.ts +++ b/src/compiler/serialization/impl/ucs-check-constraints.ts @@ -1,6 +1,6 @@ import { EsCode, EsSnippet, esline } from 'esgen'; -import { UcSchema } from '../../schema/uc-schema.js'; -import { UC_MODULE_SERIALIZER } from './uc-modules.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; export function ucsCheckConstraints( { writer, value }: { readonly writer: EsSnippet; readonly value: EsSnippet }, diff --git a/src/compiler/serialization/impl/ucs-format-bigint.ts b/src/compiler/serialization/impl/ucs-format-bigint.ts new file mode 100644 index 00000000..b21aa691 --- /dev/null +++ b/src/compiler/serialization/impl/ucs-format-bigint.ts @@ -0,0 +1,32 @@ +import { esline } from 'esgen'; +import { UcBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; +import { UcsFormatter } from '../ucs-formatter.js'; + +export function ucsFormatBigInt({ + string = 'parse', + number = 'parse', +}: UcBigInt.Variant | void = {}): UcsFormatter { + return ({ writer, value }) => code => { + if (string === 'serialize') { + const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); + + code + .write(esline`await ${writer}.ready;`) + .write(esline`${writer}.write(${ucsApostrophe});`); + } + if (number === 'serialize') { + const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); + + code + .write(esline`await ${writer}.ready;`) + .write(esline`await ${writeAsIs}(${writer}, ${value}.toString());`); + } else { + const writeBigInt = UC_MODULE_SERIALIZER.import( + number === 'auto' ? 'ucsWriteBigIntOrNumber' : 'ucsWriteBigInt', + ); + + code.write(esline`await ${writeBigInt}(${writer}, ${value});`); + } + }; +} diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 40765c21..779094b9 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -9,10 +9,10 @@ import { } from 'esgen'; import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; -import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; +import { ucsCheckConstraints } from './impl/ucs-check-constraints.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsLib } from './ucs-lib.js'; diff --git a/src/compiler/serialization/ucs-support-bigint.ts b/src/compiler/serialization/ucs-support-bigint.ts index a8151562..4b5f3dbc 100644 --- a/src/compiler/serialization/ucs-support-bigint.ts +++ b/src/compiler/serialization/ucs-support-bigint.ts @@ -1,8 +1,7 @@ -import { esline } from 'esgen'; import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; import { UcDataType } from '../../schema/uc-schema.js'; -import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportBigInt( @@ -10,29 +9,8 @@ export function ucsSupportBigInt( target: UcBigInt.Schema | UcDataType = BigInt, ): UccConfig { return { - configure({ string = 'parse', number = 'parse' } = {}) { - setup.formatWith('charge', target, ({ writer, value }) => code => { - if (string === 'serialize') { - const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); - - code - .write(esline`await ${writer}.ready;`) - .write(esline`${writer}.write(${ucsApostrophe});`); - } - if (number === 'serialize') { - const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); - - code - .write(esline`await ${writer}.ready;`) - .write(esline`await ${writeAsIs}(${writer}, ${value}.toString());`); - } else { - const writeBigInt = UC_MODULE_SERIALIZER.import( - number === 'auto' ? 'ucsWriteBigIntOrNumber' : 'ucsWriteBigInt', - ); - - code.write(esline`await ${writeBigInt}(${writer}, ${value});`); - } - }); + configure(variant) { + setup.formatWith('charge', target, ucsFormatBigInt(variant)); }, }; } diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index b1abc97c..c5f1610d 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -15,8 +15,8 @@ import { ucOptional } from '../../schema/uc-optional.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; -import { ucsCheckConstraints } from '../impl/ucs-check-constraints.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsCheckConstraints } from './impl/ucs-check-constraints.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; diff --git a/src/schema/meta/uc-meta.deserializer.spec.ts b/src/schema/meta/uc-meta.deserializer.spec.ts index 35341043..f16b6b19 100644 --- a/src/schema/meta/uc-meta.deserializer.spec.ts +++ b/src/schema/meta/uc-meta.deserializer.spec.ts @@ -1,6 +1,6 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { EsEvaluationError, esline } from 'esgen'; -import { URIChargeCompiler } from '../../compiler/impl/uri-charge.compiler.js'; +import { URIChargeCompiler } from '../../compiler/deserialization/impl/uri-charge.compiler.js'; import { UcDeserializer } from '../uc-deserializer.js'; import { URICharge } from '../uri-charge/uri-charge.js'; diff --git a/src/schema/unknown/uc-unknown.deserializer.spec.ts b/src/schema/unknown/uc-unknown.deserializer.spec.ts index 4279c334..d2a5f210 100644 --- a/src/schema/unknown/uc-unknown.deserializer.spec.ts +++ b/src/schema/unknown/uc-unknown.deserializer.spec.ts @@ -1,7 +1,7 @@ import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; +import { UcValueCompiler } from '../../compiler/deserialization/impl/uc-value.compiler.js'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; import { ucdSupportPrimitives } from '../../compiler/deserialization/ucd-support-primitives.js'; -import { UcValueCompiler } from '../../compiler/impl/uc-value.compiler.js'; import { ucdSupportPlainEntity } from '../../spec/plain.format.js'; import { ucdSupportTimestampFormat } from '../../spec/timestamp.format.js'; import { UcDeserializer } from '../uc-deserializer.js'; diff --git a/src/schema/uri-charge/parse-uri-charge.spec.ts b/src/schema/uri-charge/parse-uri-charge.spec.ts index bbf008c7..f6130516 100644 --- a/src/schema/uri-charge/parse-uri-charge.spec.ts +++ b/src/schema/uri-charge/parse-uri-charge.spec.ts @@ -1,5 +1,5 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; -import { URIChargeCompiler } from '../../compiler/impl/uri-charge.compiler.js'; +import { URIChargeCompiler } from '../../compiler/deserialization/impl/uri-charge.compiler.js'; import '../../spec/uri-charge-matchers.js'; import { UcDeserializer } from '../uc-deserializer.js'; import { URICharge } from './uri-charge.js'; diff --git a/tsconfig.json b/tsconfig.json index 6cb6378c..65fada55 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,9 @@ "outDir": "target/js", "exactOptionalPropertyTypes": true, "paths": { - "#churi/uri-charge/deserializer.js": ["./src/compiler/impl/uri-charge.deserializer.stub.ts"] + "#churi/uri-charge/deserializer.js": [ + "./src/compiler/deserialization/impl/uri-charge.deserializer.stub.ts" + ] } }, "include": ["src/**/*.ts"] From 72c14c12b5c3e63583ef226999a66429441b06ca Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 12:04:21 +0700 Subject: [PATCH 11/36] Reorganize format implementations layout --- src/compiler/impl/uc-modules.ts | 5 +- .../serialization/impl/ucs-format-bigint.ts | 6 +- .../serialization/impl/ucs-format-integer.ts | 24 +++++++ .../serialization/impl/ucs-format-number.ts | 18 ++++++ src/compiler/serialization/mod.ts | 1 + .../serialization/ucs-allow-plain-text.ts | 64 +++++++++++++++++++ .../serialization/ucs-support-integer.ts | 19 +----- .../serialization/ucs-support-number.ts | 13 +--- .../serialization/ucs-support-string.ts | 11 +++- .../serialization/ucs-support-unknown.ts | 3 +- src/deserializer/impl/ucrx-handle.ts | 2 +- src/deserializer/sync-ucd-reader.spec.ts | 2 +- src/deserializer/sync-ucd-reader.ts | 2 +- src/rx/opaque.ucrx.spec.ts | 2 +- src/rx/opaque.ucrx.ts | 2 +- src/rx/token.ucrx.spec.ts | 2 +- src/rx/token.ucrx.ts | 2 +- src/schema/entity/uc-formatted.ts | 2 +- src/schema/list/uc-list.deserializer.spec.ts | 2 +- src/schema/map/uc-map.deserializer.spec.ts | 2 +- .../numeric/uc-number.deserializer.spec.ts | 2 +- src/syntax/formats/charge/mod.ts | 1 + .../charge}/uc-charge.lexer.spec.ts | 14 ++-- .../charge}/uc-charge.lexer.ts | 12 ++-- src/syntax/formats/mod.ts | 5 ++ src/syntax/formats/plain-text/mod.ts | 1 + .../plain-text}/uc-plain-text.lexer.spec.ts | 22 +++---- .../plain-text}/uc-plain-text.lexer.ts | 10 +-- .../{lexers => formats}/uc-opaque.lexer.ts | 0 src/syntax/formats/uri-encoded/mod.ts | 1 + .../uri-encoded}/uc-uri-encoded.lexer.spec.ts | 18 +++--- .../uri-encoded}/uc-uri-encoded.lexer.ts | 10 +-- src/syntax/formats/uri-params/mod.ts | 1 + .../uri-params}/uc-uri-params.lexer.spec.ts | 22 +++---- .../uri-params}/uc-uri-params.lexer.ts | 10 +-- src/syntax/lexers/mod.ts | 5 -- src/syntax/mod.ts | 2 +- src/syntax/uc-lexer-stream.ts | 2 +- src/uri/churi-params.ts | 2 +- 39 files changed, 212 insertions(+), 112 deletions(-) create mode 100644 src/compiler/serialization/impl/ucs-format-integer.ts create mode 100644 src/compiler/serialization/impl/ucs-format-number.ts create mode 100644 src/compiler/serialization/ucs-allow-plain-text.ts create mode 100644 src/syntax/formats/charge/mod.ts rename src/syntax/{lexers => formats/charge}/uc-charge.lexer.spec.ts (94%) rename src/syntax/{lexers => formats/charge}/uc-charge.lexer.ts (95%) create mode 100644 src/syntax/formats/mod.ts create mode 100644 src/syntax/formats/plain-text/mod.ts rename src/syntax/{lexers => formats/plain-text}/uc-plain-text.lexer.spec.ts (91%) rename src/syntax/{lexers => formats/plain-text}/uc-plain-text.lexer.ts (80%) rename src/syntax/{lexers => formats}/uc-opaque.lexer.ts (100%) create mode 100644 src/syntax/formats/uri-encoded/mod.ts rename src/syntax/{lexers => formats/uri-encoded}/uc-uri-encoded.lexer.spec.ts (92%) rename src/syntax/{lexers => formats/uri-encoded}/uc-uri-encoded.lexer.ts (89%) create mode 100644 src/syntax/formats/uri-params/mod.ts rename src/syntax/{lexers => formats/uri-params}/uc-uri-params.lexer.spec.ts (91%) rename src/syntax/{lexers => formats/uri-params}/uc-uri-params.lexer.ts (92%) delete mode 100644 src/syntax/lexers/mod.ts diff --git a/src/compiler/impl/uc-modules.ts b/src/compiler/impl/uc-modules.ts index 42bc2760..96bbbd65 100644 --- a/src/compiler/impl/uc-modules.ts +++ b/src/compiler/impl/uc-modules.ts @@ -1,6 +1,7 @@ import { EsExternalModule } from 'esgen'; +import { CHURI_MODULE, SPEC_MODULE } from '../../impl/module-names.js'; -export const UC_MODULE_CHURI = /*#__PURE__*/ EsExternalModule.byName('churi'); +export const UC_MODULE_CHURI = /*#__PURE__*/ EsExternalModule.byName(CHURI_MODULE); export const UC_MODULE_DESERIALIZER = /*#__PURE__*/ EsExternalModule.byName('churi/deserializer.js'); export const UC_MODULE_DESERIALIZER_DEFAULTS = /*#__PURE__*/ EsExternalModule.byName( @@ -11,7 +12,7 @@ export const UC_MODULE_DESERIALIZER_META = /*#__PURE__*/ EsExternalModule.byName ); export const UC_MODULE_SERIALIZER = /*#__PURE__*/ EsExternalModule.byName('churi/serializer.js'); export const UC_MODULE_VALIDATOR = /*#__PURE__*/ EsExternalModule.byName('churi/validator.js'); -export const UC_MODULE_SPEC = /*#__PURE__*/ EsExternalModule.byName('#churi/spec.js'); +export const UC_MODULE_SPEC = /*#__PURE__*/ EsExternalModule.byName(SPEC_MODULE); export const UC_MODULE_UC_VALUE_DESERIALIZER = /*#__PURE__*/ EsExternalModule.byName( '#churi/uc-value/deserializer.js', ); diff --git a/src/compiler/serialization/impl/ucs-format-bigint.ts b/src/compiler/serialization/impl/ucs-format-bigint.ts index b21aa691..e815084d 100644 --- a/src/compiler/serialization/impl/ucs-format-bigint.ts +++ b/src/compiler/serialization/impl/ucs-format-bigint.ts @@ -1,5 +1,7 @@ import { esline } from 'esgen'; import { UcBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { ucsWriteAsIs } from '../../../serializer/ucs-write-asis.js'; +import { ucsWriteBigInt, ucsWriteBigIntOrNumber } from '../../../serializer/ucs-write-bigint.js'; import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; import { UcsFormatter } from '../ucs-formatter.js'; @@ -16,14 +18,14 @@ export function ucsFormatBigInt({ .write(esline`${writer}.write(${ucsApostrophe});`); } if (number === 'serialize') { - const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); + const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); code .write(esline`await ${writer}.ready;`) .write(esline`await ${writeAsIs}(${writer}, ${value}.toString());`); } else { const writeBigInt = UC_MODULE_SERIALIZER.import( - number === 'auto' ? 'ucsWriteBigIntOrNumber' : 'ucsWriteBigInt', + number === 'auto' ? ucsWriteBigIntOrNumber.name : ucsWriteBigInt.name, ); code.write(esline`await ${writeBigInt}(${writer}, ${value});`); diff --git a/src/compiler/serialization/impl/ucs-format-integer.ts b/src/compiler/serialization/impl/ucs-format-integer.ts new file mode 100644 index 00000000..79370346 --- /dev/null +++ b/src/compiler/serialization/impl/ucs-format-integer.ts @@ -0,0 +1,24 @@ +import { esline } from 'esgen'; +import { UcInteger } from '../../../schema/numeric/uc-integer.js'; +import { ucsWriteAsIs } from '../../../serializer/ucs-write-asis.js'; +import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; +import { UcsFormatter } from '../ucs-formatter.js'; + +export function ucsFormatInteger({ string = 'parse' }: UcInteger.Variant | void = {}): UcsFormatter< + UcInteger, + UcInteger.Schema +> { + return ({ writer, value }) => code => { + const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); + + if (string === 'serialize') { + const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); + + code + .write(esline`await ${writer}.ready;`) + .write(esline`${writer}.write(${ucsApostrophe});`); + } + + code.write(esline`await ${writeAsIs}(${writer}, ${value}.toFixed(0));`); + }; +} diff --git a/src/compiler/serialization/impl/ucs-format-number.ts b/src/compiler/serialization/impl/ucs-format-number.ts new file mode 100644 index 00000000..e5c79376 --- /dev/null +++ b/src/compiler/serialization/impl/ucs-format-number.ts @@ -0,0 +1,18 @@ +import { esline } from 'esgen'; +import { UcNumber } from '../../../schema/numeric/uc-number.js'; +import { ucsWriteNumber, ucsWriteNumberAsString } from '../../../serializer/ucs-write-number.js'; +import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; +import { UcsFormatter } from '../ucs-formatter.js'; + +export function ucsFormatNumber({ string = 'parse' }: UcNumber.Variant | void = {}): UcsFormatter< + UcNumber, + UcNumber.Schema +> { + return ({ writer, value }) => { + const writeNumber = UC_MODULE_SERIALIZER.import( + string === 'serialize' ? ucsWriteNumberAsString.name : ucsWriteNumber.name, + ); + + return esline`await ${writeNumber}(${writer}, ${value});`; + }; +} diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index fef0a719..505e3b9e 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -1,3 +1,4 @@ +export * from './ucs-allow-plain-text.js'; export * from './ucs-compiler.js'; export * from './ucs-export.signature.js'; export * from './ucs-formatter.js'; diff --git a/src/compiler/serialization/ucs-allow-plain-text.ts b/src/compiler/serialization/ucs-allow-plain-text.ts new file mode 100644 index 00000000..e97dcf49 --- /dev/null +++ b/src/compiler/serialization/ucs-allow-plain-text.ts @@ -0,0 +1,64 @@ +import { esline } from 'esgen'; +import { COMPILER_MODULE } from '../../impl/module-names.js'; +import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; +import { ucsWriteAsIs } from '../../serializer/ucs-write-asis.js'; +import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; +import { UccProfile } from '../processor/ucc-profile.js'; +import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; +import { ucsFormatInteger } from './impl/ucs-format-integer.js'; +import { ucsFormatNumber } from './impl/ucs-format-number.js'; +import { UcsSetup } from './ucs-setup.js'; +import { ucsSupportBigInt } from './ucs-support-bigint.js'; +import { ucsSupportInteger } from './ucs-support-integer.js'; +import { ucsSupportNumber } from './ucs-support-number.js'; +import { ucsSupportString } from './ucs-support-string.js'; + +export function ucsAllowPlainText(activation: UccProfile.Activation): void { + activation + .onConstraint( + { + processor: 'serializer', + use: ucsSupportBigInt.name, + from: COMPILER_MODULE, + }, + ({ setup, schema, constraint: { with: options = {} } }) => { + const { number = 'parse' } = options as UcBigInt.Variant; + + setup.formatWith('plainText', schema, ucsFormatBigInt({ string: 'parse', number })); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportInteger.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('plainText', schema, ucsFormatInteger()); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportNumber.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('plainText', schema, ucsFormatNumber()); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportString.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('plainText', schema, ({ writer, value }) => { + const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); + + return esline`await ${writeAsIs}(${writer}, ${value});`; + }); + }, + ); +} diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-support-integer.ts index 86a46bf2..d87f0520 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-support-integer.ts @@ -1,7 +1,6 @@ -import { esline } from 'esgen'; import { UcInteger } from '../../schema/numeric/uc-integer.js'; -import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatInteger } from './impl/ucs-format-integer.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportInteger( @@ -9,20 +8,8 @@ export function ucsSupportInteger( target: UcInteger.Schema, ): UccConfig { return { - configure({ string = 'parse' } = {}) { - setup.formatWith('charge', target, ({ writer, value }) => code => { - const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); - - if (string === 'serialize') { - const ucsApostrophe = UC_MODULE_SERIALIZER.import('UCS_APOSTROPHE'); - - code - .write(esline`await ${writer}.ready;`) - .write(esline`${writer}.write(${ucsApostrophe});`); - } - - code.write(esline`await ${writeAsIs}(${writer}, ${value}.toFixed(0));`); - }); + configure(variant) { + setup.formatWith('charge', target, ucsFormatInteger(variant)); }, }; } diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-support-number.ts index bf86279f..55a13bdf 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-support-number.ts @@ -1,8 +1,7 @@ -import { esline } from 'esgen'; import { UcNumber } from '../../schema/numeric/uc-number.js'; import { UcDataType } from '../../schema/uc-schema.js'; -import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatNumber } from './impl/ucs-format-number.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportNumber( @@ -10,14 +9,8 @@ export function ucsSupportNumber( target: UcNumber.Schema | UcDataType = Number, ): UccConfig { return { - configure({ string = 'parse' } = {}) { - setup.formatWith('charge', target, ({ writer, value }) => { - const writeNumber = UC_MODULE_SERIALIZER.import( - string === 'serialize' ? 'ucsWriteNumberAsString' : 'ucsWriteNumber', - ); - - return esline`await ${writeNumber}(${writer}, ${value});`; - }); + configure(variant) { + setup.formatWith('charge', target, ucsFormatNumber(variant)); }, }; } diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-support-string.ts index 60ea2538..51825c50 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-support-string.ts @@ -1,6 +1,11 @@ import { esline } from 'esgen'; import { UcString } from '../../schema/string/uc-string.js'; import { UcDataType } from '../../schema/uc-schema.js'; +import { + ucsWriteNullableRawString, + ucsWriteRawString, + ucsWriteString, +} from '../../serializer/ucs-write-string.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsSetup } from './ucs-setup.js'; @@ -14,10 +19,10 @@ export function ucsSupportString( setup.formatWith('charge', target, ({ writer, value, asItem }, schema) => { const writeString = UC_MODULE_SERIALIZER.import( raw === 'escape' - ? 'ucsWriteString' + ? ucsWriteString.name : schema.nullable - ? 'ucsWriteNullableRawString' - : 'ucsWriteRawString', + ? ucsWriteNullableRawString.name + : ucsWriteRawString.name, ); return esline`await ${writeString}(${writer}, ${value}, ${asItem});`; diff --git a/src/compiler/serialization/ucs-support-unknown.ts b/src/compiler/serialization/ucs-support-unknown.ts index 237431d9..e1a66907 100644 --- a/src/compiler/serialization/ucs-support-unknown.ts +++ b/src/compiler/serialization/ucs-support-unknown.ts @@ -1,4 +1,5 @@ import { esline } from 'esgen'; +import { ucsWriteAsIs } from '../../serializer/ucs-write-asis.js'; import { UC_MODULE_CHURI, UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsSetup } from './ucs-setup.js'; @@ -8,7 +9,7 @@ export function ucsSupportUnknown(setup: UcsSetup): UccConfig { configure() { setup.formatWith('charge', 'unknown', ({ writer, value, asItem }) => { const chargeURI = UC_MODULE_CHURI.import('chargeURI'); - const writeAsIs = UC_MODULE_SERIALIZER.import('ucsWriteAsIs'); + const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); return esline`await ${writeAsIs}(${writer}, ${chargeURI}(${value}, { asItem: ${asItem} }));`; }); diff --git a/src/deserializer/impl/ucrx-handle.ts b/src/deserializer/impl/ucrx-handle.ts index 6d2db76f..43657749 100644 --- a/src/deserializer/impl/ucrx-handle.ts +++ b/src/deserializer/impl/ucrx-handle.ts @@ -5,7 +5,7 @@ import { Ucrx } from '../../rx/ucrx.js'; import { UcMeta } from '../../schema/meta/uc-meta.js'; import { UcRejection } from '../../schema/uc-error.js'; import type { URIChargePath } from '../../schema/uri-charge/uri-charge-path.js'; -import { ucOpaqueLexer } from '../../syntax/lexers/uc-opaque.lexer.js'; +import { ucOpaqueLexer } from '../../syntax/formats/uc-opaque.lexer.js'; import { UcToken } from '../../syntax/uc-token.js'; import { UcdReader } from '../ucd-reader.js'; diff --git a/src/deserializer/sync-ucd-reader.spec.ts b/src/deserializer/sync-ucd-reader.spec.ts index 2317f3b9..10bce62d 100644 --- a/src/deserializer/sync-ucd-reader.spec.ts +++ b/src/deserializer/sync-ucd-reader.spec.ts @@ -1,5 +1,5 @@ import { afterEach, beforeEach, describe, expect, it } from '@jest/globals'; -import { UcChargeLexer } from '../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../syntax/formats/charge/uc-charge.lexer.js'; import { UC_TOKEN_CRLF, UC_TOKEN_LF } from '../syntax/uc-token.js'; import { SyncUcdReader } from './sync-ucd-reader.js'; diff --git a/src/deserializer/sync-ucd-reader.ts b/src/deserializer/sync-ucd-reader.ts index 4ae15098..538f270f 100644 --- a/src/deserializer/sync-ucd-reader.ts +++ b/src/deserializer/sync-ucd-reader.ts @@ -1,6 +1,6 @@ import { UcrxInsetLexer } from '../rx/ucrx-inset-syntax.js'; import { Ucrx } from '../rx/ucrx.js'; -import { UcChargeLexer } from '../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../syntax/formats/charge/uc-charge.lexer.js'; import { scanUcTokens } from '../syntax/scan-uc-tokens.js'; import { UcLexer } from '../syntax/uc-lexer.js'; import { UcToken } from '../syntax/uc-token.js'; diff --git a/src/rx/opaque.ucrx.spec.ts b/src/rx/opaque.ucrx.spec.ts index f2d615df..558b5f17 100644 --- a/src/rx/opaque.ucrx.spec.ts +++ b/src/rx/opaque.ucrx.spec.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from '@jest/globals'; import { noop } from '@proc7ts/primitives'; -import { ucOpaqueLexer } from '../syntax/lexers/uc-opaque.lexer.js'; +import { ucOpaqueLexer } from '../syntax/formats/uc-opaque.lexer.js'; import { UC_TOKEN_INSET_URI_PARAM } from '../syntax/uc-token.js'; import { OpaqueUcrx } from './opaque.ucrx.js'; diff --git a/src/rx/opaque.ucrx.ts b/src/rx/opaque.ucrx.ts index d808f894..51986973 100644 --- a/src/rx/opaque.ucrx.ts +++ b/src/rx/opaque.ucrx.ts @@ -1,4 +1,4 @@ -import { ucOpaqueLexer } from '../syntax/lexers/uc-opaque.lexer.js'; +import { ucOpaqueLexer } from '../syntax/formats/uc-opaque.lexer.js'; import { UcLexer } from '../syntax/uc-lexer.js'; import { UcToken } from '../syntax/uc-token.js'; import { AllUcrx } from './all.ucrx.js'; diff --git a/src/rx/token.ucrx.spec.ts b/src/rx/token.ucrx.spec.ts index babbedd8..5ebcd9b1 100644 --- a/src/rx/token.ucrx.spec.ts +++ b/src/rx/token.ucrx.spec.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { noop } from '@proc7ts/primitives'; -import { ucOpaqueLexer } from '../syntax/lexers/uc-opaque.lexer.js'; +import { ucOpaqueLexer } from '../syntax/formats/uc-opaque.lexer.js'; import { UC_TOKEN_CLOSING_PARENTHESIS, UC_TOKEN_INSET_URI_PARAM, diff --git a/src/rx/token.ucrx.ts b/src/rx/token.ucrx.ts index 6a082d37..a8938e13 100644 --- a/src/rx/token.ucrx.ts +++ b/src/rx/token.ucrx.ts @@ -1,6 +1,6 @@ import { encodeURIPart } from 'httongue'; import { UC_KEY_ESCAPED, isEscapedUcString } from '../impl/uc-string-escapes.js'; -import { ucOpaqueLexer } from '../syntax/lexers/uc-opaque.lexer.js'; +import { ucOpaqueLexer } from '../syntax/formats/uc-opaque.lexer.js'; import { printUcToken } from '../syntax/print-uc-token.js'; import { UcLexer } from '../syntax/uc-lexer.js'; import { diff --git a/src/schema/entity/uc-formatted.ts b/src/schema/entity/uc-formatted.ts index 4acc2d79..1984f5cf 100644 --- a/src/schema/entity/uc-formatted.ts +++ b/src/schema/entity/uc-formatted.ts @@ -1,7 +1,7 @@ import { AllUcrx } from '../../rx/all.ucrx.js'; import { chargeURI } from '../../rx/charge-uri.js'; import { UctxMode } from '../../rx/uctx-mode.js'; -import { UcChargeLexer } from '../../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../../syntax/formats/charge/uc-charge.lexer.js'; import { UcToken } from '../../syntax/uc-token.js'; /** diff --git a/src/schema/list/uc-list.deserializer.spec.ts b/src/schema/list/uc-list.deserializer.spec.ts index 308cd442..270c1719 100644 --- a/src/schema/list/uc-list.deserializer.spec.ts +++ b/src/schema/list/uc-list.deserializer.spec.ts @@ -3,7 +3,7 @@ import { asis } from '@proc7ts/primitives'; import { UnsupportedUcSchemaError } from '../../compiler/common/unsupported-uc-schema.error.js'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; import { parseTokens } from '../../spec/read-chunks.js'; -import { UcChargeLexer } from '../../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../../syntax/formats/charge/uc-charge.lexer.js'; import { ucMap } from '../map/uc-map.js'; import { UcDeserializer } from '../uc-deserializer.js'; import { UcError, UcErrorInfo } from '../uc-error.js'; diff --git a/src/schema/map/uc-map.deserializer.spec.ts b/src/schema/map/uc-map.deserializer.spec.ts index ea4ad63f..8e932277 100644 --- a/src/schema/map/uc-map.deserializer.spec.ts +++ b/src/schema/map/uc-map.deserializer.spec.ts @@ -2,7 +2,7 @@ import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { UnsupportedUcSchemaError } from '../../compiler/common/unsupported-uc-schema.error.js'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; import { parseTokens } from '../../spec/read-chunks.js'; -import { UcChargeLexer } from '../../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../../syntax/formats/charge/uc-charge.lexer.js'; import { ucList } from '../list/uc-list.js'; import { ucMultiValue } from '../list/uc-multi-value.js'; import { ucNumber } from '../numeric/uc-number.js'; diff --git a/src/schema/numeric/uc-number.deserializer.spec.ts b/src/schema/numeric/uc-number.deserializer.spec.ts index 8c73092b..510c6fe8 100644 --- a/src/schema/numeric/uc-number.deserializer.spec.ts +++ b/src/schema/numeric/uc-number.deserializer.spec.ts @@ -3,7 +3,7 @@ import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; import { ucdSupportNonFinite } from '../../compiler/deserialization/ucd-support-non-finite.js'; import { ucdSupportPrimitives } from '../../compiler/deserialization/ucd-support-primitives.js'; import { parseTokens } from '../../spec/read-chunks.js'; -import { UcChargeLexer } from '../../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../../syntax/formats/charge/uc-charge.lexer.js'; import { UcDeserializer } from '../uc-deserializer.js'; import { UcErrorInfo } from '../uc-error.js'; import { UcDataType } from '../uc-schema.js'; diff --git a/src/syntax/formats/charge/mod.ts b/src/syntax/formats/charge/mod.ts new file mode 100644 index 00000000..dd76b3e8 --- /dev/null +++ b/src/syntax/formats/charge/mod.ts @@ -0,0 +1 @@ +export * from './uc-charge.lexer.js'; diff --git a/src/syntax/lexers/uc-charge.lexer.spec.ts b/src/syntax/formats/charge/uc-charge.lexer.spec.ts similarity index 94% rename from src/syntax/lexers/uc-charge.lexer.spec.ts rename to src/syntax/formats/charge/uc-charge.lexer.spec.ts index f4846d87..9880f4a4 100644 --- a/src/syntax/lexers/uc-charge.lexer.spec.ts +++ b/src/syntax/formats/charge/uc-charge.lexer.spec.ts @@ -1,10 +1,10 @@ import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { esline } from 'esgen'; -import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { UC_MODULE_CHURI } from '../../compiler/impl/uc-modules.js'; -import { ucMap } from '../../schema/map/uc-map.js'; -import { UcDeserializer } from '../../schema/uc-deserializer.js'; -import { ucUnknown } from '../../schema/unknown/uc-unknown.js'; +import { UcdCompiler } from '../../../compiler/deserialization/ucd-compiler.js'; +import { UC_MODULE_CHURI } from '../../../compiler/impl/uc-modules.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; +import { UcDeserializer } from '../../../schema/uc-deserializer.js'; +import { ucUnknown } from '../../../schema/unknown/uc-unknown.js'; import { UC_TOKEN_AMPERSAND, UC_TOKEN_APOSTROPHE, @@ -30,9 +30,9 @@ import { UC_TOKEN_SEMICOLON, UC_TOKEN_SLASH, UcToken, -} from '../uc-token.js'; +} from '../../uc-token.js'; +import { UcURIParamsLexer } from '../uri-params/uc-uri-params.lexer.js'; import { UcChargeLexer, ucInsetCharge } from './uc-charge.lexer.js'; -import { UcURIParamsLexer } from './uc-uri-params.lexer.js'; describe('UcChargeLexer', () => { let lexer: UcChargeLexer; diff --git a/src/syntax/lexers/uc-charge.lexer.ts b/src/syntax/formats/charge/uc-charge.lexer.ts similarity index 95% rename from src/syntax/lexers/uc-charge.lexer.ts rename to src/syntax/formats/charge/uc-charge.lexer.ts index 0bc7bc49..4374d776 100644 --- a/src/syntax/lexers/uc-charge.lexer.ts +++ b/src/syntax/formats/charge/uc-charge.lexer.ts @@ -1,8 +1,8 @@ -import { UcdInsetOptions } from '../../compiler/deserialization/ucd-support-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../impl/module-names.js'; -import { UcOmniConstraints } from '../../schema/uc-constraints.js'; -import { scanUcTokens } from '../scan-uc-tokens.js'; -import { UcLexer } from '../uc-lexer.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; +import { scanUcTokens } from '../../scan-uc-tokens.js'; +import { UcLexer } from '../../uc-lexer.js'; import { UC_TOKEN_AMPERSAND, UC_TOKEN_APOSTROPHE, @@ -26,7 +26,7 @@ import { UC_TOKEN_SEMICOLON, UC_TOKEN_SLASH, UcToken, -} from '../uc-token.js'; +} from '../../uc-token.js'; /** * URI charge lexer that splits input string(s) onto tokens. diff --git a/src/syntax/formats/mod.ts b/src/syntax/formats/mod.ts new file mode 100644 index 00000000..990df47c --- /dev/null +++ b/src/syntax/formats/mod.ts @@ -0,0 +1,5 @@ +export * from './charge/mod.js'; +export * from './plain-text/mod.js'; +export * from './uc-opaque.lexer.js'; +export * from './uri-encoded/mod.js'; +export * from './uri-params/mod.js'; diff --git a/src/syntax/formats/plain-text/mod.ts b/src/syntax/formats/plain-text/mod.ts new file mode 100644 index 00000000..2d19a41f --- /dev/null +++ b/src/syntax/formats/plain-text/mod.ts @@ -0,0 +1 @@ +export * from './uc-plain-text.lexer.js'; diff --git a/src/syntax/lexers/uc-plain-text.lexer.spec.ts b/src/syntax/formats/plain-text/uc-plain-text.lexer.spec.ts similarity index 91% rename from src/syntax/lexers/uc-plain-text.lexer.spec.ts rename to src/syntax/formats/plain-text/uc-plain-text.lexer.spec.ts index 9e816768..a1b4b072 100644 --- a/src/syntax/lexers/uc-plain-text.lexer.spec.ts +++ b/src/syntax/formats/plain-text/uc-plain-text.lexer.spec.ts @@ -1,15 +1,15 @@ import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { esline } from 'esgen'; -import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { UC_MODULE_CHURI } from '../../compiler/impl/uc-modules.js'; -import { ucList } from '../../schema/list/uc-list.js'; -import { ucMap } from '../../schema/map/uc-map.js'; -import { UcNumber, ucNumber } from '../../schema/numeric/uc-number.js'; -import { UcString, ucString } from '../../schema/string/uc-string.js'; -import { UcDeserializer } from '../../schema/uc-deserializer.js'; -import { UcErrorInfo } from '../../schema/uc-error.js'; -import { ucUnknown } from '../../schema/unknown/uc-unknown.js'; -import { readTokens } from '../../spec/read-chunks.js'; +import { UcdCompiler } from '../../../compiler/deserialization/ucd-compiler.js'; +import { UC_MODULE_CHURI } from '../../../compiler/impl/uc-modules.js'; +import { ucList } from '../../../schema/list/uc-list.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; +import { UcNumber, ucNumber } from '../../../schema/numeric/uc-number.js'; +import { UcString, ucString } from '../../../schema/string/uc-string.js'; +import { UcDeserializer } from '../../../schema/uc-deserializer.js'; +import { UcErrorInfo } from '../../../schema/uc-error.js'; +import { ucUnknown } from '../../../schema/unknown/uc-unknown.js'; +import { readTokens } from '../../../spec/read-chunks.js'; import { UC_TOKEN_APOSTROPHE, UC_TOKEN_CLOSING_PARENTHESIS, @@ -18,7 +18,7 @@ import { UC_TOKEN_INSET_URI_PARAM, UC_TOKEN_OPENING_PARENTHESIS, UC_TOKEN_PREFIX_SPACE, -} from '../uc-token.js'; +} from '../../uc-token.js'; describe('UcPlainTextLexer', () => { let errors: UcErrorInfo[]; diff --git a/src/syntax/lexers/uc-plain-text.lexer.ts b/src/syntax/formats/plain-text/uc-plain-text.lexer.ts similarity index 80% rename from src/syntax/lexers/uc-plain-text.lexer.ts rename to src/syntax/formats/plain-text/uc-plain-text.lexer.ts index fd0b4dbb..8a3d6663 100644 --- a/src/syntax/lexers/uc-plain-text.lexer.ts +++ b/src/syntax/formats/plain-text/uc-plain-text.lexer.ts @@ -1,8 +1,8 @@ -import { UcdInsetOptions } from '../../compiler/deserialization/ucd-support-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../impl/module-names.js'; -import { UcOmniConstraints } from '../../schema/uc-constraints.js'; -import { UcLexer } from '../uc-lexer.js'; -import { UC_TOKEN_APOSTROPHE, UcToken } from '../uc-token.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; +import { UcLexer } from '../../uc-lexer.js'; +import { UC_TOKEN_APOSTROPHE, UcToken } from '../../uc-token.js'; /** * Plain text lexer. diff --git a/src/syntax/lexers/uc-opaque.lexer.ts b/src/syntax/formats/uc-opaque.lexer.ts similarity index 100% rename from src/syntax/lexers/uc-opaque.lexer.ts rename to src/syntax/formats/uc-opaque.lexer.ts diff --git a/src/syntax/formats/uri-encoded/mod.ts b/src/syntax/formats/uri-encoded/mod.ts new file mode 100644 index 00000000..b5bb4a83 --- /dev/null +++ b/src/syntax/formats/uri-encoded/mod.ts @@ -0,0 +1 @@ +export * from './uc-uri-encoded.lexer.js'; diff --git a/src/syntax/lexers/uc-uri-encoded.lexer.spec.ts b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts similarity index 92% rename from src/syntax/lexers/uc-uri-encoded.lexer.spec.ts rename to src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts index 97929f6c..d65f5830 100644 --- a/src/syntax/lexers/uc-uri-encoded.lexer.spec.ts +++ b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts @@ -1,15 +1,15 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { esline } from 'esgen'; -import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { UC_MODULE_CHURI } from '../../compiler/impl/uc-modules.js'; -import { ucMap } from '../../schema/map/uc-map.js'; -import { UcDeserializer } from '../../schema/uc-deserializer.js'; -import { ucUnknown } from '../../schema/unknown/uc-unknown.js'; -import { readChunks } from '../../spec/read-chunks.js'; -import { scanUcTokens } from '../scan-uc-tokens.js'; -import { UC_TOKEN_APOSTROPHE, UcToken } from '../uc-token.js'; +import { UcdCompiler } from '../../../compiler/deserialization/ucd-compiler.js'; +import { UC_MODULE_CHURI } from '../../../compiler/impl/uc-modules.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; +import { UcDeserializer } from '../../../schema/uc-deserializer.js'; +import { ucUnknown } from '../../../schema/unknown/uc-unknown.js'; +import { readChunks } from '../../../spec/read-chunks.js'; +import { scanUcTokens } from '../../scan-uc-tokens.js'; +import { UC_TOKEN_APOSTROPHE, UcToken } from '../../uc-token.js'; +import { UcURIParamsLexer } from '../uri-params/uc-uri-params.lexer.js'; import { UcURIEncodedLexer, ucInsetURIEncoded } from './uc-uri-encoded.lexer.js'; -import { UcURIParamsLexer } from './uc-uri-params.lexer.js'; describe('UcURIEncodedLexer', () => { it('decodes percent-encoded entities', () => { diff --git a/src/syntax/lexers/uc-uri-encoded.lexer.ts b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts similarity index 89% rename from src/syntax/lexers/uc-uri-encoded.lexer.ts rename to src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts index 13e79489..d482dff5 100644 --- a/src/syntax/lexers/uc-uri-encoded.lexer.ts +++ b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts @@ -1,9 +1,9 @@ import { decodeURISearchPart } from 'httongue'; -import { UcdInsetOptions } from '../../compiler/deserialization/ucd-support-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../impl/module-names.js'; -import { UcOmniConstraints } from '../../schema/uc-constraints.js'; -import { UcLexer } from '../uc-lexer.js'; -import { UC_TOKEN_APOSTROPHE, UcToken } from '../uc-token.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; +import { UcLexer } from '../../uc-lexer.js'; +import { UC_TOKEN_APOSTROPHE, UcToken } from '../../uc-token.js'; /** * URI-encoded text lexer. diff --git a/src/syntax/formats/uri-params/mod.ts b/src/syntax/formats/uri-params/mod.ts new file mode 100644 index 00000000..cffd32e4 --- /dev/null +++ b/src/syntax/formats/uri-params/mod.ts @@ -0,0 +1 @@ +export * from './uc-uri-params.lexer.js'; diff --git a/src/syntax/lexers/uc-uri-params.lexer.spec.ts b/src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts similarity index 91% rename from src/syntax/lexers/uc-uri-params.lexer.spec.ts rename to src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts index c1567d99..84739c56 100644 --- a/src/syntax/lexers/uc-uri-params.lexer.spec.ts +++ b/src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts @@ -1,13 +1,13 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { esline } from 'esgen'; -import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { UC_MODULE_CHURI } from '../../compiler/impl/uc-modules.js'; -import { ucList } from '../../schema/list/uc-list.js'; -import { ucMap } from '../../schema/map/uc-map.js'; -import { ucString } from '../../schema/string/uc-string.js'; -import { UcDeserializer } from '../../schema/uc-deserializer.js'; -import { ucUnknown } from '../../schema/unknown/uc-unknown.js'; -import { scanUcTokens } from '../scan-uc-tokens.js'; +import { UcdCompiler } from '../../../compiler/deserialization/ucd-compiler.js'; +import { UC_MODULE_CHURI } from '../../../compiler/impl/uc-modules.js'; +import { ucList } from '../../../schema/list/uc-list.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; +import { ucString } from '../../../schema/string/uc-string.js'; +import { UcDeserializer } from '../../../schema/uc-deserializer.js'; +import { ucUnknown } from '../../../schema/unknown/uc-unknown.js'; +import { scanUcTokens } from '../../scan-uc-tokens.js'; import { UC_TOKEN_CLOSING_PARENTHESIS, UC_TOKEN_DOLLAR_SIGN, @@ -15,9 +15,9 @@ import { UC_TOKEN_INSET_URI_PARAM, UC_TOKEN_OPENING_PARENTHESIS, UcToken, -} from '../uc-token.js'; -import { ucInsetPlainText } from './uc-plain-text.lexer.js'; -import { ucInsetURIEncoded } from './uc-uri-encoded.lexer.js'; +} from '../../uc-token.js'; +import { ucInsetPlainText } from '../plain-text/uc-plain-text.lexer.js'; +import { ucInsetURIEncoded } from '../uri-encoded/uc-uri-encoded.lexer.js'; import { UcURIParamsLexer, ucInsetURIParams } from './uc-uri-params.lexer.js'; describe('UcURIParamsLexer', () => { diff --git a/src/syntax/lexers/uc-uri-params.lexer.ts b/src/syntax/formats/uri-params/uc-uri-params.lexer.ts similarity index 92% rename from src/syntax/lexers/uc-uri-params.lexer.ts rename to src/syntax/formats/uri-params/uc-uri-params.lexer.ts index 64bfc41a..5e7cd153 100644 --- a/src/syntax/lexers/uc-uri-params.lexer.ts +++ b/src/syntax/formats/uri-params/uc-uri-params.lexer.ts @@ -1,9 +1,9 @@ import { esStringLiteral } from 'esgen'; import { decodeURISearchPart } from 'httongue'; -import { UcdInsetOptions } from '../../compiler/deserialization/ucd-support-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../impl/module-names.js'; -import { UcOmniConstraints } from '../../schema/uc-constraints.js'; -import { UcLexer } from '../uc-lexer.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; +import { UcLexer } from '../../uc-lexer.js'; import { UC_TOKEN_CLOSING_PARENTHESIS, UC_TOKEN_DOLLAR_SIGN, @@ -11,7 +11,7 @@ import { UC_TOKEN_INSET_URI_PARAM, UC_TOKEN_OPENING_PARENTHESIS, UcToken, -} from '../uc-token.js'; +} from '../../uc-token.js'; /** * URI parameters lexer. diff --git a/src/syntax/lexers/mod.ts b/src/syntax/lexers/mod.ts deleted file mode 100644 index 08f8024d..00000000 --- a/src/syntax/lexers/mod.ts +++ /dev/null @@ -1,5 +0,0 @@ -export * from './uc-charge.lexer.js'; -export * from './uc-opaque.lexer.js'; -export * from './uc-plain-text.lexer.js'; -export * from './uc-uri-encoded.lexer.js'; -export * from './uc-uri-params.lexer.js'; diff --git a/src/syntax/mod.ts b/src/syntax/mod.ts index 9b036181..e17ba2f1 100644 --- a/src/syntax/mod.ts +++ b/src/syntax/mod.ts @@ -1,4 +1,4 @@ -export * from './lexers/mod.js'; +export * from './formats/mod.js'; export * from './print-uc-token.js'; export * from './scan-uc-tokens.js'; export * from './trim-uc-tokens-tail.js'; diff --git a/src/syntax/uc-lexer-stream.ts b/src/syntax/uc-lexer-stream.ts index 26eb1592..5eddaccb 100644 --- a/src/syntax/uc-lexer-stream.ts +++ b/src/syntax/uc-lexer-stream.ts @@ -1,4 +1,4 @@ -import { UcChargeLexer } from './lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from './formats/charge/uc-charge.lexer.js'; import { UcLexer } from './uc-lexer.js'; import { UcToken } from './uc-token.js'; diff --git a/src/uri/churi-params.ts b/src/uri/churi-params.ts index 777d790c..d3092977 100644 --- a/src/uri/churi-params.ts +++ b/src/uri/churi-params.ts @@ -1,7 +1,7 @@ import { parseURICharge } from '#churi/uri-charge/deserializer.js'; import { URICharge$List } from '../schema/uri-charge/impl/uri-charge.some.js'; import { URICharge } from '../schema/uri-charge/uri-charge.js'; -import { UcChargeLexer } from '../syntax/lexers/uc-charge.lexer.js'; +import { UcChargeLexer } from '../syntax/formats/charge/uc-charge.lexer.js'; import { ChURIAnchor$splitter, ChURIMatrix$splitter, From a9cb9e2e8a815327e735d6ecce26302c4cabcb55 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 14:08:06 +0700 Subject: [PATCH 12/36] Plain text serialization support --- src/compiler/deserialization/ucd-compiler.ts | 7 +- src/compiler/processor/ucc-processor.ts | 20 +++ src/compiler/processor/ucc-profile.ts | 22 +++ .../serialization/impl/ucs-format-boolean.ts | 16 ++ .../serialization/ucs-allow-plain-text.ts | 33 ++++- src/compiler/serialization/ucs-compiler.ts | 10 +- .../serialization/ucs-support-boolean.ts | 18 +-- src/schema/boolean/uc-boolean.ts | 14 ++ src/schema/numeric/uc-bigint.ts | 2 +- .../plain-text/plain-text.serializer.spec.ts | 140 ++++++++++++++++++ 10 files changed, 255 insertions(+), 27 deletions(-) create mode 100644 src/compiler/serialization/impl/ucs-format-boolean.ts create mode 100644 src/syntax/formats/plain-text/plain-text.serializer.spec.ts diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index ad46fb7b..6959f682 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -19,6 +19,7 @@ import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UccFeature } from '../processor/ucc-feature.js'; +import { UccProfile } from '../processor/ucc-profile.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; import { UcrxProcessor } from '../rx/ucrx-processor.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; @@ -54,11 +55,12 @@ export class UcdCompiler * @param options - Compiler options. */ constructor(options: UcdCompiler.Options) { - const { models, presentations, validate = true, features } = options; + const { presentations, profiles, models, validate = true, features } = options; super({ processors: validate ? ['validator', 'deserializer'] : ['deserializer'], presentations, + profiles, models: Object.values(models).map(({ model }) => model), features, }); @@ -287,8 +289,9 @@ export class UcdCompiler export namespace UcdCompiler { export interface Options extends Omit { - readonly models: TModels; readonly presentations?: UcPresentationName | UcPresentationName[] | undefined; + readonly profiles?: UccProfile | readonly UccProfile[] | undefined; + readonly models: TModels; readonly validate?: boolean | undefined; readonly features?: UccFeature | readonly UccFeature[] | undefined; readonly exportDefaults?: boolean | undefined; diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 1e5beeeb..9e88819e 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -168,6 +168,7 @@ export abstract class UccProcessor> protected async processInstructions(): Promise { this.#applyProfiles(); + this.#profiler.init(); this.#collectInstructions(); await this.#processInstructions(); this.#enableExplicitFeatures(); @@ -270,6 +271,7 @@ interface UccProcessor$Current { class UccProcessor$Profiler> { readonly #processor: UccProcessor; + readonly #init: ((setup: TSetup) => void)[] = []; readonly #configure: ( current: UccProcessor$Current, action: () => Promise, @@ -289,6 +291,18 @@ class UccProcessor$Profiler> { return this.#processor.setup; } + enable(feature: UccFeature, options: TOptions): void { + this.#init.push(setup => setup.enable(feature, options)); + } + + init(): void { + const { setup } = this; + + for (const init of this.#init) { + init(setup); + } + } + onConstraint( { processor, within, use, from }: UccProfile.ConstraintCriterion, handler: UccProfile.ConstraintHandler, @@ -470,6 +484,12 @@ class UccProcessor$ProfileActivation> this.#profiler = profiler; } + enable(feature: UccFeature, options?: TOptions): this { + this.#profiler.enable(feature, options!); + + return this; + } + onConstraint( criterion: UccProfile.ConstraintCriterion, handler: UccProfile.ConstraintHandler, diff --git a/src/compiler/processor/ucc-profile.ts b/src/compiler/processor/ucc-profile.ts index 0abf6c67..cf17ac7a 100644 --- a/src/compiler/processor/ucc-profile.ts +++ b/src/compiler/processor/ucc-profile.ts @@ -1,6 +1,7 @@ import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; +import { UccFeature } from './ucc-feature.js'; import { UccSetup } from './ucc-setup.js'; /** @@ -24,6 +25,27 @@ export namespace UccProfile { * @typeParam TSetup - Type of schema processing setup. */ export interface Activation> { + /** + * Enables the given processing `feature`. + * + * @typeParam TOptions - Type of schema processing options. + * @param feature - Feature to enable. + * @param options - Processing options. + * + * @returns `this` instance. + */ + enable(feature: UccFeature, options: TOptions): this; + + /** + * Enables the given processing `feature` that does not require options. + * + * @typeParam TOptions - Type of schema processing options. + * @param feature - Feature to enable. + * + * @returns `this` instance. + */ + enable(feature: UccFeature): this; + /** * Registers {@link churi!UcFeatureConstraint schema constraint} application handler. * diff --git a/src/compiler/serialization/impl/ucs-format-boolean.ts b/src/compiler/serialization/impl/ucs-format-boolean.ts new file mode 100644 index 00000000..d1f2039d --- /dev/null +++ b/src/compiler/serialization/impl/ucs-format-boolean.ts @@ -0,0 +1,16 @@ +import { esline } from 'esgen'; +import { UcBoolean } from '../../../schema/boolean/uc-boolean.js'; +import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; +import { UcsFormatter } from '../ucs-formatter.js'; + +export function ucsFormatBoolean(): UcsFormatter { + return ({ writer, value }) => code => { + const ucsTrue = UC_MODULE_SERIALIZER.import('UCS_TRUE'); + const ucsFalse = UC_MODULE_SERIALIZER.import('UCS_FALSE'); + + code.write( + esline`await ${writer}.ready;`, + esline`${writer}.write(${value} ? ${ucsTrue} : ${ucsFalse});`, + ); + }; +} diff --git a/src/compiler/serialization/ucs-allow-plain-text.ts b/src/compiler/serialization/ucs-allow-plain-text.ts index e97dcf49..5fc6a6e6 100644 --- a/src/compiler/serialization/ucs-allow-plain-text.ts +++ b/src/compiler/serialization/ucs-allow-plain-text.ts @@ -1,12 +1,16 @@ import { esline } from 'esgen'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; +import { UcString } from '../../schema/string/uc-string.js'; import { ucsWriteAsIs } from '../../serializer/ucs-write-asis.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; +import { UccConfig } from '../processor/ucc-config.js'; import { UccProfile } from '../processor/ucc-profile.js'; import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; +import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; import { ucsFormatInteger } from './impl/ucs-format-integer.js'; import { ucsFormatNumber } from './impl/ucs-format-number.js'; +import { UcsFormatter } from './ucs-formatter.js'; import { UcsSetup } from './ucs-setup.js'; import { ucsSupportBigInt } from './ucs-support-bigint.js'; import { ucsSupportInteger } from './ucs-support-integer.js'; @@ -15,13 +19,14 @@ import { ucsSupportString } from './ucs-support-string.js'; export function ucsAllowPlainText(activation: UccProfile.Activation): void { activation + .enable(ucsSupportPlainText) .onConstraint( { processor: 'serializer', use: ucsSupportBigInt.name, from: COMPILER_MODULE, }, - ({ setup, schema, constraint: { with: options = {} } }) => { + ({ setup, schema, constraint: { with: options } }) => { const { number = 'parse' } = options as UcBigInt.Variant; setup.formatWith('plainText', schema, ucsFormatBigInt({ string: 'parse', number })); @@ -54,11 +59,27 @@ export function ucsAllowPlainText(activation: UccProfile.Activation): from: COMPILER_MODULE, }, ({ setup, schema }) => { - setup.formatWith('plainText', schema, ({ writer, value }) => { - const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); - - return esline`await ${writeAsIs}(${writer}, ${value});`; - }); + setup.formatWith('plainText', schema, ucsFormatStringAsPlainText()); }, ); } + +function ucsSupportPlainText(setup: UcsSetup): UccConfig { + return { + configure() { + setup + .formatWith('plainText', BigInt, ucsFormatBigInt()) + .formatWith('plainText', Boolean, ucsFormatBoolean()) + .formatWith('plainText', Number, ucsFormatNumber()) + .formatWith('plainText', String, ucsFormatStringAsPlainText()); + }, + }; +} + +function ucsFormatStringAsPlainText(): UcsFormatter { + return ({ writer, value }) => { + const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); + + return esline`await ${writeAsIs}(${writer}, ${value});`; + }; +} diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 779094b9..e91557ab 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -7,11 +7,13 @@ import { esEvaluate, esGenerate, } from 'esgen'; -import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; +import { UcFormatName, UcInsetName, UcPresentationName } from '../../schema/uc-presentations.js'; import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; +import { UccProfile } from '../processor/ucc-profile.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; +import { UccSetup } from '../processor/ucc-setup.js'; import { ucsCheckConstraints } from './impl/ucs-check-constraints.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; @@ -40,10 +42,12 @@ export class UcsCompiler * @param options - Setup options. */ constructor(options: UcsCompiler.Options) { - const { models, features } = options; + const { presentations, profiles, models, features } = options; super({ processors: 'serializer', + presentations, + profiles, models: Object.values(models).map(({ model }) => model), features, }); @@ -221,6 +225,8 @@ export class UcsCompiler export namespace UcsCompiler { export interface Options { + readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; + readonly profiles?: UccProfile | readonly UccProfile[] | undefined; readonly models: TModels; readonly features?: UccFeature | readonly UccFeature[] | undefined; diff --git a/src/compiler/serialization/ucs-support-boolean.ts b/src/compiler/serialization/ucs-support-boolean.ts index de66dc51..c5b7d645 100644 --- a/src/compiler/serialization/ucs-support-boolean.ts +++ b/src/compiler/serialization/ucs-support-boolean.ts @@ -1,25 +1,11 @@ -import { EsSnippet, esline } from 'esgen'; -import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UcsFormatterSignature } from './ucs-formatter.js'; +import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportBoolean(setup: UcsSetup): UccConfig { return { configure() { - setup.formatWith('charge', Boolean, ucsWriteBoolean); + setup.formatWith('charge', Boolean, ucsFormatBoolean()); }, }; } - -function ucsWriteBoolean({ writer, value }: UcsFormatterSignature.AllValues): EsSnippet { - return code => { - const ucsTrue = UC_MODULE_SERIALIZER.import('UCS_TRUE'); - const ucsFalse = UC_MODULE_SERIALIZER.import('UCS_FALSE'); - - code.write( - esline`await ${writer}.ready;`, - esline`${writer}.write(${value} ? ${ucsTrue} : ${ucsFalse});`, - ); - }; -} diff --git a/src/schema/boolean/uc-boolean.ts b/src/schema/boolean/uc-boolean.ts index d65ac431..9977f378 100644 --- a/src/schema/boolean/uc-boolean.ts +++ b/src/schema/boolean/uc-boolean.ts @@ -1,8 +1,22 @@ import { UcDataType, UcSchema, ucSchema } from '../uc-schema.js'; +/** + * Boolean type alias used in {@link UcBoolean.Schema schema} processing. + */ export type UcBoolean = boolean; export namespace UcBoolean { + /** + * Schema for {@link UcBoolean boolean value}. + * + * Boolean schema is created automatically when [Boolean] constructor is used as model. + * + * BigInt values may be represented with and without `0n` prefix. This differs from schema-less processing, + * where `0n` prefix is required for BigInt values. When serializing, the `0n` prefix is either added or not + * according to {@link Variant#number number processing policy}. + * + * [Boolean]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean/Boolean + */ export interface Schema extends UcSchema { readonly type: UcDataType; } diff --git a/src/schema/numeric/uc-bigint.ts b/src/schema/numeric/uc-bigint.ts index 82e77791..53f67481 100644 --- a/src/schema/numeric/uc-bigint.ts +++ b/src/schema/numeric/uc-bigint.ts @@ -3,7 +3,7 @@ import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcDataType, UcSchema, ucSchema } from '../uc-schema.js'; /** - * BigInt type alias used in {@link UcNumber.Schema schema} processing. + * BigInt type alias used in {@link UcBigInt.Schema schema} processing. */ export type UcBigInt = bigint; diff --git a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts new file mode 100644 index 00000000..f2b59ed4 --- /dev/null +++ b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts @@ -0,0 +1,140 @@ +import { describe, expect, it } from '@jest/globals'; +import { ucBoolean, ucInteger, ucMap, ucString } from 'churi'; +import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; +import { ucsAllowPlainText } from '../../../compiler/serialization/ucs-allow-plain-text.js'; +import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; +import { ucList } from '../../../schema/list/uc-list.js'; +import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { ucNumber } from '../../../schema/numeric/uc-number.js'; +import { TextOutStream } from '../../../spec/text-out-stream.js'; + +describe('plain text serializer', () => { + it('serializes bigint', async () => { + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writePrimitive: { model: BigInt, format: 'plainText' }, + writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'plainText' }, + writeNumber: { model: ucBigInt({ number: 'serialize' }), format: 'plainText' }, + writeAuto: { model: ucBigInt({ number: 'auto' }), format: 'plainText' }, + }, + }); + + const { writePrimitive, writeValue, writeNumber, writeAuto } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, -13n))).resolves.toBe( + '-0n13', + ); + await expect(TextOutStream.read(async to => await writeValue(to, -13n))).resolves.toBe('-0n13'); + await expect(TextOutStream.read(async to => await writeNumber(to, -13n))).resolves.toBe('-13'); + await expect(TextOutStream.read(async to => await writeAuto(to, -13n))).resolves.toBe('-13'); + await expect( + TextOutStream.read(async to => await writeNumber(to, BigInt(Number.MAX_SAFE_INTEGER + 1))), + ).resolves.toBe(BigInt(Number.MAX_SAFE_INTEGER + 1).toString()); + await expect( + TextOutStream.read(async to => await writeAuto(to, BigInt(Number.MAX_SAFE_INTEGER + 1))), + ).resolves.toBe(`0n` + BigInt(Number.MAX_SAFE_INTEGER + 1)); + }); + it('serializes boolean', async () => { + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writePrimitive: { model: Boolean, format: 'plainText' }, + writeValue: { model: ucBoolean(), format: 'plainText' }, + }, + }); + + const { writePrimitive, writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, true))).resolves.toBe('!'); + await expect(TextOutStream.read(async to => await writePrimitive(to, false))).resolves.toBe( + '-', + ); + await expect(TextOutStream.read(async to => await writeValue(to, true))).resolves.toBe('!'); + await expect(TextOutStream.read(async to => await writeValue(to, false))).resolves.toBe('-'); + }); + it('serializes number', async () => { + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writePrimitive: { model: Number, format: 'plainText' }, + writeValue: { model: ucNumber({ string: 'serialize' }), format: 'plainText' }, + }, + }); + + const { writePrimitive, writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, -13.1))).resolves.toBe( + '-13.1', + ); + await expect(TextOutStream.read(async to => await writeValue(to, -13))).resolves.toBe('-13'); + await expect(TextOutStream.read(async to => await writeValue(to, -Infinity))).resolves.toBe( + '!-Infinity', + ); + }); + it('serializes integer', async () => { + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writeValue: { model: ucInteger({ string: 'serialize' }), format: 'plainText' }, + }, + }); + + const { writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writeValue(to, -13.1))).resolves.toBe('-13'); + await expect(TextOutStream.read(async to => await writeValue(to, -Infinity))).resolves.toBe( + '-Infinity', + ); + }); + it('serializes string', async () => { + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writePrimitive: { model: String, format: 'plainText' }, + writeValue: { model: ucString({ raw: 'escape' }), format: 'plainText' }, + }, + }); + + const { writePrimitive, writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, 'a b c'))).resolves.toBe( + 'a b c', + ); + await expect(TextOutStream.read(async to => await writeValue(to, 'a b c'))).resolves.toBe( + 'a b c', + ); + }); + it('can not serialize list', async () => { + const schema = ucList(Number); + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writeList: { model: schema, format: 'plainText' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'list$plainText(writer, value, asItem?): Can not serialize type "Number[]"', + ), + ); + }); + it('can not serialize map', async () => { + const schema = ucMap({ foo: Number }); + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + writeList: { model: schema, format: 'plainText' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'map$plainText(writer, value, asItem?): Can not serialize type "{foo: Number}"', + ), + ); + }); +}); From 72ee716fca0e9fed181765f4c7e7beafda236205 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 14:55:22 +0700 Subject: [PATCH 13/36] Stricter checks for nullable/optional values --- ...ck-constraints.ts => ucs-format-charge.ts} | 11 ++++- .../serialization/impl/ucs-format-string.ts | 26 +++++++++++ .../serialization/ucs-allow-plain-text.ts | 41 ++++++++++++++---- src/compiler/serialization/ucs-compiler.ts | 10 +---- .../serialization/ucs-support-bigint.ts | 3 +- .../serialization/ucs-support-boolean.ts | 3 +- .../serialization/ucs-support-integer.ts | 3 +- .../serialization/ucs-support-list.ts | 13 +++--- src/compiler/serialization/ucs-support-map.ts | 6 +-- .../serialization/ucs-support-number.ts | 3 +- .../serialization/ucs-support-string.ts | 23 ++-------- .../plain-text/plain-text.serializer.spec.ts | 43 ++++++++++++++++++- 12 files changed, 134 insertions(+), 51 deletions(-) rename src/compiler/serialization/impl/{ucs-check-constraints.ts => ucs-format-charge.ts} (76%) create mode 100644 src/compiler/serialization/impl/ucs-format-string.ts diff --git a/src/compiler/serialization/impl/ucs-check-constraints.ts b/src/compiler/serialization/impl/ucs-format-charge.ts similarity index 76% rename from src/compiler/serialization/impl/ucs-check-constraints.ts rename to src/compiler/serialization/impl/ucs-format-charge.ts index efe024a6..36b6a3b1 100644 --- a/src/compiler/serialization/impl/ucs-check-constraints.ts +++ b/src/compiler/serialization/impl/ucs-format-charge.ts @@ -1,8 +1,17 @@ import { EsCode, EsSnippet, esline } from 'esgen'; import { UcSchema } from '../../../schema/uc-schema.js'; import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; +import { UcsFormatter } from '../ucs-formatter.js'; -export function ucsCheckConstraints( +export function ucsFormatCharge(formatter: UcsFormatter): UcsFormatter { + return (args, schema, context) => { + const onValue = formatter(args, schema, context); + + return onValue && ucsCheckCharge(args, schema, onValue); + }; +} + +export function ucsCheckCharge( { writer, value }: { readonly writer: EsSnippet; readonly value: EsSnippet }, schema: UcSchema, onValue: EsSnippet, diff --git a/src/compiler/serialization/impl/ucs-format-string.ts b/src/compiler/serialization/impl/ucs-format-string.ts new file mode 100644 index 00000000..e19ea300 --- /dev/null +++ b/src/compiler/serialization/impl/ucs-format-string.ts @@ -0,0 +1,26 @@ +import { esline } from 'esgen'; +import { UcString } from '../../../schema/string/uc-string.js'; +import { + ucsWriteNullableRawString, + ucsWriteRawString, + ucsWriteString, +} from '../../../serializer/ucs-write-string.js'; +import { UC_MODULE_SERIALIZER } from '../../impl/uc-modules.js'; +import { UcsFormatter } from '../ucs-formatter.js'; + +export function ucsFormatString({ raw = 'escape' }: UcString.Variant = {}): UcsFormatter< + UcString, + UcString.Schema +> { + return ({ writer, value, asItem }, schema) => { + const writeString = UC_MODULE_SERIALIZER.import( + raw === 'escape' + ? ucsWriteString.name + : schema.nullable + ? ucsWriteNullableRawString.name + : ucsWriteRawString.name, + ); + + return esline`await ${writeString}(${writer}, ${value}, ${asItem});`; + }; +} diff --git a/src/compiler/serialization/ucs-allow-plain-text.ts b/src/compiler/serialization/ucs-allow-plain-text.ts index 5fc6a6e6..23674fe0 100644 --- a/src/compiler/serialization/ucs-allow-plain-text.ts +++ b/src/compiler/serialization/ucs-allow-plain-text.ts @@ -2,7 +2,9 @@ import { esline } from 'esgen'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; import { UcString } from '../../schema/string/uc-string.js'; +import { ucModelName } from '../../schema/uc-model-name.js'; import { ucsWriteAsIs } from '../../serializer/ucs-write-asis.js'; +import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UccProfile } from '../processor/ucc-profile.js'; @@ -29,7 +31,7 @@ export function ucsAllowPlainText(activation: UccProfile.Activation): ({ setup, schema, constraint: { with: options } }) => { const { number = 'parse' } = options as UcBigInt.Variant; - setup.formatWith('plainText', schema, ucsFormatBigInt({ string: 'parse', number })); + setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatBigInt({ number }))); }, ) .onConstraint( @@ -39,7 +41,7 @@ export function ucsAllowPlainText(activation: UccProfile.Activation): from: COMPILER_MODULE, }, ({ setup, schema }) => { - setup.formatWith('plainText', schema, ucsFormatInteger()); + setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatInteger())); }, ) .onConstraint( @@ -49,7 +51,7 @@ export function ucsAllowPlainText(activation: UccProfile.Activation): from: COMPILER_MODULE, }, ({ setup, schema }) => { - setup.formatWith('plainText', schema, ucsFormatNumber()); + setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatNumber())); }, ) .onConstraint( @@ -68,18 +70,41 @@ function ucsSupportPlainText(setup: UcsSetup): UccConfig { return { configure() { setup - .formatWith('plainText', BigInt, ucsFormatBigInt()) - .formatWith('plainText', Boolean, ucsFormatBoolean()) - .formatWith('plainText', Number, ucsFormatNumber()) + .formatWith('plainText', BigInt, ucsFormatPlainText(ucsFormatBigInt())) + .formatWith('plainText', Boolean, ucsFormatPlainText(ucsFormatBoolean())) + .formatWith('plainText', Number, ucsFormatPlainText(ucsFormatNumber())) .formatWith('plainText', String, ucsFormatStringAsPlainText()); }, }; } -function ucsFormatStringAsPlainText(): UcsFormatter { - return ({ writer, value }) => { +function ucsFormatStringAsPlainText(): UcsFormatter { + return ucsFormatPlainText(({ writer, value }) => { const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); return esline`await ${writeAsIs}(${writer}, ${value});`; + }); +} + +function ucsFormatPlainText(formatter: UcsFormatter): UcsFormatter { + return (args, schema, context) => { + if (schema.nullable) { + throw new UnsupportedUcSchemaError( + schema, + `${context}: Can not serialize nullable values of type "${ucModelName( + schema, + )}" to plain text`, + ); + } + if (schema.optional) { + throw new UnsupportedUcSchemaError( + schema, + `${context}: Can not serialize optional values of type "${ucModelName( + schema, + )}" to plain text`, + ); + } + + return formatter(args, schema, context); }; } diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index e91557ab..efd5c77d 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -14,7 +14,6 @@ import { UccProcessor } from '../processor/ucc-processor.js'; import { UccProfile } from '../processor/ucc-profile.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UccSetup } from '../processor/ucc-setup.js'; -import { ucsCheckConstraints } from './impl/ucs-check-constraints.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsLib } from './ucs-lib.js'; @@ -66,16 +65,11 @@ export class UcsCompiler ): this { const inset = this.currentPresentation as UcInsetName | undefined; const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; - const fullFormatter: UcsFormatter = (args, schema, context) => { - const onValue = formatter(args, schema, context); - - return onValue && ucsCheckConstraints(args, schema, onValue); - }; if (typeof target === 'object') { - this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, fullFormatter); + this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, formatter); } else { - this.#typeEntryFor(id, format, target).formatTypeWith(fullFormatter); + this.#typeEntryFor(id, format, target).formatTypeWith(formatter); } return this; diff --git a/src/compiler/serialization/ucs-support-bigint.ts b/src/compiler/serialization/ucs-support-bigint.ts index 4b5f3dbc..d89435d5 100644 --- a/src/compiler/serialization/ucs-support-bigint.ts +++ b/src/compiler/serialization/ucs-support-bigint.ts @@ -2,6 +2,7 @@ import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; import { UcDataType } from '../../schema/uc-schema.js'; import { UccConfig } from '../processor/ucc-config.js'; import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; +import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportBigInt( @@ -10,7 +11,7 @@ export function ucsSupportBigInt( ): UccConfig { return { configure(variant) { - setup.formatWith('charge', target, ucsFormatBigInt(variant)); + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatBigInt(variant))); }, }; } diff --git a/src/compiler/serialization/ucs-support-boolean.ts b/src/compiler/serialization/ucs-support-boolean.ts index c5b7d645..d26b6472 100644 --- a/src/compiler/serialization/ucs-support-boolean.ts +++ b/src/compiler/serialization/ucs-support-boolean.ts @@ -1,11 +1,12 @@ import { UccConfig } from '../processor/ucc-config.js'; import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; +import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportBoolean(setup: UcsSetup): UccConfig { return { configure() { - setup.formatWith('charge', Boolean, ucsFormatBoolean()); + setup.formatWith('charge', Boolean, ucsFormatCharge(ucsFormatBoolean())); }, }; } diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-support-integer.ts index d87f0520..23e30104 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-support-integer.ts @@ -1,5 +1,6 @@ import { UcInteger } from '../../schema/numeric/uc-integer.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatInteger } from './impl/ucs-format-integer.js'; import { UcsSetup } from './ucs-setup.js'; @@ -9,7 +10,7 @@ export function ucsSupportInteger( ): UccConfig { return { configure(variant) { - setup.formatWith('charge', target, ucsFormatInteger(variant)); + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatInteger(variant))); }, }; } diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-support-list.ts index adc75efd..95e43301 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-support-list.ts @@ -8,23 +8,24 @@ import { UccListOptions } from '../common/ucc-list-options.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportList(setup: UcsSetup, schema: UcList.Schema): UccConfig { return { configure(options) { - setup - .processModel(schema.item) - .formatWith( - 'charge', - schema, + setup.processModel(schema.item).formatWith( + 'charge', + schema, + ucsFormatCharge( ( args: UcsFormatterSignature.AllValues, schema: UcList.Schema, context: UcsFormatterContext, ) => ucsWriteList(args, schema, context, options), - ); + ), + ); }, }; } diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index c5f1610d..66ff9582 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -16,7 +16,7 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { ucsCheckConstraints } from './impl/ucs-check-constraints.js'; +import { ucsCheckCharge, ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; @@ -25,7 +25,7 @@ export function ucsSupportMap(setup: UcsSetup, schema: UcMap.Schema): UccConfig; export function ucsSupportMap(setup: UcsSetup, { entries, extra }: UcMap.Schema): UccConfig { return { configure() { - setup.formatWith('charge', 'map', ucsWriteMap); + setup.formatWith('charge', 'map', ucsFormatCharge(ucsWriteMap)); Object.values(entries).forEach(entrySchema => setup.processModel(entrySchema)); // istanbul ignore next if (extra) { @@ -88,7 +88,7 @@ function ucsWriteMap( for (const [key, entrySchema] of Object.entries(schema.entries)) { code.write( esline`${entryValue} = ${value}${esMemberAccessor(key).accessor};`, - ucsCheckConstraints( + ucsCheckCharge( { writer, value: entryValue }, entrySchema, code => { diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-support-number.ts index 55a13bdf..7029568b 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-support-number.ts @@ -1,6 +1,7 @@ import { UcNumber } from '../../schema/numeric/uc-number.js'; import { UcDataType } from '../../schema/uc-schema.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatNumber } from './impl/ucs-format-number.js'; import { UcsSetup } from './ucs-setup.js'; @@ -10,7 +11,7 @@ export function ucsSupportNumber( ): UccConfig { return { configure(variant) { - setup.formatWith('charge', target, ucsFormatNumber(variant)); + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatNumber(variant))); }, }; } diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-support-string.ts index 51825c50..afe9badb 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-support-string.ts @@ -1,13 +1,8 @@ -import { esline } from 'esgen'; import { UcString } from '../../schema/string/uc-string.js'; import { UcDataType } from '../../schema/uc-schema.js'; -import { - ucsWriteNullableRawString, - ucsWriteRawString, - ucsWriteString, -} from '../../serializer/ucs-write-string.js'; -import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatCharge } from './impl/ucs-format-charge.js'; +import { ucsFormatString } from './impl/ucs-format-string.js'; import { UcsSetup } from './ucs-setup.js'; export function ucsSupportString( @@ -15,18 +10,8 @@ export function ucsSupportString( target: UcString.Schema | UcDataType = String, ): UccConfig { return { - configure({ raw = 'escape' } = {}) { - setup.formatWith('charge', target, ({ writer, value, asItem }, schema) => { - const writeString = UC_MODULE_SERIALIZER.import( - raw === 'escape' - ? ucsWriteString.name - : schema.nullable - ? ucsWriteNullableRawString.name - : ucsWriteRawString.name, - ); - - return esline`await ${writeString}(${writer}, ${value}, ${asItem});`; - }); + configure(variant?: UcString.Variant) { + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatString(variant))); }, }; } diff --git a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts index f2b59ed4..148cf42b 100644 --- a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts +++ b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts @@ -1,11 +1,16 @@ import { describe, expect, it } from '@jest/globals'; -import { ucBoolean, ucInteger, ucMap, ucString } from 'churi'; import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; import { ucsAllowPlainText } from '../../../compiler/serialization/ucs-allow-plain-text.js'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; +import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; import { ucList } from '../../../schema/list/uc-list.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { ucInteger } from '../../../schema/numeric/uc-integer.js'; import { ucNumber } from '../../../schema/numeric/uc-number.js'; +import { ucString } from '../../../schema/string/uc-string.js'; +import { ucNullable } from '../../../schema/uc-nullable.js'; +import { ucOptional } from '../../../schema/uc-optional.js'; import { TextOutStream } from '../../../spec/text-out-stream.js'; describe('plain text serializer', () => { @@ -126,7 +131,7 @@ describe('plain text serializer', () => { const compiler = new UcsCompiler({ profiles: ucsAllowPlainText, models: { - writeList: { model: schema, format: 'plainText' }, + writeMap: { model: schema, format: 'plainText' }, }, }); @@ -137,4 +142,38 @@ describe('plain text serializer', () => { ), ); }); + it('can not serialize nullable values', async () => { + const schema = ucNullable(ucNumber()); + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + write: { model: schema, format: 'plainText' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'Number$plainTextN(writer, value, asItem?): Can not serialize nullable values' + + ' of type "(Number | null)" to plain text', + ), + ); + }); + it('can not serialize optional values', async () => { + const schema = ucOptional(ucNumber()); + const compiler = new UcsCompiler({ + profiles: ucsAllowPlainText, + models: { + write: { model: schema, format: 'plainText' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'Number$plainTextO(writer, value, asItem?): Can not serialize optional values' + + ' of type "Number?" to plain text', + ), + ); + }); }); From d1d58ad9bd261b7aa85a2eeae0412ea70777bf1a Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 15:42:43 +0700 Subject: [PATCH 14/36] Rename "profiles" to "capabilities" --- src/compiler/deserialization/ucd-compiler.ts | 11 ++++-- .../{ucc-profile.ts => ucc-capability.ts} | 16 ++++---- src/compiler/processor/ucc-processor.spec.ts | 12 +++--- src/compiler/processor/ucc-processor.ts | 38 +++++++++---------- src/compiler/serialization/mod.ts | 2 +- src/compiler/serialization/ucs-compiler.ts | 11 ++++-- ...plain-text.ts => ucs-enable-plain-text.ts} | 8 ++-- .../plain-text/plain-text.serializer.spec.ts | 20 +++++----- 8 files changed, 63 insertions(+), 55 deletions(-) rename src/compiler/processor/{ucc-profile.ts => ucc-capability.ts} (90%) rename src/compiler/serialization/{ucs-allow-plain-text.ts => ucs-enable-plain-text.ts} (93%) diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 6959f682..07f4cbfb 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -17,9 +17,9 @@ import { import { capitalize } from 'httongue'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; +import { UccCapability } from '../processor/ucc-capability.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UccFeature } from '../processor/ucc-feature.js'; -import { UccProfile } from '../processor/ucc-profile.js'; import { UcrxLib } from '../rx/ucrx-lib.js'; import { UcrxProcessor } from '../rx/ucrx-processor.js'; import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; @@ -55,12 +55,12 @@ export class UcdCompiler * @param options - Compiler options. */ constructor(options: UcdCompiler.Options) { - const { presentations, profiles, models, validate = true, features } = options; + const { presentations, capabilities, models, validate = true, features } = options; super({ processors: validate ? ['validator', 'deserializer'] : ['deserializer'], presentations, - profiles, + capabilities, models: Object.values(models).map(({ model }) => model), features, }); @@ -290,7 +290,10 @@ export namespace UcdCompiler { export interface Options extends Omit { readonly presentations?: UcPresentationName | UcPresentationName[] | undefined; - readonly profiles?: UccProfile | readonly UccProfile[] | undefined; + readonly capabilities?: + | UccCapability + | readonly UccCapability[] + | undefined; readonly models: TModels; readonly validate?: boolean | undefined; readonly features?: UccFeature | readonly UccFeature[] | undefined; diff --git a/src/compiler/processor/ucc-profile.ts b/src/compiler/processor/ucc-capability.ts similarity index 90% rename from src/compiler/processor/ucc-profile.ts rename to src/compiler/processor/ucc-capability.ts index cf17ac7a..1e47942d 100644 --- a/src/compiler/processor/ucc-profile.ts +++ b/src/compiler/processor/ucc-capability.ts @@ -5,22 +5,24 @@ import { UccFeature } from './ucc-feature.js'; import { UccSetup } from './ucc-setup.js'; /** - * Schema {@link UccProcessor processing} profile. + * Schema {@link UccProcessor processing} capability. * - * Can be used e.g. to refine {@link churi!UcConstraints schema constraints}, or to enable {@link UccFeature processing - * features}. + * Called by {@lint UccProcessorInit#capabilities schema processor} to activate the capability. + * + * Capabilities used e.g. to refine {@link churi!UcConstraints schema constraints}, or to enable + * {@link UccFeature processing features}. * * @typeParam TSetup - Type of schema processing setup. * @param activation - Activation context. */ -export type UccProfile> = ( +export type UccCapability> = ( this: void, - activation: UccProfile.Activation, + activation: UccCapability.Activation, ) => void; -export namespace UccProfile { +export namespace UccCapability { /** - * Activation context of {@link UccProfile schema processing profile}. + * Activation context of {@link UccCapability schema processing capability}. * * @typeParam TSetup - Type of schema processing setup. */ diff --git a/src/compiler/processor/ucc-processor.spec.ts b/src/compiler/processor/ucc-processor.spec.ts index 0ae8ffa1..44e38e84 100644 --- a/src/compiler/processor/ucc-processor.spec.ts +++ b/src/compiler/processor/ucc-processor.spec.ts @@ -132,12 +132,12 @@ describe('UccProcessor', () => { }); }); - describe('profiles', () => { + describe('capabilities', () => { it('applies constraint at most once', async () => { const constraints = ucTestRecord('test'); const schema: UcSchema = { type: 'test', where: constraints }; const processor = new UccTestProcessor({ - profiles: activation => { + capabilities: activation => { activation.onConstraint( { processor: 'deserializer', @@ -173,7 +173,7 @@ describe('UccProcessor', () => { const constraints = ucTestRecord('test'); const schema: UcSchema = { type: 'test', where: constraints }; const processor = new UccTestProcessor({ - profiles: activation => { + capabilities: activation => { activation.onConstraint( { processor: 'deserializer', @@ -197,7 +197,7 @@ describe('UccProcessor', () => { const constraints = ucTestRecord('test'); const schema: UcSchema = { type: 'test', within: { charge: constraints } }; const processor = new UccTestProcessor({ - profiles: activation => { + capabilities: activation => { activation.onConstraint( { processor: 'deserializer', @@ -221,7 +221,7 @@ describe('UccProcessor', () => { const constraints = ucTestRecord('test'); const schema: UcSchema = { type: 'test', within: { charge: constraints } }; const processor = new UccTestProcessor({ - profiles: activation => { + capabilities: activation => { activation.onConstraint( { processor: 'deserializer', @@ -246,7 +246,7 @@ describe('UccProcessor', () => { const constraints = ucTestRecord('test'); const schema: UcSchema = { type: 'test', within: { charge: constraints } }; const processor = new UccTestProcessor({ - profiles: activation => { + capabilities: activation => { activation .onConstraint( { diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 9e88819e..498c5a17 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -7,9 +7,9 @@ import { } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; +import { UccCapability } from './ucc-capability.js'; import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; -import { UccProfile } from './ucc-profile.js'; import { UccSchemaFeature } from './ucc-schema-feature.js'; import { UccSchemaIndex } from './ucc-schema-index.js'; import { UccSetup } from './ucc-setup.js'; @@ -25,7 +25,7 @@ export abstract class UccProcessor> implements UccSetup { readonly #schemaIndex: UccSchemaIndex; - readonly #profiles: readonly UccProfile[] | undefined; + readonly #capabilities: readonly UccCapability[] | undefined; readonly #models: readonly UcModel[] | undefined; readonly #features: readonly UccFeature[] | undefined; readonly #getSetup = lazyValue(() => this.createSetup()); @@ -46,7 +46,7 @@ export abstract class UccProcessor> constructor({ processors, presentations = [], - profiles, + capabilities, models, features, }: UccProcessorInit) { @@ -54,7 +54,7 @@ export abstract class UccProcessor> asArray(processors), asArray(presentations), ); - this.#profiles = profiles && asArray(profiles); + this.#capabilities = capabilities && asArray(capabilities); this.#models = models; this.#features = features && asArray(features); } @@ -167,7 +167,7 @@ export abstract class UccProcessor> } protected async processInstructions(): Promise { - this.#applyProfiles(); + this.#activateCapabilities(); this.#profiler.init(); this.#collectInstructions(); await this.#processInstructions(); @@ -175,9 +175,9 @@ export abstract class UccProcessor> await this.#processInstructions(); // More instructions may be added by explicit features. } - #applyProfiles(): void { - this.#profiles?.forEach(profile => { - profile(new UccProcessor$ProfileActivation(this.#profiler)); + #activateCapabilities(): void { + this.#capabilities?.forEach(capability => { + capability(new UccProcessor$CapabilityActivation(this.#profiler)); }); } @@ -246,9 +246,9 @@ export interface UccProcessorInit> { readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; /** - * Processor profiles to enable. + * Processor capabilities to activate. */ - readonly profiles?: UccProfile | readonly UccProfile[] | undefined; + readonly capabilities?: UccCapability | readonly UccCapability[] | undefined; /** * Models with constraints to extract processing instructions from. @@ -277,7 +277,7 @@ class UccProcessor$Profiler> { action: () => Promise, ) => Promise; - readonly #handlers = new Map>(); + readonly #handlers = new Map>(); constructor( processor: UccProcessor, @@ -304,8 +304,8 @@ class UccProcessor$Profiler> { } onConstraint( - { processor, within, use, from }: UccProfile.ConstraintCriterion, - handler: UccProfile.ConstraintHandler, + { processor, within, use, from }: UccCapability.ConstraintCriterion, + handler: UccCapability.ConstraintHandler, ): void { const handlerId = this.#handlerId(processor, within, use, from); const prevHandler = this.#handlers.get(handlerId); @@ -350,7 +350,7 @@ class UccProcessor$Profiler> { processor: UcProcessorName, within: UcPresentationName | undefined, { use, from }: UcFeatureConstraint, - ): UccProfile.ConstraintHandler | undefined { + ): UccCapability.ConstraintHandler | undefined { return this.#handlers.get(this.#handlerId(processor, within, use, from)); // Match concrete presentations.; } @@ -366,7 +366,7 @@ class UccProcessor$Profiler> { } class UccProcessor$ConstraintApplication> - implements UccProfile.ConstraintApplication { + implements UccCapability.ConstraintApplication { readonly #profiler: UccProcessor$Profiler; readonly #processor: UcProcessorName; @@ -475,8 +475,8 @@ class UccProcessor$ConstraintApplication> } -class UccProcessor$ProfileActivation> - implements UccProfile.Activation { +class UccProcessor$CapabilityActivation> + implements UccCapability.Activation { readonly #profiler: UccProcessor$Profiler; @@ -491,8 +491,8 @@ class UccProcessor$ProfileActivation> } onConstraint( - criterion: UccProfile.ConstraintCriterion, - handler: UccProfile.ConstraintHandler, + criterion: UccCapability.ConstraintCriterion, + handler: UccCapability.ConstraintHandler, ): this { this.#profiler.onConstraint(criterion, handler); diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 505e3b9e..01941ac9 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -1,5 +1,5 @@ -export * from './ucs-allow-plain-text.js'; export * from './ucs-compiler.js'; +export * from './ucs-enable-plain-text.js'; export * from './ucs-export.signature.js'; export * from './ucs-formatter.js'; export * from './ucs-function.js'; diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index efd5c77d..672a2b61 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -9,9 +9,9 @@ import { } from 'esgen'; import { UcFormatName, UcInsetName, UcPresentationName } from '../../schema/uc-presentations.js'; import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; +import { UccCapability } from '../processor/ucc-capability.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; -import { UccProfile } from '../processor/ucc-profile.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UccSetup } from '../processor/ucc-setup.js'; import { UcsFormatter } from './ucs-formatter.js'; @@ -41,12 +41,12 @@ export class UcsCompiler * @param options - Setup options. */ constructor(options: UcsCompiler.Options) { - const { presentations, profiles, models, features } = options; + const { presentations, capabilities, models, features } = options; super({ processors: 'serializer', presentations, - profiles, + capabilities, models: Object.values(models).map(({ model }) => model), features, }); @@ -220,7 +220,10 @@ export class UcsCompiler export namespace UcsCompiler { export interface Options { readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; - readonly profiles?: UccProfile | readonly UccProfile[] | undefined; + readonly capabilities?: + | UccCapability + | readonly UccCapability[] + | undefined; readonly models: TModels; readonly features?: UccFeature | readonly UccFeature[] | undefined; diff --git a/src/compiler/serialization/ucs-allow-plain-text.ts b/src/compiler/serialization/ucs-enable-plain-text.ts similarity index 93% rename from src/compiler/serialization/ucs-allow-plain-text.ts rename to src/compiler/serialization/ucs-enable-plain-text.ts index 23674fe0..72f63ceb 100644 --- a/src/compiler/serialization/ucs-allow-plain-text.ts +++ b/src/compiler/serialization/ucs-enable-plain-text.ts @@ -6,8 +6,8 @@ import { ucModelName } from '../../schema/uc-model-name.js'; import { ucsWriteAsIs } from '../../serializer/ucs-write-asis.js'; import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; +import { UccCapability } from '../processor/ucc-capability.js'; import { UccConfig } from '../processor/ucc-config.js'; -import { UccProfile } from '../processor/ucc-profile.js'; import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; import { ucsFormatInteger } from './impl/ucs-format-integer.js'; @@ -19,9 +19,9 @@ import { ucsSupportInteger } from './ucs-support-integer.js'; import { ucsSupportNumber } from './ucs-support-number.js'; import { ucsSupportString } from './ucs-support-string.js'; -export function ucsAllowPlainText(activation: UccProfile.Activation): void { +export function ucsEnablePlainText(activation: UccCapability.Activation): void { activation - .enable(ucsSupportPlainText) + .enable(ucsSupportPlainTextDefaults) .onConstraint( { processor: 'serializer', @@ -66,7 +66,7 @@ export function ucsAllowPlainText(activation: UccProfile.Activation): ); } -function ucsSupportPlainText(setup: UcsSetup): UccConfig { +function ucsSupportPlainTextDefaults(setup: UcsSetup): UccConfig { return { configure() { setup diff --git a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts index 148cf42b..47b729f2 100644 --- a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts +++ b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; -import { ucsAllowPlainText } from '../../../compiler/serialization/ucs-allow-plain-text.js'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; +import { ucsEnablePlainText } from '../../../compiler/serialization/ucs-enable-plain-text.js'; import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; import { ucList } from '../../../schema/list/uc-list.js'; import { ucMap } from '../../../schema/map/uc-map.js'; @@ -16,7 +16,7 @@ import { TextOutStream } from '../../../spec/text-out-stream.js'; describe('plain text serializer', () => { it('serializes bigint', async () => { const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writePrimitive: { model: BigInt, format: 'plainText' }, writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'plainText' }, @@ -42,7 +42,7 @@ describe('plain text serializer', () => { }); it('serializes boolean', async () => { const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writePrimitive: { model: Boolean, format: 'plainText' }, writeValue: { model: ucBoolean(), format: 'plainText' }, @@ -60,7 +60,7 @@ describe('plain text serializer', () => { }); it('serializes number', async () => { const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writePrimitive: { model: Number, format: 'plainText' }, writeValue: { model: ucNumber({ string: 'serialize' }), format: 'plainText' }, @@ -79,7 +79,7 @@ describe('plain text serializer', () => { }); it('serializes integer', async () => { const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writeValue: { model: ucInteger({ string: 'serialize' }), format: 'plainText' }, }, @@ -94,7 +94,7 @@ describe('plain text serializer', () => { }); it('serializes string', async () => { const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writePrimitive: { model: String, format: 'plainText' }, writeValue: { model: ucString({ raw: 'escape' }), format: 'plainText' }, @@ -113,7 +113,7 @@ describe('plain text serializer', () => { it('can not serialize list', async () => { const schema = ucList(Number); const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writeList: { model: schema, format: 'plainText' }, }, @@ -129,7 +129,7 @@ describe('plain text serializer', () => { it('can not serialize map', async () => { const schema = ucMap({ foo: Number }); const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { writeMap: { model: schema, format: 'plainText' }, }, @@ -145,7 +145,7 @@ describe('plain text serializer', () => { it('can not serialize nullable values', async () => { const schema = ucNullable(ucNumber()); const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { write: { model: schema, format: 'plainText' }, }, @@ -162,7 +162,7 @@ describe('plain text serializer', () => { it('can not serialize optional values', async () => { const schema = ucOptional(ucNumber()); const compiler = new UcsCompiler({ - profiles: ucsAllowPlainText, + capabilities: ucsEnablePlainText, models: { write: { model: schema, format: 'plainText' }, }, From 3fd80bcb40a7afa0ddf81151b42c33252ba9986c Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 16:06:39 +0700 Subject: [PATCH 15/36] Rename formatter --- src/compiler/serialization/ucs-enable-plain-text.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/compiler/serialization/ucs-enable-plain-text.ts b/src/compiler/serialization/ucs-enable-plain-text.ts index 72f63ceb..ee9beb06 100644 --- a/src/compiler/serialization/ucs-enable-plain-text.ts +++ b/src/compiler/serialization/ucs-enable-plain-text.ts @@ -61,7 +61,7 @@ export function ucsEnablePlainText(activation: UccCapability.Activation { - setup.formatWith('plainText', schema, ucsFormatStringAsPlainText()); + setup.formatWith('plainText', schema, ucsFormatPlainTextString()); }, ); } @@ -73,12 +73,12 @@ function ucsSupportPlainTextDefaults(setup: UcsSetup): UccConfig { .formatWith('plainText', BigInt, ucsFormatPlainText(ucsFormatBigInt())) .formatWith('plainText', Boolean, ucsFormatPlainText(ucsFormatBoolean())) .formatWith('plainText', Number, ucsFormatPlainText(ucsFormatNumber())) - .formatWith('plainText', String, ucsFormatStringAsPlainText()); + .formatWith('plainText', String, ucsFormatPlainTextString()); }, }; } -function ucsFormatStringAsPlainText(): UcsFormatter { +function ucsFormatPlainTextString(): UcsFormatter { return ucsFormatPlainText(({ writer, value }) => { const writeAsIs = UC_MODULE_SERIALIZER.import(ucsWriteAsIs.name); From dbdf9da94ac5d5f0bc6369b4f03778517636a3d0 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Sun, 30 Jul 2023 16:07:09 +0700 Subject: [PATCH 16/36] URI-encoded serialization capability --- src/compiler/serialization/mod.ts | 1 + .../serialization/ucs-enable-uri-encoded.ts | 106 +++++++++++ src/serializer/ucs-write-string.ts | 6 + .../uri-encoded.serializer.spec.ts | 179 ++++++++++++++++++ 4 files changed, 292 insertions(+) create mode 100644 src/compiler/serialization/ucs-enable-uri-encoded.ts create mode 100644 src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 01941ac9..e1ed99d6 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -1,5 +1,6 @@ export * from './ucs-compiler.js'; export * from './ucs-enable-plain-text.js'; +export * from './ucs-enable-uri-encoded.js'; export * from './ucs-export.signature.js'; export * from './ucs-formatter.js'; export * from './ucs-function.js'; diff --git a/src/compiler/serialization/ucs-enable-uri-encoded.ts b/src/compiler/serialization/ucs-enable-uri-encoded.ts new file mode 100644 index 00000000..65919e78 --- /dev/null +++ b/src/compiler/serialization/ucs-enable-uri-encoded.ts @@ -0,0 +1,106 @@ +import { esline } from 'esgen'; +import { COMPILER_MODULE } from '../../impl/module-names.js'; +import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; +import { UcString } from '../../schema/string/uc-string.js'; +import { ucModelName } from '../../schema/uc-model-name.js'; +import { ucsWriteURIEncoded } from '../../serializer/ucs-write-string.js'; +import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js'; +import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; +import { UccCapability } from '../processor/ucc-capability.js'; +import { UccConfig } from '../processor/ucc-config.js'; +import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; +import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; +import { ucsFormatInteger } from './impl/ucs-format-integer.js'; +import { ucsFormatNumber } from './impl/ucs-format-number.js'; +import { UcsFormatter } from './ucs-formatter.js'; +import { UcsSetup } from './ucs-setup.js'; +import { ucsSupportBigInt } from './ucs-support-bigint.js'; +import { ucsSupportInteger } from './ucs-support-integer.js'; +import { ucsSupportNumber } from './ucs-support-number.js'; +import { ucsSupportString } from './ucs-support-string.js'; + +export function ucsEnableURIEncoded(activation: UccCapability.Activation): void { + activation + .enable(ucsSupportURIEncodedDefaults) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportBigInt.name, + from: COMPILER_MODULE, + }, + ({ setup, schema, constraint: { with: options } }) => { + const { number = 'parse' } = options as UcBigInt.Variant; + + setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatBigInt({ number }))); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportInteger.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatInteger())); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportNumber.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatNumber())); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsSupportString.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('uriEncoded', schema, ucsFormatURIEncodedString()); + }, + ); +} + +function ucsSupportURIEncodedDefaults(setup: UcsSetup): UccConfig { + return { + configure() { + setup + .formatWith('uriEncoded', BigInt, ucsFormatURIEncoded(ucsFormatBigInt())) + .formatWith('uriEncoded', Boolean, ucsFormatURIEncoded(ucsFormatBoolean())) + .formatWith('uriEncoded', Number, ucsFormatURIEncoded(ucsFormatNumber())) + .formatWith('uriEncoded', String, ucsFormatURIEncodedString()); + }, + }; +} + +export function ucsFormatURIEncodedString(): UcsFormatter { + return ucsFormatURIEncoded(({ writer, value }) => { + const write = UC_MODULE_SERIALIZER.import(ucsWriteURIEncoded.name); + + return esline`await ${write}(${writer}, ${value});`; + }); +} + +function ucsFormatURIEncoded(formatter: UcsFormatter): UcsFormatter { + return (args, schema, context) => { + if (schema.nullable) { + throw new UnsupportedUcSchemaError( + schema, + `${context}: Can not URI-encode nullable values of type "${ucModelName(schema)}"`, + ); + } + if (schema.optional) { + throw new UnsupportedUcSchemaError( + schema, + `${context}: Can not URI-encode optional values of type "${ucModelName(schema)}"`, + ); + } + + return formatter(args, schema, context); + }; +} diff --git a/src/serializer/ucs-write-string.ts b/src/serializer/ucs-write-string.ts index 5ca2275d..66c28cf1 100644 --- a/src/serializer/ucs-write-string.ts +++ b/src/serializer/ucs-write-string.ts @@ -1,3 +1,4 @@ +import { encodeURIPart } from 'httongue'; import { encodeUcsString, isEscapedUcsString } from '../impl/encode-ucs-string.js'; import { UCS_APOSTROPHE, UCS_ESCAPED_DOUBLE_HYPHEN } from './ucs-constants.js'; import { ucsWriteAsIs } from './ucs-write-asis.js'; @@ -44,6 +45,11 @@ export async function ucsWriteRawString( await ucsWriteAsIs(ucsWriter, encodeUcsString(value)); } +export async function ucsWriteURIEncoded(ucsWriter: UcsWriter, value: string): Promise { + await ucsWriter.ready; + await ucsWriteAsIs(ucsWriter, encodeURIPart(value)); +} + export async function ucsWriteNullableRawString( ucsWriter: UcsWriter, value: string, diff --git a/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts b/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts new file mode 100644 index 00000000..5a256828 --- /dev/null +++ b/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts @@ -0,0 +1,179 @@ +import { describe, expect, it } from '@jest/globals'; +import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; +import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; +import { ucsEnableURIEncoded } from '../../../compiler/serialization/ucs-enable-uri-encoded.js'; +import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; +import { ucList } from '../../../schema/list/uc-list.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; +import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { ucInteger } from '../../../schema/numeric/uc-integer.js'; +import { ucNumber } from '../../../schema/numeric/uc-number.js'; +import { ucString } from '../../../schema/string/uc-string.js'; +import { ucNullable } from '../../../schema/uc-nullable.js'; +import { ucOptional } from '../../../schema/uc-optional.js'; +import { TextOutStream } from '../../../spec/text-out-stream.js'; + +describe('URI-encoded serializer', () => { + it('serializes bigint', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writePrimitive: { model: BigInt, format: 'uriEncoded' }, + writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'uriEncoded' }, + writeNumber: { model: ucBigInt({ number: 'serialize' }), format: 'uriEncoded' }, + writeAuto: { model: ucBigInt({ number: 'auto' }), format: 'uriEncoded' }, + }, + }); + + const { writePrimitive, writeValue, writeNumber, writeAuto } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, -13n))).resolves.toBe( + '-0n13', + ); + await expect(TextOutStream.read(async to => await writeValue(to, -13n))).resolves.toBe('-0n13'); + await expect(TextOutStream.read(async to => await writeNumber(to, -13n))).resolves.toBe('-13'); + await expect(TextOutStream.read(async to => await writeAuto(to, -13n))).resolves.toBe('-13'); + await expect( + TextOutStream.read(async to => await writeNumber(to, BigInt(Number.MAX_SAFE_INTEGER + 1))), + ).resolves.toBe(BigInt(Number.MAX_SAFE_INTEGER + 1).toString()); + await expect( + TextOutStream.read(async to => await writeAuto(to, BigInt(Number.MAX_SAFE_INTEGER + 1))), + ).resolves.toBe(`0n` + BigInt(Number.MAX_SAFE_INTEGER + 1)); + }); + it('serializes boolean', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writePrimitive: { model: Boolean, format: 'uriEncoded' }, + writeValue: { model: ucBoolean(), format: 'uriEncoded' }, + }, + }); + + const { writePrimitive, writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, true))).resolves.toBe('!'); + await expect(TextOutStream.read(async to => await writePrimitive(to, false))).resolves.toBe( + '-', + ); + await expect(TextOutStream.read(async to => await writeValue(to, true))).resolves.toBe('!'); + await expect(TextOutStream.read(async to => await writeValue(to, false))).resolves.toBe('-'); + }); + it('serializes number', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writePrimitive: { model: Number, format: 'uriEncoded' }, + writeValue: { model: ucNumber({ string: 'serialize' }), format: 'uriEncoded' }, + }, + }); + + const { writePrimitive, writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, -13.1))).resolves.toBe( + '-13.1', + ); + await expect(TextOutStream.read(async to => await writeValue(to, -13))).resolves.toBe('-13'); + await expect(TextOutStream.read(async to => await writeValue(to, -Infinity))).resolves.toBe( + '!-Infinity', + ); + }); + it('serializes integer', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writeValue: { model: ucInteger({ string: 'serialize' }), format: 'uriEncoded' }, + }, + }); + + const { writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writeValue(to, -13.1))).resolves.toBe('-13'); + await expect(TextOutStream.read(async to => await writeValue(to, -Infinity))).resolves.toBe( + '-Infinity', + ); + }); + it('serializes string', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writePrimitive: { model: String, format: 'uriEncoded' }, + writeValue: { model: ucString({ raw: 'escape' }), format: 'uriEncoded' }, + }, + }); + + const { writePrimitive, writeValue } = await compiler.evaluate(); + + await expect(TextOutStream.read(async to => await writePrimitive(to, '1, b c!'))).resolves.toBe( + '1%2C%20b%20c%21', + ); + await expect(TextOutStream.read(async to => await writeValue(to, '1, b c!'))).resolves.toBe( + '1%2C%20b%20c%21', + ); + }); + it('can not serialize list', async () => { + const schema = ucList(Number); + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writeList: { model: schema, format: 'uriEncoded' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'list$uriEncoded(writer, value, asItem?): Can not serialize type "Number[]"', + ), + ); + }); + it('can not serialize map', async () => { + const schema = ucMap({ foo: Number }); + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + writeMap: { model: schema, format: 'uriEncoded' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'map$uriEncoded(writer, value, asItem?): Can not serialize type "{foo: Number}"', + ), + ); + }); + it('can not serialize nullable values', async () => { + const schema = ucNullable(ucNumber()); + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + write: { model: schema, format: 'uriEncoded' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'Number$uriEncodedN(writer, value, asItem?): Can not URI-encode nullable values' + + ' of type "(Number | null)"', + ), + ); + }); + it('can not serialize optional values', async () => { + const schema = ucOptional(ucNumber()); + const compiler = new UcsCompiler({ + capabilities: ucsEnableURIEncoded, + models: { + write: { model: schema, format: 'uriEncoded' }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + schema, + 'Number$uriEncodedO(writer, value, asItem?): Can not URI-encode optional values' + + ' of type "Number?"', + ), + ); + }); +}); From 396244dcdb872624d616e265731c440711c4f8cb Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 11:57:52 +0700 Subject: [PATCH 17/36] Decompose `UccProcessor` --- .../ucc-processor.capability-activation.ts | 30 ++ .../ucc-processor.constraint-application.ts | 120 ++++++ .../processor/impl/ucc-processor.current.ts | 10 + .../impl/ucc-processor.feature-use.ts | 41 +++ .../processor/impl/ucc-processor.profiler.ts | 106 ++++++ src/compiler/processor/ucc-processor.ts | 346 ++---------------- 6 files changed, 344 insertions(+), 309 deletions(-) create mode 100644 src/compiler/processor/impl/ucc-processor.capability-activation.ts create mode 100644 src/compiler/processor/impl/ucc-processor.constraint-application.ts create mode 100644 src/compiler/processor/impl/ucc-processor.current.ts create mode 100644 src/compiler/processor/impl/ucc-processor.feature-use.ts create mode 100644 src/compiler/processor/impl/ucc-processor.profiler.ts diff --git a/src/compiler/processor/impl/ucc-processor.capability-activation.ts b/src/compiler/processor/impl/ucc-processor.capability-activation.ts new file mode 100644 index 00000000..1972517f --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.capability-activation.ts @@ -0,0 +1,30 @@ +import { UccCapability } from '../ucc-capability.js'; +import { UccFeature } from '../ucc-feature.js'; +import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; + +export class UccProcessor$CapabilityActivation> + implements UccCapability.Activation { + + readonly #profiler: UccProcessor$Profiler; + + constructor(profiler: UccProcessor$Profiler) { + this.#profiler = profiler; + } + + enable(feature: UccFeature, options?: TOptions): this { + this.#profiler.enable(feature, options!); + + return this; + } + + onConstraint( + criterion: UccCapability.ConstraintCriterion, + handler: UccCapability.ConstraintHandler, + ): this { + this.#profiler.onConstraint(criterion, handler); + + return this; + } + +} diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts new file mode 100644 index 00000000..0836c1c3 --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -0,0 +1,120 @@ +import { mayHaveProperties } from '@proc7ts/primitives'; +import { esQuoteKey, esStringLiteral } from 'esgen'; +import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js'; +import { UcPresentationName } from '../../../schema/uc-presentations.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccCapability } from '../ucc-capability.js'; +import { UccFeature } from '../ucc-feature.js'; +import { UccSchemaFeature } from '../ucc-schema-feature.js'; +import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; + +export class UccProcessor$ConstraintApplication> + implements UccCapability.ConstraintApplication { + + readonly #profiler: UccProcessor$Profiler; + readonly #processor: UcProcessorName; + readonly #schema: UcSchema; + readonly #within: UcPresentationName | undefined; + readonly #constraint: UcFeatureConstraint; + #applied = 0; + + constructor( + profiler: UccProcessor$Profiler, + processor: UcProcessorName, + schema: UcSchema, + within: UcPresentationName | undefined, + constraint: UcFeatureConstraint, + ) { + this.#profiler = profiler; + this.#processor = processor; + this.#schema = schema; + this.#within = within; + this.#constraint = constraint; + } + + get setup(): TSetup { + return this.#profiler.setup; + } + + get schema(): UcSchema { + return this.#schema; + } + + get processor(): UcProcessorName { + return this.#processor; + } + + get within(): UcPresentationName | undefined { + return this.#within; + } + + get constraint(): UcFeatureConstraint { + return this.#constraint; + } + + isApplied(): boolean { + return this.#applied > 0; + } + + isIgnored(): boolean { + return this.#applied < 0; + } + + async apply(): Promise { + if (this.isApplied()) { + return; + } + + this.#applied = 1; + + const { use, from, with: options } = this.constraint; + + const { + [use]: constraint, + }: { [name: string]: UccFeature | UccSchemaFeature } = + await import(from); + + if (mayHaveProperties(constraint)) { + let configured = false; + + if ('uccProcess' in constraint) { + this.setup.enable(constraint, options); + configured = true; + } + if ('uccProcessSchema' in constraint) { + constraint.uccProcessSchema(this.setup, this.schema).configure(options); + configured = true; + } + + if (configured) { + return; + } + + if (typeof constraint === 'function') { + constraint(this.setup, this.schema).configure(options); + + return; + } + } + + if (constraint === undefined) { + throw new ReferenceError(`No such schema processing feature: ${this}`); + } + + throw new ReferenceError(`Not a schema processing feature: ${this}`); + } + + ignore(): void { + if (!this.isApplied()) { + this.#applied = -1; + } + } + + toString(): string { + const { use, from } = this.#constraint; + + return `import(${esStringLiteral(from)}).${esQuoteKey(use)}`; + } + +} diff --git a/src/compiler/processor/impl/ucc-processor.current.ts b/src/compiler/processor/impl/ucc-processor.current.ts new file mode 100644 index 00000000..e717aa60 --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.current.ts @@ -0,0 +1,10 @@ +import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js'; +import { UcPresentationName } from '../../../schema/uc-presentations.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; + +export interface UccProcessor$Current { + readonly processor?: UcProcessorName | undefined; + readonly schema?: UcSchema | undefined; + readonly within?: UcPresentationName | undefined; + readonly constraint?: UcFeatureConstraint | undefined; +} diff --git a/src/compiler/processor/impl/ucc-processor.feature-use.ts b/src/compiler/processor/impl/ucc-processor.feature-use.ts new file mode 100644 index 00000000..735e9a28 --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.feature-use.ts @@ -0,0 +1,41 @@ +import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js'; +import { UcPresentationName } from '../../../schema/uc-presentations.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; + +export class UccProcessor$FeatureUse> { + + readonly #schema: UcSchema; + readonly #constraints: [UcProcessorName, UcPresentationName | undefined, UcFeatureConstraint][] = + []; + + #applied = false; + #profiler: UccProcessor$Profiler; + + constructor(profiler: UccProcessor$Profiler, schema: UcSchema) { + this.#profiler = profiler; + this.#schema = schema; + } + + addConfig( + processor: UcProcessorName, + presentation: UcPresentationName | undefined, + constraint: UcFeatureConstraint, + ): void { + this.#constraints.push([processor, presentation, constraint]); + } + + async apply(): Promise { + if (this.#applied) { + return; + } + + this.#applied = true; + + for (const [processor, within, constraint] of this.#constraints) { + await this.#profiler.applyConstraint(processor, this.#schema, within, constraint); + } + } + +} diff --git a/src/compiler/processor/impl/ucc-processor.profiler.ts b/src/compiler/processor/impl/ucc-processor.profiler.ts new file mode 100644 index 00000000..b3b173ec --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.profiler.ts @@ -0,0 +1,106 @@ +import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js'; +import { UcPresentationName } from '../../../schema/uc-presentations.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccCapability } from '../ucc-capability.js'; +import { UccFeature } from '../ucc-feature.js'; +import { UccProcessor } from '../ucc-processor.js'; +import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$ConstraintApplication } from './ucc-processor.constraint-application.js'; +import { UccProcessor$Current } from './ucc-processor.current.js'; + +export class UccProcessor$Profiler> { + + readonly #processor: UccProcessor; + readonly #init: ((setup: TSetup) => void)[] = []; + readonly #configure: ( + current: UccProcessor$Current, + action: () => Promise, + ) => Promise; + + readonly #handlers = new Map>(); + + constructor( + processor: UccProcessor, + configure: (current: UccProcessor$Current, action: () => Promise) => Promise, + ) { + this.#processor = processor; + this.#configure = configure; + } + + get setup(): TSetup { + return this.#processor.setup; + } + + enable(feature: UccFeature, options: TOptions): void { + this.#init.push(setup => setup.enable(feature, options)); + } + + init(): void { + const { setup } = this; + + for (const init of this.#init) { + init(setup); + } + } + + onConstraint( + { processor, within, use, from }: UccCapability.ConstraintCriterion, + handler: UccCapability.ConstraintHandler, + ): void { + const handlerId = this.#handlerId(processor, within, use, from); + const prevHandler = this.#handlers.get(handlerId); + + if (prevHandler) { + this.#handlers.set(handlerId, async application => { + await prevHandler(application); + await handler(application); + }); + } else { + this.#handlers.set(handlerId, handler); + } + } + + async applyConstraint( + processor: UcProcessorName, + schema: UcSchema, + within: UcPresentationName | undefined, + constraint: UcFeatureConstraint, + ): Promise { + const application = new UccProcessor$ConstraintApplication( + this, + processor, + schema, + within, + constraint, + ); + + await this.#configure({ processor, schema, within, constraint }, async () => { + await this.#findHandler(processor, within, constraint)?.(application); + if (within) { + // Apply any presentation handler. + await this.#findHandler(processor, undefined, constraint)?.(application); + } + if (!application.isIgnored()) { + await application.apply(); + } + }); + } + + #findHandler( + processor: UcProcessorName, + within: UcPresentationName | undefined, + { use, from }: UcFeatureConstraint, + ): UccCapability.ConstraintHandler | undefined { + return this.#handlers.get(this.#handlerId(processor, within, use, from)); // Match concrete presentations.; + } + + #handlerId( + processor: UcProcessorName, + within: UcPresentationName | undefined, + use: string, + from: string, + ): string { + return `${processor}(${within} ?? '*'):${use}@${from}`; + } + +} diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 498c5a17..0ab63a85 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -1,5 +1,4 @@ -import { asArray, lazyValue, mayHaveProperties } from '@proc7ts/primitives'; -import { esQuoteKey, esStringLiteral } from 'esgen'; +import { asArray, lazyValue } from '@proc7ts/primitives'; import { UcConstraints, UcFeatureConstraint, @@ -7,10 +6,13 @@ import { } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; +import { UccProcessor$CapabilityActivation } from './impl/ucc-processor.capability-activation.js'; +import { UccProcessor$Current } from './impl/ucc-processor.current.js'; +import { UccProcessor$FeatureUse } from './impl/ucc-processor.feature-use.js'; +import { UccProcessor$Profiler } from './impl/ucc-processor.profiler.js'; import { UccCapability } from './ucc-capability.js'; import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; -import { UccSchemaFeature } from './ucc-schema-feature.js'; import { UccSchemaIndex } from './ucc-schema-index.js'; import { UccSetup } from './ucc-setup.js'; @@ -42,14 +44,14 @@ export abstract class UccProcessor> * * @param init - Processor initialization options. */ - constructor(init: UccProcessorInit); + constructor(init: UccProcessor.Options); constructor({ processors, presentations = [], capabilities, models, features, - }: UccProcessorInit) { + }: UccProcessor.Options) { this.#schemaIndex = new UccSchemaIndex( asArray(processors), asArray(presentations), @@ -227,312 +229,38 @@ export abstract class UccProcessor> } -/** - * Schema {@link UccProcessor processor} initialization options. - * - * @typeParam TSetup - Schema processing setup type. - */ -export interface UccProcessorInit> { +export namespace UccProcessor { /** - * Processor names within {@link churi!UcConstraints schema constraints}. - */ - readonly processors: UcProcessorName | readonly UcProcessorName[]; - - /** - * Schema instance presentation names within {@link churi!UcPresentations presentation constraints}. + * Schema {@link UccProcessor processor} initialization options. * - * All presentations enabled when missing or empty. - */ - readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; - - /** - * Processor capabilities to activate. + * @typeParam TSetup - Schema processing setup type. */ - readonly capabilities?: UccCapability | readonly UccCapability[] | undefined; - - /** - * Models with constraints to extract processing instructions from. - */ - readonly models?: readonly UcModel[] | undefined; - - /** - * Additional schema processing features to enable and use. - */ - readonly features?: UccFeature | readonly UccFeature[] | undefined; -} - -interface UccProcessor$Current { - readonly processor?: UcProcessorName | undefined; - readonly schema?: UcSchema | undefined; - readonly within?: UcPresentationName | undefined; - readonly constraint?: UcFeatureConstraint | undefined; -} - -class UccProcessor$Profiler> { - - readonly #processor: UccProcessor; - readonly #init: ((setup: TSetup) => void)[] = []; - readonly #configure: ( - current: UccProcessor$Current, - action: () => Promise, - ) => Promise; - - readonly #handlers = new Map>(); - - constructor( - processor: UccProcessor, - configure: (current: UccProcessor$Current, action: () => Promise) => Promise, - ) { - this.#processor = processor; - this.#configure = configure; - } - - get setup(): TSetup { - return this.#processor.setup; - } - - enable(feature: UccFeature, options: TOptions): void { - this.#init.push(setup => setup.enable(feature, options)); - } - - init(): void { - const { setup } = this; - - for (const init of this.#init) { - init(setup); - } - } - - onConstraint( - { processor, within, use, from }: UccCapability.ConstraintCriterion, - handler: UccCapability.ConstraintHandler, - ): void { - const handlerId = this.#handlerId(processor, within, use, from); - const prevHandler = this.#handlers.get(handlerId); - - if (prevHandler) { - this.#handlers.set(handlerId, async application => { - await prevHandler(application); - await handler(application); - }); - } else { - this.#handlers.set(handlerId, handler); - } - } - - async applyConstraint( - processor: UcProcessorName, - schema: UcSchema, - within: UcPresentationName | undefined, - constraint: UcFeatureConstraint, - ): Promise { - const application = new UccProcessor$ConstraintApplication( - this, - processor, - schema, - within, - constraint, - ); - - await this.#configure({ processor, schema, within, constraint }, async () => { - await this.#findHandler(processor, within, constraint)?.(application); - if (within) { - // Apply any presentation handler. - await this.#findHandler(processor, undefined, constraint)?.(application); - } - if (!application.isIgnored()) { - await application.apply(); - } - }); - } - - #findHandler( - processor: UcProcessorName, - within: UcPresentationName | undefined, - { use, from }: UcFeatureConstraint, - ): UccCapability.ConstraintHandler | undefined { - return this.#handlers.get(this.#handlerId(processor, within, use, from)); // Match concrete presentations.; - } - - #handlerId( - processor: UcProcessorName, - within: UcPresentationName | undefined, - use: string, - from: string, - ): string { - return `${processor}(${within} ?? '*'):${use}@${from}`; - } - -} - -class UccProcessor$ConstraintApplication> - implements UccCapability.ConstraintApplication { - - readonly #profiler: UccProcessor$Profiler; - readonly #processor: UcProcessorName; - readonly #schema: UcSchema; - readonly #within: UcPresentationName | undefined; - readonly #constraint: UcFeatureConstraint; - #applied = 0; - - constructor( - profiler: UccProcessor$Profiler, - processor: UcProcessorName, - schema: UcSchema, - within: UcPresentationName | undefined, - constraint: UcFeatureConstraint, - ) { - this.#profiler = profiler; - this.#processor = processor; - this.#schema = schema; - this.#within = within; - this.#constraint = constraint; - } - - get setup(): TSetup { - return this.#profiler.setup; - } - - get schema(): UcSchema { - return this.#schema; + export interface Options> { + /** + * Processor names within {@link churi!UcConstraints schema constraints}. + */ + readonly processors: UcProcessorName | readonly UcProcessorName[]; + + /** + * Schema instance presentation names within {@link churi!UcPresentations presentation constraints}. + * + * All presentations enabled when missing or empty. + */ + readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; + + /** + * Processor capabilities to activate. + */ + readonly capabilities?: UccCapability | readonly UccCapability[] | undefined; + + /** + * Models with constraints to extract processing instructions from. + */ + readonly models?: readonly UcModel[] | undefined; + + /** + * Additional schema processing features to enable and use. + */ + readonly features?: UccFeature | readonly UccFeature[] | undefined; } - - get processor(): UcProcessorName { - return this.#processor; - } - - get within(): UcPresentationName | undefined { - return this.#within; - } - - get constraint(): UcFeatureConstraint { - return this.#constraint; - } - - isApplied(): boolean { - return this.#applied > 0; - } - - isIgnored(): boolean { - return this.#applied < 0; - } - - async apply(): Promise { - if (this.isApplied()) { - return; - } - - this.#applied = 1; - - const { use, from, with: options } = this.constraint; - - const { - [use]: constraint, - }: { [name: string]: UccFeature | UccSchemaFeature } = - await import(from); - - if (mayHaveProperties(constraint)) { - let configured = false; - - if ('uccProcess' in constraint) { - this.setup.enable(constraint, options); - configured = true; - } - if ('uccProcessSchema' in constraint) { - constraint.uccProcessSchema(this.setup, this.schema).configure(options); - configured = true; - } - - if (configured) { - return; - } - - if (typeof constraint === 'function') { - constraint(this.setup, this.schema).configure(options); - - return; - } - } - - if (constraint === undefined) { - throw new ReferenceError(`No such schema processing feature: ${this}`); - } - - throw new ReferenceError(`Not a schema processing feature: ${this}`); - } - - ignore(): void { - if (!this.isApplied()) { - this.#applied = -1; - } - } - - toString(): string { - const { use, from } = this.#constraint; - - return `import(${esStringLiteral(from)}).${esQuoteKey(use)}`; - } - -} - -class UccProcessor$CapabilityActivation> - implements UccCapability.Activation { - - readonly #profiler: UccProcessor$Profiler; - - constructor(profiler: UccProcessor$Profiler) { - this.#profiler = profiler; - } - - enable(feature: UccFeature, options?: TOptions): this { - this.#profiler.enable(feature, options!); - - return this; - } - - onConstraint( - criterion: UccCapability.ConstraintCriterion, - handler: UccCapability.ConstraintHandler, - ): this { - this.#profiler.onConstraint(criterion, handler); - - return this; - } - -} - -class UccProcessor$FeatureUse> { - - readonly #schema: UcSchema; - readonly #constraints: [UcProcessorName, UcPresentationName | undefined, UcFeatureConstraint][] = - []; - - #applied = false; - #profiler: UccProcessor$Profiler; - - constructor(profiler: UccProcessor$Profiler, schema: UcSchema) { - this.#profiler = profiler; - this.#schema = schema; - } - - addConfig( - processor: UcProcessorName, - presentation: UcPresentationName | undefined, - constraint: UcFeatureConstraint, - ): void { - this.#constraints.push([processor, presentation, constraint]); - } - - async apply(): Promise { - if (this.#applied) { - return; - } - - this.#applied = true; - - for (const [processor, within, constraint] of this.#constraints) { - await this.#profiler.applyConstraint(processor, this.#schema, within, constraint); - } - } - } From 5876b01f6dc1fae209672fd18dc4909243fbcc56 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 12:09:36 +0700 Subject: [PATCH 18/36] Unify schema processing options --- src/compiler/deserialization/ucd-compiler.ts | 7 ++---- src/compiler/processor/ucc-processor.spec.ts | 21 ++++++++-------- src/compiler/processor/ucc-processor.ts | 26 +++++++++++++++----- src/compiler/serialization/ucs-compiler.ts | 7 +----- 4 files changed, 33 insertions(+), 28 deletions(-) diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 07f4cbfb..47b9dd97 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -55,14 +55,11 @@ export class UcdCompiler * @param options - Compiler options. */ constructor(options: UcdCompiler.Options) { - const { presentations, capabilities, models, validate = true, features } = options; + const { validate = true } = options; super({ + ...options, processors: validate ? ['validator', 'deserializer'] : ['deserializer'], - presentations, - capabilities, - models: Object.values(models).map(({ model }) => model), - features, }); this.#options = options; diff --git a/src/compiler/processor/ucc-processor.spec.ts b/src/compiler/processor/ucc-processor.spec.ts index 44e38e84..fe50a663 100644 --- a/src/compiler/processor/ucc-processor.spec.ts +++ b/src/compiler/processor/ucc-processor.spec.ts @@ -26,7 +26,7 @@ describe('UccProcessor', () => { await expect( new UccTestProcessor({ processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }).bootstrap(), ).rejects.toThrow( new ReferenceError( @@ -48,7 +48,7 @@ describe('UccProcessor', () => { await expect( new UccTestProcessor({ processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }).bootstrap(), ).rejects.toThrow( new ReferenceError( @@ -61,7 +61,7 @@ describe('UccProcessor', () => { const schema: UcSchema = { type: 'test', where: constraints }; const processor = new UccTestProcessor({ processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -80,7 +80,7 @@ describe('UccProcessor', () => { const schema: UcSchema = { type: 'test', within: { charge: constraints } }; const processor = new UccTestProcessor({ processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -100,7 +100,7 @@ describe('UccProcessor', () => { const schema: UcSchema = { type: 'test', within: { charge: constraints } }; const processor = new UccTestProcessor({ processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -118,7 +118,6 @@ describe('UccProcessor', () => { it('enables feature', async () => { const processor = new UccTestProcessor({ processors: [], - models: [], features: setup => ({ configure(options) { recordUcTestData(setup, options); @@ -155,7 +154,7 @@ describe('UccProcessor', () => { ); }, processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -186,7 +185,7 @@ describe('UccProcessor', () => { ); }, processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -210,7 +209,7 @@ describe('UccProcessor', () => { ); }, processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -235,7 +234,7 @@ describe('UccProcessor', () => { ); }, processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); @@ -276,7 +275,7 @@ describe('UccProcessor', () => { ); }, processors: ['deserializer'], - models: [schema], + models: { test: { model: schema } }, }); await processor.bootstrap(); diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 0ab63a85..c0451c8a 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -1,4 +1,4 @@ -import { asArray, lazyValue } from '@proc7ts/primitives'; +import { asArray, isPresent, lazyValue } from '@proc7ts/primitives'; import { UcConstraints, UcFeatureConstraint, @@ -42,9 +42,9 @@ export abstract class UccProcessor> /** * Constructs schema processor. * - * @param init - Processor initialization options. + * @param options - Schema processing options. */ - constructor(init: UccProcessor.Options); + constructor(options: UccProcessor.Options); constructor({ processors, presentations = [], @@ -57,7 +57,11 @@ export abstract class UccProcessor> asArray(presentations), ); this.#capabilities = capabilities && asArray(capabilities); - this.#models = models; + this.#models = + models + && Object.values(models) + .filter(isPresent) + .map(({ model }: UccProcessor.Entry) => model); this.#features = features && asArray(features); } @@ -231,7 +235,7 @@ export abstract class UccProcessor> export namespace UccProcessor { /** - * Schema {@link UccProcessor processor} initialization options. + * Schema {@link UccProcessor processing} options. * * @typeParam TSetup - Schema processing setup type. */ @@ -256,11 +260,21 @@ export namespace UccProcessor { /** * Models with constraints to extract processing instructions from. */ - readonly models?: readonly UcModel[] | undefined; + readonly models?: { readonly [name in string]?: Entry | undefined } | undefined; /** * Additional schema processing features to enable and use. */ readonly features?: UccFeature | readonly UccFeature[] | undefined; } + + /** + * Processed model entry. + */ + export interface Entry { + /** + * Model to process. + */ + readonly model: UcModel; + } } diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 672a2b61..87e795dc 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -41,14 +41,9 @@ export class UcsCompiler * @param options - Setup options. */ constructor(options: UcsCompiler.Options) { - const { presentations, capabilities, models, features } = options; - super({ + ...options, processors: 'serializer', - presentations, - capabilities, - models: Object.values(models).map(({ model }) => model), - features, }); this.#options = options; From 024fd0ffbec258a43e950c0f5e02b929dee708b5 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 12:37:51 +0700 Subject: [PATCH 19/36] Custom schema processing data --- src/compiler/deserialization/ucd-compiler.ts | 2 +- .../ucc-processor.constraint-application.ts | 34 ++++++++++--------- .../impl/ucc-processor.constraint-config.ts | 9 +++++ .../impl/ucc-processor.feature-use.ts | 18 ++++------ .../processor/impl/ucc-processor.profiler.ts | 17 +++------- src/compiler/processor/ucc-capability.ts | 5 +++ src/compiler/processor/ucc-config.ts | 3 +- src/compiler/processor/ucc-processor.ts | 33 ++++++++++-------- src/compiler/processor/ucc-setup.ts | 9 +++-- 9 files changed, 69 insertions(+), 61 deletions(-) create mode 100644 src/compiler/processor/impl/ucc-processor.constraint-config.ts diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 47b9dd97..8b311485 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -98,7 +98,7 @@ export class UcdCompiler // Stop registering default handlers. // Start registering custom ones. - defaultConfig.configure(undefined); + defaultConfig.configure(undefined, undefined); this.#entities.makeDefault(); this.#formats.makeDefault(); diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts index 0836c1c3..2206e5ee 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-application.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -7,30 +7,25 @@ import { UccCapability } from '../ucc-capability.js'; import { UccFeature } from '../ucc-feature.js'; import { UccSchemaFeature } from '../ucc-schema-feature.js'; import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; export class UccProcessor$ConstraintApplication> implements UccCapability.ConstraintApplication { readonly #profiler: UccProcessor$Profiler; - readonly #processor: UcProcessorName; readonly #schema: UcSchema; - readonly #within: UcPresentationName | undefined; - readonly #constraint: UcFeatureConstraint; + readonly #config: UccProcessor$ConstraintConfig; #applied = 0; constructor( profiler: UccProcessor$Profiler, - processor: UcProcessorName, schema: UcSchema, - within: UcPresentationName | undefined, - constraint: UcFeatureConstraint, + config: UccProcessor$ConstraintConfig, ) { this.#profiler = profiler; - this.#processor = processor; this.#schema = schema; - this.#within = within; - this.#constraint = constraint; + this.#config = config; } get setup(): TSetup { @@ -42,15 +37,19 @@ export class UccProcessor$ConstraintApplication> { readonly #schema: UcSchema; - readonly #constraints: [UcProcessorName, UcPresentationName | undefined, UcFeatureConstraint][] = - []; + readonly #configs: UccProcessor$ConstraintConfig[] = []; #applied = false; #profiler: UccProcessor$Profiler; @@ -18,12 +16,8 @@ export class UccProcessor$FeatureUse> { this.#schema = schema; } - addConfig( - processor: UcProcessorName, - presentation: UcPresentationName | undefined, - constraint: UcFeatureConstraint, - ): void { - this.#constraints.push([processor, presentation, constraint]); + addConfig(config: UccProcessor$ConstraintConfig): void { + this.#configs.push(config); } async apply(): Promise { @@ -33,8 +27,8 @@ export class UccProcessor$FeatureUse> { this.#applied = true; - for (const [processor, within, constraint] of this.#constraints) { - await this.#profiler.applyConstraint(processor, this.#schema, within, constraint); + for (const config of this.#configs) { + await this.#profiler.applyConstraint(this.#schema, config); } } diff --git a/src/compiler/processor/impl/ucc-processor.profiler.ts b/src/compiler/processor/impl/ucc-processor.profiler.ts index b3b173ec..0eb1abf8 100644 --- a/src/compiler/processor/impl/ucc-processor.profiler.ts +++ b/src/compiler/processor/impl/ucc-processor.profiler.ts @@ -6,6 +6,7 @@ import { UccFeature } from '../ucc-feature.js'; import { UccProcessor } from '../ucc-processor.js'; import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$ConstraintApplication } from './ucc-processor.constraint-application.js'; +import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; import { UccProcessor$Current } from './ucc-processor.current.js'; export class UccProcessor$Profiler> { @@ -60,19 +61,9 @@ export class UccProcessor$Profiler> { } } - async applyConstraint( - processor: UcProcessorName, - schema: UcSchema, - within: UcPresentationName | undefined, - constraint: UcFeatureConstraint, - ): Promise { - const application = new UccProcessor$ConstraintApplication( - this, - processor, - schema, - within, - constraint, - ); + async applyConstraint(schema: UcSchema, config: UccProcessor$ConstraintConfig): Promise { + const application = new UccProcessor$ConstraintApplication(this, schema, config); + const { processor, within, constraint } = config; await this.#configure({ processor, schema, within, constraint }, async () => { await this.#findHandler(processor, within, constraint)?.(application); diff --git a/src/compiler/processor/ucc-capability.ts b/src/compiler/processor/ucc-capability.ts index 1e47942d..cc546073 100644 --- a/src/compiler/processor/ucc-capability.ts +++ b/src/compiler/processor/ucc-capability.ts @@ -136,6 +136,11 @@ export namespace UccCapability { */ get constraint(): UcFeatureConstraint; + /** + * Custom data passed by parent schema processor. + */ + get data(): unknown; + /** * Informs whether the {@link constraint} is {@link apply applied} already. * diff --git a/src/compiler/processor/ucc-config.ts b/src/compiler/processor/ucc-config.ts index 46d68365..7445f911 100644 --- a/src/compiler/processor/ucc-config.ts +++ b/src/compiler/processor/ucc-config.ts @@ -12,6 +12,7 @@ export interface UccConfig { * May be called multiple times. * * @param options - Configuration options. + * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. */ - configure(options: TOptions): void; + configure(options: TOptions, data: unknown): void; } diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index c0451c8a..d7889ba0 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -7,6 +7,7 @@ import { import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccProcessor$CapabilityActivation } from './impl/ucc-processor.capability-activation.js'; +import { UccProcessor$ConstraintConfig } from './impl/ucc-processor.constraint-config.js'; import { UccProcessor$Current } from './impl/ucc-processor.current.js'; import { UccProcessor$FeatureUse } from './impl/ucc-processor.feature-use.js'; import { UccProcessor$Profiler } from './impl/ucc-processor.profiler.js'; @@ -89,7 +90,11 @@ export abstract class UccProcessor> return this.#current.constraint; } - enable(feature: UccFeature, options?: TOptions): this { + enable( + feature: UccFeature, + options?: TOptions, + data?: unknown, + ): this { let getConfig = this.#configs.get(feature) as (() => UccConfig) | undefined; if (!getConfig) { @@ -97,7 +102,7 @@ export abstract class UccProcessor> this.#configs.set(feature, getConfig); } - this.#configureSync({}, () => getConfig!().configure(options!)); + this.#configureSync({}, () => getConfig!().configure(options!, data)); return this; } @@ -130,12 +135,12 @@ export abstract class UccProcessor> return prev; } - processModel(model: UcModel): this { + processModel(model: UcModel, data?: unknown): this { const schema = ucSchema(model); - this.#applyConstraints(schema, undefined, schema.where); + this.#applyConstraints(schema, undefined, schema.where, data); for (const within of this.schemaIndex.listPresentations(schema.within)) { - this.#applyConstraints(schema, within, schema.within![within]); + this.#applyConstraints(schema, within, schema.within![within], data); } return this; @@ -145,21 +150,19 @@ export abstract class UccProcessor> schema: UcSchema, within: UcPresentationName | undefined, constraints: UcConstraints | undefined, + data: unknown, ): void { for (const processor of this.schemaIndex.processors) { - for (const feature of asArray(constraints?.[processor])) { - this.#useFeature(processor, schema, within, feature); + for (const constraint of asArray(constraints?.[processor])) { + this.#useFeature(schema, { processor, within, constraint, data }); } } } - #useFeature( - processor: UcProcessorName, - schema: UcSchema, - within: UcPresentationName | undefined, - constraint: UcFeatureConstraint, - ): void { - const { use: feature, from } = constraint; + #useFeature(schema: UcSchema, config: UccProcessor$ConstraintConfig): void { + const { + constraint: { use: feature, from }, + } = config; const useId = `${this.schemaIndex.schemaId(schema)}::${from}::${feature}`; let use = this.#uses.get(useId); @@ -169,7 +172,7 @@ export abstract class UccProcessor> this.#uses.set(useId, use); } - use.addConfig(processor, within, constraint); + use.addConfig(config); } protected async processInstructions(): Promise { diff --git a/src/compiler/processor/ucc-setup.ts b/src/compiler/processor/ucc-setup.ts index 8082fd7c..feb33ebe 100644 --- a/src/compiler/processor/ucc-setup.ts +++ b/src/compiler/processor/ucc-setup.ts @@ -37,28 +37,31 @@ export interface UccSetup { * @typeParam TOptions - Type of schema processing options. * @param feature - Feature to enable. * @param options - Processing options. + * @param data - Custom data to pass to {@link UccConfig#configure schema processing configuration}. * * @returns `this` instance. */ - enable(feature: UccFeature, options: TOptions): this; + enable(feature: UccFeature, options: TOptions, data?: unknown): this; /** * Enables the given processing `feature` that does not require options. * * @typeParam TOptions - Type of schema processing options. * @param feature - Feature to enable. + * @param data - Custom data to pass to {@link UccConfig#configure schema processing configuration}. * * @returns `this` instance. */ - enable(feature: UccFeature): this; + enable(feature: UccFeature, options?: void, data?: unknown): this; /** * Applies model processing instructions specified as its {@link churi!UcSchema#where constraints}. * * @typeParam T - Implied data type. * @param model - Target model. + * @param data - Custom data to pass to {@link UccConfig#configure schema processing configuration}. * * @returns `this` instance. */ - processModel(model: UcModel): this; + processModel(model: UcModel, data?: unknown): this; } From f9abf78d4426db94f706fbe301c4f1c43f67c1ad Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 16:36:40 +0700 Subject: [PATCH 20/36] Unify schema processing features API --- .../deserialization/bigint.ucrx.class.ts | 11 +-- .../deserialization/boolean.ucrx.class.ts | 5 +- .../deserialization/integer.ucrx.class.ts | 7 +- .../deserialization/list.ucrx.class.ts | 4 +- .../deserialization/map.ucrx.class.ts | 8 +- .../deserialization/number.ucrx.class.ts | 15 ++-- .../deserialization/string.ucrx.class.ts | 20 ++--- src/compiler/deserialization/ucd-compiler.ts | 4 +- .../deserialization/ucd-support-inset.ts | 5 +- .../deserialization/ucd-support-non-finite.ts | 7 +- .../deserialization/unknown.ucrx.class.ts | 4 +- .../ucc-processor.capability-activation.ts | 2 +- .../ucc-processor.constraint-application.ts | 39 ++------- .../impl/ucc-processor.feature-config.ts | 52 ++++++++++++ .../processor/impl/ucc-processor.profiler.ts | 84 ++++++++++++++++--- src/compiler/processor/mod.ts | 2 +- src/compiler/processor/ucc-capability.ts | 2 +- src/compiler/processor/ucc-config.ts | 15 +++- src/compiler/processor/ucc-processor.spec.ts | 14 ++-- src/compiler/processor/ucc-processor.ts | 55 ++---------- src/compiler/processor/ucc-schema-feature.ts | 48 ----------- .../serialization/ucs-support-bigint.ts | 19 +++-- .../serialization/ucs-support-integer.ts | 9 +- .../serialization/ucs-support-list.ts | 4 +- src/compiler/serialization/ucs-support-map.ts | 5 +- .../serialization/ucs-support-number.ts | 19 +++-- .../serialization/ucs-support-string.ts | 16 ++-- .../validation/ucv-support-numeric-range.ts | 8 +- .../validation/ucv-support-string-length.ts | 8 +- .../validation/ucv-support-string-pattern.ts | 11 +-- src/schema/boolean/uc-boolean.ts | 4 - src/spec/meta-map.entity.ts | 2 +- src/spec/plain.format.ts | 2 +- src/spec/timestamp.format.ts | 10 +-- src/spec/ucc-test-setup.ts | 16 +++- src/spec/write-uc-radix-number.ts | 15 ++-- 36 files changed, 275 insertions(+), 276 deletions(-) create mode 100644 src/compiler/processor/impl/ucc-processor.feature-config.ts delete mode 100644 src/compiler/processor/ucc-schema-feature.ts diff --git a/src/compiler/deserialization/bigint.ucrx.class.ts b/src/compiler/deserialization/bigint.ucrx.class.ts index 56bfb0fe..51fd636d 100644 --- a/src/compiler/deserialization/bigint.ucrx.class.ts +++ b/src/compiler/deserialization/bigint.ucrx.class.ts @@ -9,17 +9,12 @@ import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; export class BigIntUcrxClass extends UcrxClass { - static uccProcess(setup: UcrxSetup): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { setup.useUcrxClass(BigInt, (lib, schema: UcBigInt.Schema) => new this(lib, schema)); }, - }; - } - - static uccProcessSchema(setup: UcrxSetup, schema: UcBigInt.Schema): UccConfig { - return { - configure: variant => { + configureSchema: (schema, variant) => { setup.useUcrxClass( schema, (lib, schema) => new this(lib, schema as UcBigInt.Schema, variant), @@ -31,7 +26,7 @@ export class BigIntUcrxClass extends UcrxClass { - setup.useUcrxClass( - Boolean, - (lib, schema: UcBoolean.Schema) => new this(lib, schema), - ); + setup.useUcrxClass(Boolean, (lib, schema: UcBoolean.Schema) => new this(lib, schema)); }, }; } diff --git a/src/compiler/deserialization/integer.ucrx.class.ts b/src/compiler/deserialization/integer.ucrx.class.ts index 2e94b7d7..55ac8778 100644 --- a/src/compiler/deserialization/integer.ucrx.class.ts +++ b/src/compiler/deserialization/integer.ucrx.class.ts @@ -9,12 +9,9 @@ import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; export class IntegerUcrxClass extends UcrxClass { - static uccProcessSchema( - setup: UcrxSetup, - schema: UcInteger.Schema, - ): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { - configure: variant => { + configureSchema: (schema, variant) => { setup.useUcrxClass( schema, (lib, schema: UcInteger.Schema) => new this(lib, schema, variant), diff --git a/src/compiler/deserialization/list.ucrx.class.ts b/src/compiler/deserialization/list.ucrx.class.ts index d9792fd3..2cab9376 100644 --- a/src/compiler/deserialization/list.ucrx.class.ts +++ b/src/compiler/deserialization/list.ucrx.class.ts @@ -31,9 +31,9 @@ export class ListUcrxClass< TItemModel extends UcModel = UcModel, > extends UcrxClass> { - static uccProcessSchema(setup: UcrxSetup, schema: UcList.Schema): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { - configure: options => { + configureSchema: (schema: UcList.Schema, options) => { setup .processModel(schema.item) .useUcrxClass(schema, (lib, schema: UcList.Schema) => new this(lib, schema, options)); diff --git a/src/compiler/deserialization/map.ucrx.class.ts b/src/compiler/deserialization/map.ucrx.class.ts index 3f1b86e5..99045a18 100644 --- a/src/compiler/deserialization/map.ucrx.class.ts +++ b/src/compiler/deserialization/map.ucrx.class.ts @@ -32,11 +32,11 @@ export class MapUcrxClass< UcMap.Schema > { - static uccProcessSchema(setup: UcrxSetup, schema: UcMap.Schema): UccConfig { - const { entries, extra } = schema; - + static uccProcess(setup: UcrxSetup): UccConfig { return { - configure: variant => { + configureSchema: (schema: UcMap.Schema, variant) => { + const { entries, extra } = schema; + setup.useUcrxClass(schema, (lib, schema: UcMap.Schema) => new this(lib, schema, variant)); for (const entrySchema of Object.values(entries)) { setup.processModel(entrySchema); diff --git a/src/compiler/deserialization/number.ucrx.class.ts b/src/compiler/deserialization/number.ucrx.class.ts index 6cd01951..525ada19 100644 --- a/src/compiler/deserialization/number.ucrx.class.ts +++ b/src/compiler/deserialization/number.ucrx.class.ts @@ -9,17 +9,12 @@ import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; export class NumberUcrxClass extends UcrxClass { - static uccProcess(setup: UcrxSetup): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { setup.useUcrxClass(Number, (lib, schema: UcNumber.Schema) => new this(lib, schema)); }, - }; - } - - static uccProcessSchema(setup: UcrxSetup, schema: UcNumber.Schema): UccConfig { - return { - configure: variant => { + configureSchema: (schema, variant) => { setup.useUcrxClass( schema, (lib, schema: UcNumber.Schema) => new this(lib, schema, variant), @@ -28,7 +23,11 @@ export class NumberUcrxClass extends UcrxClass { - static uccProcess(setup: UcrxSetup): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { configure: () => { setup.useUcrxClass(String, (lib, schema: UcString.Schema) => new this(lib, schema)); }, - }; - } - - static uccProcessSchema( - processor: UcrxSetup, - schema: UcString.Schema, - ): UccConfig { - return { - configure: variant => { - processor.useUcrxClass( + configureSchema: (schema, variant) => { + setup.useUcrxClass( schema, (lib, schema: UcString.Schema) => new this(lib, schema, variant), ); @@ -30,7 +22,11 @@ export class StringUcrxClass extends UcrxClass return this; } - protected override createConfig( + override createConfig( setup: UcdSetup, feature: UccFeature, ): UccConfig { @@ -98,7 +98,7 @@ export class UcdCompiler // Stop registering default handlers. // Start registering custom ones. - defaultConfig.configure(undefined, undefined); + defaultConfig.configure!(undefined, undefined); this.#entities.makeDefault(); this.#formats.makeDefault(); diff --git a/src/compiler/deserialization/ucd-support-inset.ts b/src/compiler/deserialization/ucd-support-inset.ts index 60e27229..3b4c03a1 100644 --- a/src/compiler/deserialization/ucd-support-inset.ts +++ b/src/compiler/deserialization/ucd-support-inset.ts @@ -1,15 +1,14 @@ import { esImport, esMemberAccessor, esline } from 'esgen'; import { UcPresentationName } from '../../schema/uc-presentations.js'; -import { UcSchema } from '../../schema/uc-schema.js'; import { UC_TOKEN_INSET_URI_PARAM } from '../../syntax/uc-token.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore$stubBody } from '../rx/impl/ucrx-core.stub.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxSetup } from '../rx/ucrx-setup.js'; -export function ucdSupportInset(setup: UcrxSetup, schema: UcSchema): UccConfig { +export function ucdSupportInset(setup: UcrxSetup): UccConfig { return { - configure({ lexer, from, method, args }) { + configureSchema(schema, { lexer, from, method, args }) { const within = setup.currentPresentation; setup diff --git a/src/compiler/deserialization/ucd-support-non-finite.ts b/src/compiler/deserialization/ucd-support-non-finite.ts index 9129b245..831fde90 100644 --- a/src/compiler/deserialization/ucd-support-non-finite.ts +++ b/src/compiler/deserialization/ucd-support-non-finite.ts @@ -1,3 +1,4 @@ +import { ucdInfinity, ucdNaN, ucdNegativeInfinity } from '../../deserializer/ucd-non-finite.js'; import { UC_MODULE_DESERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcdHandlerFeature, UcdHandlerSetup } from './ucd-handler-feature.js'; @@ -7,9 +8,9 @@ export function ucdSupportNonFinite(setup: UcdSetup): UccConfig { return { configure() { setup - .handleEntity('Infinity', handleUcdNonFinite('ucdInfinity')) - .handleEntity('-Infinity', handleUcdNonFinite('ucdNegativeInfinity')) - .handleEntity('NaN', handleUcdNonFinite('ucdNaN')); + .handleEntity('Infinity', handleUcdNonFinite(ucdInfinity.name)) + .handleEntity('-Infinity', handleUcdNonFinite(ucdNegativeInfinity.name)) + .handleEntity('NaN', handleUcdNonFinite(ucdNaN.name)); }, }; } diff --git a/src/compiler/deserialization/unknown.ucrx.class.ts b/src/compiler/deserialization/unknown.ucrx.class.ts index 1a722095..469c549c 100644 --- a/src/compiler/deserialization/unknown.ucrx.class.ts +++ b/src/compiler/deserialization/unknown.ucrx.class.ts @@ -24,9 +24,9 @@ import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js'; export class UnknownUcrxClass extends UcrxClass { - static uccProcessSchema(setup: UcrxSetup, schema: UcSchema): UccConfig { + static uccProcess(setup: UcrxSetup): UccConfig { return { - configure: () => { + configureSchema: schema => { setup .useUcrxClass('unknown', (lib, schema) => new this(lib, schema)) .processModel(this.listSchemaFor(schema)) diff --git a/src/compiler/processor/impl/ucc-processor.capability-activation.ts b/src/compiler/processor/impl/ucc-processor.capability-activation.ts index 1972517f..02adef66 100644 --- a/src/compiler/processor/impl/ucc-processor.capability-activation.ts +++ b/src/compiler/processor/impl/ucc-processor.capability-activation.ts @@ -13,7 +13,7 @@ export class UccProcessor$CapabilityActivation(feature: UccFeature, options?: TOptions): this { - this.#profiler.enable(feature, options!); + this.#profiler.addFeature(feature, options!); return this; } diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts index 2206e5ee..8ba52bc6 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-application.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -5,7 +5,6 @@ import { UcPresentationName } from '../../../schema/uc-presentations.js'; import { UcSchema } from '../../../schema/uc-schema.js'; import { UccCapability } from '../ucc-capability.js'; import { UccFeature } from '../ucc-feature.js'; -import { UccSchemaFeature } from '../ucc-schema-feature.js'; import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; @@ -68,43 +67,19 @@ export class UccProcessor$ConstraintApplication } = await import(from); - const { - [use]: constraint, - }: { [name: string]: UccFeature | UccSchemaFeature } = - await import(from); - - if (mayHaveProperties(constraint)) { - let configured = false; - - if ('uccProcess' in constraint) { - this.setup.enable(constraint, options); - configured = true; - } - if ('uccProcessSchema' in constraint) { - constraint.uccProcessSchema(this.setup, this.schema).configure(options, data); - configured = true; - } - - if (configured) { - return; - } - - if (typeof constraint === 'function') { - constraint(this.setup, this.schema).configure(options, data); - - return; - } - } - - if (constraint === undefined) { + if ((mayHaveProperties(feature) && 'uccProcess' in feature) || typeof feature === 'function') { + this.#profiler.enableSchema(schema, this.#config, feature, options, data); + } else if (feature === undefined) { throw new ReferenceError(`No such schema processing feature: ${this}`); + } else { + throw new ReferenceError(`Not a schema processing feature: ${this}`); } - - throw new ReferenceError(`Not a schema processing feature: ${this}`); } ignore(): void { diff --git a/src/compiler/processor/impl/ucc-processor.feature-config.ts b/src/compiler/processor/impl/ucc-processor.feature-config.ts new file mode 100644 index 00000000..d53d3ea5 --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.feature-config.ts @@ -0,0 +1,52 @@ +import { lazyValue } from '@proc7ts/primitives'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccConfig } from '../ucc-config.js'; +import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; +import { UccProcessor$Current } from './ucc-processor.current.js'; +import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; + +export class UccProcessor$FeatureConfig, in TOptions = never> { + + readonly #getConfig: () => UccConfig; + #featureConfigured = false; + + constructor(getConfig: () => UccConfig) { + this.#getConfig = lazyValue(getConfig); + } + + configureFeature( + profiler: UccProcessor$Profiler, + options: TOptions, + data: unknown, + ): void { + this.#configureFeature(profiler, {}, options, data); + } + + configureSchema( + profiler: UccProcessor$Profiler, + schema: UcSchema, + constraint: UccProcessor$ConstraintConfig, + options: TOptions, + data: unknown, + ): void { + if (!this.#featureConfigured) { + this.#configureFeature(profiler, { processor: constraint.processor }, undefined!, undefined); + } + + profiler.configureSync({ ...constraint, schema }, () => { + this.#getConfig().configureSchema?.(schema, options, data); + }); + } + + #configureFeature( + profiler: UccProcessor$Profiler, + current: UccProcessor$Current, + options: TOptions, + data: unknown, + ): void { + this.#featureConfigured = true; + profiler.configureSync(current, () => this.#getConfig().configure?.(options, data)); + } + +} diff --git a/src/compiler/processor/impl/ucc-processor.profiler.ts b/src/compiler/processor/impl/ucc-processor.profiler.ts index 0eb1abf8..b6b3471f 100644 --- a/src/compiler/processor/impl/ucc-processor.profiler.ts +++ b/src/compiler/processor/impl/ucc-processor.profiler.ts @@ -8,31 +8,91 @@ import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$ConstraintApplication } from './ucc-processor.constraint-application.js'; import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; import { UccProcessor$Current } from './ucc-processor.current.js'; +import { UccProcessor$FeatureConfig } from './ucc-processor.feature-config.js'; export class UccProcessor$Profiler> { readonly #processor: UccProcessor; readonly #init: ((setup: TSetup) => void)[] = []; - readonly #configure: ( - current: UccProcessor$Current, - action: () => Promise, - ) => Promise; - + readonly #configs = new Map, UccProcessor$FeatureConfig>(); readonly #handlers = new Map>(); - constructor( - processor: UccProcessor, - configure: (current: UccProcessor$Current, action: () => Promise) => Promise, - ) { + #current: UccProcessor$Current = {}; + + constructor(processor: UccProcessor) { this.#processor = processor; - this.#configure = configure; } get setup(): TSetup { return this.#processor.setup; } - enable(feature: UccFeature, options: TOptions): void { + get current(): UccProcessor$Current { + return this.#current; + } + + enableFeature( + feature: UccFeature, + options: TOptions, + data: unknown, + ): void { + this.#featureConfig(feature).configureFeature(this, options, data); + } + + enableSchema( + schema: UcSchema, + constraint: UccProcessor$ConstraintConfig, + feature: UccFeature, + options: TOptions, + data: unknown, + ): void { + this.#featureConfig(feature).configureSchema(this, schema, constraint, options, data); + } + + #featureConfig( + feature: UccFeature, + ): UccProcessor$FeatureConfig { + let config = this.#configs.get(feature) as + | UccProcessor$FeatureConfig + | undefined; + + if (!config) { + config = new UccProcessor$FeatureConfig(() => this.#processor.createConfig(this.setup, feature)); + this.#configs.set(feature, config); + } + + return config; + } + + configureSync(current: UccProcessor$Current, action: () => void): void { + const prev = this.#pushCurrent(current); + + try { + action(); + } finally { + this.#current = prev; + } + } + + async configureAsync(current: UccProcessor$Current, action: () => Promise): Promise { + const prev = this.#pushCurrent(current); + + try { + await action(); + } finally { + this.#current = prev; + } + } + + #pushCurrent(current: UccProcessor$Current): UccProcessor$Current { + const prev = this.#current; + + this.#current = current.processor ? current : { ...current, processor: prev.processor }; + + return prev; + } + + addFeature(feature: UccFeature, options: TOptions): void { this.#init.push(setup => setup.enable(feature, options)); } @@ -65,7 +125,7 @@ export class UccProcessor$Profiler> { const application = new UccProcessor$ConstraintApplication(this, schema, config); const { processor, within, constraint } = config; - await this.#configure({ processor, schema, within, constraint }, async () => { + await this.configureAsync({ processor, schema, within, constraint }, async () => { await this.#findHandler(processor, within, constraint)?.(application); if (within) { // Apply any presentation handler. diff --git a/src/compiler/processor/mod.ts b/src/compiler/processor/mod.ts index b17a9217..f7462385 100644 --- a/src/compiler/processor/mod.ts +++ b/src/compiler/processor/mod.ts @@ -1,6 +1,6 @@ +export * from './ucc-capability.js'; export * from './ucc-config.js'; export * from './ucc-feature.js'; export * from './ucc-processor.js'; -export * from './ucc-schema-feature.js'; export * from './ucc-schema-index.js'; export * from './ucc-setup.js'; diff --git a/src/compiler/processor/ucc-capability.ts b/src/compiler/processor/ucc-capability.ts index cc546073..e17d1bb8 100644 --- a/src/compiler/processor/ucc-capability.ts +++ b/src/compiler/processor/ucc-capability.ts @@ -7,7 +7,7 @@ import { UccSetup } from './ucc-setup.js'; /** * Schema {@link UccProcessor processing} capability. * - * Called by {@lint UccProcessorInit#capabilities schema processor} to activate the capability. + * Called by {@link UccProcessor.Options#capabilities schema processor} to activate the capability. * * Capabilities used e.g. to refine {@link churi!UcConstraints schema constraints}, or to enable * {@link UccFeature processing features}. diff --git a/src/compiler/processor/ucc-config.ts b/src/compiler/processor/ucc-config.ts index 7445f911..e294f7e3 100644 --- a/src/compiler/processor/ucc-config.ts +++ b/src/compiler/processor/ucc-config.ts @@ -1,3 +1,5 @@ +import { UcSchema } from '../../schema/uc-schema.js'; + /** * Schema processing configuration. * @@ -14,5 +16,16 @@ export interface UccConfig { * @param options - Configuration options. * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. */ - configure(options: TOptions, data: unknown): void; + configure?(options: TOptions, data: unknown): void; + + /** + * Configures processing of concrete `schema`. + * + * May be called multiple times. + * + * @param schema - Schema which processing + * @param options - Configuration options. + * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. + */ + configureSchema?(schema: UcSchema, options: TOptions, data: unknown): void; } diff --git a/src/compiler/processor/ucc-processor.spec.ts b/src/compiler/processor/ucc-processor.spec.ts index fe50a663..9c89d60c 100644 --- a/src/compiler/processor/ucc-processor.spec.ts +++ b/src/compiler/processor/ucc-processor.spec.ts @@ -6,7 +6,7 @@ import { recordUcTestData, ucTestRecord, ucTestSubRecord, - ucTestSupportRecord, + ucTestSupportSchemaRecord, } from '../../spec/ucc-test-setup.js'; import { UccProcessor } from './ucc-processor.js'; @@ -140,7 +140,7 @@ describe('UccProcessor', () => { activation.onConstraint( { processor: 'deserializer', - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, }, async application => { @@ -176,7 +176,7 @@ describe('UccProcessor', () => { activation.onConstraint( { processor: 'deserializer', - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -200,7 +200,7 @@ describe('UccProcessor', () => { activation.onConstraint( { processor: 'deserializer', - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -225,7 +225,7 @@ describe('UccProcessor', () => { { processor: 'deserializer', within: 'charge', - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -250,7 +250,7 @@ describe('UccProcessor', () => { .onConstraint( { processor: 'deserializer', - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -266,7 +266,7 @@ describe('UccProcessor', () => { .onConstraint( { processor: 'deserializer', - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, }, ({ setup }) => { diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index d7889ba0..2fcb0d90 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -8,7 +8,6 @@ import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccProcessor$CapabilityActivation } from './impl/ucc-processor.capability-activation.js'; import { UccProcessor$ConstraintConfig } from './impl/ucc-processor.constraint-config.js'; -import { UccProcessor$Current } from './impl/ucc-processor.current.js'; import { UccProcessor$FeatureUse } from './impl/ucc-processor.feature-use.js'; import { UccProcessor$Profiler } from './impl/ucc-processor.profiler.js'; import { UccCapability } from './ucc-capability.js'; @@ -32,13 +31,10 @@ export abstract class UccProcessor> readonly #models: readonly UcModel[] | undefined; readonly #features: readonly UccFeature[] | undefined; readonly #getSetup = lazyValue(() => this.createSetup()); - readonly #profiler = new UccProcessor$Profiler(this, this.#configureAsync.bind(this)); - - readonly #configs = new Map, () => UccConfig>(); + readonly #profiler = new UccProcessor$Profiler(this); readonly #uses = new Map>(); #hasPendingInstructions = false; - #current: UccProcessor$Current = {}; /** * Constructs schema processor. @@ -75,19 +71,19 @@ export abstract class UccProcessor> } get currentProcessor(): UcProcessorName | undefined { - return this.#current.processor; + return this.#profiler.current.processor; } get currentSchema(): UcSchema | undefined { - return this.#current.schema; + return this.#profiler.current.schema; } get currentPresentation(): UcPresentationName | undefined { - return this.#current.within; + return this.#profiler.current.within; } get currentConstraint(): UcFeatureConstraint | undefined { - return this.#current.constraint; + return this.#profiler.current.constraint; } enable( @@ -95,46 +91,11 @@ export abstract class UccProcessor> options?: TOptions, data?: unknown, ): this { - let getConfig = this.#configs.get(feature) as (() => UccConfig) | undefined; - - if (!getConfig) { - getConfig = lazyValue(() => this.createConfig(this.setup, feature)); - this.#configs.set(feature, getConfig); - } - - this.#configureSync({}, () => getConfig!().configure(options!, data)); + this.#profiler.enableFeature(feature, options!, data); return this; } - #configureSync(current: UccProcessor$Current, action: () => void): void { - const prev = this.#pushCurrent(current); - - try { - action(); - } finally { - this.#current = prev; - } - } - - async #configureAsync(current: UccProcessor$Current, action: () => Promise): Promise { - const prev = this.#pushCurrent(current); - - try { - await action(); - } finally { - this.#current = prev; - } - } - - #pushCurrent(current: UccProcessor$Current): UccProcessor$Current { - const prev = this.#current; - - this.#current = current.processor ? current : { ...current, processor: prev.processor }; - - return prev; - } - processModel(model: UcModel, data?: unknown): this { const schema = ucSchema(model); @@ -220,14 +181,14 @@ export abstract class UccProcessor> protected abstract createSetup(): TSetup; /** - * Creates schema processing configuration for just {@link enable enabled} `feature`. + * Creates schema processing feature configuration for just {@link enable enabled} `feature`. * * @param setup - Schema processing setup. * @param feature - Enabled feature. * * @returns Schema processing configuration. */ - protected createConfig( + createConfig( setup: TSetup, feature: UccFeature, ): UccConfig { diff --git a/src/compiler/processor/ucc-schema-feature.ts b/src/compiler/processor/ucc-schema-feature.ts deleted file mode 100644 index 2f0c6512..00000000 --- a/src/compiler/processor/ucc-schema-feature.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { UcSchema } from '../../schema/uc-schema.js'; -import { UccConfig } from './ucc-config.js'; -import { UccSetup } from './ucc-setup.js'; - -/** - * Schema-specific processing feature. - * - * Can be enabled by {@link churi!UcConstraints schema constraints}. - * - * @typeParam TSetup - Schema processing setup type. - * @typeParam TOptions - Type of schema processing options. - */ -export type UccSchemaFeature, TOptions = unknown> = - | UccSchemaFeature.Object - | UccSchemaFeature.Function; - -export namespace UccSchemaFeature { - /** - * Schema-specific processing feature interface. - * - * @typeParam TSetup - Schema processing setup type. - * @typeParam TOptions - Type of schema processing options. - */ - export interface Object, in TOptions = unknown> { - /** - * Enables particular `schema` processing. - * - * Called when feature enabled in processor, at most once per schema per processor. - * - * @param setup - Target schema processing setup. - * @param schema - Schema to process. - * - * @returns Configuration of schema processing. - */ - uccProcessSchema(setup: TSetup, schema: UcSchema): UccConfig; - } - - /** - * Schema-specific processing feature signature. - * - * @typeParam TSetup - Schema processing setup type. - * @typeParam TOptions - Type of schema processing options. - */ - export type Function< - TSetup extends UccSetup, - TOptions = unknown, - > = UccSchemaFeature.Object['uccProcessSchema']; -} diff --git a/src/compiler/serialization/ucs-support-bigint.ts b/src/compiler/serialization/ucs-support-bigint.ts index d89435d5..293ad5d1 100644 --- a/src/compiler/serialization/ucs-support-bigint.ts +++ b/src/compiler/serialization/ucs-support-bigint.ts @@ -1,17 +1,22 @@ import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; -import { UcDataType } from '../../schema/uc-schema.js'; +import { UcSchema } from '../../schema/uc-schema.js'; import { UccConfig } from '../processor/ucc-config.js'; import { ucsFormatBigInt } from './impl/ucs-format-bigint.js'; import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportBigInt( - setup: UcsSetup, - target: UcBigInt.Schema | UcDataType = BigInt, -): UccConfig { +export function ucsSupportBigInt(setup: UcsSetup): UccConfig { + const configureSchema = ( + target: UcSchema | typeof BigInt, + variant?: UcBigInt.Variant | void, + ): void => { + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatBigInt(variant))); + }; + return { - configure(variant) { - setup.formatWith('charge', target, ucsFormatCharge(ucsFormatBigInt(variant))); + configure() { + configureSchema(BigInt, undefined); }, + configureSchema, }; } diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-support-integer.ts index 23e30104..4f647bba 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-support-integer.ts @@ -4,13 +4,10 @@ import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatInteger } from './impl/ucs-format-integer.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportInteger( - setup: UcsSetup, - target: UcInteger.Schema, -): UccConfig { +export function ucsSupportInteger(setup: UcsSetup): UccConfig { return { - configure(variant) { - setup.formatWith('charge', target, ucsFormatCharge(ucsFormatInteger(variant))); + configureSchema(schema: UcInteger.Schema, variant) { + setup.formatWith('charge', schema, ucsFormatCharge(ucsFormatInteger(variant))); }, }; } diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-support-list.ts index 95e43301..fc68dac7 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-support-list.ts @@ -12,9 +12,9 @@ import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportList(setup: UcsSetup, schema: UcList.Schema): UccConfig { +export function ucsSupportList(setup: UcsSetup): UccConfig { return { - configure(options) { + configureSchema(schema: UcList.Schema, options) { setup.processModel(schema.item).formatWith( 'charge', schema, diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-support-map.ts index 66ff9582..056f8c6a 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-support-map.ts @@ -21,11 +21,12 @@ import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportMap(setup: UcsSetup, schema: UcMap.Schema): UccConfig; -export function ucsSupportMap(setup: UcsSetup, { entries, extra }: UcMap.Schema): UccConfig { +export function ucsSupportMap(setup: UcsSetup): UccConfig { return { configure() { setup.formatWith('charge', 'map', ucsFormatCharge(ucsWriteMap)); + }, + configureSchema({ entries, extra }: UcMap.Schema) { Object.values(entries).forEach(entrySchema => setup.processModel(entrySchema)); // istanbul ignore next if (extra) { diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-support-number.ts index 7029568b..cb810820 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-support-number.ts @@ -1,17 +1,22 @@ import { UcNumber } from '../../schema/numeric/uc-number.js'; -import { UcDataType } from '../../schema/uc-schema.js'; +import { UcSchema } from '../../schema/uc-schema.js'; import { UccConfig } from '../processor/ucc-config.js'; import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatNumber } from './impl/ucs-format-number.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportNumber( - setup: UcsSetup, - target: UcNumber.Schema | UcDataType = Number, -): UccConfig { +export function ucsSupportNumber(setup: UcsSetup): UccConfig { + const configureSchema = ( + target: UcSchema | typeof Number, + variant?: UcNumber.Variant | void, + ): void => { + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatNumber(variant))); + }; + return { - configure(variant) { - setup.formatWith('charge', target, ucsFormatCharge(ucsFormatNumber(variant))); + configure() { + configureSchema(Number); }, + configureSchema, }; } diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-support-string.ts index afe9badb..358c734b 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-support-string.ts @@ -1,17 +1,19 @@ import { UcString } from '../../schema/string/uc-string.js'; -import { UcDataType } from '../../schema/uc-schema.js'; +import { UcSchema } from '../../schema/uc-schema.js'; import { UccConfig } from '../processor/ucc-config.js'; import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatString } from './impl/ucs-format-string.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportString( - setup: UcsSetup, - target: UcString.Schema | UcDataType = String, -): UccConfig { +export function ucsSupportString(setup: UcsSetup): UccConfig { + const configureSchema = (target: UcSchema | typeof String, variant?: UcString.Variant): void => { + setup.formatWith('charge', target, ucsFormatCharge(ucsFormatString(variant))); + }; + return { - configure(variant?: UcString.Variant) { - setup.formatWith('charge', target, ucsFormatCharge(ucsFormatString(variant))); + configure() { + configureSchema(String); }, + configureSchema, }; } diff --git a/src/compiler/validation/ucv-support-numeric-range.ts b/src/compiler/validation/ucv-support-numeric-range.ts index 84d47918..1b9d2c4f 100644 --- a/src/compiler/validation/ucv-support-numeric-range.ts +++ b/src/compiler/validation/ucv-support-numeric-range.ts @@ -1,5 +1,4 @@ import { esStringLiteral, esline } from 'esgen'; -import { UcSchema } from '../../schema/uc-schema.js'; import { UC_MODULE_VALIDATOR } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; @@ -13,12 +12,9 @@ export type UcvNumericRange = [ or?: string | undefined, ]; -export function ucvSupportNumericRange( - setup: UcrxSetup, - schema: UcSchema, -): UccConfig { +export function ucvSupportNumericRange(setup: UcrxSetup): UccConfig { return { - configure([constraint, than, or]) { + configureSchema(schema, [constraint, than, or]) { let setter: UcrxSetter; let bound: string; diff --git a/src/compiler/validation/ucv-support-string-length.ts b/src/compiler/validation/ucv-support-string-length.ts index f46ca428..14a0e527 100644 --- a/src/compiler/validation/ucv-support-string-length.ts +++ b/src/compiler/validation/ucv-support-string-length.ts @@ -1,5 +1,4 @@ import { esStringLiteral, esline } from 'esgen'; -import { UcSchema } from '../../schema/uc-schema.js'; import { UC_MODULE_VALIDATOR } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; @@ -12,12 +11,9 @@ export type UcvStringLength = [ or?: string | undefined, ]; -export function ucvSupportStringLength( - setup: UcrxSetup, - schema: UcSchema, -): UccConfig { +export function ucvSupportStringLength(setup: UcrxSetup): UccConfig { return { - configure([constraint, than, or]) { + configureSchema(schema, [constraint, than, or]) { setup.modifyUcrxMethod(schema, UcrxCore.str, { before({ member: { args } }) { return ucvValidate(args, ({ value, reject }) => code => { diff --git a/src/compiler/validation/ucv-support-string-pattern.ts b/src/compiler/validation/ucv-support-string-pattern.ts index a35996c0..0486b806 100644 --- a/src/compiler/validation/ucv-support-string-pattern.ts +++ b/src/compiler/validation/ucv-support-string-pattern.ts @@ -1,5 +1,5 @@ import { esStringLiteral, esline } from 'esgen'; -import { UcSchema } from '../../schema/uc-schema.js'; +import { ucvViolateItMatches } from '../../validator/ucv-string-pattern.violation.js'; import { UC_MODULE_VALIDATOR } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcrxCore } from '../rx/ucrx-core.js'; @@ -8,18 +8,15 @@ import { ucvValidate } from './ucv-validate.js'; export type UcvStringPattern = [match: RegExp, or?: string | undefined]; -export function ucvSupportStringPattern( - setup: UcrxSetup, - schema: UcSchema, -): UccConfig { +export function ucvSupportStringPattern(setup: UcrxSetup): UccConfig { return { - configure([match, or]) { + configureSchema(schema, [match, or]) { setup.modifyUcrxMethod(schema, UcrxCore.str, { before({ member: { args } }) { return ucvValidate(args, ({ value, reject }) => code => { const pattern = String(match); const message = or != null ? `, ${esStringLiteral(or)}` : ''; - const ucvReject = UC_MODULE_VALIDATOR.import(`ucvViolateItMatches`); + const ucvReject = UC_MODULE_VALIDATOR.import(ucvViolateItMatches.name); code .write(esline`if (!${pattern}.test(${value})) {`) diff --git a/src/schema/boolean/uc-boolean.ts b/src/schema/boolean/uc-boolean.ts index 9977f378..4a88ca94 100644 --- a/src/schema/boolean/uc-boolean.ts +++ b/src/schema/boolean/uc-boolean.ts @@ -11,10 +11,6 @@ export namespace UcBoolean { * * Boolean schema is created automatically when [Boolean] constructor is used as model. * - * BigInt values may be represented with and without `0n` prefix. This differs from schema-less processing, - * where `0n` prefix is required for BigInt values. When serializing, the `0n` prefix is either added or not - * according to {@link Variant#number number processing policy}. - * * [Boolean]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean/Boolean */ export interface Schema extends UcSchema { diff --git a/src/spec/meta-map.entity.ts b/src/spec/meta-map.entity.ts index c3917bdd..749f2eae 100644 --- a/src/spec/meta-map.entity.ts +++ b/src/spec/meta-map.entity.ts @@ -29,7 +29,7 @@ export function ucdSupportMetaMapEntity(setup: UcdSetup): UccConfig { return { configure() { setup.handleEntity('meta-map', ({ register }) => code => { - code.write(register(UC_MODULE_SPEC.import('readMetaMap'))); + code.write(register(UC_MODULE_SPEC.import(readMetaMap.name))); }); }, }; diff --git a/src/spec/plain.format.ts b/src/spec/plain.format.ts index bc6239a5..de0f8c93 100644 --- a/src/spec/plain.format.ts +++ b/src/spec/plain.format.ts @@ -19,7 +19,7 @@ export function ucdSupportPlainEntity(setup: UcdSetup): UccConfig { return { configure() { setup.handleFormat('plain', ({ register }) => code => { - code.write(register(UC_MODULE_SPEC.import('readPlainFormat'))); + code.write(register(UC_MODULE_SPEC.import(readPlainFormat.name))); }); }, }; diff --git a/src/spec/timestamp.format.ts b/src/spec/timestamp.format.ts index 01d5babd..3dd9a945 100644 --- a/src/spec/timestamp.format.ts +++ b/src/spec/timestamp.format.ts @@ -3,12 +3,12 @@ import { UcdSetup } from '../compiler/deserialization/ucd-setup.js'; import { UC_MODULE_CHURI } from '../compiler/impl/uc-modules.js'; import { UccConfig } from '../compiler/processor/ucc-config.js'; import { UccFeature } from '../compiler/processor/ucc-feature.js'; -import { UccSchemaFeature } from '../compiler/processor/ucc-schema-feature.js'; import { UcrxCore } from '../compiler/rx/ucrx-core.js'; import { UcrxLib } from '../compiler/rx/ucrx-lib.js'; import { UcrxSetter } from '../compiler/rx/ucrx-setter.js'; import { UcrxClass } from '../compiler/rx/ucrx.class.js'; import { UcSchema } from '../schema/uc-schema.js'; +import { printUcTokens } from '../syntax/print-uc-token.js'; export const TimestampUcrxMethod = new UcrxSetter('date', { stub: { @@ -46,7 +46,7 @@ const readTimestampEntityFn = new EsFunction( return (code, scope) => { const lib = scope.get(UcrxLib); const date = new EsVarSymbol('date'); - const printTokens = UC_MODULE_CHURI.import('printUcTokens'); + const printTokens = UC_MODULE_CHURI.import(printUcTokens.name); const setDate = lib.baseUcrx.member(TimestampUcrxMethod); code @@ -82,10 +82,10 @@ export const UcdSupportTimestamp: UccFeature.Object = { }, }; -export const UcdSupportTimestampSchema: UccSchemaFeature.Object = { - uccProcessSchema(setup, _schema) { +export const UcdSupportTimestampSchema: UccFeature.Object = { + uccProcess(setup) { return { - configure() { + configureSchema() { setup.enable(UcdSupportTimestamp); }, }; diff --git a/src/spec/ucc-test-setup.ts b/src/spec/ucc-test-setup.ts index 679c7f7c..4e07d83a 100644 --- a/src/spec/ucc-test-setup.ts +++ b/src/spec/ucc-test-setup.ts @@ -12,14 +12,22 @@ export const WrongFeature = 'WrongFeature'; export function ucTestRecord(options?: unknown): UcOmniConstraints { return { deserializer: { - use: ucTestSupportRecord.name, + use: ucTestSupportSchemaRecord.name, from: SPEC_MODULE, with: options, }, }; } -export function ucTestSupportRecord(setup: UccTestSetup): UccConfig { +export function ucTestSupportSchemaRecord(setup: UccTestSetup): UccConfig { + return { + configureSchema(_, options) { + recordUcTestData(setup, options); + }, + }; +} + +export function ucTestSupportFeatureRecord(setup: UccTestSetup): UccConfig { return { configure(options) { recordUcTestData(setup, options); @@ -39,8 +47,8 @@ export function ucTestSubRecord(options?: unknown): UcOmniConstraints { export function ucTestSupportSubRecord(setup: UccTestSetup): UccConfig { return { - configure(options) { - setup.enable(ucTestSupportRecord, options); + configureSchema(_, options) { + setup.enable(ucTestSupportFeatureRecord, options); }, }; } diff --git a/src/spec/write-uc-radix-number.ts b/src/spec/write-uc-radix-number.ts index fc88825d..5207432a 100644 --- a/src/spec/write-uc-radix-number.ts +++ b/src/spec/write-uc-radix-number.ts @@ -1,7 +1,6 @@ import { esline } from 'esgen'; import { UC_MODULE_SPEC } from '../compiler/impl/uc-modules.js'; import { UccFeature } from '../compiler/processor/ucc-feature.js'; -import { UccSchemaFeature } from '../compiler/processor/ucc-schema-feature.js'; import { UcsSetup } from '../compiler/serialization/ucs-setup.js'; import { UcSchema } from '../schema/uc-schema.js'; import { ucsWriteAsIs } from '../serializer/ucs-write-asis.js'; @@ -18,7 +17,7 @@ export const UcsSupportNumberWithRadix: UccFeature.Object = { return { configure() { compiler.formatWith('charge', Number, ({ writer, value }) => { - const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); + const write = UC_MODULE_SPEC.import(writeUcRadixNumber.name); return esline`await ${write}(${writer}, ${value});`; }); @@ -32,7 +31,7 @@ export const UcsSupportRadixNumber: UccFeature.Object = { return { configure() { compiler.formatWith('charge', 'radixNumber', ({ writer, value }) => { - const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); + const write = UC_MODULE_SPEC.import(writeUcRadixNumber.name); return esline`await ${write}(${writer}, ${value});`; }); @@ -41,12 +40,12 @@ export const UcsSupportRadixNumber: UccFeature.Object = { }, }; -export const UcsSupportRadixNumberSchema: UccSchemaFeature.Object = { - uccProcessSchema(compiler, schema: UcSchema) { +export const UcsSupportRadixNumberSchema: UccFeature.Object = { + uccProcess(setup) { return { - configure() { - compiler.formatWith('charge', schema.type, ({ writer, value }) => { - const write = UC_MODULE_SPEC.import('writeUcRadixNumber'); + configureSchema(schema: UcSchema) { + setup.formatWith('charge', schema.type, ({ writer, value }) => { + const write = UC_MODULE_SPEC.import(writeUcRadixNumber.name); return esline`await ${write}(${writer}, ${value});`; }); From c3e2d13978a293acb4593425be9ac3bbee96ae48 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 16:50:20 +0700 Subject: [PATCH 21/36] Use "process" as feature prefix --- build/emit-exports.js | 4 ++-- .../impl/uri-charge.compiler.ts | 9 +++++---- src/compiler/deserialization/mod.ts | 8 ++++---- .../deserialization/ucd-compiler.spec.ts | 16 +++++++-------- src/compiler/deserialization/ucd-compiler.ts | 8 ++++---- .../deserialization/ucd-process-defaults.ts | 12 +++++++++++ ...-support-inset.ts => ucd-process-inset.ts} | 2 +- ...on-finite.ts => ucd-process-non-finite.ts} | 2 +- ...rimitives.ts => ucd-process-primitives.ts} | 2 +- .../deserialization/ucd-support-defaults.ts | 12 ----------- src/compiler/processor/ucc-processor.spec.ts | 14 ++++++------- src/compiler/rx/ucrx-setter.spec.ts | 4 ++-- src/compiler/serialization/mod.ts | 20 +++++++++---------- .../serialization/ucs-compiler.spec.ts | 12 +++++------ src/compiler/serialization/ucs-compiler.ts | 4 ++-- .../serialization/ucs-enable-plain-text.ts | 20 +++++++++---------- .../serialization/ucs-enable-uri-encoded.ts | 20 +++++++++---------- ...upport-bigint.ts => ucs-process-bigint.ts} | 2 +- ...port-boolean.ts => ucs-process-boolean.ts} | 2 +- .../serialization/ucs-process-defaults.ts | 11 ++++++++++ ...port-integer.ts => ucs-process-integer.ts} | 2 +- ...cs-support-list.ts => ucs-process-list.ts} | 2 +- ...{ucs-support-map.ts => ucs-process-map.ts} | 2 +- ...upport-number.ts => ucs-process-number.ts} | 2 +- .../serialization/ucs-process-primitives.ts | 18 +++++++++++++++++ ...upport-string.ts => ucs-process-string.ts} | 2 +- ...port-unknown.ts => ucs-process-unknown.ts} | 2 +- .../serialization/ucs-support-defaults.ts | 11 ---------- .../serialization/ucs-support-primitives.ts | 18 ----------------- src/compiler/validation/mod.ts | 6 +++--- ...-range.ts => ucv-process-numeric-range.ts} | 2 +- ...length.ts => ucv-process-string-length.ts} | 2 +- ...ttern.ts => ucv-process-string-pattern.ts} | 2 +- .../entity/uc-entity.deserializer.spec.ts | 6 +++--- .../entity/uc-formatted.deserializer.spec.ts | 18 ++++++++--------- src/schema/list/uc-list.impl.ts | 2 +- src/schema/map/uc-map.ts | 2 +- src/schema/meta/uc-meta.deserializer.spec.ts | 8 ++++---- src/schema/numeric/uc-bigint.ts | 2 +- src/schema/numeric/uc-integer.ts | 2 +- .../numeric/uc-number.deserializer.spec.ts | 8 ++++---- src/schema/numeric/uc-number.ts | 2 +- .../numeric/uc-numeric-range.validator.ts | 4 ++-- .../string/uc-string-length.validator.ts | 4 ++-- .../string/uc-string-pattern.validator.ts | 4 ++-- src/schema/string/uc-string.ts | 2 +- .../unknown/uc-unknown.deserializer.spec.ts | 8 ++++---- src/schema/unknown/uc-unknown.ts | 2 +- src/spec/meta-map.entity.ts | 2 +- src/spec/plain.format.ts | 2 +- src/spec/timestamp.format.ts | 18 ++++++++--------- src/spec/ucc-test-setup.ts | 12 +++++------ src/spec/write-uc-radix-number.ts | 6 +++--- src/syntax/formats/charge/uc-charge.lexer.ts | 4 ++-- .../formats/plain-text/uc-plain-text.lexer.ts | 4 ++-- .../uri-encoded/uc-uri-encoded.lexer.ts | 4 ++-- .../formats/uri-params/uc-uri-params.lexer.ts | 4 ++-- 57 files changed, 193 insertions(+), 192 deletions(-) create mode 100644 src/compiler/deserialization/ucd-process-defaults.ts rename src/compiler/deserialization/{ucd-support-inset.ts => ucd-process-inset.ts} (96%) rename src/compiler/deserialization/{ucd-support-non-finite.ts => ucd-process-non-finite.ts} (93%) rename src/compiler/deserialization/{ucd-support-primitives.ts => ucd-process-primitives.ts} (89%) delete mode 100644 src/compiler/deserialization/ucd-support-defaults.ts rename src/compiler/serialization/{ucs-support-bigint.ts => ucs-process-bigint.ts} (91%) rename src/compiler/serialization/{ucs-support-boolean.ts => ucs-process-boolean.ts} (84%) create mode 100644 src/compiler/serialization/ucs-process-defaults.ts rename src/compiler/serialization/{ucs-support-integer.ts => ucs-process-integer.ts} (88%) rename src/compiler/serialization/{ucs-support-list.ts => ucs-process-list.ts} (98%) rename src/compiler/serialization/{ucs-support-map.ts => ucs-process-map.ts} (98%) rename src/compiler/serialization/{ucs-support-number.ts => ucs-process-number.ts} (91%) create mode 100644 src/compiler/serialization/ucs-process-primitives.ts rename src/compiler/serialization/{ucs-support-string.ts => ucs-process-string.ts} (91%) rename src/compiler/serialization/{ucs-support-unknown.ts => ucs-process-unknown.ts} (91%) delete mode 100644 src/compiler/serialization/ucs-support-defaults.ts delete mode 100644 src/compiler/serialization/ucs-support-primitives.ts rename src/compiler/validation/{ucv-support-numeric-range.ts => ucv-process-numeric-range.ts} (96%) rename src/compiler/validation/{ucv-support-string-length.ts => ucv-process-string-length.ts} (95%) rename src/compiler/validation/{ucv-support-string-pattern.ts => ucv-process-string-pattern.ts} (94%) diff --git a/build/emit-exports.js b/build/emit-exports.js index fc15faf5..aa26be90 100644 --- a/build/emit-exports.js +++ b/build/emit-exports.js @@ -1,6 +1,6 @@ import { UcValueCompiler } from '#churi/uc-value/compiler.js'; import { URIChargeCompiler } from '#churi/uri-charge/compiler.js'; -import { UcdCompiler, ucdSupportDefaults } from 'churi/compiler.js'; +import { UcdCompiler, ucdProcessDefaults } from 'churi/compiler.js'; import fs from 'node:fs/promises'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; @@ -26,7 +26,7 @@ async function emitDeserializerDefaults() { exportDefaults: true, features(compiler) { // Call explicitly rather enable to force default handlers generation. - return ucdSupportDefaults(compiler); + return ucdProcessDefaults(compiler); }, }); diff --git a/src/compiler/deserialization/impl/uri-charge.compiler.ts b/src/compiler/deserialization/impl/uri-charge.compiler.ts index 4addfb5d..3bf7ba6d 100644 --- a/src/compiler/deserialization/impl/uri-charge.compiler.ts +++ b/src/compiler/deserialization/impl/uri-charge.compiler.ts @@ -15,12 +15,13 @@ import { UcrxEntitySetterSignature } from '../../rx/ucrx-entity-setter.js'; import { UcrxFormattedSetterSignature } from '../../rx/ucrx-formatted-setter.js'; import { UcrxBeforeMod, UcrxMethod } from '../../rx/ucrx-method.js'; import { UcrxSetter, UcrxSetterSignature, isUcrxSetter } from '../../rx/ucrx-setter.js'; +import { ucsProcessUnknown } from '../../serialization/ucs-process-unknown.js'; import { ListUcrxClass } from '../list.ucrx.class.js'; import { MapUcrxClass, MapUcrxStore } from '../map.ucrx.class.js'; import { UcdCompiler } from '../ucd-compiler.js'; import { UcdLib } from '../ucd-lib.js'; import { UcdModels } from '../ucd-models.js'; -import { ucdSupportDefaults } from '../ucd-support-defaults.js'; +import { ucdProcessDefaults } from '../ucd-process-defaults.js'; import { UnknownUcrxClass } from '../unknown.ucrx.class.js'; export class URIChargeCompiler extends UcdCompiler<{ @@ -34,7 +35,7 @@ export class URIChargeCompiler extends UcdCompiler<{ return { configure: () => { compiler - .enable(ucdSupportDefaults) + .enable(ucdProcessDefaults) .useUcrxClass(URICharge$Schema, (lib, schema) => new URIChargeUcrxClass(lib, schema)) .useUcrxClass( ucList(URICharge$Schema), @@ -68,12 +69,12 @@ const URICharge$Schema: UcNullable = { nullable: true, where: { deserializer: { - use: 'UnknownUcrxClass', + use: UnknownUcrxClass.name, from: COMPILER_MODULE, with: 'charge', }, serializer: { - use: 'ucsSupportUnknown', + use: ucsProcessUnknown.name, from: COMPILER_MODULE, with: 'charge', }, diff --git a/src/compiler/deserialization/mod.ts b/src/compiler/deserialization/mod.ts index 3077e3f3..48fa22cf 100644 --- a/src/compiler/deserialization/mod.ts +++ b/src/compiler/deserialization/mod.ts @@ -12,9 +12,9 @@ export * from './ucd-function.js'; export * from './ucd-handler-feature.js'; export * from './ucd-lib.js'; export * from './ucd-models.js'; +export * from './ucd-process-defaults.js'; +export * from './ucd-process-inset.js'; +export * from './ucd-process-non-finite.js'; +export * from './ucd-process-primitives.js'; export * from './ucd-setup.js'; -export * from './ucd-support-defaults.js'; -export * from './ucd-support-inset.js'; -export * from './ucd-support-non-finite.js'; -export * from './ucd-support-primitives.js'; export * from './unknown.ucrx.class.js'; diff --git a/src/compiler/deserialization/ucd-compiler.spec.ts b/src/compiler/deserialization/ucd-compiler.spec.ts index c512ecae..a15a0fca 100644 --- a/src/compiler/deserialization/ucd-compiler.spec.ts +++ b/src/compiler/deserialization/ucd-compiler.spec.ts @@ -2,9 +2,9 @@ import { describe, expect, it } from '@jest/globals'; import { EsBundleFormat } from 'esgen'; import { SPEC_MODULE } from '../../impl/module-names.js'; import { UcSchema } from '../../schema/uc-schema.js'; -import { ucdSupportTimestampFormat } from '../../spec/timestamp.format.js'; +import { ucdProcessTimestampFormat } from '../../spec/timestamp.format.js'; import { UcdCompiler } from './ucd-compiler.js'; -import { ucdSupportDefaults } from './ucd-support-defaults.js'; +import { ucdProcessDefaults } from './ucd-process-defaults.js'; describe('UcdCompiler', () => { describe('features', () => { @@ -18,12 +18,12 @@ describe('UcdCompiler', () => { uccProcess(compiler) { return { configure() { - compiler.enable(ucdSupportTimestampFormat); + compiler.enable(ucdProcessTimestampFormat); }, }; }, }, - ucdSupportDefaults, + ucdProcessDefaults, ], }); @@ -40,7 +40,7 @@ describe('UcdCompiler', () => { models: {}, exportDefaults: true, features(compiler) { - return ucdSupportDefaults(compiler); + return ucdProcessDefaults(compiler); }, }); @@ -60,7 +60,7 @@ describe('UcdCompiler', () => { type: 'timestamp', where: { deserializer: { - use: 'UcdSupportTimestamp', + use: 'UcdProcessTimestamp', from: SPEC_MODULE, }, }, @@ -81,7 +81,7 @@ describe('UcdCompiler', () => { type: 'timestamp', where: { deserializer: { - use: 'UcdSupportTimestampSchema', + use: 'UcdProcessTimestampSchema', from: SPEC_MODULE, }, }, @@ -102,7 +102,7 @@ describe('UcdCompiler', () => { type: 'timestamp', where: { deserializer: { - use: 'ucdSupportTimestampSchema', + use: 'ucdProcessTimestampSchema', from: SPEC_MODULE, }, }, diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 4f136261..87a519cd 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -27,8 +27,8 @@ import { UcdHandlerRegistry } from './impl/ucd-handler-registry.js'; import { UcdHandlerFeature } from './ucd-handler-feature.js'; import { UcdLib } from './ucd-lib.js'; import { UcdExports, UcdModels } from './ucd-models.js'; +import { ucdProcessDefaults } from './ucd-process-defaults.js'; import { UcdSetup } from './ucd-setup.js'; -import { ucdSupportDefaults } from './ucd-support-defaults.js'; /** * Compiler of schema {@link churi!UcDeserializer deserializers}. @@ -76,7 +76,7 @@ export class UcdCompiler setup: UcdSetup, feature: UccFeature, ): UccConfig { - if (feature === ucdSupportDefaults) { + if (feature === ucdProcessDefaults) { return this.#enableDefault() as UccConfig; } @@ -90,7 +90,7 @@ export class UcdCompiler } #configureDefaults(): void { - const defaultConfig = ucdSupportDefaults(this); + const defaultConfig = ucdProcessDefaults(this); this.#entities.configureDefaults(); this.#formats.configureDefaults(); @@ -277,7 +277,7 @@ export class UcdCompiler const { features } = this.#options; if (!features) { - this.enable(ucdSupportDefaults); + this.enable(ucdProcessDefaults); } } diff --git a/src/compiler/deserialization/ucd-process-defaults.ts b/src/compiler/deserialization/ucd-process-defaults.ts new file mode 100644 index 00000000..18d08227 --- /dev/null +++ b/src/compiler/deserialization/ucd-process-defaults.ts @@ -0,0 +1,12 @@ +import { UccConfig } from '../processor/ucc-config.js'; +import { UcrxSetup } from '../rx/ucrx-setup.js'; +import { ucdProcessNonFinite } from './ucd-process-non-finite.js'; +import { ucdProcessPrimitives } from './ucd-process-primitives.js'; + +export function ucdProcessDefaults(setup: UcrxSetup): UccConfig { + return { + configure() { + setup.enable(ucdProcessPrimitives).enable(ucdProcessNonFinite); + }, + }; +} diff --git a/src/compiler/deserialization/ucd-support-inset.ts b/src/compiler/deserialization/ucd-process-inset.ts similarity index 96% rename from src/compiler/deserialization/ucd-support-inset.ts rename to src/compiler/deserialization/ucd-process-inset.ts index 3b4c03a1..497dea06 100644 --- a/src/compiler/deserialization/ucd-support-inset.ts +++ b/src/compiler/deserialization/ucd-process-inset.ts @@ -6,7 +6,7 @@ import { UcrxCore$stubBody } from '../rx/impl/ucrx-core.stub.js'; import { UcrxCore } from '../rx/ucrx-core.js'; import { UcrxSetup } from '../rx/ucrx-setup.js'; -export function ucdSupportInset(setup: UcrxSetup): UccConfig { +export function ucdProcessInset(setup: UcrxSetup): UccConfig { return { configureSchema(schema, { lexer, from, method, args }) { const within = setup.currentPresentation; diff --git a/src/compiler/deserialization/ucd-support-non-finite.ts b/src/compiler/deserialization/ucd-process-non-finite.ts similarity index 93% rename from src/compiler/deserialization/ucd-support-non-finite.ts rename to src/compiler/deserialization/ucd-process-non-finite.ts index 831fde90..ecbfcea2 100644 --- a/src/compiler/deserialization/ucd-support-non-finite.ts +++ b/src/compiler/deserialization/ucd-process-non-finite.ts @@ -4,7 +4,7 @@ import { UccConfig } from '../processor/ucc-config.js'; import { UcdHandlerFeature, UcdHandlerSetup } from './ucd-handler-feature.js'; import { UcdSetup } from './ucd-setup.js'; -export function ucdSupportNonFinite(setup: UcdSetup): UccConfig { +export function ucdProcessNonFinite(setup: UcdSetup): UccConfig { return { configure() { setup diff --git a/src/compiler/deserialization/ucd-support-primitives.ts b/src/compiler/deserialization/ucd-process-primitives.ts similarity index 89% rename from src/compiler/deserialization/ucd-support-primitives.ts rename to src/compiler/deserialization/ucd-process-primitives.ts index 321a10c6..c60d79fa 100644 --- a/src/compiler/deserialization/ucd-support-primitives.ts +++ b/src/compiler/deserialization/ucd-process-primitives.ts @@ -5,7 +5,7 @@ import { BooleanUcrxClass } from './boolean.ucrx.class.js'; import { NumberUcrxClass } from './number.ucrx.class.js'; import { StringUcrxClass } from './string.ucrx.class.js'; -export function ucdSupportPrimitives(setup: UcrxSetup): UccConfig { +export function ucdProcessPrimitives(setup: UcrxSetup): UccConfig { return { configure() { setup diff --git a/src/compiler/deserialization/ucd-support-defaults.ts b/src/compiler/deserialization/ucd-support-defaults.ts deleted file mode 100644 index 7acf76a4..00000000 --- a/src/compiler/deserialization/ucd-support-defaults.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { UccConfig } from '../processor/ucc-config.js'; -import { UcrxSetup } from '../rx/ucrx-setup.js'; -import { ucdSupportNonFinite } from './ucd-support-non-finite.js'; -import { ucdSupportPrimitives } from './ucd-support-primitives.js'; - -export function ucdSupportDefaults(setup: UcrxSetup): UccConfig { - return { - configure() { - setup.enable(ucdSupportPrimitives).enable(ucdSupportNonFinite); - }, - }; -} diff --git a/src/compiler/processor/ucc-processor.spec.ts b/src/compiler/processor/ucc-processor.spec.ts index 9c89d60c..f04f1977 100644 --- a/src/compiler/processor/ucc-processor.spec.ts +++ b/src/compiler/processor/ucc-processor.spec.ts @@ -4,9 +4,9 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UccTestSetup, recordUcTestData, + ucTestProcessSchemaRecord, ucTestRecord, ucTestSubRecord, - ucTestSupportSchemaRecord, } from '../../spec/ucc-test-setup.js'; import { UccProcessor } from './ucc-processor.js'; @@ -140,7 +140,7 @@ describe('UccProcessor', () => { activation.onConstraint( { processor: 'deserializer', - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, }, async application => { @@ -176,7 +176,7 @@ describe('UccProcessor', () => { activation.onConstraint( { processor: 'deserializer', - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -200,7 +200,7 @@ describe('UccProcessor', () => { activation.onConstraint( { processor: 'deserializer', - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -225,7 +225,7 @@ describe('UccProcessor', () => { { processor: 'deserializer', within: 'charge', - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -250,7 +250,7 @@ describe('UccProcessor', () => { .onConstraint( { processor: 'deserializer', - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, }, application => { @@ -266,7 +266,7 @@ describe('UccProcessor', () => { .onConstraint( { processor: 'deserializer', - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, }, ({ setup }) => { diff --git a/src/compiler/rx/ucrx-setter.spec.ts b/src/compiler/rx/ucrx-setter.spec.ts index 0c3b289a..f1a79087 100644 --- a/src/compiler/rx/ucrx-setter.spec.ts +++ b/src/compiler/rx/ucrx-setter.spec.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { UcdCompiler } from '../deserialization/ucd-compiler.js'; -import { ucdSupportDefaults } from '../deserialization/ucd-support-defaults.js'; +import { ucdProcessDefaults } from '../deserialization/ucd-process-defaults.js'; import { UcrxSetter } from './ucrx-setter.js'; describe('UcrxSetter', () => { @@ -12,7 +12,7 @@ describe('UcrxSetter', () => { readValue: { model: Number }, }, features: [ - ucdSupportDefaults, + ucdProcessDefaults, compiler => ({ configure() { compiler.declareUcrxMethod(new UcrxSetter('test', { typeName: 'test-type' })); diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index e1ed99d6..fa838ec1 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -6,15 +6,15 @@ export * from './ucs-formatter.js'; export * from './ucs-function.js'; export * from './ucs-lib.js'; export * from './ucs-models.js'; +export * from './ucs-process-bigint.js'; +export * from './ucs-process-boolean.js'; +export * from './ucs-process-defaults.js'; +export * from './ucs-process-integer.js'; +export * from './ucs-process-list.js'; +export * from './ucs-process-map.js'; +export * from './ucs-process-number.js'; +export * from './ucs-process-primitives.js'; +export * from './ucs-process-string.js'; +export * from './ucs-process-unknown.js'; export * from './ucs-setup.js'; -export * from './ucs-support-bigint.js'; -export * from './ucs-support-boolean.js'; -export * from './ucs-support-defaults.js'; -export * from './ucs-support-integer.js'; -export * from './ucs-support-list.js'; -export * from './ucs-support-map.js'; -export * from './ucs-support-number.js'; -export * from './ucs-support-primitives.js'; -export * from './ucs-support-string.js'; -export * from './ucs-support-unknown.js'; export * from './ucs-writer.class.js'; diff --git a/src/compiler/serialization/ucs-compiler.spec.ts b/src/compiler/serialization/ucs-compiler.spec.ts index b037cc9a..428318ca 100644 --- a/src/compiler/serialization/ucs-compiler.spec.ts +++ b/src/compiler/serialization/ucs-compiler.spec.ts @@ -3,8 +3,8 @@ import { SPEC_MODULE } from '../../impl/module-names.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { TextOutStream } from '../../spec/text-out-stream.js'; import { - UcsSupportNumberWithRadix, - UcsSupportRadixNumber, + UcsProcessNumberWithRadix, + UcsProcessRadixNumber, } from '../../spec/write-uc-radix-number.js'; import { UcsCompiler } from './ucs-compiler.js'; @@ -12,7 +12,7 @@ describe('UcsCompiler', () => { it('respects custom serializer', async () => { const compiler = new UcsCompiler({ models: { writeValue: { model: Number } }, - features: UcsSupportNumberWithRadix, + features: UcsProcessNumberWithRadix, }); const { writeValue } = await compiler.evaluate(); @@ -40,7 +40,7 @@ export async function writeValue(stream, value, options) { it('fails to serialize unknown schema', async () => { const compiler = new UcsCompiler({ models: { writeValue: { model: { type: 'test-type' } } }, - features: UcsSupportRadixNumber, + features: UcsProcessRadixNumber, }); await expect(compiler.generate()).rejects.toThrow( @@ -55,7 +55,7 @@ export async function writeValue(stream, value, options) { type: 'radixNumber', where: { serializer: { - use: 'UcsSupportRadixNumber', + use: 'UcsProcessRadixNumber', from: SPEC_MODULE, }, }, @@ -73,7 +73,7 @@ export async function writeValue(stream, value, options) { type: 'hexNumber', where: { serializer: { - use: 'UcsSupportRadixNumberSchema', + use: 'UcsProcessRadixNumberSchema', from: SPEC_MODULE, }, }, diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 87e795dc..58f0413d 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -18,8 +18,8 @@ import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsLib } from './ucs-lib.js'; import { UcsExports, UcsModels } from './ucs-models.js'; +import { ucsProcessDefaults } from './ucs-process-defaults.js'; import { UcsSetup } from './ucs-setup.js'; -import { ucsSupportDefaults } from './ucs-support-defaults.js'; /** * Compiler of schema {@link churi!UcSerializer serializers}. @@ -180,7 +180,7 @@ export class UcsCompiler const { features } = this.#options; if (!features) { - this.enable(ucsSupportDefaults); + this.enable(ucsProcessDefaults); } } diff --git a/src/compiler/serialization/ucs-enable-plain-text.ts b/src/compiler/serialization/ucs-enable-plain-text.ts index ee9beb06..153aa68b 100644 --- a/src/compiler/serialization/ucs-enable-plain-text.ts +++ b/src/compiler/serialization/ucs-enable-plain-text.ts @@ -13,19 +13,19 @@ import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; import { ucsFormatInteger } from './impl/ucs-format-integer.js'; import { ucsFormatNumber } from './impl/ucs-format-number.js'; import { UcsFormatter } from './ucs-formatter.js'; +import { ucsProcessBigInt } from './ucs-process-bigint.js'; +import { ucsProcessInteger } from './ucs-process-integer.js'; +import { ucsProcessNumber } from './ucs-process-number.js'; +import { ucsProcessString } from './ucs-process-string.js'; import { UcsSetup } from './ucs-setup.js'; -import { ucsSupportBigInt } from './ucs-support-bigint.js'; -import { ucsSupportInteger } from './ucs-support-integer.js'; -import { ucsSupportNumber } from './ucs-support-number.js'; -import { ucsSupportString } from './ucs-support-string.js'; export function ucsEnablePlainText(activation: UccCapability.Activation): void { activation - .enable(ucsSupportPlainTextDefaults) + .enable(ucsProcessPlainTextDefaults) .onConstraint( { processor: 'serializer', - use: ucsSupportBigInt.name, + use: ucsProcessBigInt.name, from: COMPILER_MODULE, }, ({ setup, schema, constraint: { with: options } }) => { @@ -37,7 +37,7 @@ export function ucsEnablePlainText(activation: UccCapability.Activation { @@ -47,7 +47,7 @@ export function ucsEnablePlainText(activation: UccCapability.Activation { @@ -57,7 +57,7 @@ export function ucsEnablePlainText(activation: UccCapability.Activation { @@ -66,7 +66,7 @@ export function ucsEnablePlainText(activation: UccCapability.Activation): void { activation - .enable(ucsSupportURIEncodedDefaults) + .enable(ucsProcessURIEncodedDefaults) .onConstraint( { processor: 'serializer', - use: ucsSupportBigInt.name, + use: ucsProcessBigInt.name, from: COMPILER_MODULE, }, ({ setup, schema, constraint: { with: options } }) => { @@ -37,7 +37,7 @@ export function ucsEnableURIEncoded(activation: UccCapability.Activation { @@ -47,7 +47,7 @@ export function ucsEnableURIEncoded(activation: UccCapability.Activation { @@ -57,7 +57,7 @@ export function ucsEnableURIEncoded(activation: UccCapability.Activation { @@ -66,7 +66,7 @@ export function ucsEnableURIEncoded(activation: UccCapability.Activation { +export function ucsProcessBigInt(setup: UcsSetup): UccConfig { const configureSchema = ( target: UcSchema | typeof BigInt, variant?: UcBigInt.Variant | void, diff --git a/src/compiler/serialization/ucs-support-boolean.ts b/src/compiler/serialization/ucs-process-boolean.ts similarity index 84% rename from src/compiler/serialization/ucs-support-boolean.ts rename to src/compiler/serialization/ucs-process-boolean.ts index d26b6472..3503efd2 100644 --- a/src/compiler/serialization/ucs-support-boolean.ts +++ b/src/compiler/serialization/ucs-process-boolean.ts @@ -3,7 +3,7 @@ import { ucsFormatBoolean } from './impl/ucs-format-boolean.js'; import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportBoolean(setup: UcsSetup): UccConfig { +export function ucsProcessBoolean(setup: UcsSetup): UccConfig { return { configure() { setup.formatWith('charge', Boolean, ucsFormatCharge(ucsFormatBoolean())); diff --git a/src/compiler/serialization/ucs-process-defaults.ts b/src/compiler/serialization/ucs-process-defaults.ts new file mode 100644 index 00000000..0550501a --- /dev/null +++ b/src/compiler/serialization/ucs-process-defaults.ts @@ -0,0 +1,11 @@ +import { UccConfig } from '../processor/ucc-config.js'; +import { ucsProcessPrimitives } from './ucs-process-primitives.js'; +import { UcsSetup } from './ucs-setup.js'; + +export function ucsProcessDefaults(setup: UcsSetup): UccConfig { + return { + configure() { + setup.enable(ucsProcessPrimitives); + }, + }; +} diff --git a/src/compiler/serialization/ucs-support-integer.ts b/src/compiler/serialization/ucs-process-integer.ts similarity index 88% rename from src/compiler/serialization/ucs-support-integer.ts rename to src/compiler/serialization/ucs-process-integer.ts index 4f647bba..2d33685d 100644 --- a/src/compiler/serialization/ucs-support-integer.ts +++ b/src/compiler/serialization/ucs-process-integer.ts @@ -4,7 +4,7 @@ import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatInteger } from './impl/ucs-format-integer.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportInteger(setup: UcsSetup): UccConfig { +export function ucsProcessInteger(setup: UcsSetup): UccConfig { return { configureSchema(schema: UcInteger.Schema, variant) { setup.formatWith('charge', schema, ucsFormatCharge(ucsFormatInteger(variant))); diff --git a/src/compiler/serialization/ucs-support-list.ts b/src/compiler/serialization/ucs-process-list.ts similarity index 98% rename from src/compiler/serialization/ucs-support-list.ts rename to src/compiler/serialization/ucs-process-list.ts index fc68dac7..d9c3a1ca 100644 --- a/src/compiler/serialization/ucs-support-list.ts +++ b/src/compiler/serialization/ucs-process-list.ts @@ -12,7 +12,7 @@ import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportList(setup: UcsSetup): UccConfig { +export function ucsProcessList(setup: UcsSetup): UccConfig { return { configureSchema(schema: UcList.Schema, options) { setup.processModel(schema.item).formatWith( diff --git a/src/compiler/serialization/ucs-support-map.ts b/src/compiler/serialization/ucs-process-map.ts similarity index 98% rename from src/compiler/serialization/ucs-support-map.ts rename to src/compiler/serialization/ucs-process-map.ts index 056f8c6a..d47af4a3 100644 --- a/src/compiler/serialization/ucs-support-map.ts +++ b/src/compiler/serialization/ucs-process-map.ts @@ -21,7 +21,7 @@ import { UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportMap(setup: UcsSetup): UccConfig { +export function ucsProcessMap(setup: UcsSetup): UccConfig { return { configure() { setup.formatWith('charge', 'map', ucsFormatCharge(ucsWriteMap)); diff --git a/src/compiler/serialization/ucs-support-number.ts b/src/compiler/serialization/ucs-process-number.ts similarity index 91% rename from src/compiler/serialization/ucs-support-number.ts rename to src/compiler/serialization/ucs-process-number.ts index cb810820..f28bc6e1 100644 --- a/src/compiler/serialization/ucs-support-number.ts +++ b/src/compiler/serialization/ucs-process-number.ts @@ -5,7 +5,7 @@ import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatNumber } from './impl/ucs-format-number.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportNumber(setup: UcsSetup): UccConfig { +export function ucsProcessNumber(setup: UcsSetup): UccConfig { const configureSchema = ( target: UcSchema | typeof Number, variant?: UcNumber.Variant | void, diff --git a/src/compiler/serialization/ucs-process-primitives.ts b/src/compiler/serialization/ucs-process-primitives.ts new file mode 100644 index 00000000..8718c099 --- /dev/null +++ b/src/compiler/serialization/ucs-process-primitives.ts @@ -0,0 +1,18 @@ +import { UccConfig } from '../processor/ucc-config.js'; +import { ucsProcessBigInt } from './ucs-process-bigint.js'; +import { ucsProcessBoolean } from './ucs-process-boolean.js'; +import { ucsProcessNumber } from './ucs-process-number.js'; +import { ucsProcessString } from './ucs-process-string.js'; +import { UcsSetup } from './ucs-setup.js'; + +export function ucsProcessPrimitives(setup: UcsSetup): UccConfig { + return { + configure() { + setup + .enable(ucsProcessBoolean) + .enable(ucsProcessBigInt) + .enable(ucsProcessNumber) + .enable(ucsProcessString); + }, + }; +} diff --git a/src/compiler/serialization/ucs-support-string.ts b/src/compiler/serialization/ucs-process-string.ts similarity index 91% rename from src/compiler/serialization/ucs-support-string.ts rename to src/compiler/serialization/ucs-process-string.ts index 358c734b..391e3578 100644 --- a/src/compiler/serialization/ucs-support-string.ts +++ b/src/compiler/serialization/ucs-process-string.ts @@ -5,7 +5,7 @@ import { ucsFormatCharge } from './impl/ucs-format-charge.js'; import { ucsFormatString } from './impl/ucs-format-string.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportString(setup: UcsSetup): UccConfig { +export function ucsProcessString(setup: UcsSetup): UccConfig { const configureSchema = (target: UcSchema | typeof String, variant?: UcString.Variant): void => { setup.formatWith('charge', target, ucsFormatCharge(ucsFormatString(variant))); }; diff --git a/src/compiler/serialization/ucs-support-unknown.ts b/src/compiler/serialization/ucs-process-unknown.ts similarity index 91% rename from src/compiler/serialization/ucs-support-unknown.ts rename to src/compiler/serialization/ucs-process-unknown.ts index e1a66907..65ff7ee2 100644 --- a/src/compiler/serialization/ucs-support-unknown.ts +++ b/src/compiler/serialization/ucs-process-unknown.ts @@ -4,7 +4,7 @@ import { UC_MODULE_CHURI, UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; import { UccConfig } from '../processor/ucc-config.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportUnknown(setup: UcsSetup): UccConfig { +export function ucsProcessUnknown(setup: UcsSetup): UccConfig { return { configure() { setup.formatWith('charge', 'unknown', ({ writer, value, asItem }) => { diff --git a/src/compiler/serialization/ucs-support-defaults.ts b/src/compiler/serialization/ucs-support-defaults.ts deleted file mode 100644 index adc69cb6..00000000 --- a/src/compiler/serialization/ucs-support-defaults.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { UccConfig } from '../processor/ucc-config.js'; -import { UcsSetup } from './ucs-setup.js'; -import { ucsSupportPrimitives } from './ucs-support-primitives.js'; - -export function ucsSupportDefaults(setup: UcsSetup): UccConfig { - return { - configure() { - setup.enable(ucsSupportPrimitives); - }, - }; -} diff --git a/src/compiler/serialization/ucs-support-primitives.ts b/src/compiler/serialization/ucs-support-primitives.ts deleted file mode 100644 index 33315bd0..00000000 --- a/src/compiler/serialization/ucs-support-primitives.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { UccConfig } from '../processor/ucc-config.js'; -import { UcsSetup } from './ucs-setup.js'; -import { ucsSupportBigInt } from './ucs-support-bigint.js'; -import { ucsSupportBoolean } from './ucs-support-boolean.js'; -import { ucsSupportNumber } from './ucs-support-number.js'; -import { ucsSupportString } from './ucs-support-string.js'; - -export function ucsSupportPrimitives(setup: UcsSetup): UccConfig { - return { - configure() { - setup - .enable(ucsSupportBoolean) - .enable(ucsSupportBigInt) - .enable(ucsSupportNumber) - .enable(ucsSupportString); - }, - }; -} diff --git a/src/compiler/validation/mod.ts b/src/compiler/validation/mod.ts index 1511a53b..63c33538 100644 --- a/src/compiler/validation/mod.ts +++ b/src/compiler/validation/mod.ts @@ -1,4 +1,4 @@ -export * from './ucv-support-numeric-range.js'; -export * from './ucv-support-string-length.js'; -export * from './ucv-support-string-pattern.js'; +export * from './ucv-process-numeric-range.js'; +export * from './ucv-process-string-length.js'; +export * from './ucv-process-string-pattern.js'; export * from './ucv-validate.js'; diff --git a/src/compiler/validation/ucv-support-numeric-range.ts b/src/compiler/validation/ucv-process-numeric-range.ts similarity index 96% rename from src/compiler/validation/ucv-support-numeric-range.ts rename to src/compiler/validation/ucv-process-numeric-range.ts index 1b9d2c4f..9f326ad3 100644 --- a/src/compiler/validation/ucv-support-numeric-range.ts +++ b/src/compiler/validation/ucv-process-numeric-range.ts @@ -12,7 +12,7 @@ export type UcvNumericRange = [ or?: string | undefined, ]; -export function ucvSupportNumericRange(setup: UcrxSetup): UccConfig { +export function ucvProcessNumericRange(setup: UcrxSetup): UccConfig { return { configureSchema(schema, [constraint, than, or]) { let setter: UcrxSetter; diff --git a/src/compiler/validation/ucv-support-string-length.ts b/src/compiler/validation/ucv-process-string-length.ts similarity index 95% rename from src/compiler/validation/ucv-support-string-length.ts rename to src/compiler/validation/ucv-process-string-length.ts index 14a0e527..3e86d56f 100644 --- a/src/compiler/validation/ucv-support-string-length.ts +++ b/src/compiler/validation/ucv-process-string-length.ts @@ -11,7 +11,7 @@ export type UcvStringLength = [ or?: string | undefined, ]; -export function ucvSupportStringLength(setup: UcrxSetup): UccConfig { +export function ucvProcessStringLength(setup: UcrxSetup): UccConfig { return { configureSchema(schema, [constraint, than, or]) { setup.modifyUcrxMethod(schema, UcrxCore.str, { diff --git a/src/compiler/validation/ucv-support-string-pattern.ts b/src/compiler/validation/ucv-process-string-pattern.ts similarity index 94% rename from src/compiler/validation/ucv-support-string-pattern.ts rename to src/compiler/validation/ucv-process-string-pattern.ts index 0486b806..4d216132 100644 --- a/src/compiler/validation/ucv-support-string-pattern.ts +++ b/src/compiler/validation/ucv-process-string-pattern.ts @@ -8,7 +8,7 @@ import { ucvValidate } from './ucv-validate.js'; export type UcvStringPattern = [match: RegExp, or?: string | undefined]; -export function ucvSupportStringPattern(setup: UcrxSetup): UccConfig { +export function ucvProcessStringPattern(setup: UcrxSetup): UccConfig { return { configureSchema(schema, [match, or]) { setup.modifyUcrxMethod(schema, UcrxCore.str, { diff --git a/src/schema/entity/uc-entity.deserializer.spec.ts b/src/schema/entity/uc-entity.deserializer.spec.ts index 5ba9aab5..323a645e 100644 --- a/src/schema/entity/uc-entity.deserializer.spec.ts +++ b/src/schema/entity/uc-entity.deserializer.spec.ts @@ -1,6 +1,6 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { ucdSupportPrimitives } from '../../compiler/deserialization/ucd-support-primitives.js'; +import { ucdProcessPrimitives } from '../../compiler/deserialization/ucd-process-primitives.js'; import { parseTokens } from '../../spec/read-chunks.js'; import { UcErrorInfo } from '../uc-error.js'; @@ -19,7 +19,7 @@ describe('UcEntity deserializer', () => { models: { readNumber: { model: Number, mode: 'async' }, }, - features: ucdSupportPrimitives, + features: ucdProcessPrimitives, }); const { readNumber } = await compiler.evaluate(); @@ -41,7 +41,7 @@ describe('UcEntity deserializer', () => { models: { readNumber: { model: Number, mode: 'sync' }, }, - features: ucdSupportPrimitives, + features: ucdProcessPrimitives, }); const { readNumber } = await compiler.evaluate(); diff --git a/src/schema/entity/uc-formatted.deserializer.spec.ts b/src/schema/entity/uc-formatted.deserializer.spec.ts index d6792b9c..d1fcefc9 100644 --- a/src/schema/entity/uc-formatted.deserializer.spec.ts +++ b/src/schema/entity/uc-formatted.deserializer.spec.ts @@ -1,11 +1,11 @@ import { beforeEach, describe, expect, it } from '@jest/globals'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { ucdSupportPrimitives } from '../../compiler/deserialization/ucd-support-primitives.js'; -import { ucdSupportPlainEntity } from '../../spec/plain.format.js'; +import { ucdProcessPrimitives } from '../../compiler/deserialization/ucd-process-primitives.js'; +import { ucdProcessPlainEntity } from '../../spec/plain.format.js'; import { parseTokens } from '../../spec/read-chunks.js'; import { - ucdSupportTimestampFormat, - ucdSupportTimestampFormatOnly, + ucdProcessTimestampFormat, + ucdProcessTimestampFormatOnly, } from '../../spec/timestamp.format.js'; import { UC_TOKEN_EXCLAMATION_MARK } from '../../syntax/uc-token.js'; import { UcErrorInfo } from '../uc-error.js'; @@ -25,7 +25,7 @@ describe('UcFormatted deserializer', () => { models: { readString: { model: String, mode: 'sync' }, }, - features: [ucdSupportPrimitives, ucdSupportPlainEntity], + features: [ucdProcessPrimitives, ucdProcessPlainEntity], }); const { readString } = await compiler.evaluate(); @@ -36,7 +36,7 @@ describe('UcFormatted deserializer', () => { models: { readString: { model: String, mode: 'async' }, }, - features: [ucdSupportPrimitives, ucdSupportPlainEntity], + features: [ucdProcessPrimitives, ucdProcessPlainEntity], }); const { readString } = await compiler.evaluate(); @@ -49,7 +49,7 @@ describe('UcFormatted deserializer', () => { models: { readTimestamp: { model: Number, mode: 'sync' }, }, - features: [ucdSupportPrimitives, ucdSupportTimestampFormat], + features: [ucdProcessPrimitives, ucdProcessTimestampFormat], }); const now = new Date(); const { readTimestamp } = await compiler.evaluate(); @@ -61,7 +61,7 @@ describe('UcFormatted deserializer', () => { models: { readTimestamp: { model: Number, mode: 'sync' }, }, - features: [ucdSupportPrimitives, ucdSupportTimestampFormatOnly], + features: [ucdProcessPrimitives, ucdProcessTimestampFormatOnly], }); await expect(compiler.evaluate()).rejects.toThrow( @@ -73,7 +73,7 @@ describe('UcFormatted deserializer', () => { models: { readNumber: { model: Number, mode: 'sync' }, }, - features: ucdSupportPrimitives, + features: ucdProcessPrimitives, }); const { readNumber } = await compiler.evaluate(); diff --git a/src/schema/list/uc-list.impl.ts b/src/schema/list/uc-list.impl.ts index f7e3d17b..4e6ae018 100644 --- a/src/schema/list/uc-list.impl.ts +++ b/src/schema/list/uc-list.impl.ts @@ -33,7 +33,7 @@ export function createUcListSchema = id: UcList$id, }, serializer: { - use: 'ucsSupportList', + use: 'ucsProcessList', from: COMPILER_MODULE, with: options, id: UcList$id, diff --git a/src/schema/map/uc-map.ts b/src/schema/map/uc-map.ts index 161d2938..a724e12b 100644 --- a/src/schema/map/uc-map.ts +++ b/src/schema/map/uc-map.ts @@ -186,7 +186,7 @@ export function ucMap { models: { parse: { model: ucUnknown() }, }, - features: [ucdSupportDefaults, ucdSupportMetaMapEntity], + features: [ucdProcessDefaults, ucdProcessMetaMapEntity], }); try { @@ -139,7 +139,7 @@ describe('UcMeta deserializer', () => { const compiler = new UcdCompiler({ models: { parse: { model: ucUnknown() } }, features: [ - ucdSupportDefaults, + ucdProcessDefaults, compiler => ({ configure() { compiler.parseMetaValue('comment', ucString(), ({ cx, value }) => code => { diff --git a/src/schema/numeric/uc-bigint.ts b/src/schema/numeric/uc-bigint.ts index 53f67481..c1c033f9 100644 --- a/src/schema/numeric/uc-bigint.ts +++ b/src/schema/numeric/uc-bigint.ts @@ -115,7 +115,7 @@ export function ucBigInt(options?: UcBigInt.Options): UcBigInt.Schema { with: variant, }, serializer: { - use: 'ucsSupportBigInt', + use: 'ucsProcessBigInt', from: COMPILER_MODULE, with: variant, }, diff --git a/src/schema/numeric/uc-integer.ts b/src/schema/numeric/uc-integer.ts index 70b82cbe..1e6cada2 100644 --- a/src/schema/numeric/uc-integer.ts +++ b/src/schema/numeric/uc-integer.ts @@ -78,7 +78,7 @@ function UcInteger$createSchema(variant?: UcNumber.Variant): UcInteger.Schema { with: variant, }, serializer: { - use: 'ucsSupportInteger', + use: 'ucsProcessInteger', from: COMPILER_MODULE, with: variant, }, diff --git a/src/schema/numeric/uc-number.deserializer.spec.ts b/src/schema/numeric/uc-number.deserializer.spec.ts index 510c6fe8..06f301f1 100644 --- a/src/schema/numeric/uc-number.deserializer.spec.ts +++ b/src/schema/numeric/uc-number.deserializer.spec.ts @@ -1,7 +1,7 @@ import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { ucdSupportNonFinite } from '../../compiler/deserialization/ucd-support-non-finite.js'; -import { ucdSupportPrimitives } from '../../compiler/deserialization/ucd-support-primitives.js'; +import { ucdProcessNonFinite } from '../../compiler/deserialization/ucd-process-non-finite.js'; +import { ucdProcessPrimitives } from '../../compiler/deserialization/ucd-process-primitives.js'; import { parseTokens } from '../../spec/read-chunks.js'; import { UcChargeLexer } from '../../syntax/formats/charge/uc-charge.lexer.js'; import { UcDeserializer } from '../uc-deserializer.js'; @@ -27,7 +27,7 @@ describe('UcNumber deserializer', () => { models: { readValue: { model: Number as UcDataType }, }, - features: [ucdSupportPrimitives, ucdSupportNonFinite], + features: [ucdProcessPrimitives, ucdProcessNonFinite], }); ({ readValue } = await compiler.evaluate()); @@ -153,7 +153,7 @@ describe('UcNumber deserializer', () => { models: { readValue: { model: ucNumber({ string: 'reject' }) }, }, - features: [ucdSupportPrimitives, ucdSupportNonFinite], + features: [ucdProcessPrimitives, ucdProcessNonFinite], }); ({ readValue } = await compiler.evaluate()); diff --git a/src/schema/numeric/uc-number.ts b/src/schema/numeric/uc-number.ts index 13c9e51f..bcc11261 100644 --- a/src/schema/numeric/uc-number.ts +++ b/src/schema/numeric/uc-number.ts @@ -87,7 +87,7 @@ export function ucNumber(options?: UcNumber.Options): UcNumber.Schema { with: variant, }, serializer: { - use: 'ucsSupportNumber', + use: 'ucsProcessNumber', from: COMPILER_MODULE, with: variant, }, diff --git a/src/schema/numeric/uc-numeric-range.validator.ts b/src/schema/numeric/uc-numeric-range.validator.ts index f0dde66b..db2bd6cf 100644 --- a/src/schema/numeric/uc-numeric-range.validator.ts +++ b/src/schema/numeric/uc-numeric-range.validator.ts @@ -1,5 +1,5 @@ import { esEscapeString } from 'esgen'; -import type { UcvNumericRange } from '../../compiler/validation/ucv-support-numeric-range.js'; +import type { UcvNumericRange } from '../../compiler/validation/ucv-process-numeric-range.js'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcConstraints } from '../uc-constraints.js'; import { UcBigInt } from './uc-bigint.js'; @@ -72,7 +72,7 @@ function ucValidateNumericRange( ): UcConstraints & UcConstraints { return { validator: { - use: 'ucvSupportNumericRange', + use: 'ucvProcessNumericRange', from: COMPILER_MODULE, with: options, id(): string { diff --git a/src/schema/string/uc-string-length.validator.ts b/src/schema/string/uc-string-length.validator.ts index 4c540d63..c47b4b93 100644 --- a/src/schema/string/uc-string-length.validator.ts +++ b/src/schema/string/uc-string-length.validator.ts @@ -1,4 +1,4 @@ -import type { UcvStringLength } from '../../compiler/validation/ucv-support-string-length.js'; +import type { UcvStringLength } from '../../compiler/validation/ucv-process-string-length.js'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcConstraints } from '../uc-constraints.js'; import { UcString } from './uc-string.js'; @@ -22,7 +22,7 @@ function ucvValidateStringLength( ): UcConstraints { return { validator: { - use: 'ucvSupportStringLength', + use: 'ucvProcessStringLength', from: COMPILER_MODULE, with: options, }, diff --git a/src/schema/string/uc-string-pattern.validator.ts b/src/schema/string/uc-string-pattern.validator.ts index 18bae9fc..82de73e0 100644 --- a/src/schema/string/uc-string-pattern.validator.ts +++ b/src/schema/string/uc-string-pattern.validator.ts @@ -1,4 +1,4 @@ -import { UcvStringPattern } from '../../compiler/validation/ucv-support-string-pattern.js'; +import { UcvStringPattern } from '../../compiler/validation/ucv-process-string-pattern.js'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcConstraints } from '../uc-constraints.js'; import { UcString } from './uc-string.js'; @@ -9,7 +9,7 @@ export function ucItMatches( ): UcConstraints { return { validator: { - use: 'ucvSupportStringPattern', + use: 'ucvProcessStringPattern', from: COMPILER_MODULE, with: [pattern, message] satisfies UcvStringPattern, }, diff --git a/src/schema/string/uc-string.ts b/src/schema/string/uc-string.ts index fc5d5585..14249655 100644 --- a/src/schema/string/uc-string.ts +++ b/src/schema/string/uc-string.ts @@ -92,7 +92,7 @@ export function ucString(options?: UcString.Options): UcString.Schema { with: variant, }, serializer: { - use: 'ucsSupportString', + use: 'ucsProcessString', from: COMPILER_MODULE, with: variant, }, diff --git a/src/schema/unknown/uc-unknown.deserializer.spec.ts b/src/schema/unknown/uc-unknown.deserializer.spec.ts index d2a5f210..c990894d 100644 --- a/src/schema/unknown/uc-unknown.deserializer.spec.ts +++ b/src/schema/unknown/uc-unknown.deserializer.spec.ts @@ -1,9 +1,9 @@ import { beforeAll, beforeEach, describe, expect, it } from '@jest/globals'; import { UcValueCompiler } from '../../compiler/deserialization/impl/uc-value.compiler.js'; import { UcdCompiler } from '../../compiler/deserialization/ucd-compiler.js'; -import { ucdSupportPrimitives } from '../../compiler/deserialization/ucd-support-primitives.js'; -import { ucdSupportPlainEntity } from '../../spec/plain.format.js'; -import { ucdSupportTimestampFormat } from '../../spec/timestamp.format.js'; +import { ucdProcessPrimitives } from '../../compiler/deserialization/ucd-process-primitives.js'; +import { ucdProcessPlainEntity } from '../../spec/plain.format.js'; +import { ucdProcessTimestampFormat } from '../../spec/timestamp.format.js'; import { UcDeserializer } from '../uc-deserializer.js'; import { UcErrorInfo } from '../uc-error.js'; import { ucNullable } from '../uc-nullable.js'; @@ -143,7 +143,7 @@ describe('UcUnknown deserializer', () => { beforeAll(async () => { const compiler = new UcdCompiler({ models: { readValue: { model: ucUnknown() } }, - features: [ucdSupportPrimitives, ucdSupportPlainEntity, ucdSupportTimestampFormat], + features: [ucdProcessPrimitives, ucdProcessPlainEntity, ucdProcessTimestampFormat], }); ({ readValue } = await compiler.evaluate()); diff --git a/src/schema/unknown/uc-unknown.ts b/src/schema/unknown/uc-unknown.ts index a64f4606..affc3534 100644 --- a/src/schema/unknown/uc-unknown.ts +++ b/src/schema/unknown/uc-unknown.ts @@ -29,7 +29,7 @@ const UcUnknown$Schema: UcNullable = { from: COMPILER_MODULE, }, serializer: { - use: 'ucsSupportUnknown', + use: 'ucsProcessUnknown', from: COMPILER_MODULE, }, }, diff --git a/src/spec/meta-map.entity.ts b/src/spec/meta-map.entity.ts index 749f2eae..67b0a524 100644 --- a/src/spec/meta-map.entity.ts +++ b/src/spec/meta-map.entity.ts @@ -25,7 +25,7 @@ export function readMetaMap(context: UcrxContext, rx: Ucrx): 0 | 1 { return rx.map(context); } -export function ucdSupportMetaMapEntity(setup: UcdSetup): UccConfig { +export function ucdProcessMetaMapEntity(setup: UcdSetup): UccConfig { return { configure() { setup.handleEntity('meta-map', ({ register }) => code => { diff --git a/src/spec/plain.format.ts b/src/spec/plain.format.ts index de0f8c93..bcc70687 100644 --- a/src/spec/plain.format.ts +++ b/src/spec/plain.format.ts @@ -15,7 +15,7 @@ export function readPlainFormat( return rx.str(`!${format}'${printUcTokens(data)}`, context); } -export function ucdSupportPlainEntity(setup: UcdSetup): UccConfig { +export function ucdProcessPlainEntity(setup: UcdSetup): UccConfig { return { configure() { setup.handleFormat('plain', ({ register }) => code => { diff --git a/src/spec/timestamp.format.ts b/src/spec/timestamp.format.ts index 3dd9a945..407a68a9 100644 --- a/src/spec/timestamp.format.ts +++ b/src/spec/timestamp.format.ts @@ -23,10 +23,10 @@ export const TimestampUcrxMethod = new UcrxSetter('date', { typeName: 'date', }); -export function ucdSupportTimestampFormat(setup: UcdSetup): UccConfig { +export function ucdProcessTimestampFormat(setup: UcdSetup): UccConfig { return { configure() { - setup.declareUcrxMethod(TimestampUcrxMethod).enable(ucdSupportTimestampFormatOnly); + setup.declareUcrxMethod(TimestampUcrxMethod).enable(ucdProcessTimestampFormatOnly); }, }; } @@ -58,7 +58,7 @@ const readTimestampEntityFn = new EsFunction( }, ); -export function ucdSupportTimestampFormatOnly(setup: UcdSetup): UccConfig { +export function ucdProcessTimestampFormatOnly(setup: UcdSetup): UccConfig { return { configure() { setup.handleFormat('timestamp', ({ register, refer }) => code => { @@ -70,32 +70,32 @@ export function ucdSupportTimestampFormatOnly(setup: UcdSetup): UccConfig { }; } -export const UcdSupportTimestamp: UccFeature.Object = { +export const UcdProcessTimestamp: UccFeature.Object = { uccProcess(setup) { return { configure() { setup - .enable(ucdSupportTimestampFormat) + .enable(ucdProcessTimestampFormat) .useUcrxClass('timestamp', (lib, schema) => new TimestampUcrxClass(lib, schema)); }, }; }, }; -export const UcdSupportTimestampSchema: UccFeature.Object = { +export const UcdProcessTimestampSchema: UccFeature.Object = { uccProcess(setup) { return { configureSchema() { - setup.enable(UcdSupportTimestamp); + setup.enable(UcdProcessTimestamp); }, }; }, }; -export function ucdSupportTimestampSchema(setup: UcdSetup, _schema: UcSchema): UccConfig { +export function ucdProcessTimestampSchema(setup: UcdSetup, _schema: UcSchema): UccConfig { return { configure() { - setup.enable(UcdSupportTimestamp); + setup.enable(UcdProcessTimestamp); }, }; } diff --git a/src/spec/ucc-test-setup.ts b/src/spec/ucc-test-setup.ts index 4e07d83a..1fc790b8 100644 --- a/src/spec/ucc-test-setup.ts +++ b/src/spec/ucc-test-setup.ts @@ -12,14 +12,14 @@ export const WrongFeature = 'WrongFeature'; export function ucTestRecord(options?: unknown): UcOmniConstraints { return { deserializer: { - use: ucTestSupportSchemaRecord.name, + use: ucTestProcessSchemaRecord.name, from: SPEC_MODULE, with: options, }, }; } -export function ucTestSupportSchemaRecord(setup: UccTestSetup): UccConfig { +export function ucTestProcessSchemaRecord(setup: UccTestSetup): UccConfig { return { configureSchema(_, options) { recordUcTestData(setup, options); @@ -27,7 +27,7 @@ export function ucTestSupportSchemaRecord(setup: UccTestSetup): UccConfig { +export function ucTestProcessFeatureRecord(setup: UccTestSetup): UccConfig { return { configure(options) { recordUcTestData(setup, options); @@ -38,17 +38,17 @@ export function ucTestSupportFeatureRecord(setup: UccTestSetup): UccConfig { +export function ucTestProcessSubRecord(setup: UccTestSetup): UccConfig { return { configureSchema(_, options) { - setup.enable(ucTestSupportFeatureRecord, options); + setup.enable(ucTestProcessFeatureRecord, options); }, }; } diff --git a/src/spec/write-uc-radix-number.ts b/src/spec/write-uc-radix-number.ts index 5207432a..9d5674c1 100644 --- a/src/spec/write-uc-radix-number.ts +++ b/src/spec/write-uc-radix-number.ts @@ -12,7 +12,7 @@ export async function writeUcRadixNumber(writer: UcsWriter, value: number): Prom await ucsWriteAsIs(writer, (radix === 16 ? '0x' : '') + value.toString(Number(radix))); } -export const UcsSupportNumberWithRadix: UccFeature.Object = { +export const UcsProcessNumberWithRadix: UccFeature.Object = { uccProcess(compiler) { return { configure() { @@ -26,7 +26,7 @@ export const UcsSupportNumberWithRadix: UccFeature.Object = { }, }; -export const UcsSupportRadixNumber: UccFeature.Object = { +export const UcsProcessRadixNumber: UccFeature.Object = { uccProcess(compiler) { return { configure() { @@ -40,7 +40,7 @@ export const UcsSupportRadixNumber: UccFeature.Object = { }, }; -export const UcsSupportRadixNumberSchema: UccFeature.Object = { +export const UcsProcessRadixNumberSchema: UccFeature.Object = { uccProcess(setup) { return { configureSchema(schema: UcSchema) { diff --git a/src/syntax/formats/charge/uc-charge.lexer.ts b/src/syntax/formats/charge/uc-charge.lexer.ts index 4374d776..33c8258f 100644 --- a/src/syntax/formats/charge/uc-charge.lexer.ts +++ b/src/syntax/formats/charge/uc-charge.lexer.ts @@ -1,4 +1,4 @@ -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { scanUcTokens } from '../../scan-uc-tokens.js'; @@ -269,7 +269,7 @@ export function ucInsetCharge({ } = {}): UcOmniConstraints { return { deserializer: { - use: 'ucdSupportInset', + use: 'ucdProcessInset', from: COMPILER_MODULE, with: { lexer: 'UcChargeLexer', diff --git a/src/syntax/formats/plain-text/uc-plain-text.lexer.ts b/src/syntax/formats/plain-text/uc-plain-text.lexer.ts index 8a3d6663..20932a65 100644 --- a/src/syntax/formats/plain-text/uc-plain-text.lexer.ts +++ b/src/syntax/formats/plain-text/uc-plain-text.lexer.ts @@ -1,4 +1,4 @@ -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { UcLexer } from '../../uc-lexer.js'; @@ -60,7 +60,7 @@ export function ucInsetPlainText({ } = {}): UcOmniConstraints { return { deserializer: { - use: 'ucdSupportInset', + use: 'ucdProcessInset', from: COMPILER_MODULE, with: { lexer: 'UcPlainTextLexer', diff --git a/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts index d482dff5..f23be39c 100644 --- a/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts +++ b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts @@ -1,5 +1,5 @@ import { decodeURISearchPart } from 'httongue'; -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { UcLexer } from '../../uc-lexer.js'; @@ -114,7 +114,7 @@ export function ucInsetURIEncoded({ } = {}): UcOmniConstraints { return { deserializer: { - use: 'ucdSupportInset', + use: 'ucdProcessInset', from: COMPILER_MODULE, with: { lexer: 'UcURIEncodedLexer', diff --git a/src/syntax/formats/uri-params/uc-uri-params.lexer.ts b/src/syntax/formats/uri-params/uc-uri-params.lexer.ts index 5e7cd153..7a7586c0 100644 --- a/src/syntax/formats/uri-params/uc-uri-params.lexer.ts +++ b/src/syntax/formats/uri-params/uc-uri-params.lexer.ts @@ -1,6 +1,6 @@ import { esStringLiteral } from 'esgen'; import { decodeURISearchPart } from 'httongue'; -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-support-inset.js'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { UcLexer } from '../../uc-lexer.js'; @@ -159,7 +159,7 @@ const CHURI_DELIMITER_PATTERNS = { export function ucInsetURIParams(splitter?: '&' | ';'): UcOmniConstraints { return { deserializer: { - use: 'ucdSupportInset', + use: 'ucdProcessInset', from: COMPILER_MODULE, with: { lexer: 'UcURIParamsLexer', From 454b60e8a04632b6798ca501d29709c439e9133a Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 16:52:51 +0700 Subject: [PATCH 22/36] Use "support" for capability prefix --- src/compiler/serialization/mod.ts | 4 ++-- ...lain-text.ts => ucs-support-plain-text.ts} | 2 +- ...-encoded.ts => ucs-support-uri-encoded.ts} | 2 +- .../plain-text/plain-text.serializer.spec.ts | 20 +++++++++---------- .../uri-encoded.serializer.spec.ts | 20 +++++++++---------- 5 files changed, 24 insertions(+), 24 deletions(-) rename src/compiler/serialization/{ucs-enable-plain-text.ts => ucs-support-plain-text.ts} (97%) rename src/compiler/serialization/{ucs-enable-uri-encoded.ts => ucs-support-uri-encoded.ts} (97%) diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index fa838ec1..1576b899 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -1,6 +1,4 @@ export * from './ucs-compiler.js'; -export * from './ucs-enable-plain-text.js'; -export * from './ucs-enable-uri-encoded.js'; export * from './ucs-export.signature.js'; export * from './ucs-formatter.js'; export * from './ucs-function.js'; @@ -17,4 +15,6 @@ export * from './ucs-process-primitives.js'; export * from './ucs-process-string.js'; export * from './ucs-process-unknown.js'; export * from './ucs-setup.js'; +export * from './ucs-support-plain-text.js'; +export * from './ucs-support-uri-encoded.js'; export * from './ucs-writer.class.js'; diff --git a/src/compiler/serialization/ucs-enable-plain-text.ts b/src/compiler/serialization/ucs-support-plain-text.ts similarity index 97% rename from src/compiler/serialization/ucs-enable-plain-text.ts rename to src/compiler/serialization/ucs-support-plain-text.ts index 153aa68b..d7e583f3 100644 --- a/src/compiler/serialization/ucs-enable-plain-text.ts +++ b/src/compiler/serialization/ucs-support-plain-text.ts @@ -19,7 +19,7 @@ import { ucsProcessNumber } from './ucs-process-number.js'; import { ucsProcessString } from './ucs-process-string.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsEnablePlainText(activation: UccCapability.Activation): void { +export function ucsSupportPlainText(activation: UccCapability.Activation): void { activation .enable(ucsProcessPlainTextDefaults) .onConstraint( diff --git a/src/compiler/serialization/ucs-enable-uri-encoded.ts b/src/compiler/serialization/ucs-support-uri-encoded.ts similarity index 97% rename from src/compiler/serialization/ucs-enable-uri-encoded.ts rename to src/compiler/serialization/ucs-support-uri-encoded.ts index 7fce323d..1a326eab 100644 --- a/src/compiler/serialization/ucs-enable-uri-encoded.ts +++ b/src/compiler/serialization/ucs-support-uri-encoded.ts @@ -19,7 +19,7 @@ import { ucsProcessNumber } from './ucs-process-number.js'; import { ucsProcessString } from './ucs-process-string.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsEnableURIEncoded(activation: UccCapability.Activation): void { +export function ucsSupportURIEncoded(activation: UccCapability.Activation): void { activation .enable(ucsProcessURIEncodedDefaults) .onConstraint( diff --git a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts index 47b729f2..64aad34d 100644 --- a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts +++ b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; -import { ucsEnablePlainText } from '../../../compiler/serialization/ucs-enable-plain-text.js'; +import { ucsSupportPlainText } from '../../../compiler/serialization/ucs-support-plain-text.js'; import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; import { ucList } from '../../../schema/list/uc-list.js'; import { ucMap } from '../../../schema/map/uc-map.js'; @@ -16,7 +16,7 @@ import { TextOutStream } from '../../../spec/text-out-stream.js'; describe('plain text serializer', () => { it('serializes bigint', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writePrimitive: { model: BigInt, format: 'plainText' }, writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'plainText' }, @@ -42,7 +42,7 @@ describe('plain text serializer', () => { }); it('serializes boolean', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writePrimitive: { model: Boolean, format: 'plainText' }, writeValue: { model: ucBoolean(), format: 'plainText' }, @@ -60,7 +60,7 @@ describe('plain text serializer', () => { }); it('serializes number', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writePrimitive: { model: Number, format: 'plainText' }, writeValue: { model: ucNumber({ string: 'serialize' }), format: 'plainText' }, @@ -79,7 +79,7 @@ describe('plain text serializer', () => { }); it('serializes integer', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writeValue: { model: ucInteger({ string: 'serialize' }), format: 'plainText' }, }, @@ -94,7 +94,7 @@ describe('plain text serializer', () => { }); it('serializes string', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writePrimitive: { model: String, format: 'plainText' }, writeValue: { model: ucString({ raw: 'escape' }), format: 'plainText' }, @@ -113,7 +113,7 @@ describe('plain text serializer', () => { it('can not serialize list', async () => { const schema = ucList(Number); const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writeList: { model: schema, format: 'plainText' }, }, @@ -129,7 +129,7 @@ describe('plain text serializer', () => { it('can not serialize map', async () => { const schema = ucMap({ foo: Number }); const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { writeMap: { model: schema, format: 'plainText' }, }, @@ -145,7 +145,7 @@ describe('plain text serializer', () => { it('can not serialize nullable values', async () => { const schema = ucNullable(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { write: { model: schema, format: 'plainText' }, }, @@ -162,7 +162,7 @@ describe('plain text serializer', () => { it('can not serialize optional values', async () => { const schema = ucOptional(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsEnablePlainText, + capabilities: ucsSupportPlainText, models: { write: { model: schema, format: 'plainText' }, }, diff --git a/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts b/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts index 5a256828..0b50382f 100644 --- a/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts +++ b/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts @@ -1,7 +1,7 @@ import { describe, expect, it } from '@jest/globals'; import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; -import { ucsEnableURIEncoded } from '../../../compiler/serialization/ucs-enable-uri-encoded.js'; +import { ucsSupportURIEncoded } from '../../../compiler/serialization/ucs-support-uri-encoded.js'; import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; import { ucList } from '../../../schema/list/uc-list.js'; import { ucMap } from '../../../schema/map/uc-map.js'; @@ -16,7 +16,7 @@ import { TextOutStream } from '../../../spec/text-out-stream.js'; describe('URI-encoded serializer', () => { it('serializes bigint', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writePrimitive: { model: BigInt, format: 'uriEncoded' }, writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'uriEncoded' }, @@ -42,7 +42,7 @@ describe('URI-encoded serializer', () => { }); it('serializes boolean', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writePrimitive: { model: Boolean, format: 'uriEncoded' }, writeValue: { model: ucBoolean(), format: 'uriEncoded' }, @@ -60,7 +60,7 @@ describe('URI-encoded serializer', () => { }); it('serializes number', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writePrimitive: { model: Number, format: 'uriEncoded' }, writeValue: { model: ucNumber({ string: 'serialize' }), format: 'uriEncoded' }, @@ -79,7 +79,7 @@ describe('URI-encoded serializer', () => { }); it('serializes integer', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writeValue: { model: ucInteger({ string: 'serialize' }), format: 'uriEncoded' }, }, @@ -94,7 +94,7 @@ describe('URI-encoded serializer', () => { }); it('serializes string', async () => { const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writePrimitive: { model: String, format: 'uriEncoded' }, writeValue: { model: ucString({ raw: 'escape' }), format: 'uriEncoded' }, @@ -113,7 +113,7 @@ describe('URI-encoded serializer', () => { it('can not serialize list', async () => { const schema = ucList(Number); const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writeList: { model: schema, format: 'uriEncoded' }, }, @@ -129,7 +129,7 @@ describe('URI-encoded serializer', () => { it('can not serialize map', async () => { const schema = ucMap({ foo: Number }); const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { writeMap: { model: schema, format: 'uriEncoded' }, }, @@ -145,7 +145,7 @@ describe('URI-encoded serializer', () => { it('can not serialize nullable values', async () => { const schema = ucNullable(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { write: { model: schema, format: 'uriEncoded' }, }, @@ -162,7 +162,7 @@ describe('URI-encoded serializer', () => { it('can not serialize optional values', async () => { const schema = ucOptional(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsEnableURIEncoded, + capabilities: ucsSupportURIEncoded, models: { write: { model: schema, format: 'uriEncoded' }, }, From b6f7d1f4f6af8e2582da2e2a6aa84cf600b081cc Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 16:57:23 +0700 Subject: [PATCH 23/36] Prevent no-options feature re-configuration --- .../impl/ucc-processor.feature-config.ts | 21 ++++++++++++------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/src/compiler/processor/impl/ucc-processor.feature-config.ts b/src/compiler/processor/impl/ucc-processor.feature-config.ts index d53d3ea5..f52ff433 100644 --- a/src/compiler/processor/impl/ucc-processor.feature-config.ts +++ b/src/compiler/processor/impl/ucc-processor.feature-config.ts @@ -9,7 +9,7 @@ import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; export class UccProcessor$FeatureConfig, in TOptions = never> { readonly #getConfig: () => UccConfig; - #featureConfigured = false; + #autoConfigured = false; constructor(getConfig: () => UccConfig) { this.#getConfig = lazyValue(getConfig); @@ -30,9 +30,7 @@ export class UccProcessor$FeatureConfig, in TOpt options: TOptions, data: unknown, ): void { - if (!this.#featureConfigured) { - this.#configureFeature(profiler, { processor: constraint.processor }, undefined!, undefined); - } + this.#configureFeature(profiler, { processor: constraint.processor }); profiler.configureSync({ ...constraint, schema }, () => { this.#getConfig().configureSchema?.(schema, options, data); @@ -42,11 +40,18 @@ export class UccProcessor$FeatureConfig, in TOpt #configureFeature( profiler: UccProcessor$Profiler, current: UccProcessor$Current, - options: TOptions, - data: unknown, + options?: TOptions, + data?: unknown, ): void { - this.#featureConfigured = true; - profiler.configureSync(current, () => this.#getConfig().configure?.(options, data)); + if (options === undefined && data === undefined) { + if (this.#autoConfigured) { + return; + } + + this.#autoConfigured = true; + } + + profiler.configureSync(current, () => this.#getConfig().configure?.(options!, data)); } } From 427cfaadec6735c46891a8e1e102ca90bf8b65cf Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 18:17:08 +0700 Subject: [PATCH 24/36] Optimize constraint issuing --- src/compiler/deserialization/ucd-compiler.ts | 7 +- .../processor/impl/ucc-processor.config.ts | 93 +++++++++++++++++++ .../ucc-processor.constraint-application.ts | 33 ++++--- ...g.ts => ucc-processor.constraint-issue.ts} | 2 +- .../impl/ucc-processor.constraint-usage.ts | 48 ++++++++++ .../impl/ucc-processor.feature-config.ts | 38 ++++---- .../impl/ucc-processor.feature-use.ts | 35 ------- .../processor/impl/ucc-processor.profiler.ts | 91 +----------------- src/compiler/processor/ucc-processor.ts | 56 +++++------ 9 files changed, 211 insertions(+), 192 deletions(-) create mode 100644 src/compiler/processor/impl/ucc-processor.config.ts rename src/compiler/processor/impl/{ucc-processor.constraint-config.ts => ucc-processor.constraint-issue.ts} (86%) create mode 100644 src/compiler/processor/impl/ucc-processor.constraint-usage.ts delete mode 100644 src/compiler/processor/impl/ucc-processor.feature-use.ts diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 87a519cd..649f4eee 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -72,15 +72,12 @@ export class UcdCompiler return this; } - override createConfig( - setup: UcdSetup, - feature: UccFeature, - ): UccConfig { + override createConfig(feature: UccFeature): UccConfig { if (feature === ucdProcessDefaults) { return this.#enableDefault() as UccConfig; } - return super.createConfig(setup, feature); + return super.createConfig(feature); } #enableDefault(): UccConfig { diff --git a/src/compiler/processor/impl/ucc-processor.config.ts b/src/compiler/processor/impl/ucc-processor.config.ts new file mode 100644 index 00000000..030486e4 --- /dev/null +++ b/src/compiler/processor/impl/ucc-processor.config.ts @@ -0,0 +1,93 @@ +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccFeature } from '../ucc-feature.js'; +import { UccSetup } from '../ucc-setup.js'; +import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js'; +import { UccProcessor$Current } from './ucc-processor.current.js'; +import { UccProcessor$FeatureConfig } from './ucc-processor.feature-config.js'; +import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; + +export class UccProcessor$Config> { + + readonly #profiler: UccProcessor$Profiler; + readonly #configs = new Map, UccProcessor$FeatureConfig>(); + + #current: UccProcessor$Current = {}; + + constructor(profiler: UccProcessor$Profiler) { + this.#profiler = profiler; + } + + get setup(): TSetup { + return this.#profiler.setup; + } + + get profiler(): UccProcessor$Profiler { + return this.#profiler; + } + + get current(): UccProcessor$Current { + return this.#current; + } + + enableFeature( + feature: UccFeature, + options: TOptions, + data: unknown, + ): void { + this.#featureConfig(feature).configureFeature(this, options, data); + } + + enableSchema( + schema: UcSchema, + feature: UccFeature, + issue: UccProcessor$ConstraintIssue, + ): void { + this.#featureConfig(feature).configureSchema(this, schema, issue); + } + + #featureConfig( + feature: UccFeature, + ): UccProcessor$FeatureConfig { + let config = this.#configs.get(feature) as + | UccProcessor$FeatureConfig + | undefined; + + if (!config) { + const { processor } = this.#profiler; + + config = new UccProcessor$FeatureConfig(() => processor.createConfig(feature)); + this.#configs.set(feature, config); + } + + return config; + } + + configure(current: UccProcessor$Current, action: () => void): void { + const prev = this.#pushCurrent(current); + + try { + action(); + } finally { + this.#current = prev; + } + } + + async configureAsync(current: UccProcessor$Current, action: () => Promise): Promise { + const prev = this.#pushCurrent(current); + + try { + await action(); + } finally { + this.#current = prev; + } + } + + #pushCurrent(current: UccProcessor$Current): UccProcessor$Current { + const prev = this.#current; + + this.#current = current.processor ? current : { ...current, processor: prev.processor }; + + return prev; + } + +} diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts index 8ba52bc6..895d1929 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-application.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -6,29 +6,29 @@ import { UcSchema } from '../../../schema/uc-schema.js'; import { UccCapability } from '../ucc-capability.js'; import { UccFeature } from '../ucc-feature.js'; import { UccSetup } from '../ucc-setup.js'; -import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; -import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; +import { UccProcessor$Config } from './ucc-processor.config.js'; +import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js'; export class UccProcessor$ConstraintApplication> implements UccCapability.ConstraintApplication { - readonly #profiler: UccProcessor$Profiler; + readonly #config: UccProcessor$Config; readonly #schema: UcSchema; - readonly #config: UccProcessor$ConstraintConfig; + readonly #issue: UccProcessor$ConstraintIssue; #applied = 0; constructor( - profiler: UccProcessor$Profiler, + config: UccProcessor$Config, schema: UcSchema, - config: UccProcessor$ConstraintConfig, + issue: UccProcessor$ConstraintIssue, ) { - this.#profiler = profiler; - this.#schema = schema; this.#config = config; + this.#schema = schema; + this.#issue = issue; } get setup(): TSetup { - return this.#profiler.setup; + return this.#config.setup; } get schema(): UcSchema { @@ -36,19 +36,19 @@ export class UccProcessor$ConstraintApplication } = await import(from); if ((mayHaveProperties(feature) && 'uccProcess' in feature) || typeof feature === 'function') { - this.#profiler.enableSchema(schema, this.#config, feature, options, data); + this.#config.enableSchema(schema, feature, this.#issue); } else if (feature === undefined) { throw new ReferenceError(`No such schema processing feature: ${this}`); } else { @@ -89,7 +88,7 @@ export class UccProcessor$ConstraintApplication> { + + readonly #config: UccProcessor$Config; + readonly #schema: UcSchema; + readonly #issues: UccProcessor$ConstraintIssue[] = []; + + constructor(config: UccProcessor$Config, schema: UcSchema) { + this.#config = config; + this.#schema = schema; + } + + issue(issues: UccProcessor$ConstraintIssue): void { + this.#issues.push(issues); + } + + async apply(): Promise { + for (const issue of this.#issues) { + await this.#applyConstraint(issue); + } + this.#issues.length = 0; + } + + async #applyConstraint(issue: UccProcessor$ConstraintIssue): Promise { + const config = this.#config; + const schema = this.#schema; + const { processor, within, constraint } = issue; + const { profiler } = config; + const application = new UccProcessor$ConstraintApplication(config, schema, issue); + + await config.configureAsync({ processor, schema, within, constraint }, async () => { + await profiler.findHandler(processor, within, constraint)?.(application); + if (within) { + // Apply any presentation handler. + await profiler.findHandler(processor, undefined, constraint)?.(application); + } + if (!application.isIgnored()) { + await application.apply(); + } + }); + } + +} diff --git a/src/compiler/processor/impl/ucc-processor.feature-config.ts b/src/compiler/processor/impl/ucc-processor.feature-config.ts index f52ff433..508d7bfd 100644 --- a/src/compiler/processor/impl/ucc-processor.feature-config.ts +++ b/src/compiler/processor/impl/ucc-processor.feature-config.ts @@ -2,43 +2,43 @@ import { lazyValue } from '@proc7ts/primitives'; import { UcSchema } from '../../../schema/uc-schema.js'; import { UccConfig } from '../ucc-config.js'; import { UccSetup } from '../ucc-setup.js'; -import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; +import { UccProcessor$Config } from './ucc-processor.config.js'; +import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js'; import { UccProcessor$Current } from './ucc-processor.current.js'; -import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; export class UccProcessor$FeatureConfig, in TOptions = never> { readonly #getConfig: () => UccConfig; #autoConfigured = false; - constructor(getConfig: () => UccConfig) { - this.#getConfig = lazyValue(getConfig); + constructor(createConfig: () => UccConfig) { + this.#getConfig = lazyValue(createConfig); } - configureFeature( - profiler: UccProcessor$Profiler, - options: TOptions, - data: unknown, - ): void { - this.#configureFeature(profiler, {}, options, data); + configureFeature(config: UccProcessor$Config, options: TOptions, data: unknown): void { + this.#configureFeature(config, {}, options, data); } configureSchema( - profiler: UccProcessor$Profiler, + config: UccProcessor$Config, schema: UcSchema, - constraint: UccProcessor$ConstraintConfig, - options: TOptions, - data: unknown, + issue: UccProcessor$ConstraintIssue, ): void { - this.#configureFeature(profiler, { processor: constraint.processor }); + const { + processor, + constraint: { with: options }, + data, + } = issue; + + this.#configureFeature(config, { processor }); - profiler.configureSync({ ...constraint, schema }, () => { - this.#getConfig().configureSchema?.(schema, options, data); + config.configure({ ...issue, schema }, () => { + this.#getConfig().configureSchema?.(schema, options as TOptions, data); }); } #configureFeature( - profiler: UccProcessor$Profiler, + config: UccProcessor$Config, current: UccProcessor$Current, options?: TOptions, data?: unknown, @@ -51,7 +51,7 @@ export class UccProcessor$FeatureConfig, in TOpt this.#autoConfigured = true; } - profiler.configureSync(current, () => this.#getConfig().configure?.(options!, data)); + config.configure(current, () => this.#getConfig().configure?.(options!, data)); } } diff --git a/src/compiler/processor/impl/ucc-processor.feature-use.ts b/src/compiler/processor/impl/ucc-processor.feature-use.ts deleted file mode 100644 index 1cabf8a4..00000000 --- a/src/compiler/processor/impl/ucc-processor.feature-use.ts +++ /dev/null @@ -1,35 +0,0 @@ -import { UcSchema } from '../../../schema/uc-schema.js'; -import { UccSetup } from '../ucc-setup.js'; -import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; -import { UccProcessor$Profiler } from './ucc-processor.profiler.js'; - -export class UccProcessor$FeatureUse> { - - readonly #schema: UcSchema; - readonly #configs: UccProcessor$ConstraintConfig[] = []; - - #applied = false; - #profiler: UccProcessor$Profiler; - - constructor(profiler: UccProcessor$Profiler, schema: UcSchema) { - this.#profiler = profiler; - this.#schema = schema; - } - - addConfig(config: UccProcessor$ConstraintConfig): void { - this.#configs.push(config); - } - - async apply(): Promise { - if (this.#applied) { - return; - } - - this.#applied = true; - - for (const config of this.#configs) { - await this.#profiler.applyConstraint(this.#schema, config); - } - } - -} diff --git a/src/compiler/processor/impl/ucc-processor.profiler.ts b/src/compiler/processor/impl/ucc-processor.profiler.ts index b6b3471f..38c8a4a6 100644 --- a/src/compiler/processor/impl/ucc-processor.profiler.ts +++ b/src/compiler/processor/impl/ucc-processor.profiler.ts @@ -1,24 +1,16 @@ import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js'; import { UcPresentationName } from '../../../schema/uc-presentations.js'; -import { UcSchema } from '../../../schema/uc-schema.js'; import { UccCapability } from '../ucc-capability.js'; import { UccFeature } from '../ucc-feature.js'; import { UccProcessor } from '../ucc-processor.js'; import { UccSetup } from '../ucc-setup.js'; -import { UccProcessor$ConstraintApplication } from './ucc-processor.constraint-application.js'; -import { UccProcessor$ConstraintConfig } from './ucc-processor.constraint-config.js'; -import { UccProcessor$Current } from './ucc-processor.current.js'; -import { UccProcessor$FeatureConfig } from './ucc-processor.feature-config.js'; export class UccProcessor$Profiler> { readonly #processor: UccProcessor; readonly #init: ((setup: TSetup) => void)[] = []; - readonly #configs = new Map, UccProcessor$FeatureConfig>(); readonly #handlers = new Map>(); - #current: UccProcessor$Current = {}; - constructor(processor: UccProcessor) { this.#processor = processor; } @@ -27,69 +19,8 @@ export class UccProcessor$Profiler> { return this.#processor.setup; } - get current(): UccProcessor$Current { - return this.#current; - } - - enableFeature( - feature: UccFeature, - options: TOptions, - data: unknown, - ): void { - this.#featureConfig(feature).configureFeature(this, options, data); - } - - enableSchema( - schema: UcSchema, - constraint: UccProcessor$ConstraintConfig, - feature: UccFeature, - options: TOptions, - data: unknown, - ): void { - this.#featureConfig(feature).configureSchema(this, schema, constraint, options, data); - } - - #featureConfig( - feature: UccFeature, - ): UccProcessor$FeatureConfig { - let config = this.#configs.get(feature) as - | UccProcessor$FeatureConfig - | undefined; - - if (!config) { - config = new UccProcessor$FeatureConfig(() => this.#processor.createConfig(this.setup, feature)); - this.#configs.set(feature, config); - } - - return config; - } - - configureSync(current: UccProcessor$Current, action: () => void): void { - const prev = this.#pushCurrent(current); - - try { - action(); - } finally { - this.#current = prev; - } - } - - async configureAsync(current: UccProcessor$Current, action: () => Promise): Promise { - const prev = this.#pushCurrent(current); - - try { - await action(); - } finally { - this.#current = prev; - } - } - - #pushCurrent(current: UccProcessor$Current): UccProcessor$Current { - const prev = this.#current; - - this.#current = current.processor ? current : { ...current, processor: prev.processor }; - - return prev; + get processor(): UccProcessor { + return this.#processor; } addFeature(feature: UccFeature, options: TOptions): void { @@ -121,23 +52,7 @@ export class UccProcessor$Profiler> { } } - async applyConstraint(schema: UcSchema, config: UccProcessor$ConstraintConfig): Promise { - const application = new UccProcessor$ConstraintApplication(this, schema, config); - const { processor, within, constraint } = config; - - await this.configureAsync({ processor, schema, within, constraint }, async () => { - await this.#findHandler(processor, within, constraint)?.(application); - if (within) { - // Apply any presentation handler. - await this.#findHandler(processor, undefined, constraint)?.(application); - } - if (!application.isIgnored()) { - await application.apply(); - } - }); - } - - #findHandler( + findHandler( processor: UcProcessorName, within: UcPresentationName | undefined, { use, from }: UcFeatureConstraint, diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 2fcb0d90..27a99c63 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -7,8 +7,9 @@ import { import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccProcessor$CapabilityActivation } from './impl/ucc-processor.capability-activation.js'; -import { UccProcessor$ConstraintConfig } from './impl/ucc-processor.constraint-config.js'; -import { UccProcessor$FeatureUse } from './impl/ucc-processor.feature-use.js'; +import { UccProcessor$Config } from './impl/ucc-processor.config.js'; +import { UccProcessor$ConstraintIssue } from './impl/ucc-processor.constraint-issue.js'; +import { UccProcessor$ConstraintUsage } from './impl/ucc-processor.constraint-usage.js'; import { UccProcessor$Profiler } from './impl/ucc-processor.profiler.js'; import { UccCapability } from './ucc-capability.js'; import { UccConfig } from './ucc-config.js'; @@ -32,7 +33,8 @@ export abstract class UccProcessor> readonly #features: readonly UccFeature[] | undefined; readonly #getSetup = lazyValue(() => this.createSetup()); readonly #profiler = new UccProcessor$Profiler(this); - readonly #uses = new Map>(); + readonly #config = new UccProcessor$Config(this.#profiler); + readonly #usages = new Map>(); #hasPendingInstructions = false; @@ -71,19 +73,19 @@ export abstract class UccProcessor> } get currentProcessor(): UcProcessorName | undefined { - return this.#profiler.current.processor; + return this.#config.current.processor; } get currentSchema(): UcSchema | undefined { - return this.#profiler.current.schema; + return this.#config.current.schema; } get currentPresentation(): UcPresentationName | undefined { - return this.#profiler.current.within; + return this.#config.current.within; } get currentConstraint(): UcFeatureConstraint | undefined { - return this.#profiler.current.constraint; + return this.#config.current.constraint; } enable( @@ -91,7 +93,7 @@ export abstract class UccProcessor> options?: TOptions, data?: unknown, ): this { - this.#profiler.enableFeature(feature, options!, data); + this.#config.enableFeature(feature, options!, data); return this; } @@ -115,25 +117,25 @@ export abstract class UccProcessor> ): void { for (const processor of this.schemaIndex.processors) { for (const constraint of asArray(constraints?.[processor])) { - this.#useFeature(schema, { processor, within, constraint, data }); + this.#issueConstraint(schema, { processor, within, constraint, data }); } } } - #useFeature(schema: UcSchema, config: UccProcessor$ConstraintConfig): void { + #issueConstraint(schema: UcSchema, issue: UccProcessor$ConstraintIssue): void { const { constraint: { use: feature, from }, - } = config; - const useId = `${this.schemaIndex.schemaId(schema)}::${from}::${feature}`; - let use = this.#uses.get(useId); + } = issue; + const usageId = `${this.schemaIndex.schemaId(schema)}::${from}::${feature}`; + let usage = this.#usages.get(usageId); - if (!use) { + if (!usage) { this.#hasPendingInstructions = true; - use = new UccProcessor$FeatureUse(this.#profiler, schema); - this.#uses.set(useId, use); + usage = new UccProcessor$ConstraintUsage(this.#config, schema); + this.#usages.set(usageId, usage); } - use.addConfig(config); + usage.issue(issue); } protected async processInstructions(): Promise { @@ -158,14 +160,18 @@ export abstract class UccProcessor> } /** - * Processes instructions supplied by {@link enable features} and {@link processModel modules}. + * Processes constraints issued by {@link enable features} and {@link processModel modules}. */ async #processInstructions(): Promise { while (this.#hasPendingInstructions) { this.#hasPendingInstructions = false; - for (const use of this.#uses.values()) { - await use.apply(); - } + await this.#applyIssuedConstraints(); + } + } + + async #applyIssuedConstraints(): Promise { + for (const usage of this.#usages.values()) { + await usage.apply(); } } @@ -183,16 +189,12 @@ export abstract class UccProcessor> /** * Creates schema processing feature configuration for just {@link enable enabled} `feature`. * - * @param setup - Schema processing setup. * @param feature - Enabled feature. * * @returns Schema processing configuration. */ - createConfig( - setup: TSetup, - feature: UccFeature, - ): UccConfig { - return 'uccProcess' in feature ? feature.uccProcess(setup) : feature(setup); + createConfig(feature: UccFeature): UccConfig { + return 'uccProcess' in feature ? feature.uccProcess(this.setup) : feature(this.setup); } } From d8f98da7593d718e1cfe4cee019b4246bb12b519 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Mon, 31 Jul 2023 18:58:19 +0700 Subject: [PATCH 25/36] Make custom processing data typed --- src/compiler/deserialization/ucd-compiler.ts | 2 +- src/compiler/processor/impl/ucc-processor.config.ts | 3 ++- .../impl/ucc-processor.constraint-application.ts | 3 ++- .../processor/impl/ucc-processor.constraint-issue.ts | 3 ++- .../processor/impl/ucc-processor.feature-config.ts | 12 ++++++++---- src/compiler/processor/ucc-capability.ts | 3 ++- src/compiler/processor/ucc-config.ts | 11 +++++++++-- src/compiler/processor/ucc-processor.ts | 6 +++--- src/compiler/processor/ucc-setup.ts | 11 ++++++++--- 9 files changed, 37 insertions(+), 17 deletions(-) diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 649f4eee..3da21f8e 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -95,7 +95,7 @@ export class UcdCompiler // Stop registering default handlers. // Start registering custom ones. - defaultConfig.configure!(undefined, undefined); + defaultConfig.configure!(undefined, {}); this.#entities.makeDefault(); this.#formats.makeDefault(); diff --git a/src/compiler/processor/impl/ucc-processor.config.ts b/src/compiler/processor/impl/ucc-processor.config.ts index 030486e4..dfe43ddd 100644 --- a/src/compiler/processor/impl/ucc-processor.config.ts +++ b/src/compiler/processor/impl/ucc-processor.config.ts @@ -1,4 +1,5 @@ import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccConfig } from '../ucc-config.js'; import { UccFeature } from '../ucc-feature.js'; import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js'; @@ -32,7 +33,7 @@ export class UccProcessor$Config> { enableFeature( feature: UccFeature, options: TOptions, - data: unknown, + data: UccConfig.Data | undefined, ): void { this.#featureConfig(feature).configureFeature(this, options, data); } diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts index 895d1929..7a1aebbe 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-application.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -4,6 +4,7 @@ import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constra import { UcPresentationName } from '../../../schema/uc-presentations.js'; import { UcSchema } from '../../../schema/uc-schema.js'; import { UccCapability } from '../ucc-capability.js'; +import { UccConfig } from '../ucc-config.js'; import { UccFeature } from '../ucc-feature.js'; import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$Config } from './ucc-processor.config.js'; @@ -47,7 +48,7 @@ export class UccProcessor$ConstraintApplication, in TOpt this.#getConfig = lazyValue(createConfig); } - configureFeature(config: UccProcessor$Config, options: TOptions, data: unknown): void { + configureFeature( + config: UccProcessor$Config, + options: TOptions, + data: UccConfig.Data | undefined, + ): void { this.#configureFeature(config, {}, options, data); } @@ -33,7 +37,7 @@ export class UccProcessor$FeatureConfig, in TOpt this.#configureFeature(config, { processor }); config.configure({ ...issue, schema }, () => { - this.#getConfig().configureSchema?.(schema, options as TOptions, data); + this.#getConfig().configureSchema?.(schema, options as TOptions, data ?? {}); }); } @@ -41,7 +45,7 @@ export class UccProcessor$FeatureConfig, in TOpt config: UccProcessor$Config, current: UccProcessor$Current, options?: TOptions, - data?: unknown, + data?: UccConfig.Data, ): void { if (options === undefined && data === undefined) { if (this.#autoConfigured) { @@ -51,7 +55,7 @@ export class UccProcessor$FeatureConfig, in TOpt this.#autoConfigured = true; } - config.configure(current, () => this.#getConfig().configure?.(options!, data)); + config.configure(current, () => this.#getConfig().configure?.(options!, data ?? {})); } } diff --git a/src/compiler/processor/ucc-capability.ts b/src/compiler/processor/ucc-capability.ts index e17d1bb8..89bca10b 100644 --- a/src/compiler/processor/ucc-capability.ts +++ b/src/compiler/processor/ucc-capability.ts @@ -1,6 +1,7 @@ import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; +import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; import { UccSetup } from './ucc-setup.js'; @@ -139,7 +140,7 @@ export namespace UccCapability { /** * Custom data passed by parent schema processor. */ - get data(): unknown; + get data(): UccConfig.Data | undefined; /** * Informs whether the {@link constraint} is {@link apply applied} already. diff --git a/src/compiler/processor/ucc-config.ts b/src/compiler/processor/ucc-config.ts index e294f7e3..3ce7985e 100644 --- a/src/compiler/processor/ucc-config.ts +++ b/src/compiler/processor/ucc-config.ts @@ -16,7 +16,7 @@ export interface UccConfig { * @param options - Configuration options. * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. */ - configure?(options: TOptions, data: unknown): void; + configure?(options: TOptions, data: UccConfig.Data): void; /** * Configures processing of concrete `schema`. @@ -27,5 +27,12 @@ export interface UccConfig { * @param options - Configuration options. * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. */ - configureSchema?(schema: UcSchema, options: TOptions, data: unknown): void; + configureSchema?(schema: UcSchema, options: TOptions, data: UccConfig.Data): void; +} + +export namespace UccConfig { + /** + * Custom data to pass to feature configurations. + */ + export type Data = { readonly [key in PropertyKey]: unknown }; } diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 27a99c63..07e414f4 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -91,14 +91,14 @@ export abstract class UccProcessor> enable( feature: UccFeature, options?: TOptions, - data?: unknown, + data?: UccConfig.Data, ): this { this.#config.enableFeature(feature, options!, data); return this; } - processModel(model: UcModel, data?: unknown): this { + processModel(model: UcModel, data?: UccConfig.Data): this { const schema = ucSchema(model); this.#applyConstraints(schema, undefined, schema.where, data); @@ -113,7 +113,7 @@ export abstract class UccProcessor> schema: UcSchema, within: UcPresentationName | undefined, constraints: UcConstraints | undefined, - data: unknown, + data: UccConfig.Data | undefined, ): void { for (const processor of this.schemaIndex.processors) { for (const constraint of asArray(constraints?.[processor])) { diff --git a/src/compiler/processor/ucc-setup.ts b/src/compiler/processor/ucc-setup.ts index feb33ebe..aa70600d 100644 --- a/src/compiler/processor/ucc-setup.ts +++ b/src/compiler/processor/ucc-setup.ts @@ -1,6 +1,7 @@ import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema } from '../../schema/uc-schema.js'; +import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; /** @@ -41,7 +42,11 @@ export interface UccSetup { * * @returns `this` instance. */ - enable(feature: UccFeature, options: TOptions, data?: unknown): this; + enable( + feature: UccFeature, + options: TOptions, + data?: UccConfig.Data, + ): this; /** * Enables the given processing `feature` that does not require options. @@ -52,7 +57,7 @@ export interface UccSetup { * * @returns `this` instance. */ - enable(feature: UccFeature, options?: void, data?: unknown): this; + enable(feature: UccFeature, options?: void, data?: UccConfig.Data): this; /** * Applies model processing instructions specified as its {@link churi!UcSchema#where constraints}. @@ -63,5 +68,5 @@ export interface UccSetup { * * @returns `this` instance. */ - processModel(model: UcModel, data?: unknown): this; + processModel(model: UcModel, data?: UccConfig.Data): this; } From b7c5a45ef09fcd4a8009b6d1e94c4afdb022ef74 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Tue, 1 Aug 2023 12:58:25 +0700 Subject: [PATCH 26/36] Wrapper for inset formatters --- src/compiler/serialization/mod.ts | 1 + src/compiler/serialization/ucs-compiler.ts | 63 ++++++++++++---- src/compiler/serialization/ucs-function.ts | 17 ++++- .../serialization/ucs-inset-formatter.ts | 73 +++++++++++++++++++ src/compiler/serialization/ucs-lib.ts | 11 ++- src/compiler/serialization/ucs-setup.ts | 3 + 6 files changed, 146 insertions(+), 22 deletions(-) create mode 100644 src/compiler/serialization/ucs-inset-formatter.ts diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 1576b899..9527cdda 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -2,6 +2,7 @@ export * from './ucs-compiler.js'; export * from './ucs-export.signature.js'; export * from './ucs-formatter.js'; export * from './ucs-function.js'; +export * from './ucs-inset-formatter.js'; export * from './ucs-lib.js'; export * from './ucs-models.js'; export * from './ucs-process-bigint.js'; diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 58f0413d..fdb9d0d4 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -16,6 +16,7 @@ import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UccSetup } from '../processor/ucc-setup.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; +import { UcsInsetFormatter, UcsInsetWrapper } from './ucs-inset-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsExports, UcsModels } from './ucs-models.js'; import { ucsProcessDefaults } from './ucs-process-defaults.js'; @@ -57,14 +58,15 @@ export class UcsCompiler format: UcFormatName, target: UcSchema['type'] | UcSchema, formatter: UcsFormatter, + insetWrapper?: UcsInsetWrapper, ): this { const inset = this.currentPresentation as UcInsetName | undefined; const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; if (typeof target === 'object') { - this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, formatter); + this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, formatter, insetWrapper); } else { - this.#typeEntryFor(id, format, target).formatTypeWith(formatter); + this.#typeEntryFor(id, format, target).formatTypeWith(formatter, insetWrapper); } return this; @@ -194,20 +196,32 @@ export class UcsCompiler } #insetFormatterFor>( - inset: UcInsetName, + hostFormat: UcFormatName, + hostSchema: UcSchema, + insetName: UcInsetName, schema: TSchema, - ): [UcFormatName, UcsFormatter] | undefined { - const typeEntry = this.#findTypeEntryFor(`inset:${inset}`, schema.type); - - if (typeEntry) { - const formatter = typeEntry?.formatterFor(schema); - - if (formatter) { - return [typeEntry.format, formatter]; - } + ): UcsInsetFormatter | undefined { + const insetTypeEntry = this.#findTypeEntryFor(`inset:${insetName}`, schema.type); + const insetFormatter = insetTypeEntry?.formatterFor(schema); + const insetWrapper = this.#findTypeEntryFor( + `format:${hostFormat}`, + hostSchema.type, + )?.insetWrapperFor(hostSchema); + + if (insetWrapper) { + return insetWrapper({ + hostFormat, + hostSchema, + insetName, + schema, + formatter: insetFormatter && { + insetFormat: insetTypeEntry!.format, + format: insetFormatter, + }, + }); } - return; + return insetFormatter && { insetFormat: insetTypeEntry!.format, format: insetFormatter }; } } @@ -235,20 +249,33 @@ class UcsTypeEntry = UcSchema>(); + readonly #insetWrappers = new Map(); #typeFormatter: UcsFormatter | undefined; + #typeInsetWrapper?: UcsInsetWrapper | undefined; constructor(schemaIndex: UccSchemaIndex, readonly format: UcFormatName) { this.#schemaIndex = schemaIndex; } - formatTypeWith(formatter: UcsFormatter): void { + formatTypeWith( + formatter: UcsFormatter, + insetWrapper: UcsInsetWrapper | undefined, + ): void { this.#typeFormatter = formatter; + this.#typeInsetWrapper = insetWrapper; } - formatSchemaWith(schema: TSchema, formatter: UcsFormatter): void { + formatSchemaWith( + schema: TSchema, + formatter: UcsFormatter, + insetWrapper: UcsInsetWrapper | undefined, + ): void { const schemaId = this.#schemaIndex.schemaId(schema); this.#schemaFormatters.set(schemaId, formatter); + if (insetWrapper) { + this.#insetWrappers.set(schemaId, insetWrapper); + } } formatterFor(schema: TSchema): UcsFormatter | undefined { @@ -257,4 +284,10 @@ class UcsTypeEntry = UcSchema = UcSc { format, inset, + hostFormat, }: | { format: UcFormatName; inset?: undefined; + hostFormat?: undefined; } | { format?: undefined; inset: UcInsetName; + hostFormat: UcFormatName; }, schema: UcSchema, args: UcsFormatterSignature.AllValues, @@ -60,14 +63,14 @@ export class UcsFunction = UcSc let formatter: UcsFormatter | undefined; if (inset) { - const insetFormatter = lib.insetFormatterFor(inset, schema); + const insetFormatter = lib.insetFormatterFor(hostFormat, this.schema, inset, schema); if (!insetFormatter) { onUnknownInset(schema, inset); } - context = this.#contextFor(insetFormatter[0], inset); - formatter = insetFormatter[1]; + context = this.#contextFor(insetFormatter.insetFormat, inset); + formatter = insetFormatter.format; } else { context = this.#contextFor(format); formatter = lib.formatterFor(format, schema); @@ -205,7 +208,13 @@ class UcsFunction$Context implements UcsFormatterContext { onUnknownInset?: (schema: UcSchema, inset: UcInsetName) => never, onUnknownSchema?: (schema: UcSchema, context: UcsFormatterContext) => never, ): EsSnippet { - return this.#serializer.format({ inset }, schema, args, onUnknownSchema, onUnknownInset); + return this.#serializer.format( + { inset, hostFormat: this.formatName }, + schema, + args, + onUnknownSchema, + onUnknownInset, + ); } toString(): string { diff --git a/src/compiler/serialization/ucs-inset-formatter.ts b/src/compiler/serialization/ucs-inset-formatter.ts new file mode 100644 index 00000000..df1a0029 --- /dev/null +++ b/src/compiler/serialization/ucs-inset-formatter.ts @@ -0,0 +1,73 @@ +import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UcsFormatter } from './ucs-formatter.js'; + +/** + * Inset formatter generates code for type instance formatting. + * + * @typeParam T - Implied data type. + * @typeParam TSchema - Schema type. + */ +export interface UcsInsetFormatter = UcSchema> { + /** + * Inset format name. + */ + readonly insetFormat: UcFormatName; + + /** + * Formats inset. + */ + readonly format: UcsFormatter; +} + +/** + * Wrapper for inset formatters. + * + * Creates inset formatters based on the wrapped ones. May also create a default formatter for insets without + * pre-configured formatters. + * + * @typeParam T - Implied data type. + * @typeParam TSchema - Schema type. + * @param context - Inset formatting context. + * + * @returns Either inset formatter, or nothing for unexpected inset. + */ +export type UcsInsetWrapper = { + createInsetFormatter = UcSchema>( + this: void, + context: UcsInsetContext, + ): UcsInsetFormatter | undefined; +}['createInsetFormatter']; + +/** + * Inset formatting context. + * + * @typeParam T - Implied data type. + * @typeParam TSchema - Schema type. + */ +export interface UcsInsetContext = UcSchema> { + /** + * The name of the host format containing the inset. + */ + readonly hostFormat: UcFormatName; + + /** + * Schema of the value containing the inset. + */ + readonly hostSchema: UcSchema; + + /** + * The name of the target inset. + */ + readonly insetName: UcInsetName; + + /** + * Inset value schema; + */ + readonly schema: TSchema; + + /** + * The inset formatter to wrap, if any. + */ + readonly formatter?: UcsInsetFormatter | undefined; +} diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index 73b49dc2..dffa433f 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -13,6 +13,7 @@ import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; +import { UcsInsetFormatter } from './ucs-inset-formatter.js'; import { UcsModels } from './ucs-models.js'; /** @@ -91,10 +92,12 @@ export class UcsLib { } insetFormatterFor = UcSchema>( + hostFormat: UcFormatName, + hostSchema: UcSchema, inset: UcInsetName, schema: TSchema, - ): [UcFormatName, UcsFormatter] | undefined { - return this.#options.insetFormatterFor?.(inset, schema); + ): UcsInsetFormatter | undefined { + return this.#options.insetFormatterFor?.(hostFormat, hostSchema, inset, schema); } binConst(value: string): EsSymbol { @@ -135,9 +138,11 @@ export namespace UcsLib { insetFormatterFor?>( this: void, + hostFormat: UcFormatName, + hostSchema: UcSchema, inset: UcInsetName, schema: TSchema, - ): [UcFormatName, UcsFormatter] | undefined; + ): UcsInsetFormatter | undefined; createSerializer>( this: void, diff --git a/src/compiler/serialization/ucs-setup.ts b/src/compiler/serialization/ucs-setup.ts index 57722c5c..ccb9e1e5 100644 --- a/src/compiler/serialization/ucs-setup.ts +++ b/src/compiler/serialization/ucs-setup.ts @@ -2,6 +2,7 @@ import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UccSetup } from '../processor/ucc-setup.js'; import { UcsFormatter } from './ucs-formatter.js'; +import { UcsInsetWrapper } from './ucs-inset-formatter.js'; /** * Schema {@link UcsCompiler serializer} setup. @@ -16,6 +17,7 @@ export interface UcsSetup extends UccSetup { * @typeParam format - Name of target format. * @param target - Name or class of target value type, or target schema instance. * @param formatter - Assigned formatter. + * @param insetWrapper - Wrapper of inset formatters to use for target schema. * * @returns `this` instance. */ @@ -23,5 +25,6 @@ export interface UcsSetup extends UccSetup { format: UcFormatName, target: UcSchema['type'] | UcSchema, formatter: UcsFormatter, + insetWrapper?: UcsInsetWrapper, ): this; } From 223406bf14a2f65cc94837470b58a8add3bbbea5 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Tue, 1 Aug 2023 13:17:46 +0700 Subject: [PATCH 27/36] Setup inset wrappers --- src/compiler/serialization/ucs-compiler.ts | 72 +++++++++++++++------- src/compiler/serialization/ucs-setup.ts | 26 +++++++- 2 files changed, 73 insertions(+), 25 deletions(-) diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index fdb9d0d4..be1b5f8f 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -33,6 +33,7 @@ export class UcsCompiler readonly #options: UcsCompiler.Options; readonly #presentations = new Map>(); + readonly #insetWrappers = new Map(); #bootstrapped = false; @@ -58,15 +59,43 @@ export class UcsCompiler format: UcFormatName, target: UcSchema['type'] | UcSchema, formatter: UcsFormatter, - insetWrapper?: UcsInsetWrapper, ): this { const inset = this.currentPresentation as UcInsetName | undefined; const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; if (typeof target === 'object') { - this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, formatter, insetWrapper); + this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, formatter); } else { - this.#typeEntryFor(id, format, target).formatTypeWith(formatter, insetWrapper); + this.#typeEntryFor(id, format, target).formatTypeWith(formatter); + } + + return this; + } + + modifyInsets(format: UcFormatName, wrapper: UcsInsetWrapper): this; + modifyInsets( + format: UcFormatName, + target: UcSchema['type'] | UcSchema, + wrapper: UcsInsetWrapper, + ): this; + + modifyInsets( + format: UcFormatName, + targetOrWrapper: UcSchema['type'] | UcSchema | UcsInsetWrapper, + wrapper?: UcsInsetWrapper, + ): this { + if (wrapper) { + const target = targetOrWrapper as UcSchema['type'] | UcSchema; + const inset = this.currentPresentation as UcInsetName | undefined; + const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; + + if (typeof target === 'object') { + this.#typeEntryFor(id, format, target.type).modifySchemaInsets(target, wrapper); + } else { + this.#typeEntryFor(id, format, target).modifyTypeInsets(wrapper); + } + } else { + this.#insetWrappers.set(format, targetOrWrapper as UcsInsetWrapper); } return this; @@ -203,10 +232,10 @@ export class UcsCompiler ): UcsInsetFormatter | undefined { const insetTypeEntry = this.#findTypeEntryFor(`inset:${insetName}`, schema.type); const insetFormatter = insetTypeEntry?.formatterFor(schema); - const insetWrapper = this.#findTypeEntryFor( - `format:${hostFormat}`, - hostSchema.type, - )?.insetWrapperFor(hostSchema); + const insetWrapper = + this.#findTypeEntryFor(`format:${hostFormat}`, hostSchema.type)?.insetWrapperFor( + hostSchema, + ) ?? this.#insetWrappers.get(hostFormat); if (insetWrapper) { return insetWrapper({ @@ -249,7 +278,7 @@ class UcsTypeEntry = UcSchema>(); - readonly #insetWrappers = new Map(); + readonly #schemaInsetWrappers = new Map(); #typeFormatter: UcsFormatter | undefined; #typeInsetWrapper?: UcsInsetWrapper | undefined; @@ -257,25 +286,24 @@ class UcsTypeEntry = UcSchema, - insetWrapper: UcsInsetWrapper | undefined, - ): void { + formatTypeWith(formatter: UcsFormatter): void { this.#typeFormatter = formatter; - this.#typeInsetWrapper = insetWrapper; } - formatSchemaWith( - schema: TSchema, - formatter: UcsFormatter, - insetWrapper: UcsInsetWrapper | undefined, - ): void { + modifyTypeInsets(wrapper: UcsInsetWrapper): void { + this.#typeInsetWrapper = wrapper; + } + + formatSchemaWith(schema: TSchema, formatter: UcsFormatter): void { const schemaId = this.#schemaIndex.schemaId(schema); this.#schemaFormatters.set(schemaId, formatter); - if (insetWrapper) { - this.#insetWrappers.set(schemaId, insetWrapper); - } + } + + modifySchemaInsets(schema: TSchema, wrapper: UcsInsetWrapper): void { + const schemaId = this.#schemaIndex.schemaId(schema); + + this.#schemaInsetWrappers.set(schemaId, wrapper); } formatterFor(schema: TSchema): UcsFormatter | undefined { @@ -287,7 +315,7 @@ class UcsTypeEntry = UcSchema { * Formatter provided for particular schema takes precedence over the one provided for the type. * * @typeParam T - Implied data type. - * @typeParam format - Name of target format. + * @param format - Name of target format. * @param target - Name or class of target value type, or target schema instance. * @param formatter - Assigned formatter. - * @param insetWrapper - Wrapper of inset formatters to use for target schema. * * @returns `this` instance. */ @@ -25,6 +24,27 @@ export interface UcsSetup extends UccSetup { format: UcFormatName, target: UcSchema['type'] | UcSchema, formatter: UcsFormatter, - insetWrapper?: UcsInsetWrapper, + ): this; + + /** + * Modifies inset formatters for the given `format`. + * + * @param format - Name of target format. + * @param wrapper - Wrapper to apply to matching inset formatters. + */ + modifyInsets(format: UcFormatName, wrapper: UcsInsetWrapper): this; + + /** + * Modifies inset formatters for the given type. + * + * @typeParam T - Implied data type. + * @param format - Name of target format. + * @param target - Name or class of target value type, or target schema instance. + * @param wrapper - Wrapper to apply to matching inset formatters. + */ + modifyInsets( + format: UcFormatName, + target: UcSchema['type'] | UcSchema, + wrapper: UcsInsetWrapper, ): this; } From 18700a0981088928c061c6462ec3dced70c89f67 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Tue, 1 Aug 2023 13:25:26 +0700 Subject: [PATCH 28/36] Proper parameter names --- src/compiler/serialization/ucs-compiler.ts | 20 +++++++++---------- .../serialization/ucs-inset-formatter.ts | 4 ++-- src/compiler/serialization/ucs-setup.ts | 16 +++++++-------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index be1b5f8f..c5719f65 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -72,30 +72,30 @@ export class UcsCompiler return this; } - modifyInsets(format: UcFormatName, wrapper: UcsInsetWrapper): this; + modifyInsets(hostFormat: UcFormatName, wrapper: UcsInsetWrapper): this; modifyInsets( - format: UcFormatName, - target: UcSchema['type'] | UcSchema, + hostFormat: UcFormatName, + host: UcSchema['type'] | UcSchema, wrapper: UcsInsetWrapper, ): this; modifyInsets( - format: UcFormatName, - targetOrWrapper: UcSchema['type'] | UcSchema | UcsInsetWrapper, + hostFormat: UcFormatName, + hostOrWrapper: UcSchema['type'] | UcSchema | UcsInsetWrapper, wrapper?: UcsInsetWrapper, ): this { if (wrapper) { - const target = targetOrWrapper as UcSchema['type'] | UcSchema; + const target = hostOrWrapper as UcSchema['type'] | UcSchema; const inset = this.currentPresentation as UcInsetName | undefined; - const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; + const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${hostFormat}`; if (typeof target === 'object') { - this.#typeEntryFor(id, format, target.type).modifySchemaInsets(target, wrapper); + this.#typeEntryFor(id, hostFormat, target.type).modifySchemaInsets(target, wrapper); } else { - this.#typeEntryFor(id, format, target).modifyTypeInsets(wrapper); + this.#typeEntryFor(id, hostFormat, target).modifyTypeInsets(wrapper); } } else { - this.#insetWrappers.set(format, targetOrWrapper as UcsInsetWrapper); + this.#insetWrappers.set(hostFormat, hostOrWrapper as UcsInsetWrapper); } return this; diff --git a/src/compiler/serialization/ucs-inset-formatter.ts b/src/compiler/serialization/ucs-inset-formatter.ts index df1a0029..de24d5e0 100644 --- a/src/compiler/serialization/ucs-inset-formatter.ts +++ b/src/compiler/serialization/ucs-inset-formatter.ts @@ -33,11 +33,11 @@ export interface UcsInsetFormatter = UcSc * @returns Either inset formatter, or nothing for unexpected inset. */ export type UcsInsetWrapper = { - createInsetFormatter = UcSchema>( + wrapInsetFormatter = UcSchema>( this: void, context: UcsInsetContext, ): UcsInsetFormatter | undefined; -}['createInsetFormatter']; +}['wrapInsetFormatter']; /** * Inset formatting context. diff --git a/src/compiler/serialization/ucs-setup.ts b/src/compiler/serialization/ucs-setup.ts index e3464286..28ecc2f8 100644 --- a/src/compiler/serialization/ucs-setup.ts +++ b/src/compiler/serialization/ucs-setup.ts @@ -27,24 +27,24 @@ export interface UcsSetup extends UccSetup { ): this; /** - * Modifies inset formatters for the given `format`. + * Modifies insets of the given format. * - * @param format - Name of target format. + * @param hostFormat - Name of the format containing insets. * @param wrapper - Wrapper to apply to matching inset formatters. */ - modifyInsets(format: UcFormatName, wrapper: UcsInsetWrapper): this; + modifyInsets(hostFormat: UcFormatName, wrapper: UcsInsetWrapper): this; /** - * Modifies inset formatters for the given type. + * Modifies inset of the given type. * * @typeParam T - Implied data type. - * @param format - Name of target format. - * @param target - Name or class of target value type, or target schema instance. + * @param hostFormat - Name of target format. + * @param host - Name or class of value type, or the schema instance containing insets. * @param wrapper - Wrapper to apply to matching inset formatters. */ modifyInsets( - format: UcFormatName, - target: UcSchema['type'] | UcSchema, + hostFormat: UcFormatName, + host: UcSchema['type'] | UcSchema, wrapper: UcsInsetWrapper, ): this; } From 14859319983bb100623a397b2615dde50ea222c9 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Wed, 2 Aug 2023 18:20:25 +0700 Subject: [PATCH 29/36] Serialize URI params --- .../impl/ucc-processor.constraint-usage.ts | 4 +- src/compiler/serialization/mod.ts | 2 + src/compiler/serialization/ucs-compiler.ts | 73 +++++++----- src/compiler/serialization/ucs-formatter.ts | 2 +- src/compiler/serialization/ucs-function.ts | 2 +- .../serialization/ucs-process-inset.ts | 15 +++ src/compiler/serialization/ucs-setup.ts | 7 +- .../serialization/ucs-support-uri-params.ts | 106 ++++++++++++++++++ src/syntax/formats/charge/mod.ts | 1 + .../formats/charge/uc-charge.lexer.spec.ts | 3 +- src/syntax/formats/charge/uc-charge.lexer.ts | 37 ------ src/syntax/formats/charge/uc-inset-charge.ts | 46 ++++++++ src/syntax/formats/plain-text/mod.ts | 1 + .../formats/plain-text/uc-inset-plain-text.ts | 46 ++++++++ .../formats/plain-text/uc-plain-text.lexer.ts | 37 ------ src/syntax/formats/uri-encoded/mod.ts | 1 + .../uri-encoded/uc-inset-uri-encoded.ts | 55 +++++++++ .../uri-encoded/uc-uri-encoded.lexer.spec.ts | 3 +- .../uri-encoded/uc-uri-encoded.lexer.ts | 46 -------- src/syntax/formats/uri-params/mod.ts | 1 + .../formats/uri-params/uc-inset-uri-params.ts | 38 +++++++ .../uri-params/uc-uri-params.lexer.spec.ts | 7 +- .../formats/uri-params/uc-uri-params.lexer.ts | 29 ----- .../uri-params/uri-params.serializer.spec.ts | 35 ++++++ 24 files changed, 410 insertions(+), 187 deletions(-) create mode 100644 src/compiler/serialization/ucs-process-inset.ts create mode 100644 src/compiler/serialization/ucs-support-uri-params.ts create mode 100644 src/syntax/formats/charge/uc-inset-charge.ts create mode 100644 src/syntax/formats/plain-text/uc-inset-plain-text.ts create mode 100644 src/syntax/formats/uri-encoded/uc-inset-uri-encoded.ts create mode 100644 src/syntax/formats/uri-params/uc-inset-uri-params.ts create mode 100644 src/syntax/formats/uri-params/uri-params.serializer.spec.ts diff --git a/src/compiler/processor/impl/ucc-processor.constraint-usage.ts b/src/compiler/processor/impl/ucc-processor.constraint-usage.ts index 089dcf6c..f31414fd 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-usage.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-usage.ts @@ -15,8 +15,8 @@ export class UccProcessor$ConstraintUsage this.#schema = schema; } - issue(issues: UccProcessor$ConstraintIssue): void { - this.#issues.push(issues); + issue(issue: UccProcessor$ConstraintIssue): void { + this.#issues.push(issue); } async apply(): Promise { diff --git a/src/compiler/serialization/mod.ts b/src/compiler/serialization/mod.ts index 9527cdda..4507d84b 100644 --- a/src/compiler/serialization/mod.ts +++ b/src/compiler/serialization/mod.ts @@ -8,6 +8,7 @@ export * from './ucs-models.js'; export * from './ucs-process-bigint.js'; export * from './ucs-process-boolean.js'; export * from './ucs-process-defaults.js'; +export * from './ucs-process-inset.js'; export * from './ucs-process-integer.js'; export * from './ucs-process-list.js'; export * from './ucs-process-map.js'; @@ -18,4 +19,5 @@ export * from './ucs-process-unknown.js'; export * from './ucs-setup.js'; export * from './ucs-support-plain-text.js'; export * from './ucs-support-uri-encoded.js'; +export * from './ucs-support-uri-params.js'; export * from './ucs-writer.class.js'; diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index c5719f65..e68e8876 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -13,7 +13,6 @@ import { UccCapability } from '../processor/ucc-capability.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; -import { UccSetup } from '../processor/ucc-setup.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsInsetFormatter, UcsInsetWrapper } from './ucs-inset-formatter.js'; @@ -32,7 +31,11 @@ export class UcsCompiler implements UcsSetup { readonly #options: UcsCompiler.Options; - readonly #presentations = new Map>(); + readonly #presentations = new Map< + UcsPresentationId, + Map + >(); + readonly #insetWrappers = new Map(); #bootstrapped = false; @@ -58,15 +61,15 @@ export class UcsCompiler formatWith( format: UcFormatName, target: UcSchema['type'] | UcSchema, - formatter: UcsFormatter, + formatter?: UcsFormatter, ): this { const inset = this.currentPresentation as UcInsetName | undefined; const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${format}`; if (typeof target === 'object') { - this.#typeEntryFor(id, format, target.type).formatSchemaWith(target, formatter); + this.#presentationFor(id, format, target.type).formatSchemaWith(target, formatter); } else { - this.#typeEntryFor(id, format, target).formatTypeWith(formatter); + this.#presentationFor(id, format, target).formatTypeWith(formatter); } return this; @@ -90,9 +93,9 @@ export class UcsCompiler const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${hostFormat}`; if (typeof target === 'object') { - this.#typeEntryFor(id, hostFormat, target.type).modifySchemaInsets(target, wrapper); + this.#presentationFor(id, hostFormat, target.type).modifySchemaInsets(target, wrapper); } else { - this.#typeEntryFor(id, hostFormat, target).modifyTypeInsets(wrapper); + this.#presentationFor(id, hostFormat, target).modifyTypeInsets(wrapper); } } else { this.#insetWrappers.set(hostFormat, hostOrWrapper as UcsInsetWrapper); @@ -101,11 +104,11 @@ export class UcsCompiler return this; } - #typeEntryFor>( + #presentationFor>( id: UcsPresentationId, format: UcFormatName, type: TSchema['type'], - ): UcsTypeEntry { + ): UcsPresentationEntry { let perType = this.#presentations.get(id); if (!perType) { @@ -113,21 +116,21 @@ export class UcsCompiler this.#presentations.set(id, perType); } - let typeEntry = perType.get(type) as UcsTypeEntry | undefined; + let typeEntry = perType.get(type) as UcsPresentationEntry | undefined; if (!typeEntry) { - typeEntry = new UcsTypeEntry(this.schemaIndex, format); + typeEntry = new UcsPresentationEntry(this.schemaIndex, format); perType.set(type, typeEntry); } return typeEntry; } - #findTypeEntryFor>( + #findPresentationFor>( id: UcsPresentationId, type: TSchema['type'], - ): UcsTypeEntry | undefined { - return this.#presentations.get(id)?.get(type) as UcsTypeEntry | undefined; + ): UcsPresentationEntry | undefined { + return this.#presentations.get(id)?.get(type) as UcsPresentationEntry | undefined; } /** @@ -219,7 +222,7 @@ export class UcsCompiler format: UcFormatName, schema: TSchema, ): UcsFormatter | undefined { - return this.#findTypeEntryFor(`format:${format}`, schema.type)?.formatterFor( + return this.#findPresentationFor(`format:${format}`, schema.type)?.formatterFor( schema, ); } @@ -230,10 +233,20 @@ export class UcsCompiler insetName: UcInsetName, schema: TSchema, ): UcsInsetFormatter | undefined { - const insetTypeEntry = this.#findTypeEntryFor(`inset:${insetName}`, schema.type); - const insetFormatter = insetTypeEntry?.formatterFor(schema); + const insetPresentationEntry = this.#findPresentationFor( + `inset:${insetName}`, + schema.type, + ); + let insetFormatter: UcsFormatter | undefined; + + if (insetPresentationEntry) { + insetFormatter = + insetPresentationEntry.formatterFor(schema) + ?? this.#formatterFor(insetPresentationEntry.format, schema); + } + const insetWrapper = - this.#findTypeEntryFor(`format:${hostFormat}`, hostSchema.type)?.insetWrapperFor( + this.#findPresentationFor(`format:${hostFormat}`, hostSchema.type)?.insetWrapperFor( hostSchema, ) ?? this.#insetWrappers.get(hostFormat); @@ -244,13 +257,15 @@ export class UcsCompiler insetName, schema, formatter: insetFormatter && { - insetFormat: insetTypeEntry!.format, + insetFormat: insetPresentationEntry!.format, format: insetFormatter, }, }); } - return insetFormatter && { insetFormat: insetTypeEntry!.format, format: insetFormatter }; + return ( + insetFormatter && { insetFormat: insetPresentationEntry!.format, format: insetFormatter } + ); } } @@ -260,7 +275,7 @@ export namespace UcsCompiler { readonly presentations?: UcPresentationName | readonly UcPresentationName[] | undefined; readonly capabilities?: | UccCapability - | readonly UccCapability[] + | readonly UccCapability[] | undefined; readonly models: TModels; readonly features?: UccFeature | readonly UccFeature[] | undefined; @@ -274,7 +289,7 @@ export namespace UcsCompiler { type UcsPresentationId = `format:${UcFormatName}` | `inset:${UcInsetName}`; -class UcsTypeEntry = UcSchema> { +class UcsPresentationEntry = UcSchema> { readonly #schemaIndex: UccSchemaIndex; readonly #schemaFormatters = new Map>(); @@ -286,18 +301,22 @@ class UcsTypeEntry = UcSchema): void { - this.#typeFormatter = formatter; + formatTypeWith(formatter: UcsFormatter | undefined): void { + if (formatter) { + this.#typeFormatter = formatter; + } } modifyTypeInsets(wrapper: UcsInsetWrapper): void { this.#typeInsetWrapper = wrapper; } - formatSchemaWith(schema: TSchema, formatter: UcsFormatter): void { - const schemaId = this.#schemaIndex.schemaId(schema); + formatSchemaWith(schema: TSchema, formatter: UcsFormatter | undefined): void { + if (formatter) { + const schemaId = this.#schemaIndex.schemaId(schema); - this.#schemaFormatters.set(schemaId, formatter); + this.#schemaFormatters.set(schemaId, formatter); + } } modifySchemaInsets(schema: TSchema, wrapper: UcsInsetWrapper): void { diff --git a/src/compiler/serialization/ucs-formatter.ts b/src/compiler/serialization/ucs-formatter.ts index 2980bc33..c52d8188 100644 --- a/src/compiler/serialization/ucs-formatter.ts +++ b/src/compiler/serialization/ucs-formatter.ts @@ -18,7 +18,7 @@ export type UcsFormatter = UcSc args: UcsFormatterSignature.AllValues, schema: TSchema, context: UcsFormatterContext, - ): EsSnippet | undefined; + ): EsSnippet; }['format']; export interface UcsFormatterContext { diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index 28a49e95..dd60feac 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -154,7 +154,7 @@ function UcsFunction$onUnknownSchema(schema: UcSchema, context: UcsFormatterCont function UcsFunction$onUnknownInset(schema: UcSchema, inset: UcInsetName): never { throw new UnsupportedUcSchemaError( schema, - `Can not serialize inset "${inset} of type "${ucModelName(schema)}"`, + `Can not serialize inset "${inset}" of type "${ucModelName(schema)}"`, ); } diff --git a/src/compiler/serialization/ucs-process-inset.ts b/src/compiler/serialization/ucs-process-inset.ts new file mode 100644 index 00000000..c1283be5 --- /dev/null +++ b/src/compiler/serialization/ucs-process-inset.ts @@ -0,0 +1,15 @@ +import { UcFormatName } from '../../schema/uc-presentations.js'; +import { UccConfig } from '../processor/ucc-config.js'; +import { UcsSetup } from './ucs-setup.js'; + +export function ucsProcessInset(setup: UcsSetup): UccConfig { + return { + configureSchema(schema, { format }) { + setup.formatWith(format, schema); + }, + }; +} + +export interface UcsInsetOptions { + readonly format: UcFormatName; +} diff --git a/src/compiler/serialization/ucs-setup.ts b/src/compiler/serialization/ucs-setup.ts index 28ecc2f8..694241a2 100644 --- a/src/compiler/serialization/ucs-setup.ts +++ b/src/compiler/serialization/ucs-setup.ts @@ -9,7 +9,7 @@ import { UcsInsetWrapper } from './ucs-inset-formatter.js'; */ export interface UcsSetup extends UccSetup { /** - * Assigns formatter to use for `target` value type or schema. + * Assigns formatter or format to use for `target` value type or schema. * * Formatter provided for particular schema takes precedence over the one provided for the type. * @@ -18,12 +18,15 @@ export interface UcsSetup extends UccSetup { * @param target - Name or class of target value type, or target schema instance. * @param formatter - Assigned formatter. * + * When omitted, the given `format` is assigned instead to {@link currentPresentation current inset}, given the + * formatter for that `format` is assigned somewhere else. + * * @returns `this` instance. */ formatWith( format: UcFormatName, target: UcSchema['type'] | UcSchema, - formatter: UcsFormatter, + formatter?: UcsFormatter, ): this; /** diff --git a/src/compiler/serialization/ucs-support-uri-params.ts b/src/compiler/serialization/ucs-support-uri-params.ts new file mode 100644 index 00000000..7e06bfed --- /dev/null +++ b/src/compiler/serialization/ucs-support-uri-params.ts @@ -0,0 +1,106 @@ +import { EsCallable, EsSnippet, EsVarKind, EsVarSymbol, esMemberAccessor, esline } from 'esgen'; +import { encodeURIPart } from 'httongue'; +import { COMPILER_MODULE } from '../../impl/module-names.js'; +import { UcMap } from '../../schema/map/uc-map.js'; +import { ucOptional } from '../../schema/uc-optional.js'; +import { UcSchema } from '../../schema/uc-schema.js'; +import { UccCapability } from '../processor/ucc-capability.js'; +import { UcsInsetContext, UcsInsetFormatter } from './ucs-inset-formatter.js'; +import { UcsLib } from './ucs-lib.js'; +import { UcsSetup } from './ucs-setup.js'; + +export function ucsSupportURIParams(activation: UccCapability.Activation): void { + activation.onConstraint( + { + processor: 'serializer', + use: 'ucsProcessMap', + from: COMPILER_MODULE, + }, + ({ setup }) => { + setup + .formatWith( + 'uriParams', + 'map', + ({ writer, value }, schema: UcMap.Schema, cx) => (code, scope) => { + const lib = scope.get(UcsLib); + const { entries } = schema; + const entriesWritten = new EsVarSymbol('entriesWritten'); + const currentKey = new EsVarSymbol('currentKey'); + const writeKey = new EsCallable({}).lambda( + () => code => { + code + .write(esline`await ${writer}.ready;`) + .write( + esline`${writer}.write(${entriesWritten}++ ? ${currentKey} : ${currentKey}.slice(1));`, + ); + }, + { + async: true, + }, + ); + + code.write( + entriesWritten.declare({ as: EsVarKind.Let, value: () => '0' }), + currentKey.declare(), + esline`${writer}.data.writeKey = ${writeKey};`, + ); + + for (const [entryKey, entrySchema] of Object.entries(entries)) { + const keyConst = lib.binConst(`&${encodeURIPart(entryKey)}=`); + const writeEntry = + (schema: UcSchema, value: EsSnippet): EsSnippet => code => { + code.write( + esline`${currentKey} = ${keyConst};`, + cx.formatInset('uriParam', schema, { + writer, + value, + asItem: '0', + }), + ); + }; + + if (entrySchema.optional) { + const currentValue = new EsVarSymbol(entryKey); + + code + .write( + currentValue.declare({ + value: () => esline`${value}${esMemberAccessor(entryKey).accessor}`, + }), + ) + .write(esline`if (${currentValue} !== undefined) {`) + .indent(writeEntry(ucOptional(entrySchema, false), currentValue)) + .write('}'); + } else { + code.write( + writeEntry(entrySchema, esline`${value}${esMemberAccessor(entryKey).accessor}`), + ); + } + } + }, + ) + .modifyInsets('uriParams', 'map', ucsModifyURIParam); + }, + ); +} + +function ucsModifyURIParam>({ + formatter, +}: UcsInsetContext): UcsInsetFormatter | undefined { + if (!formatter) { + return; + } + + const { insetFormat, format } = formatter; + + return { + insetFormat, + format(args, schema, cx) { + const { writer } = args; + + return code => { + code.write(esline`await ${writer}.data.writeKey();`, format(args, schema, cx)); + }; + }, + }; +} diff --git a/src/syntax/formats/charge/mod.ts b/src/syntax/formats/charge/mod.ts index dd76b3e8..50f30904 100644 --- a/src/syntax/formats/charge/mod.ts +++ b/src/syntax/formats/charge/mod.ts @@ -1 +1,2 @@ export * from './uc-charge.lexer.js'; +export * from './uc-inset-charge.js'; diff --git a/src/syntax/formats/charge/uc-charge.lexer.spec.ts b/src/syntax/formats/charge/uc-charge.lexer.spec.ts index 9880f4a4..46082afd 100644 --- a/src/syntax/formats/charge/uc-charge.lexer.spec.ts +++ b/src/syntax/formats/charge/uc-charge.lexer.spec.ts @@ -32,7 +32,8 @@ import { UcToken, } from '../../uc-token.js'; import { UcURIParamsLexer } from '../uri-params/uc-uri-params.lexer.js'; -import { UcChargeLexer, ucInsetCharge } from './uc-charge.lexer.js'; +import { UcChargeLexer } from './uc-charge.lexer.js'; +import { ucInsetCharge } from './uc-inset-charge.js'; describe('UcChargeLexer', () => { let lexer: UcChargeLexer; diff --git a/src/syntax/formats/charge/uc-charge.lexer.ts b/src/syntax/formats/charge/uc-charge.lexer.ts index 33c8258f..2dce0523 100644 --- a/src/syntax/formats/charge/uc-charge.lexer.ts +++ b/src/syntax/formats/charge/uc-charge.lexer.ts @@ -1,6 +1,3 @@ -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; -import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { scanUcTokens } from '../../scan-uc-tokens.js'; import { UcLexer } from '../../uc-lexer.js'; import { @@ -245,37 +242,3 @@ export class UcChargeLexer implements UcLexer { const UC_TOKEN_PATTERN = /([\r\n!#$&'()*+,/:;=?@[\]])/; const UC_FIRST_NON_PAD_PATTERN = /[^ \t]/; const UC_TRAILING_PADS_PATTERN = /[ \t]+$/; - -/** - * Enables processing of inset encoded with {@link UcChargeLexer URI Charge Notation}. - * - * @param options - Lexer options. - * - * @returns Schema constraints. - */ -export function ucInsetCharge(options?: { - /** - * Whether to decode _plus sign_ (`"+" (U+002B)`) as {@link UC_TOKEN_PREFIX_SPACE space padding}. - * - * @defaultValue `false` - */ - readonly plusAsSpace?: boolean | undefined; -}): UcOmniConstraints; - -export function ucInsetCharge({ - plusAsSpace, -}: { - readonly plusAsSpace?: boolean | undefined; -} = {}): UcOmniConstraints { - return { - deserializer: { - use: 'ucdProcessInset', - from: COMPILER_MODULE, - with: { - lexer: 'UcChargeLexer', - from: CHURI_MODULE, - method: plusAsSpace ? 'plusAsSpace' : undefined, - } satisfies UcdInsetOptions, - }, - }; -} diff --git a/src/syntax/formats/charge/uc-inset-charge.ts b/src/syntax/formats/charge/uc-inset-charge.ts new file mode 100644 index 00000000..76ae64b8 --- /dev/null +++ b/src/syntax/formats/charge/uc-inset-charge.ts @@ -0,0 +1,46 @@ +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; +import { UcsInsetOptions } from '../../../compiler/serialization/ucs-process-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; + +/** + * Enables processing of inset encoded with {@link UcChargeLexer URI Charge Notation}. + * + * @param options - Lexer options. + * + * @returns Schema constraints. + */ + +export function ucInsetCharge(options?: { + /** + * Whether to decode _plus sign_ (`"+" (U+002B)`) as {@link UC_TOKEN_PREFIX_SPACE space padding}. + * + * @defaultValue `false` + */ + readonly plusAsSpace?: boolean | undefined; +}): UcOmniConstraints; + +export function ucInsetCharge({ + plusAsSpace, +}: { + readonly plusAsSpace?: boolean | undefined; +} = {}): UcOmniConstraints { + return { + deserializer: { + use: 'ucdProcessInset', + from: COMPILER_MODULE, + with: { + lexer: 'UcChargeLexer', + from: CHURI_MODULE, + method: plusAsSpace ? 'plusAsSpace' : undefined, + } satisfies UcdInsetOptions, + }, + serializer: { + use: 'ucsProcessInset', + from: COMPILER_MODULE, + with: { + format: 'charge', + } satisfies UcsInsetOptions, + }, + }; +} diff --git a/src/syntax/formats/plain-text/mod.ts b/src/syntax/formats/plain-text/mod.ts index 2d19a41f..9d678497 100644 --- a/src/syntax/formats/plain-text/mod.ts +++ b/src/syntax/formats/plain-text/mod.ts @@ -1 +1,2 @@ +export * from './uc-inset-plain-text.js'; export * from './uc-plain-text.lexer.js'; diff --git a/src/syntax/formats/plain-text/uc-inset-plain-text.ts b/src/syntax/formats/plain-text/uc-inset-plain-text.ts new file mode 100644 index 00000000..25bd1fb2 --- /dev/null +++ b/src/syntax/formats/plain-text/uc-inset-plain-text.ts @@ -0,0 +1,46 @@ +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; +import { UcsInsetOptions } from '../../../compiler/serialization/ucs-process-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; + +/** + * Enables inset processing as {@link UcPlainTextLexer plain text}. + * + * @param options - Lexer options. + * + * @returns Schema constraints. + */ + +export function ucInsetPlainText(options?: { + /** + * Whether to emit a raw string rather quoted string. + * + * @defaultValue `false`. + */ + readonly raw?: boolean | undefined; +}): UcOmniConstraints; + +export function ucInsetPlainText({ + raw, +}: { + readonly raw?: boolean | undefined; +} = {}): UcOmniConstraints { + return { + deserializer: { + use: 'ucdProcessInset', + from: COMPILER_MODULE, + with: { + lexer: 'UcPlainTextLexer', + from: CHURI_MODULE, + args: raw ? [`true`] : undefined, + } satisfies UcdInsetOptions, + }, + serializer: { + use: 'ucsProcessInset', + from: COMPILER_MODULE, + with: { + format: 'plainText', + } satisfies UcsInsetOptions, + }, + }; +} diff --git a/src/syntax/formats/plain-text/uc-plain-text.lexer.ts b/src/syntax/formats/plain-text/uc-plain-text.lexer.ts index 20932a65..a08b7a99 100644 --- a/src/syntax/formats/plain-text/uc-plain-text.lexer.ts +++ b/src/syntax/formats/plain-text/uc-plain-text.lexer.ts @@ -1,6 +1,3 @@ -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; -import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { UcLexer } from '../../uc-lexer.js'; import { UC_TOKEN_APOSTROPHE, UcToken } from '../../uc-token.js'; @@ -36,37 +33,3 @@ export class UcPlainTextLexer implements UcLexer { flush(): void {} } - -/** - * Enables inset processing as {@link UcPlainTextLexer plain text}. - * - * @param options - Lexer options. - * - * @returns Schema constraints. - */ -export function ucInsetPlainText(options?: { - /** - * Whether to emit a raw string rather quoted string. - * - * @defaultValue `false`. - */ - readonly raw?: boolean | undefined; -}): UcOmniConstraints; - -export function ucInsetPlainText({ - raw, -}: { - readonly raw?: boolean | undefined; -} = {}): UcOmniConstraints { - return { - deserializer: { - use: 'ucdProcessInset', - from: COMPILER_MODULE, - with: { - lexer: 'UcPlainTextLexer', - from: CHURI_MODULE, - args: raw ? [`true`] : undefined, - } satisfies UcdInsetOptions, - }, - }; -} diff --git a/src/syntax/formats/uri-encoded/mod.ts b/src/syntax/formats/uri-encoded/mod.ts index b5bb4a83..5abb17c8 100644 --- a/src/syntax/formats/uri-encoded/mod.ts +++ b/src/syntax/formats/uri-encoded/mod.ts @@ -1 +1,2 @@ +export * from './uc-inset-uri-encoded.js'; export * from './uc-uri-encoded.lexer.js'; diff --git a/src/syntax/formats/uri-encoded/uc-inset-uri-encoded.ts b/src/syntax/formats/uri-encoded/uc-inset-uri-encoded.ts new file mode 100644 index 00000000..7b041854 --- /dev/null +++ b/src/syntax/formats/uri-encoded/uc-inset-uri-encoded.ts @@ -0,0 +1,55 @@ +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; +import { UcsInsetOptions } from '../../../compiler/serialization/ucs-process-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; + +/** + * Enables inset processing as {@link UcPlainTextLexer URI-encoded text}. + * + * @param options - Lexer options. + * + * @returns Schema constraints. + */ + +export function ucInsetURIEncoded(options?: { + /** + * Whether to decode _plus sign_ (`"+" (U+002B)`) as {@link UC_TOKEN_PREFIX_SPACE space padding}. + * + * @defaultValue `false` + */ + readonly plusAsSpace?: boolean | undefined; + /** + * Whether to emit a raw string rather quoted string. + * + * @defaultValue `false`. + */ + readonly raw?: boolean | undefined; +}): UcOmniConstraints; + +export function ucInsetURIEncoded({ + plusAsSpace, + raw, +}: { + readonly plusAsSpace?: boolean | undefined; + readonly raw?: boolean | undefined; +} = {}): UcOmniConstraints { + return { + deserializer: { + use: 'ucdProcessInset', + from: COMPILER_MODULE, + with: { + lexer: 'UcURIEncodedLexer', + from: CHURI_MODULE, + method: plusAsSpace ? 'plusAsSpace' : undefined, + args: raw ? [`true`] : undefined, + } satisfies UcdInsetOptions, + }, + serializer: { + use: 'ucsProcessInset', + from: COMPILER_MODULE, + with: { + format: 'uriEncoded', + } satisfies UcsInsetOptions, + }, + }; +} diff --git a/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts index d65f5830..6848a3b6 100644 --- a/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts +++ b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.spec.ts @@ -9,7 +9,8 @@ import { readChunks } from '../../../spec/read-chunks.js'; import { scanUcTokens } from '../../scan-uc-tokens.js'; import { UC_TOKEN_APOSTROPHE, UcToken } from '../../uc-token.js'; import { UcURIParamsLexer } from '../uri-params/uc-uri-params.lexer.js'; -import { UcURIEncodedLexer, ucInsetURIEncoded } from './uc-uri-encoded.lexer.js'; +import { ucInsetURIEncoded } from './uc-inset-uri-encoded.js'; +import { UcURIEncodedLexer } from './uc-uri-encoded.lexer.js'; describe('UcURIEncodedLexer', () => { it('decodes percent-encoded entities', () => { diff --git a/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts index f23be39c..dac09b9f 100644 --- a/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts +++ b/src/syntax/formats/uri-encoded/uc-uri-encoded.lexer.ts @@ -1,7 +1,4 @@ import { decodeURISearchPart } from 'httongue'; -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; -import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { UcLexer } from '../../uc-lexer.js'; import { UC_TOKEN_APOSTROPHE, UcToken } from '../../uc-token.js'; @@ -82,46 +79,3 @@ export class UcURIEncodedLexer implements UcLexer { } const PERCENT_ENCODED_TAIL_PATTERN = /%[\da-fA-F]{0,2}$/; - -/** - * Enables inset processing as {@link UcPlainTextLexer URI-encoded text}. - * - * @param options - Lexer options. - * - * @returns Schema constraints. - */ -export function ucInsetURIEncoded(options?: { - /** - * Whether to decode _plus sign_ (`"+" (U+002B)`) as {@link UC_TOKEN_PREFIX_SPACE space padding}. - * - * @defaultValue `false` - */ - readonly plusAsSpace?: boolean | undefined; - /** - * Whether to emit a raw string rather quoted string. - * - * @defaultValue `false`. - */ - readonly raw?: boolean | undefined; -}): UcOmniConstraints; - -export function ucInsetURIEncoded({ - plusAsSpace, - raw, -}: { - readonly plusAsSpace?: boolean | undefined; - readonly raw?: boolean | undefined; -} = {}): UcOmniConstraints { - return { - deserializer: { - use: 'ucdProcessInset', - from: COMPILER_MODULE, - with: { - lexer: 'UcURIEncodedLexer', - from: CHURI_MODULE, - method: plusAsSpace ? 'plusAsSpace' : undefined, - args: raw ? [`true`] : undefined, - } satisfies UcdInsetOptions, - }, - }; -} diff --git a/src/syntax/formats/uri-params/mod.ts b/src/syntax/formats/uri-params/mod.ts index cffd32e4..6acfd855 100644 --- a/src/syntax/formats/uri-params/mod.ts +++ b/src/syntax/formats/uri-params/mod.ts @@ -1 +1,2 @@ +export * from './uc-inset-uri-params.js'; export * from './uc-uri-params.lexer.js'; diff --git a/src/syntax/formats/uri-params/uc-inset-uri-params.ts b/src/syntax/formats/uri-params/uc-inset-uri-params.ts new file mode 100644 index 00000000..b0807ede --- /dev/null +++ b/src/syntax/formats/uri-params/uc-inset-uri-params.ts @@ -0,0 +1,38 @@ +import { esStringLiteral } from 'esgen'; +import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; +import { UcsInsetOptions } from '../../../compiler/serialization/ucs-process-inset.js'; +import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; +import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; + +/** + * Enables inset processing as {@link UcURIParamsLexer URI params}. + * + * E.g. for `application/x-www-form-urlencoded` processing. + * + * @param splitter - Parameters splitter character. + * + * Either `'&'` (by default), or `';'`. + * + * @returns Schema constraints. + */ + +export function ucInsetURIParams(splitter?: '&' | ';'): UcOmniConstraints { + return { + deserializer: { + use: 'ucdProcessInset', + from: COMPILER_MODULE, + with: { + lexer: 'UcURIParamsLexer', + from: CHURI_MODULE, + args: splitter ? [esStringLiteral(splitter)] : undefined, + } satisfies UcdInsetOptions, + }, + serializer: { + use: 'ucsProcessInset', + from: COMPILER_MODULE, + with: { + format: 'uriParams', + } satisfies UcsInsetOptions, + }, + }; +} diff --git a/src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts b/src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts index 84739c56..c217d204 100644 --- a/src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts +++ b/src/syntax/formats/uri-params/uc-uri-params.lexer.spec.ts @@ -16,9 +16,10 @@ import { UC_TOKEN_OPENING_PARENTHESIS, UcToken, } from '../../uc-token.js'; -import { ucInsetPlainText } from '../plain-text/uc-plain-text.lexer.js'; -import { ucInsetURIEncoded } from '../uri-encoded/uc-uri-encoded.lexer.js'; -import { UcURIParamsLexer, ucInsetURIParams } from './uc-uri-params.lexer.js'; +import { ucInsetPlainText } from '../plain-text/uc-inset-plain-text.js'; +import { ucInsetURIEncoded } from '../uri-encoded/uc-inset-uri-encoded.js'; +import { UcURIParamsLexer } from './uc-uri-params.lexer.js'; +import { ucInsetURIParams } from './uc-inset-uri-params.js'; describe('UcURIParamsLexer', () => { it('recognizes query params', () => { diff --git a/src/syntax/formats/uri-params/uc-uri-params.lexer.ts b/src/syntax/formats/uri-params/uc-uri-params.lexer.ts index 7a7586c0..59d94cf2 100644 --- a/src/syntax/formats/uri-params/uc-uri-params.lexer.ts +++ b/src/syntax/formats/uri-params/uc-uri-params.lexer.ts @@ -1,8 +1,4 @@ -import { esStringLiteral } from 'esgen'; import { decodeURISearchPart } from 'httongue'; -import { UcdInsetOptions } from '../../../compiler/deserialization/ucd-process-inset.js'; -import { CHURI_MODULE, COMPILER_MODULE } from '../../../impl/module-names.js'; -import { UcOmniConstraints } from '../../../schema/uc-constraints.js'; import { UcLexer } from '../../uc-lexer.js'; import { UC_TOKEN_CLOSING_PARENTHESIS, @@ -144,28 +140,3 @@ const CHURI_DELIMITER_PATTERNS = { '&': /=|&+/, ';': /=|;+/, }; - -/** - * Enables inset processing as {@link UcURIParamsLexer URI params}. - * - * E.g. for `application/x-www-form-urlencoded` processing. - * - * @param splitter - Parameters splitter character. - * - * Either `'&'` (by default), or `';'`. - * - * @returns Schema constraints. - */ -export function ucInsetURIParams(splitter?: '&' | ';'): UcOmniConstraints { - return { - deserializer: { - use: 'ucdProcessInset', - from: COMPILER_MODULE, - with: { - lexer: 'UcURIParamsLexer', - from: CHURI_MODULE, - args: splitter ? [esStringLiteral(splitter)] : undefined, - } satisfies UcdInsetOptions, - }, - }; -} diff --git a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts new file mode 100644 index 00000000..6e85c016 --- /dev/null +++ b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts @@ -0,0 +1,35 @@ +import { describe, expect, it } from '@jest/globals'; +import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; +import { ucsSupportURIEncoded } from '../../../compiler/serialization/ucs-support-uri-encoded.js'; +import { ucsSupportURIParams } from '../../../compiler/serialization/ucs-support-uri-params.js'; +import { ucMap } from '../../../schema/map/uc-map.js'; +import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { TextOutStream } from '../../../spec/text-out-stream.js'; +import { ucInsetURIEncoded } from '../uri-encoded/uc-inset-uri-encoded.js'; + +describe('URI params serializer', () => { + it('serializes bigint', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucBigInt({ + string: 'serialize', + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: -13n })), + ).resolves.toBe('test=-0n13'); + }); +}); From 5c627b6eca9da8eb5f814a888a0fccec340e5bb2 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 12:23:15 +0700 Subject: [PATCH 30/36] Customize `UcsWriter` per format --- .../impl/ucs-presentation-config.ts | 58 +++++++ src/compiler/serialization/ucs-compiler.ts | 119 +++++--------- src/compiler/serialization/ucs-function.ts | 22 ++- src/compiler/serialization/ucs-lib.ts | 36 ++-- src/compiler/serialization/ucs-setup.ts | 15 ++ .../serialization/ucs-support-uri-params.ts | 11 ++ .../serialization/ucs-writer.class.ts | 19 ++- .../string/uc-string.serializer.spec.ts | 25 +-- src/serializer/ucs-write-string.ts | 12 +- src/serializer/ucs-writer.ts | 20 ++- .../uri-params/uri-params.serializer.spec.ts | 154 ++++++++++++++++++ 11 files changed, 373 insertions(+), 118 deletions(-) create mode 100644 src/compiler/serialization/impl/ucs-presentation-config.ts diff --git a/src/compiler/serialization/impl/ucs-presentation-config.ts b/src/compiler/serialization/impl/ucs-presentation-config.ts new file mode 100644 index 00000000..5ed2b96c --- /dev/null +++ b/src/compiler/serialization/impl/ucs-presentation-config.ts @@ -0,0 +1,58 @@ +import { UcFormatName } from '../../../schema/uc-presentations.js'; +import { UcSchema } from '../../../schema/uc-schema.js'; +import { UccSchemaIndex } from '../../processor/ucc-schema-index.js'; +import { UcsFormatter } from '../ucs-formatter.js'; +import { UcsInsetWrapper } from '../ucs-inset-formatter.js'; +import { CreateUcsWriterExpr } from '../ucs-writer.class.js'; + +export class UcsPresentationConfig = UcSchema> { + + readonly #schemaIndex: UccSchemaIndex; + readonly #schemaFormatters = new Map>(); + readonly #schemaInsetWrappers = new Map(); + #typeFormatter: UcsFormatter | undefined; + #typeInsetWrapper?: UcsInsetWrapper | undefined; + insetWrapper?: UcsInsetWrapper | undefined; + createWriter?: CreateUcsWriterExpr | undefined; + + constructor(schemaIndex: UccSchemaIndex, readonly format: UcFormatName) { + this.#schemaIndex = schemaIndex; + } + + formatTypeWith(formatter: UcsFormatter | undefined): void { + if (formatter) { + this.#typeFormatter = formatter; + } + } + + modifyTypeInsets(wrapper: UcsInsetWrapper): void { + this.#typeInsetWrapper = wrapper; + } + + formatSchemaWith(schema: TSchema, formatter: UcsFormatter | undefined): void { + if (formatter) { + const schemaId = this.#schemaIndex.schemaId(schema); + + this.#schemaFormatters.set(schemaId, formatter); + } + } + + modifySchemaInsets(schema: TSchema, wrapper: UcsInsetWrapper): void { + const schemaId = this.#schemaIndex.schemaId(schema); + + this.#schemaInsetWrappers.set(schemaId, wrapper); + } + + formatterFor(schema: TSchema): UcsFormatter | undefined { + const schemaId = this.#schemaIndex.schemaId(schema); + + return this.#schemaFormatters.get(schemaId) ?? this.#typeFormatter; + } + + insetWrapperFor(schema: TSchema): UcsInsetWrapper | undefined { + const schemaId = this.#schemaIndex.schemaId(schema); + + return this.#schemaInsetWrappers.get(schemaId) ?? this.#typeInsetWrapper; + } + +} diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index e68e8876..6df1c857 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -12,7 +12,7 @@ import { UcDataType, UcSchema } from '../../schema/uc-schema.js'; import { UccCapability } from '../processor/ucc-capability.js'; import { UccFeature } from '../processor/ucc-feature.js'; import { UccProcessor } from '../processor/ucc-processor.js'; -import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; +import { UcsPresentationConfig } from './impl/ucs-presentation-config.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsInsetFormatter, UcsInsetWrapper } from './ucs-inset-formatter.js'; @@ -20,6 +20,7 @@ import { UcsLib } from './ucs-lib.js'; import { UcsExports, UcsModels } from './ucs-models.js'; import { ucsProcessDefaults } from './ucs-process-defaults.js'; import { UcsSetup } from './ucs-setup.js'; +import { CreateUcsWriterExpr } from './ucs-writer.class.js'; /** * Compiler of schema {@link churi!UcSerializer serializers}. @@ -33,10 +34,10 @@ export class UcsCompiler readonly #options: UcsCompiler.Options; readonly #presentations = new Map< UcsPresentationId, - Map + Map >(); - readonly #insetWrappers = new Map(); + readonly #formatConfigs = new Map(); #bootstrapped = false; @@ -98,17 +99,28 @@ export class UcsCompiler this.#presentationFor(id, hostFormat, target).modifyTypeInsets(wrapper); } } else { - this.#insetWrappers.set(hostFormat, hostOrWrapper as UcsInsetWrapper); + this.#formatConfigFor(hostFormat).insetWrapper = hostOrWrapper as UcsInsetWrapper; } return this; } + #formatConfigFor(format: UcFormatName): UcsFormatConfig { + let config = this.#formatConfigs.get(format); + + if (!config) { + config = {}; + this.#formatConfigs.set(format, config); + } + + return config; + } + #presentationFor>( id: UcsPresentationId, format: UcFormatName, type: TSchema['type'], - ): UcsPresentationEntry { + ): UcsPresentationConfig { let perType = this.#presentations.get(id); if (!perType) { @@ -116,21 +128,27 @@ export class UcsCompiler this.#presentations.set(id, perType); } - let typeEntry = perType.get(type) as UcsPresentationEntry | undefined; + let typeConfig = perType.get(type); - if (!typeEntry) { - typeEntry = new UcsPresentationEntry(this.schemaIndex, format); - perType.set(type, typeEntry); + if (!typeConfig) { + typeConfig = new UcsPresentationConfig(this.schemaIndex, format); + perType.set(type, typeConfig); } - return typeEntry; + return typeConfig as UcsPresentationConfig; } #findPresentationFor>( id: UcsPresentationId, type: TSchema['type'], - ): UcsPresentationEntry | undefined { - return this.#presentations.get(id)?.get(type) as UcsPresentationEntry | undefined; + ): UcsPresentationConfig | undefined { + return this.#presentations.get(id)?.get(type) as UcsPresentationConfig | undefined; + } + + writeWith(format: UcFormatName, createWriter: CreateUcsWriterExpr): this { + this.#formatConfigFor(format).createWriter = createWriter; + + return this; } /** @@ -189,14 +207,15 @@ export class UcsCompiler async bootstrapOptions(): Promise> { await this.#bootstrap(); - const { createSerializer = options => new UcsFunction(options) } = this.#options; - return { ...this.#options, schemaIndex: this.schemaIndex, formatterFor: this.#formatterFor.bind(this), insetFormatterFor: this.#insetFormatterFor.bind(this), - createSerializer, + createWriterFor: format => this.#formatConfigs.get(format)?.createWriter, + createSerializer(options) { + return new UcsFunction(options); + }, }; } @@ -233,22 +252,22 @@ export class UcsCompiler insetName: UcInsetName, schema: TSchema, ): UcsInsetFormatter | undefined { - const insetPresentationEntry = this.#findPresentationFor( + const insetPresentationConfig = this.#findPresentationFor( `inset:${insetName}`, schema.type, ); let insetFormatter: UcsFormatter | undefined; - if (insetPresentationEntry) { + if (insetPresentationConfig) { insetFormatter = - insetPresentationEntry.formatterFor(schema) - ?? this.#formatterFor(insetPresentationEntry.format, schema); + insetPresentationConfig.formatterFor(schema) + ?? this.#formatterFor(insetPresentationConfig.format, schema); } const insetWrapper = this.#findPresentationFor(`format:${hostFormat}`, hostSchema.type)?.insetWrapperFor( hostSchema, - ) ?? this.#insetWrappers.get(hostFormat); + ) ?? this.#formatConfigs.get(hostFormat)?.insetWrapper; if (insetWrapper) { return insetWrapper({ @@ -257,14 +276,14 @@ export class UcsCompiler insetName, schema, formatter: insetFormatter && { - insetFormat: insetPresentationEntry!.format, + insetFormat: insetPresentationConfig!.format, format: insetFormatter, }, }); } return ( - insetFormatter && { insetFormat: insetPresentationEntry!.format, format: insetFormatter } + insetFormatter && { insetFormat: insetPresentationConfig!.format, format: insetFormatter } ); } @@ -279,62 +298,12 @@ export namespace UcsCompiler { | undefined; readonly models: TModels; readonly features?: UccFeature | readonly UccFeature[] | undefined; - - createSerializer?>( - this: void, - options: UcsFunction.Options, - ): UcsFunction; } } type UcsPresentationId = `format:${UcFormatName}` | `inset:${UcInsetName}`; -class UcsPresentationEntry = UcSchema> { - - readonly #schemaIndex: UccSchemaIndex; - readonly #schemaFormatters = new Map>(); - readonly #schemaInsetWrappers = new Map(); - #typeFormatter: UcsFormatter | undefined; - #typeInsetWrapper?: UcsInsetWrapper | undefined; - - constructor(schemaIndex: UccSchemaIndex, readonly format: UcFormatName) { - this.#schemaIndex = schemaIndex; - } - - formatTypeWith(formatter: UcsFormatter | undefined): void { - if (formatter) { - this.#typeFormatter = formatter; - } - } - - modifyTypeInsets(wrapper: UcsInsetWrapper): void { - this.#typeInsetWrapper = wrapper; - } - - formatSchemaWith(schema: TSchema, formatter: UcsFormatter | undefined): void { - if (formatter) { - const schemaId = this.#schemaIndex.schemaId(schema); - - this.#schemaFormatters.set(schemaId, formatter); - } - } - - modifySchemaInsets(schema: TSchema, wrapper: UcsInsetWrapper): void { - const schemaId = this.#schemaIndex.schemaId(schema); - - this.#schemaInsetWrappers.set(schemaId, wrapper); - } - - formatterFor(schema: TSchema): UcsFormatter | undefined { - const schemaId = this.#schemaIndex.schemaId(schema); - - return this.#schemaFormatters.get(schemaId) ?? this.#typeFormatter; - } - - insetWrapperFor(schema: TSchema): UcsInsetWrapper | undefined { - const schemaId = this.#schemaIndex.schemaId(schema); - - return this.#schemaInsetWrappers.get(schemaId) ?? this.#typeInsetWrapper; - } - +interface UcsFormatConfig { + insetWrapper?: UcsInsetWrapper | undefined; + createWriter?: CreateUcsWriterExpr | undefined; } diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index dd60feac..d46e7f71 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -9,24 +9,21 @@ import { UcsExportSignature } from './ucs-export.signature.js'; import { UcsFormatter, UcsFormatterContext, UcsFormatterSignature } from './ucs-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsModels } from './ucs-models.js'; -import { UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js'; +import { CreateUcsWriterExpr, UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js'; export class UcsFunction = UcSchema> { readonly #schema: TSchema; - readonly #createWriter: Exclude['createWriter'], undefined>; + readonly #createWriterFor: UcsFunction.Options['createWriterFor']; readonly #formats = new Map< UcFormatName | `${UcInsetName}(${UcFormatName})`, UcsFunction$Context >(); constructor(options: UcsFunction.Options); - constructor({ - schema, - createWriter = UcsFunction$createWriter, - }: UcsFunction.Options) { + constructor({ schema, createWriterFor }: UcsFunction.Options) { this.#schema = schema; - this.#createWriter = createWriter; + this.#createWriterFor = createWriterFor; } get schema(): TSchema { @@ -86,6 +83,10 @@ export class UcsFunction = UcSc }; } + exportFn( + externalName: string, + entry: UcsModels.Entry, + ): EsFunction; exportFn( externalName: string, { format = 'charge' }: UcsModels.Entry, @@ -101,7 +102,10 @@ export class UcsFunction = UcSc code .line( writer.declare({ - value: () => this.#createWriter({ stream, options }, this), + value: () => (this.#createWriterFor?.(format) ?? UcsFunction$createWriter)( + { stream, options }, + this, + ), }), ) .write(`try {`) @@ -132,7 +136,7 @@ export namespace UcsFunction { export interface Options> { readonly schema: TSchema; - createWriter?(this: void, args: UcsWriterSignature.Values, serializer: UcsFunction): EsSnippet; + createWriterFor?: ((format: UcFormatName) => CreateUcsWriterExpr | undefined) | undefined; } } diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index dffa433f..a41ec6d5 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -15,6 +15,7 @@ import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; import { UcsInsetFormatter } from './ucs-inset-formatter.js'; import { UcsModels } from './ucs-models.js'; +import { CreateUcsWriterExpr } from './ucs-writer.class.js'; /** * Serializer library allocated by {@link UcsCompiler#bootstrap compiler}. @@ -77,6 +78,7 @@ export class UcsLib { if (!serializer) { serializer = this.#createSerializer({ schema, + createWriterFor: this.#options.createWriterFor, }); this.#serializers.set(schemaId, serializer); } @@ -130,19 +132,27 @@ export namespace UcsLib { readonly schemaIndex: UccSchemaIndex; readonly models: TModels; - formatterFor?>( - this: void, - format: UcFormatName, - schema: TSchema, - ): UcsFormatter | undefined; - - insetFormatterFor?>( - this: void, - hostFormat: UcFormatName, - hostSchema: UcSchema, - inset: UcInsetName, - schema: TSchema, - ): UcsInsetFormatter | undefined; + readonly formatterFor?: + | (>( + this: void, + format: UcFormatName, + schema: TSchema, + ) => UcsFormatter | undefined) + | undefined; + + readonly insetFormatterFor?: + | (>( + this: void, + hostFormat: UcFormatName, + hostSchema: UcSchema, + inset: UcInsetName, + schema: TSchema, + ) => UcsInsetFormatter | undefined) + | undefined; + + readonly createWriterFor?: + | ((this: void, format: UcFormatName) => CreateUcsWriterExpr | undefined) + | undefined; createSerializer>( this: void, diff --git a/src/compiler/serialization/ucs-setup.ts b/src/compiler/serialization/ucs-setup.ts index 694241a2..954c020c 100644 --- a/src/compiler/serialization/ucs-setup.ts +++ b/src/compiler/serialization/ucs-setup.ts @@ -3,6 +3,7 @@ import { UcSchema } from '../../schema/uc-schema.js'; import { UccSetup } from '../processor/ucc-setup.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsInsetWrapper } from './ucs-inset-formatter.js'; +import { CreateUcsWriterExpr } from './ucs-writer.class.js'; /** * Schema {@link UcsCompiler serializer} setup. @@ -34,6 +35,8 @@ export interface UcsSetup extends UccSetup { * * @param hostFormat - Name of the format containing insets. * @param wrapper - Wrapper to apply to matching inset formatters. + * + * @returns `this` instance. */ modifyInsets(hostFormat: UcFormatName, wrapper: UcsInsetWrapper): this; @@ -44,10 +47,22 @@ export interface UcsSetup extends UccSetup { * @param hostFormat - Name of target format. * @param host - Name or class of value type, or the schema instance containing insets. * @param wrapper - Wrapper to apply to matching inset formatters. + * + * @returns `this` instance. */ modifyInsets( hostFormat: UcFormatName, host: UcSchema['type'] | UcSchema, wrapper: UcsInsetWrapper, ): this; + + /** + * Overrides {@link churi/serializer.js!UcsWriter UcsWriter} instantiation for the given `format`. + * + * @param format - Target format. + * @param createWriter - Builds `UcsWriter` instantiation expression. + * + * @returns `this` instance. + */ + writeWith(format: UcFormatName, createWriter: CreateUcsWriterExpr): this; } diff --git a/src/compiler/serialization/ucs-support-uri-params.ts b/src/compiler/serialization/ucs-support-uri-params.ts index 7e06bfed..387e8c46 100644 --- a/src/compiler/serialization/ucs-support-uri-params.ts +++ b/src/compiler/serialization/ucs-support-uri-params.ts @@ -8,6 +8,7 @@ import { UccCapability } from '../processor/ucc-capability.js'; import { UcsInsetContext, UcsInsetFormatter } from './ucs-inset-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; +import { UcsWriterClass } from './ucs-writer.class.js'; export function ucsSupportURIParams(activation: UccCapability.Activation): void { activation.onConstraint( @@ -18,6 +19,16 @@ export function ucsSupportURIParams(activation: UccCapability.Activation { setup + .writeWith('uriParams', ({ stream, options }) => async (code, { ns }) => { + const naming = await ns.refer(UcsWriterClass).whenNamed(); + + code.line( + naming.instantiate({ + stream, + options: esline`{ ...${options}, encodeURI: encodeURIComponent }`, + }), + ); + }) .formatWith( 'uriParams', 'map', diff --git a/src/compiler/serialization/ucs-writer.class.ts b/src/compiler/serialization/ucs-writer.class.ts index a62fe091..41db3e36 100644 --- a/src/compiler/serialization/ucs-writer.class.ts +++ b/src/compiler/serialization/ucs-writer.class.ts @@ -1,5 +1,7 @@ -import { EsArg, EsSignature, esImportClass } from 'esgen'; +import { EsArg, EsSignature, EsSnippet, esImportClass } from 'esgen'; +import { UcsWriter } from '../../serializer/ucs-writer.js'; import { UC_MODULE_SERIALIZER } from '../impl/uc-modules.js'; +import { UcsFunction } from './ucs-function.js'; export type UcsWriterSignature = EsSignature; @@ -15,6 +17,19 @@ export namespace UcsWriterSignature { }; export type Values = EsSignature.ValuesOf; + export type AllValues = { + readonly [key in keyof Values]-?: Exclude; + }; } -export const UcsWriterClass = esImportClass(UC_MODULE_SERIALIZER, 'UcsWriter', UcsWriterSignature); +export const UcsWriterClass = esImportClass( + UC_MODULE_SERIALIZER, + UcsWriter.name, + UcsWriterSignature, +); + +export type CreateUcsWriterExpr = ( + this: void, + args: UcsWriterSignature.AllValues, + serializer: UcsFunction, +) => EsSnippet; diff --git a/src/schema/string/uc-string.serializer.spec.ts b/src/schema/string/uc-string.serializer.spec.ts index a50c2d0b..a3d831e6 100644 --- a/src/schema/string/uc-string.serializer.spec.ts +++ b/src/schema/string/uc-string.serializer.spec.ts @@ -2,10 +2,9 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { esline } from 'esgen'; import { UC_MODULE_SPEC } from '../../compiler/impl/uc-modules.js'; import { UcsCompiler } from '../../compiler/serialization/ucs-compiler.js'; -import { UcsFunction } from '../../compiler/serialization/ucs-function.js'; +import { ucsProcessDefaults } from '../../compiler/serialization/ucs-process-defaults.js'; import { TextOutStream } from '../../spec/text-out-stream.js'; import { ucNullable } from '../uc-nullable.js'; -import { UcSchema } from '../uc-schema.js'; import { UcSerializer } from '../uc-serializer.js'; import { ucString } from './uc-string.js'; @@ -62,18 +61,20 @@ describe('UcString serializer', () => { }); it('writes multiple chunks', async () => { const compiler = new UcsCompiler({ - models: { - writeValue: { model: String }, - }, - createSerializer>(options: UcsFunction.Options) { - return new UcsFunction({ - ...options, - createWriter({ stream }) { - const UcsWriter = UC_MODULE_SPEC.import('SmallChunkUcsWriter'); + features: [ + ucsProcessDefaults, + setup => ({ + configure() { + setup.writeWith('charge', ({ stream }) => { + const UcsWriter = UC_MODULE_SPEC.import('SmallChunkUcsWriter'); - return esline`new ${UcsWriter}(${stream}, 4);`; + return esline`new ${UcsWriter}(${stream}, 4);`; + }); }, - }); + }), + ], + models: { + writeValue: { model: String }, }, }); const { writeValue } = await compiler.evaluate(); diff --git a/src/serializer/ucs-write-string.ts b/src/serializer/ucs-write-string.ts index 66c28cf1..94e25b58 100644 --- a/src/serializer/ucs-write-string.ts +++ b/src/serializer/ucs-write-string.ts @@ -1,5 +1,5 @@ import { encodeURIPart } from 'httongue'; -import { encodeUcsString, isEscapedUcsString } from '../impl/encode-ucs-string.js'; +import { isEscapedUcsString } from '../impl/encode-ucs-string.js'; import { UCS_APOSTROPHE, UCS_ESCAPED_DOUBLE_HYPHEN } from './ucs-constants.js'; import { ucsWriteAsIs } from './ucs-write-asis.js'; import { UcsWriter } from './ucs-writer.js'; @@ -19,12 +19,14 @@ export async function ucsWriteString( return; } - if (isEscapedUcsString(value)) { + const encoded = ucsWriter.encodeURI(value); + + if (isEscapedUcsString(encoded)) { // Always needs to be escaped. ucsWriter.write(UCS_APOSTROPHE); } - await ucsWriteAsIs(ucsWriter, encodeUcsString(value)); + await ucsWriteAsIs(ucsWriter, encoded); } export async function ucsWriteRawString( @@ -42,7 +44,7 @@ export async function ucsWriteRawString( return; } - await ucsWriteAsIs(ucsWriter, encodeUcsString(value)); + await ucsWriteAsIs(ucsWriter, ucsWriter.encodeURI(value)); } export async function ucsWriteURIEncoded(ucsWriter: UcsWriter, value: string): Promise { @@ -70,5 +72,5 @@ export async function ucsWriteNullableRawString( return; } - await ucsWriteAsIs(ucsWriter, encodeUcsString(value)); + await ucsWriteAsIs(ucsWriter, ucsWriter.encodeURI(value)); } diff --git a/src/serializer/ucs-writer.ts b/src/serializer/ucs-writer.ts index 639b41e0..b99be48d 100644 --- a/src/serializer/ucs-writer.ts +++ b/src/serializer/ucs-writer.ts @@ -1,3 +1,4 @@ +import { encodeUcsString } from '../impl/encode-ucs-string.js'; import { UcSerializer } from '../schema/uc-serializer.js'; import { UcsMemory } from './ucs-memory.js'; @@ -5,14 +6,19 @@ export class UcsWriter { readonly #writer: WritableStreamDefaultWriter; readonly #data: Record; + readonly #encodeURI: (this: void, value: string) => string; #whenWritten: Promise = Promise.resolve(); #memory?: UcsMemory; #encoder?: TextEncoder; - constructor(stream: WritableStream, options?: UcSerializer.Options); - constructor(stream: WritableStream, { data = {} }: UcSerializer.Options = {}) { + constructor(stream: WritableStream, options?: UcsWriter.Options); + constructor( + stream: WritableStream, + { encodeURI = encodeUcsString, data = {} }: UcsWriter.Options = {}, + ) { this.#writer = stream.getWriter(); + this.#encodeURI = encodeURI; this.#data = data; } @@ -32,6 +38,10 @@ export class UcsWriter { return (this.#encoder ??= new TextEncoder()); } + encodeURI(value: string): string { + return this.#encodeURI(value); + } + write(chunk: Uint8Array): void { this.#startWrite(this.#writer.write(chunk)); } @@ -51,3 +61,9 @@ export class UcsWriter { } } + +export namespace UcsWriter { + export interface Options extends UcSerializer.Options { + readonly encodeURI?: (this: void, value: string) => string; + } +} diff --git a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts index 6e85c016..4cf67e42 100644 --- a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts +++ b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts @@ -1,10 +1,17 @@ import { describe, expect, it } from '@jest/globals'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; +import { ucsSupportPlainText } from '../../../compiler/serialization/ucs-support-plain-text.js'; import { ucsSupportURIEncoded } from '../../../compiler/serialization/ucs-support-uri-encoded.js'; import { ucsSupportURIParams } from '../../../compiler/serialization/ucs-support-uri-params.js'; +import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; import { ucMap } from '../../../schema/map/uc-map.js'; import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; +import { ucInteger } from '../../../schema/numeric/uc-integer.js'; +import { ucNumber } from '../../../schema/numeric/uc-number.js'; +import { ucString } from '../../../schema/string/uc-string.js'; import { TextOutStream } from '../../../spec/text-out-stream.js'; +import { ucInsetCharge } from '../charge/uc-inset-charge.js'; +import { ucInsetPlainText } from '../plain-text/uc-inset-plain-text.js'; import { ucInsetURIEncoded } from '../uri-encoded/uc-inset-uri-encoded.js'; describe('URI params serializer', () => { @@ -32,4 +39,151 @@ describe('URI params serializer', () => { TextOutStream.read(async to => await writeParams(to, { test: -13n })), ).resolves.toBe('test=-0n13'); }); + it('serializes boolean', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucBoolean({ + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: true })), + ).resolves.toBe('test=!'); + await expect( + TextOutStream.read(async to => await writeParams(to, { test: false })), + ).resolves.toBe('test=-'); + }); + it('serializes integer', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucInteger({ + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: 13.1 })), + ).resolves.toBe('test=13'); + await expect( + TextOutStream.read(async to => await writeParams(to, { test: -13.1 })), + ).resolves.toBe('test=-13'); + }); + it('serializes number', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucNumber({ + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: 13.1 })), + ).resolves.toBe('test=13.1'); + await expect( + TextOutStream.read(async to => await writeParams(to, { test: -13.1 })), + ).resolves.toBe('test=-13.1'); + }); + it('serializes URI-encoded string', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucString({ + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: '3a, b c!' })), + ).resolves.toBe('test=3a%2C%20b%20c%21'); + }); + it('serializes plain text string', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportPlainText, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucString({ + within: { + uriParam: ucInsetPlainText(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: '3a, b c!' })), + ).resolves.toBe('test=3a, b c!'); + }); + it('serializes charged string', async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + models: { + writeParams: { + model: ucMap({ + test: ucString({ + within: { + uriParam: ucInsetCharge(), + }, + }), + }), + format: 'uriParams', + }, + }, + }); + + const { writeParams } = await compiler.evaluate(); + + await expect( + TextOutStream.read(async to => await writeParams(to, { test: '3a, b c!' })), + ).resolves.toBe("test='3a%2C%20b%20c!"); + }); }); From 7f686f6da2f095d49016447ce463079449a3ad6e Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 12:38:37 +0700 Subject: [PATCH 31/36] Proper URI parameter escapes --- .../serialization/ucs-support-uri-params.ts | 17 +++++++++++++---- .../uri-params/uri-params.serializer.spec.ts | 12 ++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/src/compiler/serialization/ucs-support-uri-params.ts b/src/compiler/serialization/ucs-support-uri-params.ts index 387e8c46..c8f8e2dc 100644 --- a/src/compiler/serialization/ucs-support-uri-params.ts +++ b/src/compiler/serialization/ucs-support-uri-params.ts @@ -1,5 +1,13 @@ -import { EsCallable, EsSnippet, EsVarKind, EsVarSymbol, esMemberAccessor, esline } from 'esgen'; -import { encodeURIPart } from 'httongue'; +import { + EsCallable, + EsSnippet, + EsVarKind, + EsVarSymbol, + esImport, + esMemberAccessor, + esline, +} from 'esgen'; +import { encodeURISearchPart } from 'httongue'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcMap } from '../../schema/map/uc-map.js'; import { ucOptional } from '../../schema/uc-optional.js'; @@ -20,12 +28,13 @@ export function ucsSupportURIParams(activation: UccCapability.Activation { setup .writeWith('uriParams', ({ stream, options }) => async (code, { ns }) => { + const encodeURI = esImport('httongue', encodeURISearchPart.name); const naming = await ns.refer(UcsWriterClass).whenNamed(); code.line( naming.instantiate({ stream, - options: esline`{ ...${options}, encodeURI: encodeURIComponent }`, + options: esline`{ ...${options}, encodeURI: ${encodeURI} }`, }), ); }) @@ -57,7 +66,7 @@ export function ucsSupportURIParams(activation: UccCapability.Activation code => { code.write( diff --git a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts index 4cf67e42..84bfcad7 100644 --- a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts +++ b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts @@ -146,7 +146,7 @@ describe('URI params serializer', () => { models: { writeParams: { model: ucMap({ - test: ucString({ + 'test 1': ucString({ within: { uriParam: ucInsetPlainText(), }, @@ -160,8 +160,8 @@ describe('URI params serializer', () => { const { writeParams } = await compiler.evaluate(); await expect( - TextOutStream.read(async to => await writeParams(to, { test: '3a, b c!' })), - ).resolves.toBe('test=3a, b c!'); + TextOutStream.read(async to => await writeParams(to, { 'test 1': '3a, b c!' })), + ).resolves.toBe('test+1=3a, b c!'); }); it('serializes charged string', async () => { const compiler = new UcsCompiler({ @@ -169,7 +169,7 @@ describe('URI params serializer', () => { models: { writeParams: { model: ucMap({ - test: ucString({ + 'test 2': ucString({ within: { uriParam: ucInsetCharge(), }, @@ -183,7 +183,7 @@ describe('URI params serializer', () => { const { writeParams } = await compiler.evaluate(); await expect( - TextOutStream.read(async to => await writeParams(to, { test: '3a, b c!' })), - ).resolves.toBe("test='3a%2C%20b%20c!"); + TextOutStream.read(async to => await writeParams(to, { 'test 2': '3a, (b) c!' })), + ).resolves.toBe("test+2='3a%2C+%28b%29+c%21"); }); }); From 0c7a0182b4e1a758cf7178dae209913e8eca6106 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 13:57:41 +0700 Subject: [PATCH 32/36] Serializer capability factories --- .../serialization/ucs-support-plain-text.ts | 90 +++++----- .../serialization/ucs-support-uri-encoded.ts | 90 +++++----- .../serialization/ucs-support-uri-params.ts | 161 +++++++++--------- .../plain-text/plain-text.serializer.spec.ts | 18 +- .../uri-encoded.serializer.spec.ts | 18 +- .../uri-params/uri-params.serializer.spec.ts | 70 +++++++- 6 files changed, 255 insertions(+), 192 deletions(-) diff --git a/src/compiler/serialization/ucs-support-plain-text.ts b/src/compiler/serialization/ucs-support-plain-text.ts index d7e583f3..65dbc260 100644 --- a/src/compiler/serialization/ucs-support-plain-text.ts +++ b/src/compiler/serialization/ucs-support-plain-text.ts @@ -19,51 +19,53 @@ import { ucsProcessNumber } from './ucs-process-number.js'; import { ucsProcessString } from './ucs-process-string.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportPlainText(activation: UccCapability.Activation): void { - activation - .enable(ucsProcessPlainTextDefaults) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessBigInt.name, - from: COMPILER_MODULE, - }, - ({ setup, schema, constraint: { with: options } }) => { - const { number = 'parse' } = options as UcBigInt.Variant; +export function ucsSupportPlainText(): UccCapability { + return activation => { + activation + .enable(ucsProcessPlainTextDefaults) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessBigInt.name, + from: COMPILER_MODULE, + }, + ({ setup, schema, constraint: { with: options } }) => { + const { number = 'parse' } = options as UcBigInt.Variant; - setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatBigInt({ number }))); - }, - ) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessInteger.name, - from: COMPILER_MODULE, - }, - ({ setup, schema }) => { - setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatInteger())); - }, - ) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessNumber.name, - from: COMPILER_MODULE, - }, - ({ setup, schema }) => { - setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatNumber())); - }, - ) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessString.name, - from: COMPILER_MODULE, - }, - ({ setup, schema }) => { - setup.formatWith('plainText', schema, ucsFormatPlainTextString()); - }, - ); + setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatBigInt({ number }))); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessInteger.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatInteger())); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessNumber.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('plainText', schema, ucsFormatPlainText(ucsFormatNumber())); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessString.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('plainText', schema, ucsFormatPlainTextString()); + }, + ); + }; } function ucsProcessPlainTextDefaults(setup: UcsSetup): UccConfig { diff --git a/src/compiler/serialization/ucs-support-uri-encoded.ts b/src/compiler/serialization/ucs-support-uri-encoded.ts index 1a326eab..b6730352 100644 --- a/src/compiler/serialization/ucs-support-uri-encoded.ts +++ b/src/compiler/serialization/ucs-support-uri-encoded.ts @@ -19,51 +19,53 @@ import { ucsProcessNumber } from './ucs-process-number.js'; import { ucsProcessString } from './ucs-process-string.js'; import { UcsSetup } from './ucs-setup.js'; -export function ucsSupportURIEncoded(activation: UccCapability.Activation): void { - activation - .enable(ucsProcessURIEncodedDefaults) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessBigInt.name, - from: COMPILER_MODULE, - }, - ({ setup, schema, constraint: { with: options } }) => { - const { number = 'parse' } = options as UcBigInt.Variant; +export function ucsSupportURIEncoded(): UccCapability { + return activation => { + activation + .enable(ucsProcessURIEncodedDefaults) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessBigInt.name, + from: COMPILER_MODULE, + }, + ({ setup, schema, constraint: { with: options } }) => { + const { number = 'parse' } = options as UcBigInt.Variant; - setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatBigInt({ number }))); - }, - ) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessInteger.name, - from: COMPILER_MODULE, - }, - ({ setup, schema }) => { - setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatInteger())); - }, - ) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessNumber.name, - from: COMPILER_MODULE, - }, - ({ setup, schema }) => { - setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatNumber())); - }, - ) - .onConstraint( - { - processor: 'serializer', - use: ucsProcessString.name, - from: COMPILER_MODULE, - }, - ({ setup, schema }) => { - setup.formatWith('uriEncoded', schema, ucsFormatURIEncodedString()); - }, - ); + setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatBigInt({ number }))); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessInteger.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatInteger())); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessNumber.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('uriEncoded', schema, ucsFormatURIEncoded(ucsFormatNumber())); + }, + ) + .onConstraint( + { + processor: 'serializer', + use: ucsProcessString.name, + from: COMPILER_MODULE, + }, + ({ setup, schema }) => { + setup.formatWith('uriEncoded', schema, ucsFormatURIEncodedString()); + }, + ); + }; } function ucsProcessURIEncodedDefaults(setup: UcsSetup): UccConfig { diff --git a/src/compiler/serialization/ucs-support-uri-params.ts b/src/compiler/serialization/ucs-support-uri-params.ts index c8f8e2dc..9c24044b 100644 --- a/src/compiler/serialization/ucs-support-uri-params.ts +++ b/src/compiler/serialization/ucs-support-uri-params.ts @@ -18,90 +18,95 @@ import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; import { UcsWriterClass } from './ucs-writer.class.js'; -export function ucsSupportURIParams(activation: UccCapability.Activation): void { - activation.onConstraint( - { - processor: 'serializer', - use: 'ucsProcessMap', - from: COMPILER_MODULE, - }, - ({ setup }) => { - setup - .writeWith('uriParams', ({ stream, options }) => async (code, { ns }) => { - const encodeURI = esImport('httongue', encodeURISearchPart.name); - const naming = await ns.refer(UcsWriterClass).whenNamed(); +export function ucsSupportURIParams(): UccCapability { + return activation => { + activation.onConstraint( + { + processor: 'serializer', + use: 'ucsProcessMap', + from: COMPILER_MODULE, + }, + ({ setup }) => { + setup + .writeWith('uriParams', ({ stream, options }) => async (code, { ns }) => { + const encodeURI = esImport('httongue', encodeURISearchPart.name); + const naming = await ns.refer(UcsWriterClass).whenNamed(); - code.line( - naming.instantiate({ - stream, - options: esline`{ ...${options}, encodeURI: ${encodeURI} }`, - }), - ); - }) - .formatWith( - 'uriParams', - 'map', - ({ writer, value }, schema: UcMap.Schema, cx) => (code, scope) => { - const lib = scope.get(UcsLib); - const { entries } = schema; - const entriesWritten = new EsVarSymbol('entriesWritten'); - const currentKey = new EsVarSymbol('currentKey'); - const writeKey = new EsCallable({}).lambda( - () => code => { - code - .write(esline`await ${writer}.ready;`) - .write( - esline`${writer}.write(${entriesWritten}++ ? ${currentKey} : ${currentKey}.slice(1));`, - ); - }, - { - async: true, - }, - ); + code.line( + naming.instantiate({ + stream, + options: esline`{ ...${options}, encodeURI: ${encodeURI} }`, + }), + ); + }) + .formatWith( + 'uriParams', + 'map', + ({ writer, value }, schema: UcMap.Schema, cx) => (code, scope) => { + const lib = scope.get(UcsLib); + const { entries } = schema; + const entriesWritten = new EsVarSymbol('entriesWritten'); + const currentKey = new EsVarSymbol('currentKey'); + const writeKey = new EsCallable({}).lambda( + () => code => { + code + .write(esline`await ${writer}.ready;`) + .write( + esline`${writer}.write(${entriesWritten}++ ? ${currentKey} : ${currentKey}.slice(1));`, + ); + }, + { + async: true, + }, + ); - code.write( - entriesWritten.declare({ as: EsVarKind.Let, value: () => '0' }), - currentKey.declare(), - esline`${writer}.data.writeKey = ${writeKey};`, - ); + code.write( + entriesWritten.declare({ as: EsVarKind.Let, value: () => '0' }), + currentKey.declare(), + esline`${writer}.data.writeKey = ${writeKey};`, + ); - for (const [entryKey, entrySchema] of Object.entries(entries)) { - const keyConst = lib.binConst(`&${encodeURISearchPart(entryKey)}=`); - const writeEntry = - (schema: UcSchema, value: EsSnippet): EsSnippet => code => { - code.write( - esline`${currentKey} = ${keyConst};`, - cx.formatInset('uriParam', schema, { - writer, - value, - asItem: '0', - }), - ); - }; + for (const [entryKey, entrySchema] of Object.entries(entries)) { + const keyConst = lib.binConst(`&${encodeURISearchPart(entryKey)}=`); + const writeEntry = + (schema: UcSchema, value: EsSnippet): EsSnippet => code => { + code.write( + esline`${currentKey} = ${keyConst};`, + cx.formatInset('uriParam', schema, { + writer, + value, + asItem: '0', + }), + ); + }; - if (entrySchema.optional) { - const currentValue = new EsVarSymbol(entryKey); + if (entrySchema.optional) { + const currentValue = new EsVarSymbol(entryKey); - code - .write( - currentValue.declare({ - value: () => esline`${value}${esMemberAccessor(entryKey).accessor}`, - }), - ) - .write(esline`if (${currentValue} !== undefined) {`) - .indent(writeEntry(ucOptional(entrySchema, false), currentValue)) - .write('}'); - } else { - code.write( - writeEntry(entrySchema, esline`${value}${esMemberAccessor(entryKey).accessor}`), - ); + code + .write( + currentValue.declare({ + value: () => esline`${value}${esMemberAccessor(entryKey).accessor}`, + }), + ) + .write(esline`if (${currentValue} !== undefined) {`) + .indent(writeEntry(ucOptional(entrySchema, false), currentValue)) + .write('}'); + } else { + code.write( + writeEntry( + entrySchema, + esline`${value}${esMemberAccessor(entryKey).accessor}`, + ), + ); + } } - } - }, - ) - .modifyInsets('uriParams', 'map', ucsModifyURIParam); - }, - ); + }, + ) + .modifyInsets('uriParams', 'map', ucsModifyURIParam); + }, + ); + }; } function ucsModifyURIParam>({ diff --git a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts index 64aad34d..dc271f5a 100644 --- a/src/syntax/formats/plain-text/plain-text.serializer.spec.ts +++ b/src/syntax/formats/plain-text/plain-text.serializer.spec.ts @@ -16,7 +16,7 @@ import { TextOutStream } from '../../../spec/text-out-stream.js'; describe('plain text serializer', () => { it('serializes bigint', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writePrimitive: { model: BigInt, format: 'plainText' }, writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'plainText' }, @@ -42,7 +42,7 @@ describe('plain text serializer', () => { }); it('serializes boolean', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writePrimitive: { model: Boolean, format: 'plainText' }, writeValue: { model: ucBoolean(), format: 'plainText' }, @@ -60,7 +60,7 @@ describe('plain text serializer', () => { }); it('serializes number', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writePrimitive: { model: Number, format: 'plainText' }, writeValue: { model: ucNumber({ string: 'serialize' }), format: 'plainText' }, @@ -79,7 +79,7 @@ describe('plain text serializer', () => { }); it('serializes integer', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writeValue: { model: ucInteger({ string: 'serialize' }), format: 'plainText' }, }, @@ -94,7 +94,7 @@ describe('plain text serializer', () => { }); it('serializes string', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writePrimitive: { model: String, format: 'plainText' }, writeValue: { model: ucString({ raw: 'escape' }), format: 'plainText' }, @@ -113,7 +113,7 @@ describe('plain text serializer', () => { it('can not serialize list', async () => { const schema = ucList(Number); const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writeList: { model: schema, format: 'plainText' }, }, @@ -129,7 +129,7 @@ describe('plain text serializer', () => { it('can not serialize map', async () => { const schema = ucMap({ foo: Number }); const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { writeMap: { model: schema, format: 'plainText' }, }, @@ -145,7 +145,7 @@ describe('plain text serializer', () => { it('can not serialize nullable values', async () => { const schema = ucNullable(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { write: { model: schema, format: 'plainText' }, }, @@ -162,7 +162,7 @@ describe('plain text serializer', () => { it('can not serialize optional values', async () => { const schema = ucOptional(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsSupportPlainText, + capabilities: ucsSupportPlainText(), models: { write: { model: schema, format: 'plainText' }, }, diff --git a/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts b/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts index 0b50382f..ee3db931 100644 --- a/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts +++ b/src/syntax/formats/uri-encoded/uri-encoded.serializer.spec.ts @@ -16,7 +16,7 @@ import { TextOutStream } from '../../../spec/text-out-stream.js'; describe('URI-encoded serializer', () => { it('serializes bigint', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writePrimitive: { model: BigInt, format: 'uriEncoded' }, writeValue: { model: ucBigInt({ string: 'serialize' }), format: 'uriEncoded' }, @@ -42,7 +42,7 @@ describe('URI-encoded serializer', () => { }); it('serializes boolean', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writePrimitive: { model: Boolean, format: 'uriEncoded' }, writeValue: { model: ucBoolean(), format: 'uriEncoded' }, @@ -60,7 +60,7 @@ describe('URI-encoded serializer', () => { }); it('serializes number', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writePrimitive: { model: Number, format: 'uriEncoded' }, writeValue: { model: ucNumber({ string: 'serialize' }), format: 'uriEncoded' }, @@ -79,7 +79,7 @@ describe('URI-encoded serializer', () => { }); it('serializes integer', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writeValue: { model: ucInteger({ string: 'serialize' }), format: 'uriEncoded' }, }, @@ -94,7 +94,7 @@ describe('URI-encoded serializer', () => { }); it('serializes string', async () => { const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writePrimitive: { model: String, format: 'uriEncoded' }, writeValue: { model: ucString({ raw: 'escape' }), format: 'uriEncoded' }, @@ -113,7 +113,7 @@ describe('URI-encoded serializer', () => { it('can not serialize list', async () => { const schema = ucList(Number); const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writeList: { model: schema, format: 'uriEncoded' }, }, @@ -129,7 +129,7 @@ describe('URI-encoded serializer', () => { it('can not serialize map', async () => { const schema = ucMap({ foo: Number }); const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { writeMap: { model: schema, format: 'uriEncoded' }, }, @@ -145,7 +145,7 @@ describe('URI-encoded serializer', () => { it('can not serialize nullable values', async () => { const schema = ucNullable(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { write: { model: schema, format: 'uriEncoded' }, }, @@ -162,7 +162,7 @@ describe('URI-encoded serializer', () => { it('can not serialize optional values', async () => { const schema = ucOptional(ucNumber()); const compiler = new UcsCompiler({ - capabilities: ucsSupportURIEncoded, + capabilities: ucsSupportURIEncoded(), models: { write: { model: schema, format: 'uriEncoded' }, }, diff --git a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts index 84bfcad7..0b81862e 100644 --- a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts +++ b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts @@ -1,4 +1,5 @@ -import { describe, expect, it } from '@jest/globals'; +import { beforeAll, describe, expect, it } from '@jest/globals'; +import { UcSerializer } from 'churi'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; import { ucsSupportPlainText } from '../../../compiler/serialization/ucs-support-plain-text.js'; import { ucsSupportURIEncoded } from '../../../compiler/serialization/ucs-support-uri-encoded.js'; @@ -9,6 +10,7 @@ import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; import { ucInteger } from '../../../schema/numeric/uc-integer.js'; import { ucNumber } from '../../../schema/numeric/uc-number.js'; import { ucString } from '../../../schema/string/uc-string.js'; +import { ucOptional } from '../../../schema/uc-optional.js'; import { TextOutStream } from '../../../spec/text-out-stream.js'; import { ucInsetCharge } from '../charge/uc-inset-charge.js'; import { ucInsetPlainText } from '../plain-text/uc-inset-plain-text.js'; @@ -17,7 +19,7 @@ import { ucInsetURIEncoded } from '../uri-encoded/uc-inset-uri-encoded.js'; describe('URI params serializer', () => { it('serializes bigint', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + capabilities: [ucsSupportURIEncoded(), ucsSupportURIParams()], models: { writeParams: { model: ucMap({ @@ -41,7 +43,7 @@ describe('URI params serializer', () => { }); it('serializes boolean', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + capabilities: [ucsSupportURIEncoded(), ucsSupportURIParams()], models: { writeParams: { model: ucMap({ @@ -67,7 +69,7 @@ describe('URI params serializer', () => { }); it('serializes integer', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + capabilities: [ucsSupportURIEncoded(), ucsSupportURIParams()], models: { writeParams: { model: ucMap({ @@ -93,7 +95,7 @@ describe('URI params serializer', () => { }); it('serializes number', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + capabilities: [ucsSupportURIEncoded(), ucsSupportURIParams()], models: { writeParams: { model: ucMap({ @@ -119,7 +121,7 @@ describe('URI params serializer', () => { }); it('serializes URI-encoded string', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + capabilities: [ucsSupportURIEncoded(), ucsSupportURIParams()], models: { writeParams: { model: ucMap({ @@ -142,7 +144,7 @@ describe('URI params serializer', () => { }); it('serializes plain text string', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportPlainText, ucsSupportURIParams], + capabilities: [ucsSupportPlainText(), ucsSupportURIParams()], models: { writeParams: { model: ucMap({ @@ -165,7 +167,7 @@ describe('URI params serializer', () => { }); it('serializes charged string', async () => { const compiler = new UcsCompiler({ - capabilities: [ucsSupportURIEncoded, ucsSupportURIParams], + capabilities: ucsSupportURIParams(), models: { writeParams: { model: ucMap({ @@ -186,4 +188,56 @@ describe('URI params serializer', () => { TextOutStream.read(async to => await writeParams(to, { 'test 2': '3a, (b) c!' })), ).resolves.toBe("test+2='3a%2C+%28b%29+c%21"); }); + + describe('optional properties', () => { + let writeParams: UcSerializer<{ + test?: string | undefined; + test2?: string | undefined; + }>; + + beforeAll(async () => { + const compiler = new UcsCompiler({ + capabilities: [ucsSupportURIEncoded(), ucsSupportURIParams()], + models: { + writeParams: { + model: ucMap({ + test: ucOptional( + ucString({ + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + ), + test2: ucOptional( + ucString({ + within: { + uriParam: ucInsetURIEncoded(), + }, + }), + ), + }), + format: 'uriParams', + }, + }, + }); + + ({ writeParams } = await compiler.evaluate()); + }); + + it('writes value', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test: 'abc', test2: 'def' })), + ).resolves.toBe('test=abc&test2=def'); + }); + it('skips missing value', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test2: 'abc' })), + ).resolves.toBe('test2=abc'); + }); + it('skips undefined value', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test: 'abc', test2: undefined })), + ).resolves.toBe('test=abc'); + }); + }); }); From 6e3e5e9c8de6a87f910e07a564e9d02ff10ff7cd Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 14:34:19 +0700 Subject: [PATCH 33/36] Default URI params inset format --- src/compiler/serialization/ucs-compiler.ts | 23 ++++---- src/compiler/serialization/ucs-function.ts | 9 ++- .../serialization/ucs-inset-formatter.ts | 21 ++++++- src/compiler/serialization/ucs-lib.ts | 33 +++++------ .../serialization/ucs-support-uri-params.ts | 56 +++++++++++++------ .../uri-params/uri-params.serializer.spec.ts | 28 ++++++++-- 6 files changed, 109 insertions(+), 61 deletions(-) diff --git a/src/compiler/serialization/ucs-compiler.ts b/src/compiler/serialization/ucs-compiler.ts index 6df1c857..7ddfe71e 100644 --- a/src/compiler/serialization/ucs-compiler.ts +++ b/src/compiler/serialization/ucs-compiler.ts @@ -15,7 +15,7 @@ import { UccProcessor } from '../processor/ucc-processor.js'; import { UcsPresentationConfig } from './impl/ucs-presentation-config.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; -import { UcsInsetFormatter, UcsInsetWrapper } from './ucs-inset-formatter.js'; +import { UcsInsetFormatter, UcsInsetRequest, UcsInsetWrapper } from './ucs-inset-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsExports, UcsModels } from './ucs-models.js'; import { ucsProcessDefaults } from './ucs-process-defaults.js'; @@ -210,9 +210,9 @@ export class UcsCompiler return { ...this.#options, schemaIndex: this.schemaIndex, - formatterFor: this.#formatterFor.bind(this), - insetFormatterFor: this.#insetFormatterFor.bind(this), - createWriterFor: format => this.#formatConfigs.get(format)?.createWriter, + findFormatter: this.#formatterFor.bind(this), + findInsetFormatter: this.#findInsetFormatter.bind(this), + createWriter: format => this.#formatConfigs.get(format)?.createWriter, createSerializer(options) { return new UcsFunction(options); }, @@ -246,12 +246,11 @@ export class UcsCompiler ); } - #insetFormatterFor>( - hostFormat: UcFormatName, - hostSchema: UcSchema, - insetName: UcInsetName, - schema: TSchema, + #findInsetFormatter>( + lib: UcsLib, + request: UcsInsetRequest, ): UcsInsetFormatter | undefined { + const { hostFormat, hostSchema, insetName, insetSchema: schema } = request; const insetPresentationConfig = this.#findPresentationFor( `inset:${insetName}`, schema.type, @@ -271,10 +270,8 @@ export class UcsCompiler if (insetWrapper) { return insetWrapper({ - hostFormat, - hostSchema, - insetName, - schema, + lib, + ...request, formatter: insetFormatter && { insetFormat: insetPresentationConfig!.format, format: insetFormatter, diff --git a/src/compiler/serialization/ucs-function.ts b/src/compiler/serialization/ucs-function.ts index d46e7f71..86db3f0f 100644 --- a/src/compiler/serialization/ucs-function.ts +++ b/src/compiler/serialization/ucs-function.ts @@ -60,7 +60,12 @@ export class UcsFunction = UcSc let formatter: UcsFormatter | undefined; if (inset) { - const insetFormatter = lib.insetFormatterFor(hostFormat, this.schema, inset, schema); + const insetFormatter = lib.findInsetFormatter({ + hostFormat, + hostSchema: this.schema, + insetName: inset, + insetSchema: schema, + }); if (!insetFormatter) { onUnknownInset(schema, inset); @@ -70,7 +75,7 @@ export class UcsFunction = UcSc formatter = insetFormatter.format; } else { context = this.#contextFor(format); - formatter = lib.formatterFor(format, schema); + formatter = lib.findFormatter(format, schema); } const write = formatter?.(args, schema, context); diff --git a/src/compiler/serialization/ucs-inset-formatter.ts b/src/compiler/serialization/ucs-inset-formatter.ts index de24d5e0..52d8a384 100644 --- a/src/compiler/serialization/ucs-inset-formatter.ts +++ b/src/compiler/serialization/ucs-inset-formatter.ts @@ -1,6 +1,7 @@ import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UcsFormatter } from './ucs-formatter.js'; +import { UcsLib } from './ucs-lib.js'; /** * Inset formatter generates code for type instance formatting. @@ -40,12 +41,12 @@ export type UcsInsetWrapper = { }['wrapInsetFormatter']; /** - * Inset formatting context. + * Request for inset formatter. * * @typeParam T - Implied data type. * @typeParam TSchema - Schema type. */ -export interface UcsInsetContext = UcSchema> { +export interface UcsInsetRequest> { /** * The name of the host format containing the inset. */ @@ -64,7 +65,21 @@ export interface UcsInsetContext = UcSchema> + extends UcsInsetRequest { + /** + * Serializer library. + */ + readonly lib: UcsLib; /** * The inset formatter to wrap, if any. diff --git a/src/compiler/serialization/ucs-lib.ts b/src/compiler/serialization/ucs-lib.ts index a41ec6d5..2fbf1ba9 100644 --- a/src/compiler/serialization/ucs-lib.ts +++ b/src/compiler/serialization/ucs-lib.ts @@ -8,12 +8,12 @@ import { esStringLiteral, esline, } from 'esgen'; -import { UcFormatName, UcInsetName } from '../../schema/uc-presentations.js'; +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema, ucSchema } from '../../schema/uc-schema.js'; import { UccSchemaIndex } from '../processor/ucc-schema-index.js'; import { UcsFormatter } from './ucs-formatter.js'; import { UcsFunction } from './ucs-function.js'; -import { UcsInsetFormatter } from './ucs-inset-formatter.js'; +import { UcsInsetFormatter, UcsInsetRequest } from './ucs-inset-formatter.js'; import { UcsModels } from './ucs-models.js'; import { CreateUcsWriterExpr } from './ucs-writer.class.js'; @@ -78,7 +78,7 @@ export class UcsLib { if (!serializer) { serializer = this.#createSerializer({ schema, - createWriterFor: this.#options.createWriterFor, + createWriterFor: this.#options.createWriter, }); this.#serializers.set(schemaId, serializer); } @@ -86,20 +86,17 @@ export class UcsLib { return serializer; } - formatterFor = UcSchema>( + findFormatter = UcSchema>( format: UcFormatName, schema: TSchema, - ): UcsFormatter | undefined { - return this.#options.formatterFor?.(format, schema); + ): UcsFormatter | undefined { + return this.#options.findFormatter?.(format, schema); } - insetFormatterFor = UcSchema>( - hostFormat: UcFormatName, - hostSchema: UcSchema, - inset: UcInsetName, - schema: TSchema, + findInsetFormatter = UcSchema>( + request: UcsInsetRequest, ): UcsInsetFormatter | undefined { - return this.#options.insetFormatterFor?.(hostFormat, hostSchema, inset, schema); + return this.#options.findInsetFormatter?.(this, request); } binConst(value: string): EsSymbol { @@ -132,7 +129,7 @@ export namespace UcsLib { readonly schemaIndex: UccSchemaIndex; readonly models: TModels; - readonly formatterFor?: + readonly findFormatter?: | (>( this: void, format: UcFormatName, @@ -140,17 +137,15 @@ export namespace UcsLib { ) => UcsFormatter | undefined) | undefined; - readonly insetFormatterFor?: + readonly findInsetFormatter?: | (>( this: void, - hostFormat: UcFormatName, - hostSchema: UcSchema, - inset: UcInsetName, - schema: TSchema, + lib: UcsLib, + request: UcsInsetRequest, ) => UcsInsetFormatter | undefined) | undefined; - readonly createWriterFor?: + readonly createWriter?: | ((this: void, format: UcFormatName) => CreateUcsWriterExpr | undefined) | undefined; diff --git a/src/compiler/serialization/ucs-support-uri-params.ts b/src/compiler/serialization/ucs-support-uri-params.ts index 9c24044b..6ac535e8 100644 --- a/src/compiler/serialization/ucs-support-uri-params.ts +++ b/src/compiler/serialization/ucs-support-uri-params.ts @@ -11,14 +11,18 @@ import { encodeURISearchPart } from 'httongue'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcMap } from '../../schema/map/uc-map.js'; import { ucOptional } from '../../schema/uc-optional.js'; +import { UcFormatName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; import { UccCapability } from '../processor/ucc-capability.js'; +import { UcsFormatter } from './ucs-formatter.js'; import { UcsInsetContext, UcsInsetFormatter } from './ucs-inset-formatter.js'; import { UcsLib } from './ucs-lib.js'; import { UcsSetup } from './ucs-setup.js'; import { UcsWriterClass } from './ucs-writer.class.js'; -export function ucsSupportURIParams(): UccCapability { +export function ucsSupportURIParams({ + defaultInsetFormat = 'charge', +}: UcsURIParamsOptions = {}): UccCapability { return activation => { activation.onConstraint( { @@ -107,25 +111,41 @@ export function ucsSupportURIParams(): UccCapability { }, ); }; -} -function ucsModifyURIParam>({ - formatter, -}: UcsInsetContext): UcsInsetFormatter | undefined { - if (!formatter) { - return; - } + function ucsModifyURIParam>({ + lib, + insetSchema, + formatter, + }: UcsInsetContext): UcsInsetFormatter | undefined { + let insetFormat: UcFormatName; + let format: UcsFormatter; - const { insetFormat, format } = formatter; + if (formatter) { + ({ insetFormat, format } = formatter); + } else { + const defaultFormatter = lib.findFormatter(defaultInsetFormat, insetSchema); - return { - insetFormat, - format(args, schema, cx) { - const { writer } = args; + if (!defaultFormatter) { + return; + } - return code => { - code.write(esline`await ${writer}.data.writeKey();`, format(args, schema, cx)); - }; - }, - }; + insetFormat = defaultInsetFormat; + format = defaultFormatter; + } + + return { + insetFormat, + format(args, schema, cx) { + const { writer } = args; + + return code => { + code.write(esline`await ${writer}.data.writeKey();`, format(args, schema, cx)); + }; + }, + }; + } +} + +export interface UcsURIParamsOptions { + readonly defaultInsetFormat?: UcFormatName | undefined; } diff --git a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts index 0b81862e..e8f2bc45 100644 --- a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts +++ b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts @@ -1,5 +1,6 @@ import { beforeAll, describe, expect, it } from '@jest/globals'; import { UcSerializer } from 'churi'; +import { UnsupportedUcSchemaError } from '../../../compiler/common/unsupported-uc-schema.error.js'; import { UcsCompiler } from '../../../compiler/serialization/ucs-compiler.js'; import { ucsSupportPlainText } from '../../../compiler/serialization/ucs-support-plain-text.js'; import { ucsSupportURIEncoded } from '../../../compiler/serialization/ucs-support-uri-encoded.js'; @@ -12,7 +13,6 @@ import { ucNumber } from '../../../schema/numeric/uc-number.js'; import { ucString } from '../../../schema/string/uc-string.js'; import { ucOptional } from '../../../schema/uc-optional.js'; import { TextOutStream } from '../../../spec/text-out-stream.js'; -import { ucInsetCharge } from '../charge/uc-inset-charge.js'; import { ucInsetPlainText } from '../plain-text/uc-inset-plain-text.js'; import { ucInsetURIEncoded } from '../uri-encoded/uc-inset-uri-encoded.js'; @@ -171,11 +171,7 @@ describe('URI params serializer', () => { models: { writeParams: { model: ucMap({ - 'test 2': ucString({ - within: { - uriParam: ucInsetCharge(), - }, - }), + 'test 2': String, }), format: 'uriParams', }, @@ -188,6 +184,26 @@ describe('URI params serializer', () => { TextOutStream.read(async to => await writeParams(to, { 'test 2': '3a, (b) c!' })), ).resolves.toBe("test+2='3a%2C+%28b%29+c%21"); }); + it('fails to deserialize value in unknown inset format', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsSupportURIParams({ defaultInsetFormat: 'plainText' }), + models: { + writeParams: { + model: ucMap({ + 'test 2': String, + }), + format: 'uriParams', + }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + ucString(), + 'Can not serialize inset "uriParam" of type "String"', + ), + ); + }); describe('optional properties', () => { let writeParams: UcSerializer<{ From ac47c806f6b84227adb3123c0e398ef5e9f3c895 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 16:04:14 +0700 Subject: [PATCH 34/36] URI-encode properly --- .../ucc-processor.constraint-application.ts | 1 + .../impl/ucs-presentation-config.ts | 1 + src/compiler/serialization/ucs-compiler.ts | 9 +- .../serialization/ucs-support-uri-encoded.ts | 19 +++- .../serialization/ucs-support-uri-params.ts | 65 +++++++++-- src/serializer/ucs-write-asis.ts | 10 +- src/serializer/ucs-write-string.ts | 37 ++++--- .../uri-params/uri-params.serializer.spec.ts | 101 +++++++++++++++++- 8 files changed, 200 insertions(+), 43 deletions(-) diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts index 7a1aebbe..9850b47e 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-application.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -48,6 +48,7 @@ export class UccProcessor$ConstraintApplication hostOrWrapper: UcSchema['type'] | UcSchema | UcsInsetWrapper, wrapper?: UcsInsetWrapper, ): this { + // istanbul ignore else if (wrapper) { const target = hostOrWrapper as UcSchema['type'] | UcSchema; const inset = this.currentPresentation as UcInsetName | undefined; - const id: UcsPresentationId = inset ? `inset:${inset}` : `format:${hostFormat}`; + const id: UcsPresentationId = inset + ? /* istanbul ignore next */ `inset:${inset}` + : `format:${hostFormat}`; + // istanbul ignore if if (typeof target === 'object') { this.#presentationFor(id, hostFormat, target.type).modifySchemaInsets(target, wrapper); } else { @@ -266,7 +270,7 @@ export class UcsCompiler const insetWrapper = this.#findPresentationFor(`format:${hostFormat}`, hostSchema.type)?.insetWrapperFor( hostSchema, - ) ?? this.#formatConfigs.get(hostFormat)?.insetWrapper; + ) ?? /* istanbul ignore next */ this.#formatConfigs.get(hostFormat)?.insetWrapper; if (insetWrapper) { return insetWrapper({ @@ -279,6 +283,7 @@ export class UcsCompiler }); } + // istanbul ignore next return ( insetFormatter && { insetFormat: insetPresentationConfig!.format, format: insetFormatter } ); diff --git a/src/compiler/serialization/ucs-support-uri-encoded.ts b/src/compiler/serialization/ucs-support-uri-encoded.ts index b6730352..dd18cf6a 100644 --- a/src/compiler/serialization/ucs-support-uri-encoded.ts +++ b/src/compiler/serialization/ucs-support-uri-encoded.ts @@ -1,4 +1,5 @@ -import { esline } from 'esgen'; +import { esImport, esline } from 'esgen'; +import { encodeURIPart } from 'httongue'; import { COMPILER_MODULE } from '../../impl/module-names.js'; import { UcBigInt } from '../../schema/numeric/uc-bigint.js'; import { UcString } from '../../schema/string/uc-string.js'; @@ -18,6 +19,7 @@ import { ucsProcessInteger } from './ucs-process-integer.js'; import { ucsProcessNumber } from './ucs-process-number.js'; import { ucsProcessString } from './ucs-process-string.js'; import { UcsSetup } from './ucs-setup.js'; +import { UcsWriterClass } from './ucs-writer.class.js'; export function ucsSupportURIEncoded(): UccCapability { return activation => { @@ -75,12 +77,23 @@ function ucsProcessURIEncodedDefaults(setup: UcsSetup): UccConfig { .formatWith('uriEncoded', BigInt, ucsFormatURIEncoded(ucsFormatBigInt())) .formatWith('uriEncoded', Boolean, ucsFormatURIEncoded(ucsFormatBoolean())) .formatWith('uriEncoded', Number, ucsFormatURIEncoded(ucsFormatNumber())) - .formatWith('uriEncoded', String, ucsFormatURIEncodedString()); + .formatWith('uriEncoded', String, ucsFormatURIEncodedString()) + .writeWith('uriEncoded', ({ stream, options }) => async (code, { ns }) => { + const encodeURI = esImport('httongue', encodeURIPart.name); + const naming = await ns.refer(UcsWriterClass).whenNamed(); + + code.line( + naming.instantiate({ + stream, + options: esline`{ ...${options}, encodeURI: ${encodeURI} }`, + }), + ); + }); }, }; } -export function ucsFormatURIEncodedString(): UcsFormatter { +function ucsFormatURIEncodedString(): UcsFormatter { return ucsFormatURIEncoded(({ writer, value }) => { const write = UC_MODULE_SERIALIZER.import(ucsWriteURIEncoded.name); diff --git a/src/compiler/serialization/ucs-support-uri-params.ts b/src/compiler/serialization/ucs-support-uri-params.ts index 6ac535e8..0ab43cd2 100644 --- a/src/compiler/serialization/ucs-support-uri-params.ts +++ b/src/compiler/serialization/ucs-support-uri-params.ts @@ -9,6 +9,7 @@ import { } from 'esgen'; import { encodeURISearchPart } from 'httongue'; import { COMPILER_MODULE } from '../../impl/module-names.js'; +import { UcList } from '../../schema/list/uc-list.js'; import { UcMap } from '../../schema/map/uc-map.js'; import { ucOptional } from '../../schema/uc-optional.js'; import { UcFormatName } from '../../schema/uc-presentations.js'; @@ -107,16 +108,15 @@ export function ucsSupportURIParams({ } }, ) - .modifyInsets('uriParams', 'map', ucsModifyURIParam); + .modifyInsets('uriParams', 'map', modifyURIParam); }, ); }; - function ucsModifyURIParam>({ - lib, - insetSchema, - formatter, - }: UcsInsetContext): UcsInsetFormatter | undefined { + function modifyURIParam>( + context: UcsInsetContext, + ): UcsInsetFormatter | undefined { + const { lib, insetSchema, formatter } = context; let insetFormat: UcFormatName; let format: UcsFormatter; @@ -125,12 +125,14 @@ export function ucsSupportURIParams({ } else { const defaultFormatter = lib.findFormatter(defaultInsetFormat, insetSchema); - if (!defaultFormatter) { + if (defaultFormatter) { + insetFormat = defaultInsetFormat; + format = defaultFormatter; + } else if (insetSchema.type === 'list') { + return modifyURIParamList(context); + } else { return; } - - insetFormat = defaultInsetFormat; - format = defaultFormatter; } return { @@ -144,6 +146,49 @@ export function ucsSupportURIParams({ }, }; } + + function modifyURIParamList>({ + lib, + }: UcsInsetContext): UcsInsetFormatter | undefined; + + function modifyURIParamList({ + lib, + hostFormat, + hostSchema, + insetName, + insetSchema, + }: UcsInsetContext): + | UcsInsetFormatter + | undefined { + const itemFormatter = lib.findInsetFormatter({ + hostFormat, + hostSchema, + insetName, + insetSchema: insetSchema.item, + }); + + if (!itemFormatter) { + return; + } + + const { insetFormat, format } = itemFormatter; + + return { + insetFormat, + format({ writer, value }, schema, context) { + return (code, { ns: { names } }) => { + const item = names.reserveName('item'); + + code + .write(esline`for (const ${item} of ${value}) {`) + .indent(code => { + code.write(format({ writer, value: item, asItem: '0' }, schema.item, context)); + }) + .write('}'); + }; + }, + }; + } } export interface UcsURIParamsOptions { diff --git a/src/serializer/ucs-write-asis.ts b/src/serializer/ucs-write-asis.ts index b5e039ef..4c2f95d6 100644 --- a/src/serializer/ucs-write-asis.ts +++ b/src/serializer/ucs-write-asis.ts @@ -1,15 +1,15 @@ import { UcsWriter } from './ucs-writer.js'; -export async function ucsWriteAsIs(ucsWriter: UcsWriter, value: string): Promise { - const { memory, encoder } = ucsWriter; +export async function ucsWriteAsIs(writer: UcsWriter, value: string): Promise { + const { memory, encoder } = writer; while ( await memory.use(value.length, async buffer => { const { read } = encoder.encodeInto(value, buffer); - await ucsWriter.ready; - ucsWriter.write(buffer); - await ucsWriter.written(); + await writer.ready; + writer.write(buffer); + await writer.written(); if (read! >= value.length) { return false; diff --git a/src/serializer/ucs-write-string.ts b/src/serializer/ucs-write-string.ts index 94e25b58..7915c3dc 100644 --- a/src/serializer/ucs-write-string.ts +++ b/src/serializer/ucs-write-string.ts @@ -1,76 +1,75 @@ -import { encodeURIPart } from 'httongue'; import { isEscapedUcsString } from '../impl/encode-ucs-string.js'; import { UCS_APOSTROPHE, UCS_ESCAPED_DOUBLE_HYPHEN } from './ucs-constants.js'; import { ucsWriteAsIs } from './ucs-write-asis.js'; import { UcsWriter } from './ucs-writer.js'; export async function ucsWriteString( - ucsWriter: UcsWriter, + writer: UcsWriter, value: string, asItem: boolean, ): Promise { - await ucsWriter.ready; + await writer.ready; if (!value) { if (asItem) { // Always escape empty list item. - ucsWriter.write(UCS_APOSTROPHE); + writer.write(UCS_APOSTROPHE); } return; } - const encoded = ucsWriter.encodeURI(value); + const encoded = writer.encodeURI(value); if (isEscapedUcsString(encoded)) { // Always needs to be escaped. - ucsWriter.write(UCS_APOSTROPHE); + writer.write(UCS_APOSTROPHE); } - await ucsWriteAsIs(ucsWriter, encoded); + await ucsWriteAsIs(writer, encoded); } export async function ucsWriteRawString( - ucsWriter: UcsWriter, + writer: UcsWriter, value: string, asItem: boolean, ): Promise { - await ucsWriter.ready; + await writer.ready; if (!value) { if (asItem) { // Always escape empty list item. - ucsWriter.write(UCS_APOSTROPHE); + writer.write(UCS_APOSTROPHE); } return; } - await ucsWriteAsIs(ucsWriter, ucsWriter.encodeURI(value)); + await ucsWriteAsIs(writer, writer.encodeURI(value)); } -export async function ucsWriteURIEncoded(ucsWriter: UcsWriter, value: string): Promise { - await ucsWriter.ready; - await ucsWriteAsIs(ucsWriter, encodeURIPart(value)); +export async function ucsWriteURIEncoded(writer: UcsWriter, value: string): Promise { + await writer.ready; + await ucsWriteAsIs(writer, writer.encodeURI(value)); } export async function ucsWriteNullableRawString( - ucsWriter: UcsWriter, + writer: UcsWriter, value: string, asItem: boolean, ): Promise { - await ucsWriter.ready; + await writer.ready; if (!value) { if (asItem) { // Always escape empty list item. - ucsWriter.write(UCS_APOSTROPHE); + writer.write(UCS_APOSTROPHE); } return; } if (value === '--') { - ucsWriter.write(UCS_ESCAPED_DOUBLE_HYPHEN); + writer.write(UCS_ESCAPED_DOUBLE_HYPHEN); return; } - await ucsWriteAsIs(ucsWriter, ucsWriter.encodeURI(value)); + await ucsWriteAsIs(writer, writer.encodeURI(value)); } diff --git a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts index e8f2bc45..235fbefd 100644 --- a/src/syntax/formats/uri-params/uri-params.serializer.spec.ts +++ b/src/syntax/formats/uri-params/uri-params.serializer.spec.ts @@ -6,6 +6,7 @@ import { ucsSupportPlainText } from '../../../compiler/serialization/ucs-support import { ucsSupportURIEncoded } from '../../../compiler/serialization/ucs-support-uri-encoded.js'; import { ucsSupportURIParams } from '../../../compiler/serialization/ucs-support-uri-params.js'; import { ucBoolean } from '../../../schema/boolean/uc-boolean.js'; +import { ucList } from '../../../schema/list/uc-list.js'; import { ucMap } from '../../../schema/map/uc-map.js'; import { ucBigInt } from '../../../schema/numeric/uc-bigint.js'; import { ucInteger } from '../../../schema/numeric/uc-integer.js'; @@ -140,7 +141,7 @@ describe('URI params serializer', () => { await expect( TextOutStream.read(async to => await writeParams(to, { test: '3a, b c!' })), - ).resolves.toBe('test=3a%2C%20b%20c%21'); + ).resolves.toBe('test=3a%2C+b+c%21'); }); it('serializes plain text string', async () => { const compiler = new UcsCompiler({ @@ -184,13 +185,13 @@ describe('URI params serializer', () => { TextOutStream.read(async to => await writeParams(to, { 'test 2': '3a, (b) c!' })), ).resolves.toBe("test+2='3a%2C+%28b%29+c%21"); }); - it('fails to deserialize value in unknown inset format', async () => { + it('fails to serialize value in unknown inset format', async () => { const compiler = new UcsCompiler({ capabilities: ucsSupportURIParams({ defaultInsetFormat: 'plainText' }), models: { writeParams: { model: ucMap({ - 'test 2': String, + test: String, }), format: 'uriParams', }, @@ -204,8 +205,28 @@ describe('URI params serializer', () => { ), ); }); + it('fails to serialize list item in unknown inset format', async () => { + const compiler = new UcsCompiler({ + capabilities: ucsSupportURIParams({ defaultInsetFormat: 'plainText' }), + models: { + writeParams: { + model: ucMap({ + test: ucList(String), + }), + format: 'uriParams', + }, + }, + }); + + await expect(compiler.evaluate()).rejects.toThrow( + new UnsupportedUcSchemaError( + ucString(), + 'Can not serialize inset "uriParam" of type "String[]"', + ), + ); + }); - describe('optional properties', () => { + describe('with optional properties', () => { let writeParams: UcSerializer<{ test?: string | undefined; test2?: string | undefined; @@ -256,4 +277,76 @@ describe('URI params serializer', () => { ).resolves.toBe('test=abc'); }); }); + + describe('for list', () => { + let writeParams: UcSerializer<{ + test: string[]; + }>; + + beforeAll(async () => { + const compiler = new UcsCompiler({ + capabilities: [ + ucsSupportURIEncoded(), + ucsSupportURIParams({ defaultInsetFormat: 'uriEncoded' }), + ], + models: { + writeParams: { + model: ucMap({ + test: ucList(String), + }), + format: 'uriParams', + }, + }, + }); + + ({ writeParams } = await compiler.evaluate()); + }); + + it('serializes list', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test: ['a b', '2 3', '4 5'] })), + ).resolves.toBe('test=a+b&test=2+3&test=4+5'); + }); + it('serializes empty list', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test: [] })), + ).resolves.toBe(''); + }); + }); + + describe('for list of list', () => { + let writeParams: UcSerializer<{ + test: string[]; + }>; + + beforeAll(async () => { + const compiler = new UcsCompiler({ + capabilities: [ + ucsSupportURIEncoded(), + ucsSupportURIParams({ defaultInsetFormat: 'uriEncoded' }), + ], + models: { + writeParams: { + model: ucMap({ + test: ucList(String), + }), + format: 'uriParams', + }, + }, + }); + + ({ writeParams } = await compiler.evaluate()); + }); + + it('serializes list', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test: ['a b', '2 3', '4 5'] })), + ).resolves.toBe('test=a+b&test=2+3&test=4+5'); + }); + it('serializes empty list', async () => { + await expect( + TextOutStream.read(async to => await writeParams(to, { test: [] })), + ).resolves.toBe(''); + }); + }); }); From c61f7b55462a4b5bddd9c9c6ff1e8cffce50752b Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 16:08:12 +0700 Subject: [PATCH 35/36] Drop redundant schema processing data --- src/compiler/deserialization/ucd-compiler.ts | 2 +- .../processor/impl/ucc-processor.config.ts | 9 ++------- .../ucc-processor.constraint-application.ts | 6 ------ .../impl/ucc-processor.constraint-issue.ts | 2 -- .../impl/ucc-processor.feature-config.ts | 16 +++++----------- src/compiler/processor/ucc-capability.ts | 6 ------ src/compiler/processor/ucc-config.ts | 12 ++---------- src/compiler/processor/ucc-processor.ts | 17 ++++++----------- src/compiler/processor/ucc-setup.ts | 14 +++----------- 9 files changed, 19 insertions(+), 65 deletions(-) diff --git a/src/compiler/deserialization/ucd-compiler.ts b/src/compiler/deserialization/ucd-compiler.ts index 3da21f8e..d10a6270 100644 --- a/src/compiler/deserialization/ucd-compiler.ts +++ b/src/compiler/deserialization/ucd-compiler.ts @@ -95,7 +95,7 @@ export class UcdCompiler // Stop registering default handlers. // Start registering custom ones. - defaultConfig.configure!(undefined, {}); + defaultConfig.configure!(undefined); this.#entities.makeDefault(); this.#formats.makeDefault(); diff --git a/src/compiler/processor/impl/ucc-processor.config.ts b/src/compiler/processor/impl/ucc-processor.config.ts index dfe43ddd..7dafdfed 100644 --- a/src/compiler/processor/impl/ucc-processor.config.ts +++ b/src/compiler/processor/impl/ucc-processor.config.ts @@ -1,5 +1,4 @@ import { UcSchema } from '../../../schema/uc-schema.js'; -import { UccConfig } from '../ucc-config.js'; import { UccFeature } from '../ucc-feature.js'; import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js'; @@ -30,12 +29,8 @@ export class UccProcessor$Config> { return this.#current; } - enableFeature( - feature: UccFeature, - options: TOptions, - data: UccConfig.Data | undefined, - ): void { - this.#featureConfig(feature).configureFeature(this, options, data); + enableFeature(feature: UccFeature, options: TOptions): void { + this.#featureConfig(feature).configureFeature(this, options); } enableSchema( diff --git a/src/compiler/processor/impl/ucc-processor.constraint-application.ts b/src/compiler/processor/impl/ucc-processor.constraint-application.ts index 9850b47e..1aa50b84 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-application.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-application.ts @@ -4,7 +4,6 @@ import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constra import { UcPresentationName } from '../../../schema/uc-presentations.js'; import { UcSchema } from '../../../schema/uc-schema.js'; import { UccCapability } from '../ucc-capability.js'; -import { UccConfig } from '../ucc-config.js'; import { UccFeature } from '../ucc-feature.js'; import { UccSetup } from '../ucc-setup.js'; import { UccProcessor$Config } from './ucc-processor.config.js'; @@ -48,11 +47,6 @@ export class UccProcessor$ConstraintApplication 0; } diff --git a/src/compiler/processor/impl/ucc-processor.constraint-issue.ts b/src/compiler/processor/impl/ucc-processor.constraint-issue.ts index 35f25900..2c6c09f6 100644 --- a/src/compiler/processor/impl/ucc-processor.constraint-issue.ts +++ b/src/compiler/processor/impl/ucc-processor.constraint-issue.ts @@ -1,10 +1,8 @@ import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js'; import { UcPresentationName } from '../../../schema/uc-presentations.js'; -import { UccConfig } from '../ucc-config.js'; export interface UccProcessor$ConstraintIssue { readonly processor: UcProcessorName; readonly within: UcPresentationName | undefined; readonly constraint: UcFeatureConstraint; - readonly data: UccConfig.Data | undefined; } diff --git a/src/compiler/processor/impl/ucc-processor.feature-config.ts b/src/compiler/processor/impl/ucc-processor.feature-config.ts index 7f7f2164..2cb61bfa 100644 --- a/src/compiler/processor/impl/ucc-processor.feature-config.ts +++ b/src/compiler/processor/impl/ucc-processor.feature-config.ts @@ -15,12 +15,8 @@ export class UccProcessor$FeatureConfig, in TOpt this.#getConfig = lazyValue(createConfig); } - configureFeature( - config: UccProcessor$Config, - options: TOptions, - data: UccConfig.Data | undefined, - ): void { - this.#configureFeature(config, {}, options, data); + configureFeature(config: UccProcessor$Config, options: TOptions): void { + this.#configureFeature(config, {}, options); } configureSchema( @@ -31,13 +27,12 @@ export class UccProcessor$FeatureConfig, in TOpt const { processor, constraint: { with: options }, - data, } = issue; this.#configureFeature(config, { processor }); config.configure({ ...issue, schema }, () => { - this.#getConfig().configureSchema?.(schema, options as TOptions, data ?? {}); + this.#getConfig().configureSchema?.(schema, options as TOptions); }); } @@ -45,9 +40,8 @@ export class UccProcessor$FeatureConfig, in TOpt config: UccProcessor$Config, current: UccProcessor$Current, options?: TOptions, - data?: UccConfig.Data, ): void { - if (options === undefined && data === undefined) { + if (options === undefined) { if (this.#autoConfigured) { return; } @@ -55,7 +49,7 @@ export class UccProcessor$FeatureConfig, in TOpt this.#autoConfigured = true; } - config.configure(current, () => this.#getConfig().configure?.(options!, data ?? {})); + config.configure(current, () => this.#getConfig().configure?.(options!)); } } diff --git a/src/compiler/processor/ucc-capability.ts b/src/compiler/processor/ucc-capability.ts index 89bca10b..211079f2 100644 --- a/src/compiler/processor/ucc-capability.ts +++ b/src/compiler/processor/ucc-capability.ts @@ -1,7 +1,6 @@ import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcSchema } from '../../schema/uc-schema.js'; -import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; import { UccSetup } from './ucc-setup.js'; @@ -137,11 +136,6 @@ export namespace UccCapability { */ get constraint(): UcFeatureConstraint; - /** - * Custom data passed by parent schema processor. - */ - get data(): UccConfig.Data | undefined; - /** * Informs whether the {@link constraint} is {@link apply applied} already. * diff --git a/src/compiler/processor/ucc-config.ts b/src/compiler/processor/ucc-config.ts index 3ce7985e..d5f01a09 100644 --- a/src/compiler/processor/ucc-config.ts +++ b/src/compiler/processor/ucc-config.ts @@ -14,9 +14,8 @@ export interface UccConfig { * May be called multiple times. * * @param options - Configuration options. - * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. */ - configure?(options: TOptions, data: UccConfig.Data): void; + configure?(options: TOptions): void; /** * Configures processing of concrete `schema`. @@ -27,12 +26,5 @@ export interface UccConfig { * @param options - Configuration options. * @param data - Custom data passed by parent schema processor. `undefined` for top-level schemas. */ - configureSchema?(schema: UcSchema, options: TOptions, data: UccConfig.Data): void; -} - -export namespace UccConfig { - /** - * Custom data to pass to feature configurations. - */ - export type Data = { readonly [key in PropertyKey]: unknown }; + configureSchema?(schema: UcSchema, options: TOptions): void; } diff --git a/src/compiler/processor/ucc-processor.ts b/src/compiler/processor/ucc-processor.ts index 07e414f4..5278998c 100644 --- a/src/compiler/processor/ucc-processor.ts +++ b/src/compiler/processor/ucc-processor.ts @@ -88,22 +88,18 @@ export abstract class UccProcessor> return this.#config.current.constraint; } - enable( - feature: UccFeature, - options?: TOptions, - data?: UccConfig.Data, - ): this { - this.#config.enableFeature(feature, options!, data); + enable(feature: UccFeature, options?: TOptions): this { + this.#config.enableFeature(feature, options!); return this; } - processModel(model: UcModel, data?: UccConfig.Data): this { + processModel(model: UcModel): this { const schema = ucSchema(model); - this.#applyConstraints(schema, undefined, schema.where, data); + this.#applyConstraints(schema, undefined, schema.where); for (const within of this.schemaIndex.listPresentations(schema.within)) { - this.#applyConstraints(schema, within, schema.within![within], data); + this.#applyConstraints(schema, within, schema.within![within]); } return this; @@ -113,11 +109,10 @@ export abstract class UccProcessor> schema: UcSchema, within: UcPresentationName | undefined, constraints: UcConstraints | undefined, - data: UccConfig.Data | undefined, ): void { for (const processor of this.schemaIndex.processors) { for (const constraint of asArray(constraints?.[processor])) { - this.#issueConstraint(schema, { processor, within, constraint, data }); + this.#issueConstraint(schema, { processor, within, constraint }); } } } diff --git a/src/compiler/processor/ucc-setup.ts b/src/compiler/processor/ucc-setup.ts index aa70600d..c17951fc 100644 --- a/src/compiler/processor/ucc-setup.ts +++ b/src/compiler/processor/ucc-setup.ts @@ -1,7 +1,6 @@ import { UcFeatureConstraint, UcProcessorName } from '../../schema/uc-constraints.js'; import { UcPresentationName } from '../../schema/uc-presentations.js'; import { UcModel, UcSchema } from '../../schema/uc-schema.js'; -import { UccConfig } from './ucc-config.js'; import { UccFeature } from './ucc-feature.js'; /** @@ -38,35 +37,28 @@ export interface UccSetup { * @typeParam TOptions - Type of schema processing options. * @param feature - Feature to enable. * @param options - Processing options. - * @param data - Custom data to pass to {@link UccConfig#configure schema processing configuration}. * * @returns `this` instance. */ - enable( - feature: UccFeature, - options: TOptions, - data?: UccConfig.Data, - ): this; + enable(feature: UccFeature, options: TOptions): this; /** * Enables the given processing `feature` that does not require options. * * @typeParam TOptions - Type of schema processing options. * @param feature - Feature to enable. - * @param data - Custom data to pass to {@link UccConfig#configure schema processing configuration}. * * @returns `this` instance. */ - enable(feature: UccFeature, options?: void, data?: UccConfig.Data): this; + enable(feature: UccFeature, options?: void): this; /** * Applies model processing instructions specified as its {@link churi!UcSchema#where constraints}. * * @typeParam T - Implied data type. * @param model - Target model. - * @param data - Custom data to pass to {@link UccConfig#configure schema processing configuration}. * * @returns `this` instance. */ - processModel(model: UcModel, data?: UccConfig.Data): this; + processModel(model: UcModel): this; } From 02371f7bd2dc2a55e8a870471c0580db1f856174 Mon Sep 17 00:00:00 2001 From: Ruslan Lopatin Date: Thu, 3 Aug 2023 16:15:58 +0700 Subject: [PATCH 36/36] Upgrade deps --- package.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package.json b/package.json index d4e26ebc..6737c02c 100644 --- a/package.json +++ b/package.json @@ -76,26 +76,26 @@ "httongue": "^3.1.0" }, "devDependencies": { - "@jest/globals": "^29.6.1", + "@jest/globals": "^29.6.2", "@proc7ts/async": "^2.1.0", "@run-z/eslint-config": "^3.5.0", "@run-z/prettier-config": "^2.0.0", "@run-z/project-config": "^0.19.3", - "@swc/core": "^1.3.71", + "@swc/core": "^1.3.74", "@swc/jest": "^0.2.27", - "@typescript-eslint/eslint-plugin": "^6.2.0", - "@typescript-eslint/parser": "^6.2.0", + "@typescript-eslint/eslint-plugin": "^6.2.1", + "@typescript-eslint/parser": "^6.2.1", "esgen": "^0.2.9", - "eslint": "^8.45.0", + "eslint": "^8.46.0", "eslint-plugin-jest": "^27.2.3", - "expect": "^29.6.1", + "expect": "^29.6.2", "gh-pages": "^5.0.0", - "jest": "^29.6.1", + "jest": "^29.6.2", "jest-junit": "^16.0.0", - "jest-mock": "^29.6.1", + "jest-mock": "^29.6.2", "prettier": "^2.8.8", "prettier-eslint-cli": "^7.1.0", - "rollup": "^3.26.3", + "rollup": "^3.27.0", "run-z": "^1.11.0", "ts-jest": "^29.1.1", "tslib": "^2.6.1",