---
title: "How it works · Docs · tabnas"
description: "The ideas behind the engine: grammar as parser, a uniform tree, two runtimes."
source: "https://tabnas.dev/docs/how-it-works/"
---

# How it works

tabnas is built on a few deliberate choices. Understanding them explains why the API is small and why the same grammar behaves identically across languages.

## The grammar is the parser

Most parser tools generate code from a grammar, then ask you to keep the generated code in sync. tabnas doesn’t generate anything: a grammar is a data structure the engine executes directly. That means:

-   No build step and nothing to fall out of sync.
-   You can build grammars at runtime (for example, compiled from ABNF).
-   You can inspect a live grammar (describe it, render it back to ABNF, or draw it as a railroad diagram) because it’s just data.

## A uniform tree

Parsing always produces the same node shape:

**The node shape**

TypeScript:

```ts
// The shape every tree-building grammar produces.
type TreeNode = { rule: string; src: string; kids: TreeNode[] }

const node: TreeNode = {
  rule: 'val',
  src: '1+2',
  kids: [
    { rule: 'add', src: '1', kids: [] },
    { rule: 'add', src: '2', kids: [] },
  ],
}

console.log(node.rule, node.kids.length)
```

Go:

```go
package main

import "fmt"

// The shape every tree-building grammar produces.
type Node struct {
	Rule string `json:"rule"`
	Src  string `json:"src"`
	Kids []Node `json:"kids"`
}

func main() {
	node := Node{
		Rule: "val",
		Src:  "1+2",
		Kids: []Node{
			{Rule: "add", Src: "1", Kids: []Node{}},
			{Rule: "add", Src: "2", Kids: []Node{}},
		},
	}

	fmt.Println(node.Rule, len(node.Kids))
}
```

Explain: Every tree-building grammar produces the same node shape: the rule that matched, the source text it covered, and its children. Nesting is just `kids` holding more of the same. TypeScript expresses it as a structural type; Go as a struct with JSON tags, so the same tree serialises identically from either runtime.

Output: `val 2`

`rule` is the grammar rule that matched, `src` is the source it covered, and `kids` are the child nodes. Because the shape is invariant, tooling written for one grammar works for all of them.

## One grammar, two runtimes

The engine has two implementations: TypeScript (canonical) and Go (tracking). They compile the **same** grammar fixtures and must produce the **same** trees: this is enforced in CI. Practically, you can define a language once and run it in a Node service and a Go binary with confidence they agree.

Some differences are intentional where a runtime’s API can’t mirror the other; those are documented per package.

## Scope

tabnas is the shortest path from “I have a grammar” to “I have a parser I can trust, everywhere I run.” It is not a full compiler toolchain, and it isn’t the fastest parser for every workload: it optimises for correctness, portability, and a grammar you can read.
