gfd3
Version:
API for d3 trees with gf
331 lines (274 loc) • 8.24 kB
text/typescript
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);
}
}