Skip to content

Commit

Permalink
Compiler, Templator, TemplatorTrace
Browse files Browse the repository at this point in the history
  • Loading branch information
maestrow committed May 14, 2021
1 parent e946ce9 commit 947bbe6
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 26 deletions.
100 changes: 100 additions & 0 deletions src/step2/ast2ometa.ts
Original file line number Diff line number Diff line change
@@ -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)
}
}
10 changes: 10 additions & 0 deletions src/step2/cli_ast.ts
Original file line number Diff line number Diff line change
@@ -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)
38 changes: 34 additions & 4 deletions src/step2/grammar-ast.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { IParserFn } from "./types";

// https://github.com/microsoft/TypeScript/pull/39094 Variadic tuple types
type First<T extends readonly unknown[]> = T[0];
type DropFirst<T extends readonly unknown[]> = 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
Expand All @@ -19,15 +20,44 @@ export namespace Ast {
| Ex.Regex
| Ex.Range

type ExprToStr<T> = T extends Expr[]
? string[]
: T extends Expr
? string
: T

type TemplatorArgs<T extends unknown[]> = { [K in keyof T]: ExprToStr<T[K]> };

/*
Correct ITemplator inference implementation is supposed to be:
type DropFirst<T extends readonly unknown[]> = T extends readonly [any?, ...infer U] ? U : [...T];
type TemplatorArgs<T extends unknown[]> = { [K in keyof T]: ExprToStr<T[K]> };
export type ITemplator = { [T in Expr as T[0]]: (...x: TemplatorArgs<DropFirst<T>>) => 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 unknown[]> = T extends readonly [any?, ...infer U] ? TemplatorArgs<U> : [...T];

export type IParser = { [T in Expr as T[0]]: (...args: DropFirst<T>) => IParserFn }
export type ICompiler = { [T in Expr as T[0]]: (...args: DropFirst<T>) => string }
export type ITemplator = { [T in Expr as T[0]]: (...x: ToTemplator<T>) => 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]
Expand Down
2 changes: 1 addition & 1 deletion src/step2/grammars/ometa1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ export const ometa1: AST.Grammar = [
]]],

['eQuant', ['seq', [
['rule', 'not'],
['rule', 'eNot'],
['times', 0, 1, ['alt', [
['equal', '?'],
['equal', '*'],
Expand Down
29 changes: 8 additions & 21 deletions src/step2/parser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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()
}
Expand Down Expand Up @@ -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()
}
}
Expand Down
4 changes: 4 additions & 0 deletions src/step2/utils.ts
Original file line number Diff line number Diff line change
@@ -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]

0 comments on commit 947bbe6

Please sign in to comment.