From cdb038e1547303d33145cd24c516f0da5e11886b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20V=C3=A1zquez=20P=C3=BAa?= Date: Tue, 2 Jun 2020 20:49:49 +0200 Subject: [PATCH] Initial attempt at generating AST-related code --- .eslintignore | 4 +- .gitignore | 1 + packages/delisp-core/generate-syntax.ts | 193 +++++++++++++++++++++ packages/delisp-core/package.json | 5 +- packages/delisp-core/src/macroexpand.ts | 2 +- packages/delisp-core/src/syntax-convert.ts | 2 +- packages/delisp-core/src/syntax-utils.ts | 2 +- packages/delisp-core/src/syntax.ts | 77 +++----- packages/tsconfig.base.json | 1 + 9 files changed, 225 insertions(+), 62 deletions(-) create mode 100644 packages/delisp-core/generate-syntax.ts diff --git a/.eslintignore b/.eslintignore index 6f3551be..0551efec 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,6 @@ node_modules dist *fixtures* dist-test -dist-ts \ No newline at end of file +dist-ts + +*-generated.ts \ No newline at end of file diff --git a/.gitignore b/.gitignore index 7a8ef023..5c9cd46f 100644 --- a/.gitignore +++ b/.gitignore @@ -10,3 +10,4 @@ dist-ts/ .netlify .log/ +/packages/delisp-core/src/syntax-generated.ts diff --git a/packages/delisp-core/generate-syntax.ts b/packages/delisp-core/generate-syntax.ts new file mode 100644 index 00000000..6c33c6f2 --- /dev/null +++ b/packages/delisp-core/generate-syntax.ts @@ -0,0 +1,193 @@ +import fs from "fs"; +import path from "path"; + +// TS Types + +interface TSRawType { + tag: "primitive"; + raw: string; +} +interface TSArrayType { + tag: "array"; + elements: TSType; +} + +interface TSRecordField { + key: string; + type: TSType; +} + +interface TSRecordType { + tag: "record"; + fields: TSRecordField[]; +} +type TSType = TSRawType | TSArrayType | TSRecordType; + +function tstype(raw: string): TSRawType { + return { tag: "primitive", raw }; +} + +function tsArray(t: TSType): TSArrayType { + return { tag: "array", elements: t }; +} + +function tsRecord(fields: TSRecordType["fields"]): TSRecordType { + return { tag: "record", fields }; +} + +function tsContains(E: TSRawType, t: TSType): boolean { + switch (t.tag) { + case "primitive": + return t.raw === E.raw; + case "array": + return tsContains(E, t.elements); + case "record": + return t.fields.some((f) => tsContains(E, f.type)); + } +} + +function printTSType(t: TSType): string { + switch (t.tag) { + case "primitive": + return t.raw; + case "array": + return `Array<${printTSType(t.elements)}>`; + case "record": + return `{ + ${t.fields + .map((f) => f.key + ": " + printTSType(f.type) + ";") + .join("\n")} + }`; + } +} + +// ADT + +interface ADTAlternative { + tag: string; + record: TSRecordType; +} + +interface ADT { + tag: "adt"; + name: string; + generics: TSRawType[]; + alternatives: ADTAlternative[]; + type: TSRawType; +} + +function tsADT( + name: string, + generics: TSRawType[], + alternatives: { + [tag: string]: { + [field: string]: TSType; + }; + } +): ADT { + return { + tag: "adt", + name, + generics, + alternatives: Object.keys(alternatives).map((tag) => { + return { + tag, + record: tsRecord( + Object.keys(alternatives[tag]).map((key) => ({ + key, + type: alternatives[tag][key], + })) + ), + }; + }), + type: tstype(name), + }; +} + +function printADT(adt: ADT): string { + const alternativeData = (alt: ADTAlternative) => { + const generics = adt.generics.map((t) => + tsContains(t, alt.record) ? t.raw : "_" + t.raw + ); + const name = `S${alt.tag}F`; + const nameWithGenerics = `${name}<${generics.join(", ")}>`; + return { + name, + generics, + nameWithGenerics: generics.length === 0 ? name : nameWithGenerics, + }; + }; + + const alternatives = adt.alternatives + .map((alt) => { + const altData = alternativeData(alt); + + const taggedRecord = tsRecord([ + { + key: "tag", + type: tstype(`"${alt.tag[0].toLowerCase() + alt.tag.slice(1)}"`), + }, + ...alt.record.fields, + ]); + + return ` + export interface ${altData.nameWithGenerics} + ${printTSType(taggedRecord)} + `; + }) + .join("\n"); + + const generics = + adt.generics.length === 0 + ? "" + : `<${adt.generics.map((g) => g.raw).join(", ")}>`; + + const union = ` + export type ${printTSType(adt.type)}${generics} = ${adt.alternatives + .map(alternativeData) + .map((d) => `${d.name}${generics}`) + .join(" | ")}; + `; + + return ` + ${alternatives} + ${union} + `; +} + +const E = tstype("E"); + +const Identifier = tsADT("Identifier", [], { + Identifier: { name: tstype("string"), location: tstype("Location") }, +}); + +const LambdaList = tsADT("LambdaList", [], { + LambdaList: { + positionalArguments: tsArray(Identifier.type), + location: tstype("Location"), + }, +}); + +const Expression = tsADT("FutureExpression", [E], { + Number: { value: tstype("number") }, + String: { value: tstype("string") }, + Boolean: { value: tstype("boolean") }, + None: {}, + Conditional: { + condition: E, + consequent: E, + alternative: E, + }, + Function: { lambdaList: LambdaList.type, body: tsArray(E) }, +}); + +const output = ` +// This file is generated +import { Location } from "./input"; + +${printADT(Identifier)} +${printADT(LambdaList)} +${printADT(Expression)} +`; + +fs.writeFileSync(path.join(__dirname, "src/syntax-generated.ts"), output); diff --git a/packages/delisp-core/package.json b/packages/delisp-core/package.json index c419b47a..b41c2170 100644 --- a/packages/delisp-core/package.json +++ b/packages/delisp-core/package.json @@ -4,9 +4,10 @@ "main": "dist/src/index.js", "license": "MIT", "scripts": { - "build": "tsc --build tsconfig.json", + "build": "yarn run generate-syntax && tsc --build tsconfig.json", "build:all": "tsc --build tsconfig.all.json", - "test": "jest" + "test": "jest", + "generate-syntax": "ts-node ./generate-syntax.ts" }, "files": [ "dist/" diff --git a/packages/delisp-core/src/macroexpand.ts b/packages/delisp-core/src/macroexpand.ts index 6a2acd23..ae497537 100644 --- a/packages/delisp-core/src/macroexpand.ts +++ b/packages/delisp-core/src/macroexpand.ts @@ -38,7 +38,7 @@ function macroexpandLambda(lambda: S.SFunction): S.Expression { node: { tag: "function", lambdaList: { - tag: "function-lambda-list", + tag: "lambdaList", positionalArguments: [ { tag: "identifier", name: "*context*", location: lambda.location }, ...lambdaList.positionalArguments, diff --git a/packages/delisp-core/src/syntax-convert.ts b/packages/delisp-core/src/syntax-convert.ts index 7f828df9..716f4a7e 100644 --- a/packages/delisp-core/src/syntax-convert.ts +++ b/packages/delisp-core/src/syntax-convert.ts @@ -129,7 +129,7 @@ function parseLambdaList(ll: ASExpr): S.LambdaList { }); return { - tag: "function-lambda-list", + tag: "lambdaList", positionalArguments: symbols.map(parseIdentifier), location: ll.location, }; diff --git a/packages/delisp-core/src/syntax-utils.ts b/packages/delisp-core/src/syntax-utils.ts index da29d281..e57e0367 100644 --- a/packages/delisp-core/src/syntax-utils.ts +++ b/packages/delisp-core/src/syntax-utils.ts @@ -275,7 +275,7 @@ export function wrapInLambda(e: S.Expression): S.Expression { node: { tag: "function", lambdaList: { - tag: "function-lambda-list", + tag: "lambdaList", positionalArguments: [], location: e.location, }, diff --git a/packages/delisp-core/src/syntax.ts b/packages/delisp-core/src/syntax.ts index df80b90a..43b39ceb 100644 --- a/packages/delisp-core/src/syntax.ts +++ b/packages/delisp-core/src/syntax.ts @@ -3,37 +3,23 @@ // import { Location } from "./input"; -import { TypeWithWildcards } from "./type-wildcards"; -import { Type } from "./types"; - // // Expressions // +import { + Identifier, + LambdaList, + SBooleanF, + SConditionalF, + SFunctionF, + SNoneF, + SNumberF, + SStringF, +} from "./syntax-generated"; +import { TypeWithWildcards } from "./type-wildcards"; +import { Type } from "./types"; -interface SNumberF { - tag: "number"; - value: number; -} - -interface SStringF { - tag: "string"; - value: string; -} - -interface SBooleanF { - tag: "boolean"; - value: boolean; -} - -interface SNoneF { - tag: "none"; -} - -export interface Identifier { - tag: "identifier"; - name: SVar; - location: Location; -} +export { Identifier, LambdaList }; type SVar = string; interface SVariableReferenceF { @@ -45,13 +31,6 @@ interface SVariableReferenceF { closedFunctionEffect?: Type; } -interface SConditionalF { - tag: "conditional"; - condition: E; - consequent: E; - alternative: E; -} - interface SFunctionCallF { tag: "function-call"; fn: E; @@ -62,20 +41,6 @@ interface SFunctionCallF { arguments: E[]; } -export interface LambdaList { - tag: "function-lambda-list"; - // The positional arguments for the lambda-list. This is - // internal. - positionalArguments: Identifier[]; - location: Location; -} - -interface SFunctionF { - tag: "function"; - lambdaList: LambdaList; - body: E[]; -} - interface SVectorConstructorF { tag: "vector"; values: E[]; @@ -159,10 +124,10 @@ interface SUnknownF<_E> { } type AnyExpressionF> = - | SNumberF - | SStringF - | SBooleanF - | SNoneF + | SNumberF + | SStringF + | SBooleanF + | SNoneF | SVariableReferenceF | SConditionalF | SFunctionCallF @@ -193,10 +158,10 @@ export interface Expression export interface SVariableReference extends Node {} -export interface SNumber extends Node {} -export interface SString extends Node {} -export interface SBoolean extends Node {} -export interface SNone extends Node {} +export interface SNumber extends Node>> {} +export interface SString extends Node>> {} +export interface SBoolean extends Node>> {} +export interface SNone extends Node>> {} export interface SConditional extends Node>> {} diff --git a/packages/tsconfig.base.json b/packages/tsconfig.base.json index b111c098..70a3777e 100644 --- a/packages/tsconfig.base.json +++ b/packages/tsconfig.base.json @@ -1,6 +1,7 @@ { "compilerOptions": { "target": "esnext", + "lib": ["ES2019"], "module": "commonjs", "strict": true, "moduleResolution": "node",