From 0304c2631b18859984a0f62bba1f5deeaa57e58b Mon Sep 17 00:00:00 2001 From: Tony Garnock-Jones Date: Sun, 21 Mar 2021 22:02:34 +0100 Subject: [PATCH] genctor.ts --- .../packages/schema/src/compiler.ts | 21 ++++-- .../packages/schema/src/compiler/context.ts | 5 +- .../packages/schema/src/compiler/genctor.ts | 41 ++++++++++++ .../packages/schema/src/compiler/jskw.ts | 64 +++++++++++++++++++ .../javascript/packages/schema/src/meta.ts | 23 +++++-- 5 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 implementations/javascript/packages/schema/src/compiler/genctor.ts create mode 100644 implementations/javascript/packages/schema/src/compiler/jskw.ts diff --git a/implementations/javascript/packages/schema/src/compiler.ts b/implementations/javascript/packages/schema/src/compiler.ts index 1fd0ad9..44c6200 100644 --- a/implementations/javascript/packages/schema/src/compiler.ts +++ b/implementations/javascript/packages/schema/src/compiler.ts @@ -1,11 +1,12 @@ import { Annotated, Bytes, Dictionary, Fold, fold, Record, stringify, Tuple, Value } from "@preserves/core"; import * as M from "./meta"; import { CompilerOptions, ModuleContext } from "./compiler/context"; -import { brackets, Formatter, Item, parens, seq } from "./compiler/block"; +import { block, brackets, Formatter, Item, parens, seq } from "./compiler/block"; import { typeForDefinition } from "./compiler/gentype"; // import { decoderFor } from "./compiler/decoder"; import { converterForDefinition } from "./compiler/genconverter"; import { EMPTY_TYPE, renderType } from "./compiler/type"; +import { genConstructor } from "./compiler/genctor"; export function compile(env: M.Environment, schema: M.Schema, options: CompilerOptions = {}): string { const mod = new ModuleContext(env, schema, options); @@ -30,9 +31,21 @@ export function compile(env: M.Environment, schema: M.Schema, options: CompilerO `;`)); for (const [name, def] of M.Schema._._field0(schema).get(M.$definitions)) { - mod.defineType( - seq(`export type ${stringify(name)} = `, - renderType(typeForDefinition(mod, def)), `;`)); + const t = typeForDefinition(mod, def); + const nameStr = stringify(name); + + mod.defineType(seq(`export type ${nameStr} = `, renderType(t), `;`)); + + if (t.kind === 'union') { + mod.defineFunction(_ctx => + seq(`export namespace ${nameStr} `, block( + ... Array.from(t.variants).map(([vn, vt]) => + genConstructor(vn, vn, vt, nameStr)) + ))); + } else { + mod.defineFunction(_ctx => + genConstructor(nameStr, void 0, t, nameStr)); + } } for (const [name0, def] of M.Schema._._field0(schema).get(M.$definitions)) { diff --git a/implementations/javascript/packages/schema/src/compiler/context.ts b/implementations/javascript/packages/schema/src/compiler/context.ts index f7d2c8e..cee2c7b 100644 --- a/implementations/javascript/packages/schema/src/compiler/context.ts +++ b/implementations/javascript/packages/schema/src/compiler/context.ts @@ -34,10 +34,7 @@ export class ModuleContext { literal(v: M.Input): Item { let varname = this.literals.get(v); if (varname === void 0) { - const s = v.asPreservesText() - .replace('_', '__') - .replace('*', '_STAR_'); - varname = M.isValidToken('_' + s, true) ? '$' + s : '__lit' + this.literals.size; + varname = M.jsId('$' + v.asPreservesText(), () => '__lit' + this.literals.size); this.literals.set(v, varname); } return varname; diff --git a/implementations/javascript/packages/schema/src/compiler/genctor.ts b/implementations/javascript/packages/schema/src/compiler/genctor.ts new file mode 100644 index 0000000..74ca91c --- /dev/null +++ b/implementations/javascript/packages/schema/src/compiler/genctor.ts @@ -0,0 +1,41 @@ +import * as M from '../meta'; +import { block, braces, Item, keyvalue, parens, seq } from "./block"; +import { FieldType, renderType, SimpleType } from "./type"; + +export function genConstructor( + name: string, + variant: string | undefined, + arg: SimpleType, + resultType: Item): Item +{ + const formals: Array<[string, FieldType]> = []; + let simpleValue = false; + + function examine(t: FieldType, name: string): void { + if (t.kind !== 'unit') { + formals.push([name, t]); + } + } + + if (arg.kind === 'record') { + arg.fields.forEach(examine); + } else { + examine(arg, 'value'); + simpleValue = variant === void 0; + } + + const initializers: Item[] = (variant !== void 0) + ? [keyvalue('_variant', JSON.stringify(variant))] + : []; + formals.forEach(([n, _t]) => initializers.push(n)); + + return seq(`export function ${M.jsId(name)}`, + parens(... formals.map(([n, t]) => seq(n, ': ', renderType(t)))), + ': ', resultType, ' ', block( + seq(`return `, + ((arg.kind === 'unit' && initializers.length === 0) + ? 'null' + : (simpleValue + ? 'value' + : braces(... initializers)))))); +} diff --git a/implementations/javascript/packages/schema/src/compiler/jskw.ts b/implementations/javascript/packages/schema/src/compiler/jskw.ts new file mode 100644 index 0000000..4a4bc9c --- /dev/null +++ b/implementations/javascript/packages/schema/src/compiler/jskw.ts @@ -0,0 +1,64 @@ +export const JS_KEYWORDS = new Set([ + 'abstract', + 'await', + 'boolean', + 'break', + 'byte', + 'case', + 'catch', + 'char', + 'class', + 'const', + 'continue', + 'debugger', + 'default', + 'delete', + 'do', + 'double', + 'else', + 'enum', + 'export', + 'extends', + 'false', + 'final', + 'finally', + 'float', + 'for', + 'function', + 'goto', + 'if', + 'implements', + 'import', + 'in', + 'instanceof', + 'int', + 'interface', + 'let', + 'long', + 'native', + 'new', + 'null', + 'package', + 'private', + 'protected', + 'public', + 'return', + 'short', + 'static', + 'super', + 'switch', + 'synchronized', + 'this', + 'throw', + 'throws', + 'transient', + 'true', + 'try', + 'typeof', + 'var', + 'void', + 'volatile', + 'while', + 'with', + 'yield', +]); diff --git a/implementations/javascript/packages/schema/src/meta.ts b/implementations/javascript/packages/schema/src/meta.ts index b0fe39f..7b90ad2 100644 --- a/implementations/javascript/packages/schema/src/meta.ts +++ b/implementations/javascript/packages/schema/src/meta.ts @@ -2,6 +2,7 @@ import { Value, is, Position } from '@preserves/core'; import * as M from './gen/schema'; import { SchemaSyntaxError } from './error'; import type { AtomicType } from './compiler/type'; +import { JS_KEYWORDS } from './compiler/jskw'; export * from './gen/schema'; @@ -9,12 +10,22 @@ export type Builtin = { type: AtomicType, pattern: M.Alternative }; export type Input = Value; -export function isValidToken(s: string, allowLeadingUnderscore = false): boolean { - if (allowLeadingUnderscore) { - return /^[a-zA-Z_][a-zA-Z_0-9]*$/.test(s); - } else { - return /^[a-zA-Z][a-zA-Z_0-9]*$/.test(s); - } +export function isValidToken(s: string): boolean { + return /^[a-zA-Z][a-zA-Z_0-9]*$/.test(s); +} + +export function isValidJsId(s: string): boolean { + return /^[$_a-zA-Z][$_a-zA-Z0-9]*$/.test(s) && !JS_KEYWORDS.has(s); +} + +export function jsId(v: string, kf?: () => string): string { + const s = v + .replace('_', '__') + .replace('*', '_STAR_'); + if (isValidJsId(s)) return s; + if (isValidJsId('$' + s)) return '$' + s; + if (kf !== void 0) return kf(); + throw new Error(`Internal error: idForLiteral needs to be completed (${v})`); } export const ANDSYM = Symbol.for('&');