UNPKG

gfd3

Version:

API for d3 trees with gf

331 lines (274 loc) 8.24 kB
import * as d3 from 'd3'; interface Grammar { abstract: AbstractGrammar; concretes: { [key: string]: ConcreteGrammar; }; } interface AbstractGrammar { name: string; startcat: string; funs: { [key: string]: { args: string[]; cat: string; }; }; } export interface GrammarNode { name: string; children?: GrammarNode[]; type: 'cat' | 'fun'; funs?: string[]; originalName?: string; concreteFunctions?: { [key: string]: string[]; }; } interface Production { type: string; fid: number; args: Arg[]; } interface Arg { type: string; hypos: any[]; fid: number; } interface ConcreteFunction { name: string; lins: number[]; } type Sequence = SymCat | SymKS | SymLit; interface SymCat { type: 'SymCat'; args: number[]; } interface SymKS { type: 'SymKS'; args: string[]; } interface SymLit { type: 'SymLit'; args: number[]; } interface AbstractGrammar { name: string; startcat: string; funs: { [key: string]: { args: string[]; cat: string; }; }; } export interface ConcreteGrammar { flags: { language: string; }; productions: { [key: string]: Production[]; }; functions: ConcreteFunction[]; sequences: Sequence[][]; categories: { [key: string]: { start: number; end: number; }; }; totalfids: number; } interface ConcreteFunctionWithLin extends ConcreteFunction { resolvedLins?: string[]; } interface ConcreteGrammarWithLin extends ConcreteGrammar { functions: ConcreteFunctionWithLin[]; resolvedSequences: string[]; } interface GrammarWithLin extends Grammar { concretes: { [key: string]: ConcreteGrammarWithLin; }; } // export class GFD3 { private grammar: Grammar | null = null; private abstractAST: GrammarNode | null = null; private grammarMode: 'abstract' | 'concrete' = 'abstract'; private selectedConcrete: string | null = null; async loadGrammarFromFile(file: File): Promise<void> { const text = await file.text(); this.setGrammar(JSON.parse(text)); } async loadGrammarFromURL(url: string): Promise<void> { const response = await fetch(url); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } const json = await response.json(); this.setGrammar(json); } private setGrammar(grammar: Grammar): void { this.grammar = grammar; this.abstractAST = this.transformAbstractToTree(grammar); this.grammarMode = 'abstract'; const concreteLanguages = this.getConcreteLanguages(); if (concreteLanguages.length > 0) { this.selectedConcrete = concreteLanguages[0]; } } private transformAbstractToTree(grammar: Grammar): GrammarNode { const startCat = grammar.abstract.startcat; const buildTree = (cat: string, visited: Set<string>): GrammarNode => { if (visited.has(cat)) { return { name: cat, type: 'cat' }; } visited.add(cat); const node: GrammarNode = { name: cat, children: [], type: 'cat' }; const funs = Object.entries(grammar.abstract.funs) .filter(([, funDetails]) => funDetails.cat === cat) .map(([funName]) => funName); node.funs = funs; if (grammar.concretes) { node.concreteFunctions = {}; Object.entries(grammar.concretes).forEach(([lang, concrete]) => { if (concrete.productions[cat]) { node.concreteFunctions![lang] = concrete.productions[cat] .map(prod => concrete.functions[prod.fid].name); } }); } Object.values(grammar.abstract.funs) .filter(fun => fun.cat === cat) .forEach(fun => { fun.args.forEach(arg => { if (!node.children?.some(child => child.name === arg)) { node.children?.push(buildTree(arg, new Set(visited))); } }); }); visited.delete(cat); return node; }; return buildTree(startCat, new Set<string>()); } getGrammar(): Grammar | null { return this.grammar; } getAbstractAST(): GrammarNode | null { return this.abstractAST; } getConcreteLanguages(): string[] { if (!this.grammar || !this.grammar.concretes) return []; return Object.values(this.grammar.concretes).map(concrete => concrete.flags.language); } setGrammarMode(mode: 'abstract' | 'concrete'): void { this.grammarMode = mode; } getGrammarMode(): 'abstract' | 'concrete' { return this.grammarMode; } setSelectedConcrete(language: string): void { this.selectedConcrete = language; } getSelectedConcrete(): string | null { return this.selectedConcrete; } getOptionsForNode(node: GrammarNode): string[] { if (!this.grammar) return []; const category = node.name; if (this.grammarMode === 'abstract') { return Object.entries(this.grammar.abstract.funs) .filter(([, funDetails]) => funDetails.cat === category) .map(([funName]) => funName); } else if (this.selectedConcrete) { const concreteLang = Object.keys(this.grammar.concretes).find( key => this.grammar!.concretes[key].flags.language === this.selectedConcrete ); if (concreteLang) { const concrete = this.grammar.concretes[concreteLang]; if (concrete.productions[category]) { return concrete.productions[category] .map(prod => concrete.functions[prod.fid].name); } } } return []; } updateNodeName(node: GrammarNode, newName: string): void { node.originalName = node.originalName || node.name; node.name = newName; } resetNodeName(node: GrammarNode): void { if (node.originalName) { node.name = node.originalName; } } parseLins(lins: number[], sequences: Sequence[][]): string { return lins.map(linIndex => { const sequence = sequences[linIndex]; return sequence.map(seq => { if (seq.type === 'SymKS') { return seq.args[0]; } if (seq.type === 'SymCat') { return `{${seq.args[1]}}`; } if (seq.type === 'SymLit') { return seq.args.join(''); } return ''; }).join(' '); }).join(' '); } resolveSequence(sequence: Sequence[], cats: string[]): string { return sequence.map(seq => { if (seq.type === 'SymKS') { return seq.args[0]; } if (seq.type === 'SymCat') { const catIndex = seq.args[0]; return cats[catIndex] || `{${catIndex}}`; } if (seq.type === 'SymLit') { return `<${seq.args.join(',')}>`; } return ''; }).join(' '); } replaceLins(): GrammarWithLin { if (!this.grammar) throw new Error("Grammar not loaded"); const resolvedGrammar: GrammarWithLin = { ...this.grammar, concretes: {} }; const cats = new Set<string>(); Object.values(this.grammar.abstract.funs).forEach(fun => { cats.add(fun.cat); fun.args.forEach(arg => cats.add(arg)); }); const catsArray = Array.from(cats); for (const [concreteName, concreteGrammar] of Object.entries(this.grammar.concretes)) { const resolvedFunctions: ConcreteFunctionWithLin[] = concreteGrammar.functions.map(func => ({ ...func, resolvedLins: func.lins.map(linIndex => this.resolveSequence(concreteGrammar.sequences[linIndex], catsArray) ) })); resolvedGrammar.concretes[concreteName] = { ...concreteGrammar, functions: resolvedFunctions, resolvedSequences: concreteGrammar.sequences.map(seq => this.resolveSequence(seq, catsArray) ) }; } return resolvedGrammar; } // return data getTreeData(): d3.HierarchyNode<GrammarNode> | null { if (!this.abstractAST) return null; return d3.hierarchy(this.abstractAST); } }