diff --git a/ast b/ast index 7619893..8293d6d 100644 --- a/ast +++ b/ast @@ -25,3 +25,5 @@ Name( col: number, ) +// vim: syntax=elm + diff --git a/ast.out.ts b/ast.out.ts index fca5482..0847334 100644 --- a/ast.out.ts +++ b/ast.out.ts @@ -1,53 +1,61 @@ // Generated file by ast_generator -export type EnumStatement = { - kind: "Enum"; - [0]: Enum; +export type Statement_Enum = { + kind: "Enum", + [0]: Enum, }; -export const EnumStatement = (v0: Enum): EnumStatement => ({ kind: "Enum", [0]: v0 }); -export type NodeStatement = { - kind: "Node"; - [0]: Node; +export const Statement_Enum = (v0: Enum): Statement_Enum => ({ kind: "Enum", [0]: v0 }); +export type Statement_Node = { + kind: "Node", + [0]: Node, }; -export const NodeStatement = (v0: Node): NodeStatement => ({ kind: "Node", [0]: v0 }); -export type Statement = EnumStatement | NodeStatement; +export const Statement_Node = (v0: Node): Statement_Node => ({ kind: "Node", [0]: v0 }); +export type Statement = Statement_Enum | Statement_Node; +export const Statement = { Enum: Statement_Enum, Node: Statement_Node } as const; + export type Enum = { name: Name; nodes: Node[]; }; export const Enum = (name: Name, nodes: Node[]): Enum => ({ name, nodes }); + export type Node = { name: Name; params: Param[]; }; export const Node = (name: Name, params: Param[]): Node => ({ name, params }); -export type NamedParam = { - kind: "Named"; - name: Name; - type_: Type; + +export type Param_Named = { + kind: "Named", + name: Name, + type_: Type, }; -export const NamedParam = (name: Name, type_: Type): NamedParam => ({ kind: "Named", name, type_ }); -export type UnnamedParam = { - kind: "Unnamed"; - [0]: Type; +export const Param_Named = (name: Name, type_: Type): Param_Named => ({ kind: "Named", name, type_ }); +export type Param_Unnamed = { + kind: "Unnamed", + [0]: Type, }; -export const UnnamedParam = (v0: Type): UnnamedParam => ({ kind: "Unnamed", [0]: v0 }); -export type Param = NamedParam | UnnamedParam; -export type NameType = { - kind: "Name"; - [0]: Name; +export const Param_Unnamed = (v0: Type): Param_Unnamed => ({ kind: "Unnamed", [0]: v0 }); +export type Param = Param_Named | Param_Unnamed; +export const Param = { Named: Param_Named, Unnamed: Param_Unnamed } as const; + +export type Type_Name = { + kind: "Name", + [0]: Name, }; -export const NameType = (v0: Name): NameType => ({ kind: "Name", [0]: v0 }); -export type OptionalType = { - kind: "Optional"; - [0]: Type; +export const Type_Name = (v0: Name): Type_Name => ({ kind: "Name", [0]: v0 }); +export type Type_Optional = { + kind: "Optional", + [0]: Type, }; -export const OptionalType = (v0: Type): OptionalType => ({ kind: "Optional", [0]: v0 }); -export type MultipleType = { - kind: "Multiple"; - [0]: Type; +export const Type_Optional = (v0: Type): Type_Optional => ({ kind: "Optional", [0]: v0 }); +export type Type_Multiple = { + kind: "Multiple", + [0]: Type, }; -export const MultipleType = (v0: Type): MultipleType => ({ kind: "Multiple", [0]: v0 }); -export type Type = NameType | OptionalType | MultipleType; +export const Type_Multiple = (v0: Type): Type_Multiple => ({ kind: "Multiple", [0]: v0 }); +export type Type = Type_Name | Type_Optional | Type_Multiple; +export const Type = { Name: Type_Name, Optional: Type_Optional, Multiple: Type_Multiple } as const; + export type Name = { [0]: string; line: number; @@ -55,3 +63,4 @@ export type Name = { }; export const Name = (v0: string, line: number, col: number): Name => ({ [0]: v0, line, col }); + diff --git a/ast_generator.ts b/ast_generator.ts index 01a65a9..286ce94 100644 --- a/ast_generator.ts +++ b/ast_generator.ts @@ -4,7 +4,15 @@ import { } from "https://deno.land/x/nearley@2.19.7-deno/mod.ts"; import compiledParserGrammar from "./parser.out.ts"; -import { Enum, Name, NamedParam, Node, Statement, Type } from "./ast.out.ts"; +import { + Enum, + Name, + Node, + Param, + Param_Named, + Statement, + Type, +} from "./ast.out.ts"; class TypescriptGenerator { private result = ""; @@ -20,35 +28,75 @@ class TypescriptGenerator { this.generateNode(statement[0]); break; } + this.result += "\n"; } return this.result; } private generateEnum(enum_: Enum) { + if (this.isShallowEnum(enum_)) { + return this.generateShallowEnumBody(enum_); + } else { + return this.generateEnumBody(enum_); + } + } + + private isShallowEnum(enum_: Enum): boolean { + return !enum_.nodes.some((node) => node.params.length > 0); + } + + private generateShallowEnumBody(enum_: Enum) { + const name = this.makeName(enum_.name); + const fields = enum_.nodes.map((node) => this.makeName(node.name)).map( + (name) => `${name}: "${name}"`, + ).join(", "); + this.result += `export const ${name} = { ${fields} } as const;\n`; + this.result += `export type ${name} = keyof typeof ${name};\n`; + } + + private generateEnumBody(enum_: Enum) { const enumName = this.makeName(enum_.name); - const nodeNames: string[] = []; + const nodeNames: [string, string][] = []; for (const node of enum_.nodes) { - const kind = this.makeName(node.name); - const name = `${kind}${enumName}`; - this.generateNodeBody(node, name, kind); - nodeNames.push(name); + const name = this.makeName(node.name); + const fullName = `${enumName}_${name}`; + nodeNames.push([name, fullName]); + this.result += `export type ${fullName} = {\n`; + this.result += ` kind: "${name}",\n`; + this.result += node.params.map((p, i) => this.makeParamField(p, i)) + .map((field) => ` ${field},\n`).join(""); + this.result += `};\n`; + const fnParams = this.makeFnParams(node.params); + const fields = this.makeFnFields(node.params); + this.result += `export const ${fullName}` + + ` = (${fnParams}): ${fullName}` + + ` => ({ kind: "${name}", ${fields} });\n`; } - this.result += `export type ${enumName} = ${nodeNames.join(" | ")};\n`; + const typeList = nodeNames + .map(([_, fullName]) => fullName) + .join(" | "); + this.result += `export type ${enumName} = ${typeList};\n`; + const enumFieldFns = nodeNames + .map(([name, fullName]) => `${name}: ${fullName}`) + .join(", "); + this.result += + `export const ${enumName} = { ${enumFieldFns} } as const;\n`; } - private generateNode(node: Node) { - this.generateNodeBody(node); + private makeParamField(param: Param, index: number): string { + switch (param.kind) { + case "Named": + return this.makeNamedParam(param); + case "Unnamed": + return `[${index}]: ${this.makeType(param[0])}`; + } } - private generateNodeBody( + private generateNode( node: Node, - name = this.makeName(node.name), - kind?: string, ) { + const name = this.makeName(node.name); this.result += `export type ${name} = {\n`; - if (kind) { - this.result += ` kind: "${kind}";\n`; - } for ( const [param, index] of node.params .map((v, i) => [v, i] as const) @@ -57,14 +105,23 @@ class TypescriptGenerator { case "Named": this.result += ` ${this.makeNamedParam(param)};\n`; break; - case "Unnamed": - this.result += ` [${index}]: ${ - this.makeType(param[0]) - };\n`; + case "Unnamed": { + const type_ = this.makeType(param[0]); + this.result += ` [${index}]: ${type_};\n`; + break; + } } } this.result += "};\n"; - const fnParams = node.params + const fnParams = this.makeFnParams(node.params); + const fields = this.makeFnFields(node.params); + this.result += `export const ${name}` + + ` = (${fnParams}): ${name}` + + ` => ({ ${fields} });\n`; + } + + private makeFnParams(params: Param[]): string { + return params .map((param, index) => { switch (param.kind) { case "Named": @@ -74,22 +131,21 @@ class TypescriptGenerator { } }) .join(", "); - const fields = [ - ...(kind ? [`kind: "${kind}"`] : []), - ...node.params.map((param, index) => { - switch (param.kind) { - case "Named": - return this.makeName(param.name); - case "Unnamed": - return `[${index}]: v${index}`; - } - }), - ].join(", "); - this.result += - `export const ${name} = (${fnParams}): ${name} => ({ ${fields} });\n`; } - private makeNamedParam(param: NamedParam): string { + private makeFnFields(params: Param[]): string { + return params.map((param, index) => { + switch (param.kind) { + case "Named": + return this.makeName(param.name); + case "Unnamed": + return `[${index}]: v${index}`; + } + }) + .join(", "); + } + + private makeNamedParam(param: Param_Named): string { const name = this.makeName(param.name); const type_ = this.makeType(param.type_); return `${name}: ${type_}`; diff --git a/parser.ne b/parser.ne index 7333152..414e83a 100644 --- a/parser.ne +++ b/parser.ne @@ -40,8 +40,8 @@ elements -> _ (element elementTail _):? {% v => v[1] ? [v[1][0], ...v[1][1]] : [ elementTail -> (nl__ element):* {% v => v[0].map(w => w[1]) %} -element -> enum {% v => ast.EnumStatement(v[0]) %} - | node {% v => ast.NodeStatement(v[0]) %} +element -> enum {% v => ast.Statement.Enum(v[0]) %} + | node {% v => ast.Statement.Node(v[0]) %} enum -> name _ "{" nodes "}" {% v => ast.Enum(v[0], v[3]) %} @@ -49,7 +49,9 @@ nodes -> _ (node nodeTail _):? {% v => v[1] ? [v[1][0], ...v[1][1]] : [] %} nodeTail -> (nl__ node):* {% v => v[0].map(w => w[1]) %} -node -> name _ "(" params ")" {% v => ast.Node(v[0], v[3]) %} +node -> name (_ paramList):? {% v => ast.Node(v[0], v[1] ? v[1][1] : []) %} + +paramList -> "(" params ")" {% v => v[1] %} params -> _ (param paramTail (_ ","):? _):? {% v => v[1] ? [v[1][0], ...v[1][1]] : [] %} @@ -58,22 +60,22 @@ paramTail -> ("," _ param):* {% v => v[0].map(w => w[2]) %} param -> namedParam {% id %} | unnamedParam {% id %} -namedParam -> name _ ":" _ type {% v => ast.NamedParam(v[0], v[4]) %} +namedParam -> name _ ":" _ type {% v => ast.Param.Named(v[0], v[4]) %} -unnamedParam -> type {% v => ast.UnnamedParam(v[0]) %} +unnamedParam -> type {% v => ast.Param.Unnamed(v[0]) %} type -> optional {% id %} | multiple {% id %} - | name {% v => ast.NameType(v[0]) %} + | name {% v => ast.Type.Name(v[0]) %} -optional -> type "?" {% v => ast.OptionalType(v[0]) %} +optional -> type "?" {% v => ast.Type.Optional(v[0]) %} -multiple -> type "[" "]" {% v => ast.MultipleType(v[0]) %} +multiple -> type "[" "]" {% v => ast.Type.Multiple(v[0]) %} name -> %name {% v => ast.Name(v[0].value, v[0].line, v[0].col) %} _ -> __:? -__ -> (%whitespace|%newline|%singeLineComment|%multiLineComment):+ +__ -> (%whitespace|%newline|%singleLineComment|%multiLineComment):+ nl__ -> sl_ (%newline sl_):+