Skip to content

Commit

Permalink
Merge pull request #59 from UCNot/multi-value
Browse files Browse the repository at this point in the history
Multi-value schema
  • Loading branch information
surol authored Jul 15, 2023
2 parents a2f0f40 + 2dd8a2d commit a5587a5
Show file tree
Hide file tree
Showing 21 changed files with 598 additions and 146 deletions.
2 changes: 2 additions & 0 deletions src/compiler/common/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from './ucc-list-options.js';
export * from './unsupported-uc-schema.error.js';
3 changes: 3 additions & 0 deletions src/compiler/common/ucc-list-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export interface UccListOptions {
readonly single: 'accept' | 'as-is' | 'prefer' | 'reject';
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { ucModelName } from '../schema/uc-model-name.js';
import { UcSchema } from '../schema/uc-schema.js';
import { ucModelName } from '../../schema/uc-model-name.js';
import { UcSchema } from '../../schema/uc-schema.js';

export class UnsupportedUcSchemaError extends TypeError {

Expand Down
117 changes: 98 additions & 19 deletions src/compiler/deserialization/list.ucrx.class.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import {
import { UcList } from '../../schema/list/uc-list.js';
import { ucModelName } from '../../schema/uc-model-name.js';
import { UcModel } from '../../schema/uc-schema.js';
import { UccListOptions } from '../common/ucc-list-options.js';
import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js';
import { UC_MODULE_CHURI } from '../impl/uc-modules.js';
import { ucSchemaTypeSymbol } from '../impl/uc-schema-symbol.js';
import { ucSchemaVariant } from '../impl/uc-schema-variant.js';
Expand All @@ -22,25 +24,30 @@ import { UcrxCore } from '../rx/ucrx-core.js';
import { UcrxLib } from '../rx/ucrx-lib.js';
import { UcrxBeforeMod, UcrxMethod } from '../rx/ucrx-method.js';
import { UcrxClass, UcrxSignature } from '../rx/ucrx.class.js';
import { UnsupportedUcSchemaError } from '../unsupported-uc-schema.error.js';
import { UcdCompiler } from './ucd-compiler.js';

export class ListUcrxClass<
TItem = unknown,
TItemModel extends UcModel<TItem> = UcModel<TItem>,
> extends UcrxClass<UcrxSignature.Args, TItem[], UcList.Schema<TItem, TItemModel>> {

static uccProcessSchema(compiler: UcdCompiler.Any, { item }: UcList.Schema): UccConfig {
static uccProcessSchema(
compiler: UcdCompiler.Any,
schema: UcList.Schema,
): UccConfig<UccListOptions> {
return {
configure: () => {
compiler.useUcrxClass('list', (lib, schema: UcList.Schema) => new this(lib, schema));
compiler.processModel(item);
configure: options => {
compiler
.processModel(schema.item)
.useUcrxClass('list', (lib, schema: UcList.Schema) => new this(lib, schema))
.useUcrxClass(schema, (lib, schema: UcList.Schema) => new this(lib, schema, options));
},
};
}

readonly #itemClass: UcrxClass;
readonly #isMatrix: boolean;
readonly #single: UccListOptions['single'];

readonly #items: EsFieldHandle;
readonly #addItem: EsMethodHandle<{ item: EsArg }>;
Expand All @@ -50,7 +57,11 @@ export class ListUcrxClass<
readonly #setListField: EsFieldHandle | undefined;
readonly #itemRx: EsPropertyHandle | undefined;

constructor(lib: UcrxLib, schema: UcList.Schema<TItem, TItemModel>) {
constructor(
lib: UcrxLib,
schema: UcList.Schema<TItem, TItemModel>,
{ single }: UccListOptions = { single: 'reject' },
) {
let itemClass: UcrxClass;

const { item } = schema;
Expand Down Expand Up @@ -80,6 +91,7 @@ export class ListUcrxClass<

this.#itemClass = itemClass;
this.#isMatrix = isMatrix;
this.#single = single;

this.#items = new EsField('items', { visibility: EsMemberVisibility.Private }).declareIn(this, {
initializer: () => '[]',
Expand Down Expand Up @@ -193,6 +205,18 @@ export class ListUcrxClass<
return this.#items.get('this');
}

protected countItems(_cx: EsSnippet): EsSnippet {
return esline`${this.#items.get('this')}.length`;
}

protected createSingle(_cx: EsSnippet): EsSnippet {
return esline`${this.#items.get('this')}[0]`;
}

protected createEmptyList(_cx: EsSnippet): EsSnippet {
return '[]';
}

protected createNullList(cx: EsSnippet): EsSnippet;
protected createNullList(_cx: EsSnippet): EsSnippet {
return 'null';
Expand Down Expand Up @@ -261,24 +285,79 @@ export class ListUcrxClass<
args: { cx },
},
}) => code => {
if (isNull) {
code
.write(esline`if (${isNull.get('this')}) {`)
.indent(esline`${this.#setList}(${this.createNullList(cx)});`)
.write(esline`} else if (${listCreated.get('this')}) {`);
} else {
code.write(esline`if (${listCreated.get('this')}) {`);
switch (this.#single) {
case 'reject':
if (isNull) {
code
.write(esline`if (${isNull.get('this')}) {`)
.indent(this.#storeNullList(cx))
.write(esline`} else if (${listCreated.get('this')}) {`);
} else {
code.write(esline`if (${listCreated.get('this')}) {`);
}

code
.indent(this.#storeList(cx))
.write('} else {')
.indent(esline`${cx}.reject(${ucrxRejectSingleItem}(this));`)
.write('}');

break;
case 'accept':
if (isNull) {
code
.write(esline`if (${isNull.get('this')}) {`)
.indent(this.#storeNullList(cx))
.write(esline`} else {`)
.indent(this.#storeList(cx))
.write('}');
} else {
code.write(this.#storeList(cx));
}

break;
case 'as-is':
case 'prefer':
if (isNull) {
code
.write(esline`if (${isNull.get('this')}) {`)
.indent(this.#storeNullList(cx))
.write(esline`} else if (${listCreated.get('this')}) {`);
} else {
code.write(esline`if (${listCreated.get('this')}) {`);
}
code
.indent(
this.#single === 'prefer' ? this.#storeSingleOrList(cx) : this.#storeList(cx),
)
.write('} else {')
.indent(this.#storeSingleOrEmpty(cx))
.write('}');
}

code
.indent(esline`${this.#setList}(${this.createList(cx)});`)
.write(`} else {`)
.indent(esline`${cx}.reject(${ucrxRejectSingleItem}(this));`)
.write(`}`);
},
});
}

#storeNullList(cx: EsSnippet): EsSnippet {
return esline`${this.#setList}(${this.createNullList(cx)});`;
}

#storeList(cx: EsSnippet): EsSnippet {
return esline`${this.#setList}(${this.createList(cx)});`;
}

#storeSingleOrList(cx: EsSnippet): EsSnippet {
return esline`${this.#setList}(${this.countItems(cx)} === 1 ? ${this.createSingle(
cx,
)} : ${this.createList(cx)});`;
}

#storeSingleOrEmpty(cx: EsSnippet): EsSnippet {
return esline`${this.#setList}(${this.countItems(cx)} ? ${this.createSingle(
cx,
)} : ${this.createEmptyList(cx)});`;
}

#declareMatrixMethods(): void {
const { itemClass } = this;
const listCreated = this.#listCreated;
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/deserialization/map.ucrx-entry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ import {
import { UcMap } from '../../schema/map/uc-map.js';
import { ucModelName } from '../../schema/uc-model-name.js';
import { UcModel, UcSchema } from '../../schema/uc-schema.js';
import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js';
import { ucSchemaTypeSymbol } from '../impl/uc-schema-symbol.js';
import { UcrxLib } from '../rx/ucrx-lib.js';
import { UcrxClass } from '../rx/ucrx.class.js';
import { UnsupportedUcSchemaError } from '../unsupported-uc-schema.error.js';
import { MapUcrxClass, MapUcrxStore } from './map.ucrx.class.js';

export class MapUcrxEntry {
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/deserialization/ucd-function.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import { EsCode, EsFunction, EsSnippet, EsSymbol, EsVarKind, EsVarSymbol, esline
import { UcDeserializer } from '../../schema/uc-deserializer.js';
import { ucModelName } from '../../schema/uc-model-name.js';
import { UcSchema } from '../../schema/uc-schema.js';
import { UnsupportedUcSchemaError } from '../common/unsupported-uc-schema.error.js';
import { UC_MODULE_DESERIALIZER } from '../impl/uc-modules.js';
import { ucSchemaTypeSymbol } from '../impl/uc-schema-symbol.js';
import { UcrxClass } from '../rx/ucrx.class.js';
import { UnsupportedUcSchemaError } from '../unsupported-uc-schema.error.js';
import { UcdExportSignature } from './ucd-export.signature.js';
import { UcdLib } from './ucd-lib.js';

Expand Down
6 changes: 3 additions & 3 deletions src/compiler/mod.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
/**
* Schema compiler API.
* Schema compiler SPI.
*
* __Schema compiler API considered unstable__ and may change for minor releases.
* __Schema compiler SPI considered unstable__ and may change for minor releases.
*
* @module churi/compiler.js
*/
export * from './common/mod.js';
export * from './deserialization/mod.js';
export * from './processor/mod.js';
export * from './rx/mod.js';
export * from './serialization/mod.js';
export * from './unsupported-uc-schema.error.js';
export * from './validation/mod.js';
2 changes: 1 addition & 1 deletion src/compiler/serialization/ucs-function.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { EsFunction, EsSnippet, EsVarSymbol, esline } from 'esgen';
import { ucModelName } from '../../schema/uc-model-name.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 { UnsupportedUcSchemaError } from '../unsupported-uc-schema.error.js';
import { UcsExportSignature } from './ucs-export.signature.js';
import { UcsLib } from './ucs-lib.js';
import { UcsWriterClass, UcsWriterSignature } from './ucs-writer.class.js';
Expand Down
84 changes: 64 additions & 20 deletions src/compiler/serialization/ucs-support-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,68 @@ import { UcList } from '../../schema/list/uc-list.js';
import { ucModelName } from '../../schema/uc-model-name.js';
import { ucNullable } from '../../schema/uc-nullable.js';
import { ucOptional } from '../../schema/uc-optional.js';
import { UcModel } from '../../schema/uc-schema.js';
import { UcModel, UcSchema } from '../../schema/uc-schema.js';
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 { UnsupportedUcSchemaError } from '../unsupported-uc-schema.error.js';
import { UcsCompiler } from './ucs-compiler.js';
import { UcsFunction } from './ucs-function.js';
import { UcsSignature } from './ucs.signature.js';

export function ucsSupportList(compiler: UcsCompiler, schema: UcList.Schema): UccConfig;
export function ucsSupportList(compiler: UcsCompiler, { item }: UcList.Schema): UccConfig {
export function ucsSupportList(
compiler: UcsCompiler,
schema: UcList.Schema,
): UccConfig<UccListOptions> {
return {
configure() {
compiler.useUcsGenerator('list', ucsWriteList).processModel(item);
configure(options) {
compiler
.processModel(schema.item)
.useUcsGenerator(schema, (fn, schema, args) => ucsWriteList(fn, schema, args, options));
},
};
}

function ucsWriteList<TItem, TItemModel extends UcModel<TItem>>(
fn: UcsFunction,
schema: UcList.Schema<TItem, TItemModel>,
args: UcsSignature.AllValues,
{ single }: UccListOptions,
): EsSnippet {
const { writer, value } = args;
const itemSchema = schema.item.optional
? ucOptional(ucNullable(schema.item), false) // Write `undefined` items as `null`
: schema.item;

switch (single) {
case 'prefer':
return code => {
code
.write(esline`if (!Array.isArray(${value})) {`)
.indent(ucsWriteItem(fn, itemSchema, { writer, value }))
.write(esline`} else if (${value}.length === 1) {`)
.indent(ucsWriteItem(fn, itemSchema, { writer, value: esline`${value}[0]` }))
.write('} else {')
.indent(ucsWriteListItems(fn, schema, args))
.write('}');
};
case 'as-is':
return code => {
code
.write(esline`if (Array.isArray(${value})) {`)
.indent(ucsWriteListItems(fn, schema, args))
.write(esline`} else {`)
.indent(ucsWriteItem(fn, itemSchema, { writer, value }))
.write('}');
};
case 'accept':
case 'reject':
// Always an array.
return ucsWriteListItems(fn, schema, args);
}
}

function ucsWriteListItems<TItem, TItemModel extends UcModel<TItem>>(
fn: UcsFunction,
schema: UcList.Schema<TItem, TItemModel>,
{ writer, value, asItem }: UcsSignature.AllValues,
Expand All @@ -44,20 +88,7 @@ function ucsWriteList<TItem, TItemModel extends UcModel<TItem>>(
esline`await ${writer}.ready;`,
esline`${writer}.write(${itemWritten} || !${asItem} ? ${comma} : ${openingParenthesis});`,
esline`${itemWritten} = true;`,
fn.serialize(
itemSchema,
{
writer,
value: itemValue,
asItem: '1',
},
(schema, fn) => {
throw new UnsupportedUcSchemaError(
schema,
`${fn}: Can not serialize list item of type "${ucModelName(schema)}"`,
);
},
),
ucsWriteItem(fn, itemSchema, { writer, value: itemValue }),
)
.write(`}`)
.write(esline`if (${asItem}) {`)
Expand All @@ -70,3 +101,16 @@ function ucsWriteList<TItem, TItemModel extends UcModel<TItem>>(
.write(`}`);
};
}

function ucsWriteItem(
fn: UcsFunction,
itemSchema: UcSchema,
args: Omit<UcsSignature.AllValues, 'asItem'>,
): EsSnippet {
return fn.serialize(itemSchema, { ...args, asItem: '1' }, (schema, fn) => {
throw new UnsupportedUcSchemaError(
schema,
`${fn}: Can not serialize list item of type "${ucModelName(schema)}"`,
);
});
}
2 changes: 1 addition & 1 deletion src/compiler/serialization/ucs-support-map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ import { ucModelName } from '../../schema/uc-model-name.js';
import { ucNullable } from '../../schema/uc-nullable.js';
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 { UnsupportedUcSchemaError } from '../unsupported-uc-schema.error.js';
import { UcsCompiler } from './ucs-compiler.js';
import { UcsFunction } from './ucs-function.js';
import { UcsLib } from './ucs-lib.js';
Expand Down
1 change: 1 addition & 0 deletions src/schema/list/mod.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './uc-list.js';
export * from './uc-multi-value.js';
Loading

0 comments on commit a5587a5

Please sign in to comment.