Getting started
This tutorial builds a tiny grammar by hand and runs it. By the end you’ll have transformed a graph and read the result.
1. Install
Section titled “1. Install”npm install graph-grammarThe package is ESM-only and ships TypeScript types. Its only runtime dependency
is zod.
2. Author a rule
Section titled “2. Author a rule”We’ll write an edge subdivision rule: every A,A edge gains a B in the
middle, becoming A,B,A. The builders DSL keeps
the verbose data model out of hand-written grammars.
import { rule, pn, pe, rn, re } from 'graph-grammar'
const subdivide = rule({ name: 'subdivide', // LHS: two A nodes joined by an edge lhs: { nodes: [pn('a', 'A'), pn('b', 'A')], edges: [pe('e', 'a', 'b')], }, // RHS: keep both A's (mapFrom), insert a new B between them rhs: { nodes: [rn('a', 'A', { mapFrom: 'a' }), rn('mid', 'B'), rn('b', 'A', { mapFrom: 'b' })], edges: [re('e1', 'a', 'mid'), re('e2', 'mid', 'b')], },})pn(id, label)/pe(id, source, target)build the LHS pattern.rn(id, label, { mapFrom })preserves a matched LHS node; withoutmapFromit’s a freshly created node.- The morphism (which RHS node preserves which LHS node) is derived from
mapFrom, so you don’t declare it twice.
3. Assemble a grammar and run it
Section titled “3. Assemble a grammar and run it”import { grammar, Engine } from 'graph-grammar'
// Host graph: A , Aconst start = { nodes: [ { id: '1', label: 'A', props: {} }, { id: '2', label: 'A', props: {} }, ], edges: [{ id: 'x', source: '1', target: '2', label: '', directed: false, props: {} }],}
const g = grammar('subdivide-demo', [subdivide], start, { strategy: 'maximal', // apply all non-overlapping matches each step maxSteps: 3,})
const engine = new Engine(g)engine.run()
console.log(engine.steps) // number of rewrite steps takenconsole.log(engine.graph.nodes.length) // grew as B's were insertednew Engine(g) clones the start graph, so running, resetting, and re-running is
always reproducible , the axiom is never mutated in place.
4. Step instead of run
Section titled “4. Step instead of run”For animation or inspection, drive the engine one rewrite at a time:
const e = new Engine(g)let result = e.step()while (result.applied) { console.log(result.ruleId, 'created', result.createdNodes) result = e.step()}Each step() returns a RewriteResult describing what changed (created/deleted
node and edge ids) , ideal for highlighting or diffing.
Next steps
Section titled “Next steps”- Core concepts , the full rule model (predicates, embedding, NACs, property expressions).
- Authoring rules , practical recipes.
- Application strategies ,
random,priority,sequential,maximal. - Serialization & validation , load and save grammars as JSON.
Built by Kiberon Labs