Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactored the TypeMap API #23

Merged
merged 3 commits into from
Sep 14, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deps/std/testing.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export {
assert,
assertEquals,
assertThrows,
} from "https://deno.land/std@0.106.0/testing/asserts.ts";
8 changes: 7 additions & 1 deletion lib/constants/lexicon.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
export enum ReservedType {
Number = "number",
String = "string",
Boolean = "boolean",
}

export enum Lexicon {
Identifier,
Nester,
Expand Down Expand Up @@ -38,4 +44,4 @@ export const LEXICON = {
[Lexicon.LineBreaker2]: "\r",
[Lexicon.StringMarker]: "\`",
[Lexicon.EOF]: "",
} as const;
} as const;
1 change: 1 addition & 0 deletions lib/typemap/mod.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { ReservedType, TypeMap } from "./typemap.ts";
105 changes: 105 additions & 0 deletions lib/typemap/typemap.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { ModifierMode, ReservedType, TypeMap } from "./typemap.ts";
import { assert, assertEquals, assertThrows } from "../../deps/std/testing.ts";

const PREFIX = "async_";
const SUFFIX = "_list";
const MODIFY_ASYNC = (t: string) => `Promise<${t}>`;
const MODIFY_LIST = (t: string) => `${t}[]`;

Deno.test("Throws when instantiated with empty input", () => {
assertThrows(() => new TypeMap(), Error);
});

Deno.test("Throws when missing one required type", () => {
assertThrows(() =>
new TypeMap([
// Skipping ReservedType.String
[ReservedType.Number, "float64"],
[ReservedType.Boolean, "bool"],
]), Error);
});

Deno.test("Successfully instantiates typemap", () => {
const typemap = new TypeMap([
[ReservedType.Number, "float64"],
[ReservedType.String, "string"],
[ReservedType.Boolean, "bool"],
]);
const success = typemap.size === 3;
assert(success);
});

Deno.test("Successfully instantiates typemap with a prefix modifier", () => {
const typemap = new TypeMap([
[ReservedType.Number, "number"],
[ReservedType.String, "string"],
[ReservedType.Boolean, "boolean"],
]);
// "async_" => Promise<T>
typemap.addModifier(PREFIX, MODIFY_ASYNC);
assertEquals(typemap.get(PREFIX + ReservedType.Number), "Promise<number>");
assertEquals(typemap.get(PREFIX + ReservedType.String), "Promise<string>");
assertEquals(typemap.get(PREFIX + ReservedType.Boolean), "Promise<boolean>");
});

Deno.test("Successfully instantiates typemap with a suffix modifier", () => {
const typemap = new TypeMap([
[ReservedType.Number, "number"],
[ReservedType.String, "string"],
[ReservedType.Boolean, "boolean"],
]);
// "_list" => T[] (aka Array<T>)
typemap.addModifier(SUFFIX, MODIFY_LIST, ModifierMode.Suffix);
assertEquals(typemap.get(ReservedType.Number + SUFFIX), "number[]");
assertEquals(typemap.get(ReservedType.String + SUFFIX), "string[]");
assertEquals(typemap.get(ReservedType.Boolean + SUFFIX), "boolean[]");
});

Deno.test("Successfully instantiates typemap with prefix and suffix modifiers", () => {
const typemap = new TypeMap([
[ReservedType.Number, "number"],
[ReservedType.String, "string"],
[ReservedType.Boolean, "boolean"],
]);
typemap.addModifier(PREFIX, MODIFY_ASYNC);
typemap.addModifier(SUFFIX, MODIFY_LIST, ModifierMode.Suffix);
assertEquals(typemap.get(PREFIX + ReservedType.Number), "Promise<number>");
assertEquals(typemap.get(PREFIX + ReservedType.String), "Promise<string>");
assertEquals(typemap.get(PREFIX + ReservedType.Boolean), "Promise<boolean>");
assertEquals(typemap.get(ReservedType.Number + SUFFIX), "number[]");
assertEquals(typemap.get(ReservedType.String + SUFFIX), "string[]");
assertEquals(typemap.get(ReservedType.Boolean + SUFFIX), "boolean[]");
});

Deno.test("Successfully removes modifier", () => {
const typemap = new TypeMap([
[ReservedType.Number, "number"],
[ReservedType.String, "string"],
[ReservedType.Boolean, "boolean"],
]);
const modifierId = typemap.addModifier(PREFIX, MODIFY_ASYNC);
assertEquals(typemap.get(PREFIX + ReservedType.Number), "Promise<number>");
typemap.removeModifier(modifierId);
// No error is expected to be thrown when attempting to remove a
// nonexistant modifer.
typemap.removeModifier(1234567890);
assertEquals(typemap.get(PREFIX + ReservedType.Number), undefined);
});

Deno.test("Prefixed key is modified successfully", () => {
const actual = TypeMap.modifyKey(PREFIX + ReservedType.Number, {
alias: PREFIX,
modify: MODIFY_ASYNC,
mode: ModifierMode.Prefix,
});
assertEquals(actual, ReservedType.Number);
});

Deno.test("Suffixed key is modified successfully", () => {
const actual = TypeMap.modifyKey(ReservedType.Number + SUFFIX, {
alias: SUFFIX,
modify: MODIFY_LIST,
mode: ModifierMode.Suffix,
});
assertEquals(actual, ReservedType.Number);
});
77 changes: 77 additions & 0 deletions lib/typemap/typemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import { ReservedType } from "../constants/lexicon.ts";

export enum ModifierMode {
Prefix,
Suffix,
}

export interface Modifier {
alias: string;
mode: ModifierMode;
modify: (t: string) => string;
}

export class TypeMap extends Map<string, string> {
constructor(
input?: Array<[ReservedType | string, string]>,
private modifiers: Array<Modifier | undefined> = [],
) {
super(input);
for (const reservedType of Object.values(ReservedType)) {
if (!this.has(reservedType)) {
throw new Error(`Expected required type ${reservedType} to be mapped`);
}
}
}

has(key: string): boolean {
return this.get(key) !== undefined;
}

get(key: string): string | undefined {
if (super.has(key)) return super.get(key);
for (const modifier of this.modifiers) {
if (modifier === undefined) continue;
const modifiedKey = TypeMap.modifyKey(key, modifier);
const mappedType = super.get(modifiedKey);
if (mappedType !== undefined) {
return modifier.modify(mappedType);
}
}
return undefined;
}

addModifier(
alias: string,
modify: (t: string) => string,
mode: ModifierMode = ModifierMode.Prefix,
): number {
this.modifiers.push({ alias, mode, modify });
return this.modifiers.length - 1;
}

removeModifier(modifierId: number) {
delete this.modifiers[modifierId];
}

static modifyKey(key: string, modifier: Modifier): string {
EthanThatOneKid marked this conversation as resolved.
Show resolved Hide resolved
switch (modifier.mode) {
case ModifierMode.Suffix: {
if (key.endsWith(modifier.alias)) {
return key.slice(0, key.length - modifier.alias.length);
}
break;
}
case ModifierMode.Prefix:
default: {
if (key.startsWith(modifier.alias)) {
return key.slice(modifier.alias.length);
}
break;
}
}
return key;
}
}

export { ReservedType };