Skip to content

Commit

Permalink
Codegen verification (#30)
Browse files Browse the repository at this point in the history
* Codegen verification

* Test verification on travis

* Revert test
  • Loading branch information
jscheiny authored May 31, 2018
1 parent 24691b2 commit 92c7b7f
Show file tree
Hide file tree
Showing 10 changed files with 182 additions and 108 deletions.
18 changes: 12 additions & 6 deletions codegen/common.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
export interface CommonOperatorCodeGenOptions {
export interface CodeGenSpec extends CommonSpec {
operators: PartialOperatorSpec[];
}

export interface CommonSpec {
minExponent: number;
maxExponent: number;
}

export interface OperatorCodeGenOptions extends CommonOperatorCodeGenOptions {
export interface PartialOperatorSpec {
fileNamePrefix: string;
uncurriedTypeNamePrefix: string;
curriedTypeNamePrefix: string;
Expand All @@ -12,15 +16,17 @@ export interface OperatorCodeGenOptions extends CommonOperatorCodeGenOptions {
compute: (left: number, right: number) => number;
}

export function getExponents({ minExponent, maxExponent }: CommonOperatorCodeGenOptions): number[] {
export interface OperatorSpec extends CommonSpec, PartialOperatorSpec {}

export function getExponents({ minExponent, maxExponent }: CommonSpec): number[] {
const exponents: number[] = [];
for (let exponent = minExponent; exponent <= maxExponent; exponent++) {
exponents.push(exponent);
}
return exponents;
}

export function isExponent(exponent: number, { minExponent, maxExponent }: CommonOperatorCodeGenOptions): boolean {
export function isExponent(exponent: number, { minExponent, maxExponent }: CommonSpec): boolean {
return exponent >= minExponent && exponent <= maxExponent && exponent === Math.floor(exponent);
}

Expand All @@ -46,9 +52,9 @@ export function genImport(symbols: string[], source: string): string {
return `import { ${symbols.join(", ")} } from "${source}";`;
}

export function genUncurriedTypeName(options: OperatorCodeGenOptions, left?: string | number, right?: string | number) {
export function genUncurriedTypeName(spec: OperatorSpec, left?: string | number, right?: string | number) {
const args = left !== undefined && right !== undefined ? `<${left}, ${right}>` : "";
return `${options.uncurriedTypeNamePrefix}Exponents${args}`;
return `${spec.uncurriedTypeNamePrefix}Exponents${args}`;
}

export function genValueName(value: number): string {
Expand Down
44 changes: 34 additions & 10 deletions codegen/emit.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,51 @@
import { writeFile } from "fs";
import { CommonOperatorCodeGenOptions, OperatorCodeGenOptions } from "./common";
import { genCommonTypes } from "./genCommon";
import { genOperatorTests } from "./genTests";
import { genOperatorTypes } from "./genTypes";
import { codeGenSpec } from "./spec";

const PATH_PREFIX = "src/exponent/";
const PATH_PREFIX = "src/exponent";

export function emitCommonTypes(options: CommonOperatorCodeGenOptions) {
emitFile("common.ts", genCommonTypes(options));
export interface EmitPlan {
path: string;
source: string;
}

export function emitOperator(options: OperatorCodeGenOptions) {
const { fileNamePrefix } = options;
emitFile(`${fileNamePrefix}.ts`, genOperatorTypes(options));
emitFile(`__test__/${fileNamePrefix}Spec.ts`, genOperatorTests(options));
export function emit(callback?: () => void) {
const emits: EmitPlan[] = getEmitPlans();

let index = -1;
const nextEmit = () => {
index++;
const isLastEmit = index === emits.length - 1;
emitFile(emits[index], isLastEmit ? callback : nextEmit);
};
nextEmit();
}

export function getEmitPlans(): EmitPlan[] {
const { operators, ...common } = codeGenSpec;
const emits: EmitPlan[] = [{ path: `${PATH_PREFIX}/common.ts`, source: genCommonTypes(codeGenSpec) }];
operators.forEach(operator => {
const operatorSpec = { ...operator, ...common };
const { fileNamePrefix } = operator;
emits.push(
{ path: `${PATH_PREFIX}/${fileNamePrefix}.ts`, source: genOperatorTypes(operatorSpec) },
{ path: `${PATH_PREFIX}/__test__/${fileNamePrefix}Spec.ts`, source: genOperatorTests(operatorSpec) },
);
});
return emits;
}

function emitFile(path: string, content: string) {
writeFile(PATH_PREFIX + path, content, error => {
function emitFile({ path, source }: EmitPlan, callback?: () => void) {
writeFile(path, source, error => {
if (error) {
console.error(`There was an error writing to ${path}`);
} else {
console.log(`Generated ${path}`);
if (callback) {
callback();
}
}
});
}
14 changes: 7 additions & 7 deletions codegen/genCommon.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import { CommonOperatorCodeGenOptions, genFileHeader, getExponents } from "./common";
import { CommonSpec, genFileHeader, getExponents } from "./common";

export function genCommonTypes(options: CommonOperatorCodeGenOptions): string {
export function genCommonTypes(spec: CommonSpec): string {
return [
...genFileHeader(false),
...genExtremaType("Min", options.minExponent),
...genExtremaType("Max", options.maxExponent),
...genUnionType(options),
...genExtremaType("Min", spec.minExponent),
...genExtremaType("Max", spec.maxExponent),
...genUnionType(spec),
...genErrorType(),
].join("\n");
}
Expand All @@ -17,8 +17,8 @@ function genExtremaType(prefix: string, exponent: number): string[] {
return [type, value, ""];
}

function genUnionType(options: CommonOperatorCodeGenOptions): string[] {
const exponents = getExponents(options).join(" | ");
function genUnionType(spec: CommonSpec): string[] {
const exponents = getExponents(spec).join(" | ");
return [`export type Exponent = ${exponents};`, ""];
}

Expand Down
40 changes: 20 additions & 20 deletions codegen/genTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,54 +5,54 @@ import {
genValueName,
getExponents,
isExponent,
OperatorCodeGenOptions,
OperatorSpec,
} from "./common";

export function genOperatorTests(options: OperatorCodeGenOptions): string {
const lines: string[] = [...genFileHeader(), ...genImports(options)];
const exponents = getExponents(options);
export function genOperatorTests(spec: OperatorSpec): string {
const lines: string[] = [...genFileHeader(), ...genImports(spec)];
const exponents = getExponents(spec);
for (const left of exponents) {
for (const right of exponents) {
lines.push(...genTest(options, left, right));
lines.push(...genTest(spec, left, right));
lines.push("");
}
}
return lines.join("\n");
}

function genImports(options: OperatorCodeGenOptions): string[] {
function genImports(spec: OperatorSpec): string[] {
return [
genImport([genUncurriedTypeName(options)], `../${options.fileNamePrefix}`),
genImport([genUncurriedTypeName(spec)], `../${spec.fileNamePrefix}`),
genImport(["IsArithmeticError"], "../utils"),
"",
];
}

function genTest(options: OperatorCodeGenOptions, left: number, right: number): string[] {
const result = options.compute(left, right);
if (isExponent(result, options)) {
return genValueTest(options, left, right, result);
function genTest(spec: OperatorSpec, left: number, right: number): string[] {
const result = spec.compute(left, right);
if (isExponent(result, spec)) {
return genValueTest(spec, left, right, result);
} else {
return genErrorTest(options, left, right);
return genErrorTest(spec, left, right);
}
}

function genValueTest(options: OperatorCodeGenOptions, left: number, right: number, result: number): string[] {
const typeName = genTestBaseName(options, left, right);
function genValueTest(spec: OperatorSpec, left: number, right: number, result: number): string[] {
const typeName = genTestBaseName(spec, left, right);
return [
`type ${typeName} = ${genUncurriedTypeName(options, left, right)};`,
`type ${typeName} = ${genUncurriedTypeName(spec, left, right)};`,
`const ${typeName}: ${typeName} = ${result};`,
];
}

function genErrorTest(options: OperatorCodeGenOptions, left: number, right: number): string[] {
const typeName = `${genTestBaseName(options, left, right)}IsError`;
function genErrorTest(spec: OperatorSpec, left: number, right: number): string[] {
const typeName = `${genTestBaseName(spec, left, right)}IsError`;
return [
`type ${typeName} = IsArithmeticError<${genUncurriedTypeName(options, left, right)}>;`,
`type ${typeName} = IsArithmeticError<${genUncurriedTypeName(spec, left, right)}>;`,
`const ${typeName}: ${typeName} = true;`,
];
}

function genTestBaseName(options: OperatorCodeGenOptions, left: number, right: number) {
return `${options.testTypeNamePrefix}Of${genValueName(left)}And${genValueName(right)}`;
function genTestBaseName(spec: OperatorSpec, left: number, right: number) {
return `${spec.testTypeNamePrefix}Of${genValueName(left)}And${genValueName(right)}`;
}
32 changes: 16 additions & 16 deletions codegen/genTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,15 @@ import {
getExponents,
indent,
isExponent,
OperatorCodeGenOptions,
OperatorSpec,
} from "./common";

export function genOperatorTypes(options: OperatorCodeGenOptions): string {
const exponents = getExponents(options);
let lines: string[] = [...genFileHeader(), ...genImports(), ...genUncurriedType(options, exponents)];
export function genOperatorTypes(spec: OperatorSpec): string {
const exponents = getExponents(spec);
let lines: string[] = [...genFileHeader(), ...genImports(), ...genUncurriedType(spec, exponents)];
for (const left of exponents) {
if (!(left in options.specialCases)) {
lines.push(...genCurriedType(options, exponents, left));
if (!(left in spec.specialCases)) {
lines.push(...genCurriedType(spec, exponents, left));
}
}
return lines.join("\n");
Expand All @@ -24,30 +24,30 @@ function genImports(): string[] {
return [genImport(["ArithmeticError", "Exponent"], "./common"), ""];
}

function genUncurriedType(options: OperatorCodeGenOptions, exponents: number[]): string[] {
const lines = [`export type ${genUncurriedTypeName(options, "L extends Exponent", "R extends Exponent")}`];
function genUncurriedType(spec: OperatorSpec, exponents: number[]): string[] {
const lines = [`export type ${genUncurriedTypeName(spec, "L extends Exponent", "R extends Exponent")}`];
let first = true;
for (const left of exponents) {
const operator = first ? "=" : ":";
const prefix = indent(`${operator} L extends ${left} ?`);
first = false;
if (left in options.specialCases) {
lines.push(`${prefix} ${options.specialCases[left]}`);
if (left in spec.specialCases) {
lines.push(`${prefix} ${spec.specialCases[left]}`);
} else {
lines.push(`${prefix} ${genCurriedTypeName(options, left)}<R>`);
lines.push(`${prefix} ${genCurriedTypeName(spec, left)}<R>`);
}
}
lines.push(genErrorCase());
lines.push("");
return lines;
}

function genCurriedType(options: OperatorCodeGenOptions, exponents: number[], left: number): string[] {
const lines = [`export type ${genCurriedTypeName(options, left)}<N extends Exponent>`];
function genCurriedType(spec: OperatorSpec, exponents: number[], left: number): string[] {
const lines = [`export type ${genCurriedTypeName(spec, left)}<N extends Exponent>`];
let first = true;
for (const right of exponents) {
const result = options.compute(left, right);
if (isExponent(result, options)) {
const result = spec.compute(left, right);
if (isExponent(result, spec)) {
const operator = first ? "=" : ":";
first = false;
lines.push(indent(`${operator} N extends ${right} ? ${result}`));
Expand All @@ -58,7 +58,7 @@ function genCurriedType(options: OperatorCodeGenOptions, exponents: number[], le
return lines;
}

function genCurriedTypeName({ curriedTypeNamePrefix }: OperatorCodeGenOptions, value: number): string {
function genCurriedTypeName({ curriedTypeNamePrefix }: OperatorSpec, value: number): string {
return `${curriedTypeNamePrefix}${genValueName(value)}`;
}

Expand Down
46 changes: 0 additions & 46 deletions codegen/main.ts

This file was deleted.

3 changes: 3 additions & 0 deletions codegen/produce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { emit } from "./emit";

emit();
41 changes: 41 additions & 0 deletions codegen/spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { CodeGenSpec } from "./common";

const maxExponent = 5;

export const codeGenSpec: CodeGenSpec = {
minExponent: -maxExponent,
maxExponent,
operators: [
{
fileNamePrefix: "addition",
uncurriedTypeNamePrefix: "Add",
curriedTypeNamePrefix: "Add",
testTypeNamePrefix: "Sum",
specialCases: {
[0]: "R",
},
compute: (left, right) => left + right,
},
{
fileNamePrefix: "multiplication",
uncurriedTypeNamePrefix: "Multiply",
curriedTypeNamePrefix: "MultiplyBy",
testTypeNamePrefix: "Product",
specialCases: {
[0]: "0",
[1]: "R",
},
compute: (left, right) => left * right,
},
{
fileNamePrefix: "division",
uncurriedTypeNamePrefix: "Divide",
curriedTypeNamePrefix: "DividedBy",
testTypeNamePrefix: "Quotient",
specialCases: {
[0]: "(R extends 0 ? ArithmeticError : 0)",
},
compute: (left, right) => left / right,
},
],
};
Loading

0 comments on commit 92c7b7f

Please sign in to comment.