From 947bbe6299c05fe08a93f529f1656ea92d7a1968 Mon Sep 17 00:00:00 2001 From: maestrow Date: Fri, 14 May 2021 15:50:05 +0300 Subject: [PATCH] Compiler, Templator, TemplatorTrace --- src/step2/ast2ometa.ts | 100 +++++++++++++++++++++++++++++++++++ src/step2/cli_ast.ts | 10 ++++ src/step2/grammar-ast.ts | 38 +++++++++++-- src/step2/grammars/ometa1.ts | 2 +- src/step2/parser.ts | 29 +++------- src/step2/utils.ts | 4 ++ 6 files changed, 157 insertions(+), 26 deletions(-) create mode 100644 src/step2/ast2ometa.ts create mode 100644 src/step2/cli_ast.ts create mode 100644 src/step2/utils.ts diff --git a/src/step2/ast2ometa.ts b/src/step2/ast2ometa.ts new file mode 100644 index 0000000..392a55b --- /dev/null +++ b/src/step2/ast2ometa.ts @@ -0,0 +1,100 @@ +import { Ast } from "./grammar-ast"; + +export const ast2ometa = (ast: Ast.Grammar) => { + +} + +export class Templates implements Ast.ITemplator { + _mm = (num: number) => typeof(num) !== 'number' ? '' : num + + State = { + level: 0 + } + + Grammar = (name: string, rules: string[]) => { + const r = rules.join(',\n') + return `ometa ${name} {\n${r}\n}\n` + } + + Rule = (name: string, body: string) => ` ${name} = ${body}` + + seq = (exprs: string[]) => { + return exprs.length <= 1 || this.State.level === 0 + ? exprs.join(" ") + : '(' + exprs.join(" ") + ')' + } + alt = (exprs: string[]) => { + const tpl = "\n | " + return exprs.length <= 1 + ? exprs.join() + : this.State.level === 0 + ? tpl + exprs.join(tpl) + : '(' + tpl + exprs.join(tpl) + '\n )' + } + equal = (value: string) => `'${value}'` + rule = (name: string) => name + times = (min: number, max: number, expr: string) => { + if (min === 0 && max === 1) { + return `${expr}?` + } + if (min === 0 && typeof(max) !== 'number') { + return `${expr}*` + } + if (min === 1 && typeof(max) !== 'number') { + return `${expr}+` + } + const sMin = this._mm(min) + const sMax = this._mm(max) + + return `${expr}{${sMin},${sMax}}` + } + token = (value: string) => `"${value}"` + not = (expr: string) => `~${expr}` + project = (name: string, expr: string) => `${expr} -> ${name}` + regex = (value: string) => `/${value}/` + range = (from: string, to: string) => `${from}-${to}` +} + +export class TemplatesTrace extends Templates { + alt = (exprs: string[]) => { + const tpl = " | " + return exprs.length <= 1 + ? exprs.join() + : this.State.level === 0 + ? exprs.join(tpl) + : '(' + exprs.join(tpl) + ')' + } +} + +export class Compiler { + templates: Ast.ITemplator + + constructor(tpl: Ast.ITemplator) { + this.templates = tpl + } + + /** + * A note on the interpretation of arguments as expressions. + * At the moment, the condition is used: + * 1) if arg is array and the first element is a string, then this is an expression. + * 2) if arg is array and the first element is not a string, then this is an array of expressions. + * Later, when grammar expressions will accept arrays (which are not expressions) as arguments, + * then the algorithm will need to be rewritten. + */ + compileExpr = (e: Ast.GenericExpr, level: number = 0): string => { + const args = e.slice(1).map(i => + i instanceof Array + ? typeof(i[0]) === 'string' + ? this.compileExpr(i as Ast.GenericExpr, level+1) + : i.map(j => this.compileExpr(j, level+1)) + : i + ) + this.templates.State.level = level + return this.templates[e[0]].apply(this, args) + } + + compile = (g: Ast.Grammar, gramName: string): string => { + const rules = g.map(rule => this.templates.Rule(rule[0], this.compileExpr(rule[1]))) + return this.templates.Grammar(gramName, rules) + } +} diff --git a/src/step2/cli_ast.ts b/src/step2/cli_ast.ts new file mode 100644 index 0000000..f5fe2f2 --- /dev/null +++ b/src/step2/cli_ast.ts @@ -0,0 +1,10 @@ +import { Templates, Compiler, TemplatesTrace } from "./ast2ometa"; +import { ometa1 } from "./grammars/ometa1"; +import { getRuleBodyByName } from "./utils"; + +const c = new Compiler(new Templates()) +const res = c.compile(ometa1, "Ometa") + + + +console.log(res) \ No newline at end of file diff --git a/src/step2/grammar-ast.ts b/src/step2/grammar-ast.ts index 3624fb9..7f8b0f3 100644 --- a/src/step2/grammar-ast.ts +++ b/src/step2/grammar-ast.ts @@ -1,16 +1,17 @@ import { IParserFn } from "./types"; // https://github.com/microsoft/TypeScript/pull/39094 Variadic tuple types +type First = T[0]; type DropFirst = T extends readonly [any?, ...infer U] ? U : [...T]; export namespace Ast { export type Grammar = Rule[] export type Rule = [string, Expr] - + export type GenericExpr = [string, ...(GenericExpr[]|GenericExpr|string|number)[]] export type Expr = Ex.Seq | Ex.Alt - | Ex.Atom + | Ex.Equal | Ex.Rule | Ex.Times | Ex.Token @@ -19,15 +20,44 @@ export namespace Ast { | Ex.Regex | Ex.Range + type ExprToStr = T extends Expr[] + ? string[] + : T extends Expr + ? string + : T + + type TemplatorArgs = { [K in keyof T]: ExprToStr }; + + /* + Correct ITemplator inference implementation is supposed to be: + type DropFirst = T extends readonly [any?, ...infer U] ? U : [...T]; + type TemplatorArgs = { [K in keyof T]: ExprToStr }; + export type ITemplator = { [T in Expr as T[0]]: (...x: TemplatorArgs>) => string } + + But last expression causes: A rest parameter must be of an array type. ts(2370) + + Issue: https://github.com/microsoft/TypeScript/issues/29919 When trying to use mapped tuples as rest parameters error 'A rest parameter must be of an array type' given + + So we use this not so well workaround, that duplicates DropFirst functionality: + */ + type ToTemplator = T extends readonly [any?, ...infer U] ? TemplatorArgs : [...T]; + export type IParser = { [T in Expr as T[0]]: (...args: DropFirst) => IParserFn } + export type ICompiler = { [T in Expr as T[0]]: (...args: DropFirst) => string } + export type ITemplator = { [T in Expr as T[0]]: (...x: ToTemplator) => string } & { + Grammar: (gramName: string, rules: string[]) => string, + Rule: (name: string, body: string) => string, + State: { + level: number + } + } export namespace Ex { export type Seq = ['seq', Expr[]] export type Alt = ['alt', Expr[]] - export type Atom = ['equal', string] + export type Equal = ['equal', string] export type Rule = ['rule', string] export type Times = ['times', number, number, Expr] - export type Token = ['token', string] export type Not = ['not', Expr] export type Project = ['project', string, Expr] diff --git a/src/step2/grammars/ometa1.ts b/src/step2/grammars/ometa1.ts index f75e5be..5430bd1 100644 --- a/src/step2/grammars/ometa1.ts +++ b/src/step2/grammars/ometa1.ts @@ -83,7 +83,7 @@ export const ometa1: AST.Grammar = [ ]]], ['eQuant', ['seq', [ - ['rule', 'not'], + ['rule', 'eNot'], ['times', 0, 1, ['alt', [ ['equal', '?'], ['equal', '*'], diff --git a/src/step2/parser.ts b/src/step2/parser.ts index 9f16cbd..1ea0749 100644 --- a/src/step2/parser.ts +++ b/src/step2/parser.ts @@ -3,6 +3,7 @@ import { IParseResultSuccess, IParseResultFail, IParserFn, IProjectors, ITraceIt import { Ast } from './grammar-ast' import * as equal from 'fast-deep-equal/es6' import { AsyncParallelBailHook } from 'tapable' +import { getRuleBodyByName } from './utils' export class Parser implements Ast.IParser { @@ -32,32 +33,18 @@ export class Parser implements Ast.IParser { } } - private getRuleBodyByName = (name: string): Ast.Expr => - this.grammar.find(i => i[0] === name)[1] - trace: ITraceItem[] = [] - // === Parsers - - rule = (name: string): IParserFn => { - - const parseFn = this.project(name, this.getRuleBodyByName(name)) - - return () => { - const res = parseFn() - this.trace.push({ - rule: name, - pos: this.state.pos, - success: res.success - }) - return res - } - } - expr = (e: Ast.Expr): IParserFn => { return this[e[0]].apply(this, e.slice(1)) } + // === Parsers + + rule = (name: string): IParserFn => { + return this.project(name, getRuleBodyByName(name, this.grammar)) + } + empty = (): IParserFn => () => { return this.success() } @@ -202,7 +189,7 @@ export class Parser implements Ast.IParser { // === API match = (rule: string) => { - const p = this.rule(rule) + const p = this.expr(['rule', rule]) return p() } } diff --git a/src/step2/utils.ts b/src/step2/utils.ts new file mode 100644 index 0000000..7f36e68 --- /dev/null +++ b/src/step2/utils.ts @@ -0,0 +1,4 @@ +import { Ast } from "./grammar-ast"; + +export const getRuleBodyByName = (name: string, grammar: Ast.Grammar): Ast.Expr => + grammar.find(i => i[0] === name)[1] \ No newline at end of file