Skip to content

Commit

Permalink
#42, fork stage 2 toolbox from stage 1.
Browse files Browse the repository at this point in the history
  • Loading branch information
ajvincent committed Jan 13, 2024
1 parent 24088a1 commit 0246a2d
Show file tree
Hide file tree
Showing 7 changed files with 1,070 additions and 30 deletions.
23 changes: 0 additions & 23 deletions stage_1_bootstrap/build/dist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,8 @@ import {
import BuildClassesDriver from "./BuildClassesDriver.js";
import TSDocMap from "./structureMeta/TSDocMap.js";
import reportTSDocsCoverage from "#stage_one/coverage/tsdocs.js";
import { PromiseAllParallel } from "#utilities/source/PromiseTypes.js";

const sourceUtilitiesDir = pathToModule(stageDir, "build/utilities/public");

const distDir = pathToModule(stageDir, "dist");
const distToolboxDir = path.join(distDir, "source", "toolbox");

// #endregion preamble

Expand Down Expand Up @@ -60,12 +56,6 @@ export default async function buildDist(): Promise<void>
fixPrototypeExportsAndTypeStructureImpl(distDir, "types/TypeAndTypeStructureInterfaces.d.ts"),
]);

await fs.mkdir(distToolboxDir, { recursive: true });
const files = (await fs.readdir(sourceUtilitiesDir)).filter(value => value.endsWith(".ts")).map(
file => path.join(sourceUtilitiesDir, file)
);
await PromiseAllParallel(files, exportPublicUtility);

await BuildClassesDriver(distDir);
{
const results = TSDocMap.toJSON();
Expand All @@ -88,16 +78,3 @@ async function fixPrototypeExportsAndTypeStructureImpl(
contents = contents.replace(/TypedStructureImpl/g, "TypeStructureImpl");
await fs.writeFile(modulePath, contents, { encoding: "utf-8" });
}

async function exportPublicUtility(
pathToSourceFile: string,
): Promise<void>
{
let contents = await fs.readFile(pathToSourceFile, { encoding: "utf-8" });
contents = contents.replace(
"#stage_one/prototype-snapshot/",
"../"
);
const pathToDestFile = pathToSourceFile.replace(sourceUtilitiesDir, distToolboxDir);
await fs.writeFile(pathToDestFile, contents, { encoding: "utf-8" });
}
25 changes: 18 additions & 7 deletions stage_1_integration/build/copySnapshot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,23 +26,34 @@ export default async function copySnapshot(): Promise<void> {
await fs.mkdir(snapshotDir, { recursive: true });
await fs.cp(previousDist, snapshotDir, { recursive: true });

const bootstrapTargetDir = path.join(snapshotDir, "source/bootstrap");
await Promise.all([
copyComponentsDir("bootstrap", "source/bootstrap"),
copyComponentsDir("toolbox", "source/toolbox"),
]);
}

async function copyComponentsDir(
pathToSourceDir: string,
pathToTargetDir: string,
): Promise<void>
{
const sourceDir = pathToModule(stageDir, pathToSourceDir);
const { dirs, files } = await readDirsDeep(sourceDir);

const bootstrapSourceDir = pathToModule(stageDir, "bootstrap");
const { dirs, files } = await readDirsDeep(bootstrapSourceDir);
const targetDir = path.join(snapshotDir, pathToTargetDir);

await PromiseAllSequence(dirs, async d => {
const targetDir = d.replace(bootstrapSourceDir, bootstrapTargetDir);
await fs.mkdir(targetDir);
const targetSubDir = d.replace(sourceDir, targetDir);
await fs.mkdir(targetSubDir);
});

await PromiseAllParallel(files, async f => {
let contents = await fs.readFile(f, { encoding: "utf-8" });
contents = contents.replace(/..\/snapshot\/source\//g, "../");

const targetPath = f.replace(bootstrapSourceDir, bootstrapTargetDir);
const targetPath = f.replace(sourceDir, targetDir);
await fs.writeFile(targetPath, contents, { encoding: "utf-8" });
});

await runPrettify(bootstrapTargetDir);
await runPrettify(targetDir);
}
264 changes: 264 additions & 0 deletions stage_1_integration/toolbox/ClassFieldStatementsMap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
import type {
WriterFunction
} from "ts-morph";

import type {
StatementStructureImpls
} from "../snapshot/source/exports.js";

export type StatementsArray = (string | WriterFunction | StatementStructureImpls)[];
type keyPair = {fieldName: string, statementGroup: string};

/**
* This is a map for specifying statements across several class members for a single class field.
*
* For example, a field may require statements for:
* - defining a getter and/or a setter
* - initializing in a constructor
* - implementing a .toJSON() method
*
* The field name specifies which field the statements are about.
* The statement group specifies where the statements go (what method, or an initializer).
*
* Special field keys:
* ClassFieldStatementsMap.FIELD_HEAD_SUPER_CALL:
* These statements will appear at the head of the statement block.
* ClassFieldStatementsMap.FIELD_TAIL_FINAL_RETURN:
* These statements will appear at the tail of the statement block.
*
* Special statement group keys:
* ClassFieldStatementsMap.GROUP_INITIALIZER_OR_PROPERTY:
* This represents an initializer for a property, or a value reference for a getter or setter.
* Field keys will have `get ` or `set ` stripped from them for this group key.
* Statement arrays for this group key should contain exactly one statement, and should be just a string.
*
* @example
* ```typescript
* map.set("foo", ClassFieldStatementsMap.GROUP_INITIALIZER_OR_PROPERTY, ['this.#foo.value']);
* map.set("foo", "toJSON", ["rv.foo = this.foo;"]);
* // ...
* map.set(ClassFieldsStatementsMap.FIELD_HEAD_SUPER_CALL, "toJSON", ["const rv = super.toJSON();"]);
* map.set(ClassFieldsStatementsMap.FIELD_TAIL_FINAL_RETURN, "toJSON", ["return rv;"]);
*
* Array.from(map.groupStatementsMap("toJSON")!.values())
* // [["const rv = super.toJSON();"], ["rv.foo = this.foo;"], ["return rv;"]];
* ```
*/
export default class ClassFieldStatementsMap
{
static #normalizeKeys(fieldName: string, statementGroup: string): [string, string] {
if (statementGroup === ClassFieldStatementsMap.GROUP_INITIALIZER_OR_PROPERTY) {
fieldName = fieldName.replace(/\b[gs]et /, "");
}
return [fieldName, statementGroup];
}

static #hashKey(fieldName: string, statementGroup: string): string {
return JSON.stringify({fieldName, statementGroup});
}
static #parseKey(key: string): keyPair {
return JSON.parse(key) as keyPair;
}

/** A special field name for the start of a function. */
public static readonly FIELD_HEAD_SUPER_CALL = "(super call)";
/** A special field name for the end of a function. */
public static readonly FIELD_TAIL_FINAL_RETURN = "(final return)";

public static readonly GROUP_INITIALIZER_OR_PROPERTY = "(initializer or property reference)";

/** A convenience sorting function for fields. */
public static fieldComparator(
a: string,
b: string
): number
{
if ((a === this.FIELD_HEAD_SUPER_CALL) || (b === this.FIELD_TAIL_FINAL_RETURN))
return -1;
if ((a === this.FIELD_TAIL_FINAL_RETURN) || (b === this.FIELD_HEAD_SUPER_CALL))
return +1;
return a.localeCompare(b);
}

readonly #map = new Map<string, StatementsArray>;
readonly #statementGroupMap = new Map<string, Map<string, StatementsArray>>;

public constructor(
iterable?: [string, string, StatementsArray][]
)
{
if (iterable) {
for (const [fieldName, statementGroup, statements] of iterable) {
this.set(fieldName, statementGroup, statements);
}
}
}

/**
* The number of elements in this collection.
* @returns The element count.
*/
public get size(): number
{
return this.#map.size;
}

/**
* Clear the collection.
*/
public clear(): void
{
this.#map.clear();
this.#statementGroupMap.clear();
}

/**
* Delete an element from the collection by the given key sequence.
*
* @param fieldName - The class field name for the statements.
* @param statementGroup - The statement group owning the statements.
* @returns True if we found the statements and deleted it.
*/
public delete(fieldName: string, statementGroup: string): boolean
{
[fieldName, statementGroup] = ClassFieldStatementsMap.#normalizeKeys(fieldName, statementGroup);
const rv = this.#map.delete(ClassFieldStatementsMap.#hashKey(fieldName, statementGroup));
this.#statementGroupMap.get(statementGroup)?.delete(fieldName);
return rv;
}

/**
* Yield the key-statements tuples of the collection.
*/
public * entries(): IterableIterator<[string, string, StatementsArray]>
{
const iterator = this.#map.entries();
for (const [hashed, statements] of iterator) {
const {fieldName, statementGroup} = ClassFieldStatementsMap.#parseKey(hashed);
yield [fieldName, statementGroup, statements];
}
}

/**
* Iterate over the keys and statementss.
* @param __callback__ - A function to invoke for each iteration.
* @param __thisArg__ - statements to use as this when executing callback.
*/
public forEach(
__callback__: (
statements: StatementsArray,
fieldName: string,
statementGroup: string,
__collection__: ClassFieldStatementsMap
) => void,
__thisArg__?: unknown
): void
{
this.#map.forEach(
(statements: StatementsArray, hashed: string) => {
const {fieldName, statementGroup} = ClassFieldStatementsMap.#parseKey(hashed);
__callback__.apply(__thisArg__, [statements, fieldName, statementGroup, this]);
}
);
}

/**
* Get a statements for a key set.
*
* @param fieldName - The class field name for the statements.
* @param statementGroup - The statement group owning the statements.
* @returns The statements. Undefined if it isn't in the collection.
*/
public get(fieldName: string, statementGroup: string): StatementsArray | undefined
{
[fieldName, statementGroup] = ClassFieldStatementsMap.#normalizeKeys(fieldName, statementGroup);
return this.#map.get(ClassFieldStatementsMap.#hashKey(fieldName, statementGroup));
}

/**
* Report if the collection has a statements for a key set.
*
* @param fieldName - The class field name for the statements.
* @param statementGroup - The statement group owning the statements.
* @returns True if the key set refers to a statements in the collection.
*/
public has(fieldName: string, statementGroup: string): boolean
{
[fieldName, statementGroup] = ClassFieldStatementsMap.#normalizeKeys(fieldName, statementGroup);
return this.#map.has(ClassFieldStatementsMap.#hashKey(fieldName, statementGroup));
}

/**
* Yield the key sets of the collection.
*/
public * keys(): IterableIterator<[string, string]>
{
const iterator = this.#map.keys();
for (const hashed of iterator) {
const {fieldName, statementGroup} = ClassFieldStatementsMap.#parseKey(hashed);
yield [fieldName, statementGroup];
}
}

/**
* Set a statements for a key set.
*
* @param fieldName - The class field name for the statements.
* @param statementGroup - The statement group owning the statements.
* @param statements - The statements.
* @returns This collection.
*/
public set(fieldName: string, statementGroup: string, statements: StatementsArray): this
{
[fieldName, statementGroup] = ClassFieldStatementsMap.#normalizeKeys(fieldName, statementGroup);
this.#map.set(
ClassFieldStatementsMap.#hashKey(fieldName, statementGroup), statements
);

let subMap = this.#statementGroupMap.get(statementGroup);
if (!subMap) {
subMap = new Map<string, StatementsArray>;
this.#statementGroupMap.set(statementGroup, subMap);
}
subMap.set(fieldName, statements);

return this;
}

/**
* Yield the statementss of the collection.
*/
public values(): IterableIterator<StatementsArray>
{
return this.#map.values();
}

public [Symbol.iterator](): IterableIterator<[string, string, StatementsArray]>
{
return this.entries();
}

public [Symbol.toStringTag] = "ClassFieldStatementsMap";

public groupKeys(): string[]
{
return Array.from(this.#statementGroupMap.keys());
}

/**
* Get the current set of statements for each statement group, sorted by field name.
* @param statementGroup - The statement group owning the statements.
*/
public groupStatementsMap(
statementGroup: string
): ReadonlyMap<string, StatementsArray> | undefined
{
const iterator = this.#statementGroupMap.get(statementGroup)?.entries();
if (!iterator)
return undefined;

const entries = Array.from(iterator);
entries.sort((a, b) => ClassFieldStatementsMap.fieldComparator(a[0], b[0]));
return new Map(entries);
}
}
Loading

0 comments on commit 0246a2d

Please sign in to comment.