Skip to content

Commit

Permalink
Merge pull request #67 from UCNot/processor-bootstrap
Browse files Browse the repository at this point in the history
Schema processor bootstrap
  • Loading branch information
surol committed Aug 6, 2023
2 parents 273dda2 + 5c2e52d commit ceae47c
Show file tree
Hide file tree
Showing 86 changed files with 1,227 additions and 1,440 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { UcProcessorName, UcSchemaConstraint } from '../../../schema/uc-constraints.js';
import { ucModelName } from '../../../schema/uc-model-name.js';
import { UcPresentationName } from '../../../schema/uc-presentations.js';
import { UcSchema } from '../../../schema/uc-schema.js';
import { UccBootstrap } from '../ucc-bootstrap.js';
import { UccFeature } from '../ucc-feature.js';
import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js';
import { UccProcessor$FeatureSet } from './ucc-processor.feature-set.js';

export class UccProcessor$ConstraintApplication<
in out TBoot extends UccBootstrap<TBoot>,
in out TOptions,
> implements UccFeature.ConstraintApplication<TBoot, TOptions> {

readonly #featureSet: UccProcessor$FeatureSet<TBoot>;
readonly #schema: UcSchema;
readonly #issue: UccProcessor$ConstraintIssue<TOptions>;
readonly #feature: UccFeature<TBoot, TOptions>;
#applied = 0;

constructor(
featureSet: UccProcessor$FeatureSet<TBoot>,
schema: UcSchema,
issue: UccProcessor$ConstraintIssue<TOptions>,
feature: UccFeature<TBoot, TOptions>,
) {
this.#featureSet = featureSet;
this.#schema = schema;
this.#issue = issue;
this.#feature = feature;
}

get schema(): UcSchema {
return this.#schema;
}

get processor(): UcProcessorName {
return this.#issue.processor;
}

get within(): UcPresentationName | undefined {
return this.#issue.within;
}

get constraint(): UcSchemaConstraint {
return this.#issue.constraint;
}

get options(): TOptions {
return this.#issue.options;
}

isApplied(): boolean {
return this.#applied > 0;
}

isIgnored(): boolean {
return this.#applied < 0;
}

apply(): void {
if (this.isApplied()) {
return;
}

this.#applied = 1;
this.#constrain();
}

#constrain(): void {
const handle = this.#featureSet.enableFeature(this.#feature);
const { options } = this;

if (handle) {
this.#featureSet.runWithCurrent(this, () => handle.constrain(this));
} else if (options !== undefined) {
throw new TypeError(
`Feature ${this.#issue} can not constrain schema "${ucModelName(this.schema)}"`,
);
}
}

ignore(): void {
if (!this.isApplied()) {
this.#applied = -1;
}
}

}
23 changes: 23 additions & 0 deletions src/compiler/bootstrap/impl/ucc-processor.constraint-issue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { esQuoteKey, esStringLiteral } from 'esgen';
import { UcProcessorName, UcSchemaConstraint } from '../../../schema/uc-constraints.js';
import { UcPresentationName } from '../../../schema/uc-presentations.js';

export class UccProcessor$ConstraintIssue<out TOptions> {

constructor(
readonly processor: UcProcessorName,
readonly within: UcPresentationName | undefined,
readonly constraint: UcSchemaConstraint,
) {}

get options(): TOptions {
return this.constraint.with as TOptions;
}

toString(): string {
const { use, from } = this.constraint;

return `import(${esStringLiteral(from)}).${esQuoteKey(use)}`;
}

}
52 changes: 52 additions & 0 deletions src/compiler/bootstrap/impl/ucc-processor.constraint-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { UcProcessorName, UcSchemaConstraint } from '../../../schema/uc-constraints.js';
import { UcPresentationName } from '../../../schema/uc-presentations.js';
import { UccBootstrap } from '../ucc-bootstrap.js';
import { UccFeature } from '../ucc-feature.js';

export class UccProcessor$ConstraintMapper<in out TBoot extends UccBootstrap<TBoot>> {

readonly #handlers = new Map<string, UccFeature.ConstraintHandler<TBoot>>();

onConstraint<TOptions>(
{ processor, within, use, from }: UccFeature.ConstraintCriterion,
handler: UccFeature.ConstraintHandler<TBoot, TOptions>,
): void {
const handlerId = this.#handlerId(processor, within, use, from);
const prevHandler = this.#handlers.get(handlerId) as UccFeature.ConstraintHandler<
TBoot,
TOptions
>;

if (prevHandler) {
this.#handlers.set(
handlerId,
(application: UccFeature.ConstraintApplication<TBoot, TOptions>) => {
prevHandler(application);
handler(application);
},
);
} else {
this.#handlers.set(handlerId, handler);
}
}

findHandler<TOptions>(
processor: UcProcessorName,
within: UcPresentationName | undefined,
{ use, from }: UcSchemaConstraint,
): UccFeature.ConstraintHandler<TBoot, TOptions> | undefined {
return this.#handlers.get(
this.#handlerId(processor, within, use, from),
) as UccFeature.ConstraintHandler<TBoot, TOptions>;
}

#handlerId(
processor: UcProcessorName,
within: UcPresentationName | undefined,
use: string,
from: string,
): string {
return `${processor}(${within} ?? '*'):${use}@${from}`;
}

}
56 changes: 56 additions & 0 deletions src/compiler/bootstrap/impl/ucc-processor.constraint-usage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { UcSchema } from '../../../schema/uc-schema.js';
import { UccBootstrap } from '../ucc-bootstrap.js';
import { UccProcessor$ConstraintApplication } from './ucc-processor.constraint-application.js';
import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js';
import { UccProcessor$FeatureSet } from './ucc-processor.feature-set.js';

export class UccProcessor$ConstraintUsage<
in out TBoot extends UccBootstrap<TBoot>,
out TOptions = unknown,
> {

readonly #featureSet: UccProcessor$FeatureSet<TBoot>;
readonly #schema: UcSchema;
readonly #issues: UccProcessor$ConstraintIssue<TOptions>[] = [];

constructor(featureSet: UccProcessor$FeatureSet<TBoot>, schema: UcSchema) {
this.#featureSet = featureSet;
this.#schema = schema;
}

issue(issue: UccProcessor$ConstraintIssue<TOptions>): void {
this.#issues.push(issue);
}

async apply(): Promise<void> {
for (const issue of this.#issues) {
await this.#applyConstraint(issue);
}
this.#issues.length = 0;
}

async #applyConstraint(issue: UccProcessor$ConstraintIssue<TOptions>): Promise<void> {
const featureSet = this.#featureSet;
const schema = this.#schema;
const { processor, within, constraint } = issue;
const { constraintMapper: profiler } = featureSet;
const application = new UccProcessor$ConstraintApplication(
featureSet,
schema,
issue,
await featureSet.resolveFeature(issue),
);

featureSet.runWithCurrent({ processor, schema, within, constraint }, () => {
profiler.findHandler(processor, within, constraint)?.(application);
if (within) {
// Apply any presentation handler.
profiler.findHandler(processor, undefined, constraint)?.(application);
}
if (!application.isIgnored()) {
application.apply();
}
});
}

}
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { UcFeatureConstraint, UcProcessorName } from '../../../schema/uc-constraints.js';
import { UcProcessorName, UcSchemaConstraint } 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;
readonly constraint?: UcSchemaConstraint | undefined;
}
92 changes: 92 additions & 0 deletions src/compiler/bootstrap/impl/ucc-processor.feature-set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { lazyValue, mayHaveProperties } from '@proc7ts/primitives';
import { UccBootstrap } from '../ucc-bootstrap.js';
import { UccFeature } from '../ucc-feature.js';
import { UccProcessor$ConstraintIssue } from './ucc-processor.constraint-issue.js';
import { UccProcessor$ConstraintMapper } from './ucc-processor.constraint-mapper.js';
import { UccProcessor$Current } from './ucc-processor.current.js';

export class UccProcessor$FeatureSet<in out TBoot extends UccBootstrap<TBoot>> {

readonly #constraintMapper: UccProcessor$ConstraintMapper<TBoot>;
readonly #resolutions = new Map<string, Promise<{ [key in string]: UccFeature<TBoot> }>>();
readonly #enable: <TOptions>(
feature: UccFeature<TBoot, TOptions>,
) => UccFeature.Handle<TOptions> | void;

readonly #features = new Map<UccFeature<TBoot, never>, UccFeature$Entry>();

#current: UccProcessor$Current = {};

constructor(
constraintMapper: UccProcessor$ConstraintMapper<TBoot>,
enable: <TOptions>(feature: UccFeature<TBoot, TOptions>) => UccFeature.Handle<TOptions> | void,
) {
this.#constraintMapper = constraintMapper;
this.#enable = enable;
}

get constraintMapper(): UccProcessor$ConstraintMapper<TBoot> {
return this.#constraintMapper;
}

get current(): UccProcessor$Current {
return this.#current;
}

async resolveFeature<TOptions>(
issue: UccProcessor$ConstraintIssue<TOptions>,
): Promise<UccFeature<TBoot, TOptions>> {
const {
constraint: { use, from },
} = issue;
let resolveFeatures = this.#resolutions.get(from);

if (!resolveFeatures) {
resolveFeatures = import(from);
this.#resolutions.set(from, resolveFeatures);
}

const { [use]: feature } = await resolveFeatures;

if ((mayHaveProperties(feature) && 'uccEnable' in feature) || typeof feature === 'function') {
return feature as UccFeature<TBoot, TOptions>;
}
if (feature === undefined) {
throw new ReferenceError(`No such schema processing feature: ${issue}`);
} else {
throw new ReferenceError(`Not a schema processing feature: ${issue}`);
}
}

enableFeature<TOptions>(
feature: UccFeature<TBoot, TOptions>,
): UccFeature.Handle<TOptions> | void {
let entry = this.#features.get(feature) as UccFeature$Entry<TOptions> | undefined;

if (!entry) {
entry = {
getHandle: lazyValue(() => this.runWithCurrent({}, () => this.#enable(feature))),
};
this.#features.set(feature, entry);
}

return entry.getHandle();
}

runWithCurrent<T>(current: UccProcessor$Current, action: () => T): T {
const prev = this.#current;

this.#current = current.processor ? current : { ...current, processor: prev.processor };

try {
return action();
} finally {
this.#current = prev;
}
}

}

interface UccFeature$Entry<in TOptions = never> {
readonly getHandle: () => UccFeature.Handle<TOptions> | void;
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
export * from './ucc-capability.js';
export * from './ucc-config.js';
export * from './ucc-bootstrap.js';
export * from './ucc-feature.js';
export * from './ucc-processor.js';
export * from './ucc-schema-index.js';
export * from './ucc-setup.js';
Loading

0 comments on commit ceae47c

Please sign in to comment.