genctor.ts

This commit is contained in:
Tony Garnock-Jones 2021-03-21 22:02:34 +01:00
parent 889d38bbb8
commit 0304c2631b
5 changed files with 140 additions and 14 deletions

View File

@ -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)) {

View File

@ -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;

View File

@ -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))))));
}

View File

@ -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',
]);

View File

@ -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<never>;
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('&');