|
1 | | -// import {Codegen} from '@jsonjoy.com/util/lib/codegen' |
2 | | -// import {dynamicFunction} from '@jsonjoy.com/util/lib/codegen/dynamicFunction' |
| 1 | +import {Codegen} from '@jsonjoy.com/util/lib/codegen' |
| 2 | +import {lazy} from '@jsonjoy.com/util/lib/lazyFunction' |
3 | 3 | // import {emitStringMatch} from '@jsonjoy.com/util/lib/codegen/util/helpers'; |
4 | 4 | // import {RegExpTerminalMatch, StringTerminalMatch} from '../matches'; |
5 | 5 | // import {scrub} from '../util'; |
6 | | -// import type {Grammar, Rule, MatchParser} from '../types'; |
7 | | - |
8 | | -// export class CodegenGrammar { |
9 | | -// public static readonly compile = (grammar: Grammar): MatchParser => { |
10 | | -// const codegen = new CodegenGrammar(grammar); |
11 | | -// codegen.generate(); |
12 | | -// return codegen.compile(); |
13 | | -// }; |
14 | | - |
15 | | -// public readonly codegen: Codegen<MatchParser>; |
16 | | -// protected readonly rules = new Map<string, MatchParser>(); |
17 | | - |
18 | | -// constructor(public readonly grammar: Grammar) { |
19 | | -// this.codegen = new Codegen({ |
20 | | -// args: ['str', 'pos'], |
21 | | -// }); |
22 | | -// } |
23 | | - |
24 | | -// protected compileRule(name: string, rule: Rule): MatchParser { |
25 | | -// const {codegen} = this; |
26 | | -// const {match} = rule; |
27 | | -// const codegenProduction = dynamicFunction<MatchParser>(codegen); |
28 | | -// codegenProduction(` |
29 | | -// const results = []; |
30 | | -// ${match.map((m, i) => { |
31 | | -// const dep = codegen.linkDependency(m); |
32 | | -// return ` |
33 | | -// const r${i} = ${dep}(str, pos); |
34 | | -// if (!r${i}) return; |
35 | | -// pos = r${i}.end; |
36 | | -// results.push(r${i}); |
37 | | -// `; |
38 | | -// }).join('\n')} |
39 | | -// return {kind: '${name}', pos, end: pos, children: results}; |
40 | | -// `); |
41 | | -// return codegenProduction.compile(); |
42 | | -// } |
43 | | - |
44 | | -// public generate() { |
45 | | -// const {codegen, grammar} = this; |
46 | | -// const {start, rules} = grammar; |
47 | | -// // for (const [name, rule] of Object.entries(rules)) { |
48 | | -// // const dep = codegen.linkDependency(this.compileRule(name, rule)); |
49 | | -// // this.rules.set(name, dep); |
50 | | -// // } |
51 | | -// // const start = this.rules.get(grammar.start); |
52 | | -// // if (!start) throw new Error('INVALID_START_SYMBOL'); |
53 | | -// // codegen.return(`${start}(str, pos)`); |
54 | | -// } |
55 | | - |
56 | | -// public compile(): MatchParser { |
57 | | -// return this.codegen.compile(); |
58 | | -// } |
59 | | -// } |
| 6 | +import type {Grammar, Rule, MatchParser, RuleParser, Parser, TerminalShorthand, NonTerminal, Production, Terminal, ProductionParser} from '../types'; |
| 7 | +import {CodegenTerminal} from './CodegenTerminal'; |
| 8 | +import {CodegenRule} from './CodegenRule'; |
| 9 | +import {CodegenProduction} from './CodegenProduction'; |
| 10 | + |
| 11 | +const isTerminalShorthand = (item: any): item is TerminalShorthand => |
| 12 | + typeof item === 'string' || item instanceof RegExp; |
| 13 | + |
| 14 | +const isProduction = (item: any): item is Production => |
| 15 | + item instanceof Array; |
| 16 | + |
| 17 | +const isNonTerminal = (item: any): item is NonTerminal => |
| 18 | + typeof item === 'object' && item && typeof item.n === 'string'; |
| 19 | + |
| 20 | +export class CodegenGrammar { |
| 21 | + public static readonly compile = (grammar: Grammar): MatchParser => { |
| 22 | + const codegen = new CodegenGrammar(grammar); |
| 23 | + return codegen.compile(); |
| 24 | + }; |
| 25 | + |
| 26 | + public readonly codegen: Codegen<MatchParser>; |
| 27 | + protected readonly parsers = new Map<string, RuleParser>(); |
| 28 | + |
| 29 | + constructor(public readonly grammar: Grammar) { |
| 30 | + this.codegen = new Codegen({ |
| 31 | + args: ['str', 'pos'], |
| 32 | + }); |
| 33 | + } |
| 34 | + |
| 35 | + protected compileItem(item: TerminalShorthand | Production | NonTerminal): Parser { |
| 36 | + if (isTerminalShorthand(item)) { |
| 37 | + return CodegenTerminal.compile(item); |
| 38 | + } else if (isProduction(item)) { |
| 39 | + return this.compileProduction(item); |
| 40 | + } else if (isNonTerminal(item)) { |
| 41 | + return this.compileRuleByName(item.n); |
| 42 | + } else { |
| 43 | + throw new Error(`Invalid [rule = ${name}] alternative: ${item}`); |
| 44 | + } |
| 45 | + } |
| 46 | + |
| 47 | + protected compileProduction(prod: Production): ProductionParser { |
| 48 | + const parsers: Parser[] = []; |
| 49 | + for (const item of prod) parsers.push(this.compileItem(item as any)); |
| 50 | + return CodegenProduction.compile(parsers); |
| 51 | + } |
| 52 | + |
| 53 | + private __compileRule(name: string, rule: Rule): RuleParser { |
| 54 | + const parser = lazy(() => { |
| 55 | + const {match} = rule; |
| 56 | + const parsers: Parser[] = []; |
| 57 | + for (const item of match) parsers.push(this.compileItem(item as any)); |
| 58 | + const ruleParser = CodegenRule.compile(name, rule, parsers); |
| 59 | + return ruleParser; |
| 60 | + }); |
| 61 | + return parser; |
| 62 | + } |
| 63 | + |
| 64 | + protected compileRuleByName(name: string): RuleParser { |
| 65 | + if (this.parsers.has(name)) return this.parsers.get(name)!; |
| 66 | + const {grammar} = this; |
| 67 | + const {rules} = grammar; |
| 68 | + const ruleOrAlt = rules[name]; |
| 69 | + if (!ruleOrAlt) throw new Error(`Unknown rule: ${name}`); |
| 70 | + const rule: Rule = ruleOrAlt instanceof Array ? {match: ruleOrAlt} : ruleOrAlt; |
| 71 | + const parser = this.__compileRule(name, rule); |
| 72 | + this.parsers.set(name, parser); |
| 73 | + return parser; |
| 74 | + } |
| 75 | + |
| 76 | + // public generate() { |
| 77 | + // const {codegen, grammar} = this; |
| 78 | + // const {start, rules} = grammar; |
| 79 | + // const ruleOrAlt = rules[start]; |
| 80 | + // const rule: Rule = ruleOrAlt instanceof Array ? {match: ruleOrAlt} : ruleOrAlt; |
| 81 | + // const a = this.compileRule(start, rule); |
| 82 | + // // for (const [name, rule] of Object.entries(rules)) { |
| 83 | + // // const dep = codegen.linkDependency(this.compileRule(name, rule)); |
| 84 | + // // this.rules.set(name, dep); |
| 85 | + // // } |
| 86 | + // // const start = this.rules.get(grammar.start); |
| 87 | + // // if (!start) throw new Error('INVALID_START_SYMBOL'); |
| 88 | + // // codegen.return(`${start}(str, pos)`); |
| 89 | + // } |
| 90 | + |
| 91 | + public compile(): RuleParser { |
| 92 | + return this.compileRuleByName(this.grammar.start); |
| 93 | + } |
| 94 | +} |
0 commit comments