Zum Inhalt springen

Programmatisch (Node)

@findsl/core ist der Sprachkern: Grammatik/AST, Validator, Type-Checker, Interpreter, Doc-Generator und Codegen. Es ist die Grundlage, auf der @findsl/cli und @findsl/web aufsetzen — und die richtige Wahl für eigenes Server-, Build- oder Tooling.

Terminal-Fenster
npm install @findsl/core

@findsl/core exportiert seine Bausteine über Subpfade (kein einzelner Top-Level-Export):

import { createFindslServices } from "@findsl/core/language/findsl-module.js";
import { loadModuleGraph } from "@findsl/core/interpret/module-loader.js";
import { runPruefe } from "@findsl/core/interpret/pruefe.js";

Der typische Ablauf — Module über ihren relativen verwende-Graph laden und die prüfe-Blöcke auswerten — entspricht dem, was die CLI intern tut:

const services = createFindslServices(NodeFileSystem).Findsl;
// parse: Dateipfad -> Program (über die Langium-Services)
const module = await loadModuleGraph(entryPfad, parse, {
allowedRoot: projektWurzel, // Path-Traversal-Schutz
});
const report = runPruefe(module);

Für die Auswertung gegen einen Projektbaum setzt loadModuleGraph einen allowedRoot, damit verwende-Importe das Projektverzeichnis nicht verlassen.

Prüfen und Generieren nimmt dir die CLI datei-basiert ab (findsl test, findsl codegen). Zu @findsl/core greifst du, wenn du FinDSL im eigenen Prozess auswerten und etwas Eigenes daraus bauen willst — etwas, das die CLI nicht kennt.

Ein typischer Fall im Steuerumfeld ist die Audit-Frage: Welche Gesetzesstellen setzt unser Regelwerk um — und wenn sich § 23 KStG ändert, welche Funktionen sind betroffen? Diese Information steckt bereits in den @Quelle-Annotationen. @findsl/core parst das Modul und legt den AST offen; ein einziger Lauf über die Knoten aggregiert sie zu einer Wirkungs-Matrix:

import { NodeFileSystem } from "langium/node";
import { URI, AstUtils } from "langium";
import * as fs from "node:fs/promises";
import { createFindslServices } from "@findsl/core/language/findsl-module.js";
import {
isAnnotation,
type Program,
} from "@findsl/core/language/generated/ast.js";
const services = createFindslServices(NodeFileSystem).Findsl;
async function parse(datei: string): Promise<Program> {
const text = await fs.readFile(datei, "utf-8");
const doc = services.shared.workspace.LangiumDocumentFactory.fromString(
text,
URI.file(datei),
);
await services.shared.workspace.DocumentBuilder.build([doc], {
validation: true,
});
return doc.parseResult.value as Program;
}
// Gesetzesstelle → Deklarationen, die sich darauf berufen (@Quelle).
const program = await parse("src/steuerregeln/kst.findsl");
const matrix = new Map<string, Set<string>>();
for (const node of AstUtils.streamAllContents(program)) {
if (!isAnnotation(node) || node.name !== "Quelle") continue;
const arg = node.args[0];
if (arg?.$type !== "StringLiteral") continue;
const stelle = arg.value.replace(/^"|"$/g, ""); // ohne Anführungszeichen
// Annotation → DeclPrefix → Deklaration (mit Namen):
const decl = node.$container?.$container as { name?: string } | undefined;
const liste = matrix.get(stelle) ?? new Set<string>();
liste.add(decl?.name ?? "(Modul)");
matrix.set(stelle, liste);
}
for (const stelle of [...matrix.keys()].sort()) {
console.log(`${stelle}\n ${[...matrix.get(stelle)!].join(", ")}`);
}

Ausgabe etwa:

§ 23 Absatz 1 KStG
KstSatz, KörperschaftsteuerBetrag
§ 23 Absatz 1 Nummer 1 KStG
KST_SATZ_BIS_2027
§ 24 KStG
FreibetragNach24
§ 24 Satz 1 KStG
FREIBETRAG_24, _BegrenzterFreibetrag24
§ 7 KStG
KörperschaftsteuerFall, BerechneKörperschaftsteuer

Ein Lauf über AstUtils.streamAllContents genügt — kein Interpreter, kein Codegen. Genau dafür ist @findsl/core da: FinDSL im eigenen Prozess verarbeiten und daraus bauen, was die Anwendung braucht — eine Compliance-Matrix wie hier, einen Abhängigkeitsgraphen oder einen Export ins Redaktionssystem. Über mehrere Module ziehst du dasselbe einfach über ein Verzeichnis (fs.readdir(..., { recursive: true })).