tabnasdirective

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jun 18, 2026 License: MIT Imports: 2 Imported by: 0

README

tabnas-directive (Go)

Directive-syntax plugin for the tabnas parser.

A directive is a token sequence — @name (open-only) or add<1,2> (open + close) — that pushes into a dedicated rule and fires an action to transform the parsed body. This is the Go port of the canonical TypeScript implementation in ../ts; the TypeScript version is authoritative and this package tracks it. A few intentional differences (Go static typing, engine-API limits) are listed in the concepts doc.

Documentation

The four-quadrant Go docs live in doc/: tutorial · how-to guide · reference · concepts. The canonical TypeScript docs are in ../ts/doc/.

The plugin's only dependency is the tabnas engine (github.com/tabnas/parser/go). It modifies host-grammar rules (val, list, map, pair), so you apply it to a *tabnas.Tabnas instance that already has a grammar installed — not a bare engine. A minimal host grammar is in mini_grammar_test.go.

Install

go get github.com/tabnas/parser/go
go get github.com/tabnas/directive/go

Use

package main

import (
	"fmt"
	"strings"

	tabnas "github.com/tabnas/parser/go"
	tabnasdirective "github.com/tabnas/directive/go"
)

func main() {
	j := tabnas.Make()
	j.Use(hostGrammar) // your grammar: provides val / list / map / pair
	tabnasdirective.Apply(j, tabnasdirective.DirectiveOptions{
		Name: "upper",
		Open: "@",
		Action: func(r *tabnas.Rule, _ *tabnas.Context) {
			r.Node = strings.ToUpper(fmt.Sprintf("%v", r.Child.Node))
		},
	})

	v, _ := j.Parse("[@a, @b, 1]") // []any{"A", "B", float64(1)}
	fmt.Printf("%#v\n", v)
}

Build and test

This repository consumes the engine from source. From the repository root, fetch it first, then build and test:

TABNAS_SKIP_TS_BUILD=1 ./scripts/fetch-parser.sh  # Go-only: skips the TS build
cd go && go build ./... && go vet ./... && go test ./...

Or, from the repository root, make test-go does all of the above. The go.mod replace directive points the github.com/tabnas/parser/go requirement at the fetched copy in ../vendor/tabnas-parser/go.

License

MIT.

Documentation

Index

Constants

View Source
const Version = "0.2.0"

Variables

View Source
var Directive tabnas.Plugin = func(j *tabnas.Tabnas, opts map[string]any) error {
	name, _ := opts["name"].(string)
	open, _ := opts["open"].(string)
	close_, _ := opts["close"].(string)
	action, _ := opts["action"].(Action)
	custom, _ := opts["custom"].(CustomFunc)
	hasClose := close_ != ""

	// Resolve rules: an absent "rules" key means use defaults; a present
	// (even empty) *RulesOption is honoured as-is.
	var openRules, closeRules map[string]*RuleMod
	if rulesOpt, ok := opts["rules"].(*RulesOption); ok && rulesOpt != nil {
		openRules = resolveRules(rulesOpt.Open)
		closeRules = resolveRules(rulesOpt.Close)
	} else {
		defaults := defaultRules()
		openRules = resolveRules(defaults.Open)
		closeRules = resolveRules(defaults.Close)
	}

	cfg := j.Config()
	if _, exists := cfg.FixedTokens[open]; exists {
		return fmt.Errorf("Directive open token already in use: %s", open)
	}

	openTN := "#OD_" + name
	OPEN := j.Token(openTN, open)

	// Register or look up the close fixed token.
	var CLOSE tabnas.Tin = -1
	closeTN := ""
	if hasClose {
		if existing, exists := cfg.FixedTokens[close_]; exists {

			CLOSE = existing
			closeTN = j.TinName(existing)
		} else {
			closeTN = "#CD_" + name
			CLOSE = j.Token(closeTN, close_)
		}
	}

	ref := map[tabnas.FuncRef]any{}

	ref[tabnas.FuncRef("@"+name+"-bo")] = tabnas.StateAction(
		func(r *tabnas.Rule, ctx *tabnas.Context) {
			r.Node = make(map[string]any)
		},
	)
	ref[tabnas.FuncRef("@"+name+"-bc")] = tabnas.StateAction(
		func(r *tabnas.Rule, ctx *tabnas.Context) {

			if r.Child != nil && r.Child != tabnas.NoRule {
				final := r.Child
				for final.Next != nil && final.Next != tabnas.NoRule &&
					final.Next.Prev == final {
					final = final.Next
				}
				if final != r.Child {
					r.Child.Node = final.Node
				}
			}
			if action != nil {
				action(r, ctx)
			}
		},
	)

	gs := &tabnas.GrammarSpec{
		Ref:  ref,
		Rule: map[string]*tabnas.GrammarRuleSpec{},
	}
	ruleFor := func(rn string) *tabnas.GrammarRuleSpec {
		if existing, ok := gs.Rule[rn]; ok {
			return existing
		}
		r := &tabnas.GrammarRuleSpec{}
		gs.Rule[rn] = r
		return r
	}

	for rulename, rulemod := range openRules {
		rn := rulename
		rm := rulemod

		var openAlts []*tabnas.GrammarAltSpec
		var closeAlts []*tabnas.GrammarAltSpec

		if hasClose {

			openAlts = append(openAlts, &tabnas.GrammarAltSpec{
				S: openTN + " " + closeTN,
				B: 1,
				P: name,
				N: map[string]int{"dr_" + name: 1},
				G: "start,end",
			})
			closeAlts = append(closeAlts, &tabnas.GrammarAltSpec{
				S: closeTN,
				B: 1,
				G: "end",
			})
		}

		openAlt := &tabnas.GrammarAltSpec{
			S: openTN,
			P: name,
			N: map[string]int{"dr_" + name: 1},
			G: "start",
		}
		if rm.C != nil {
			cref := tabnas.FuncRef("@dr-open-c-" + name + "-" + rn)
			ref[cref] = rm.C
			openAlt.C = string(cref)
		}
		openAlts = append(openAlts, openAlt)

		r := ruleFor(rn)
		r.Open = openAlts
		if len(closeAlts) > 0 {
			r.Close = closeAlts
		}
	}

	if hasClose {
		for rulename, rulemod := range closeRules {
			rn := rulename
			rm := rulemod

			closeCRef := tabnas.FuncRef("@dr-close-c-" + name + "-" + rn)
			ref[closeCRef] = tabnas.AltCond(
				func(r *tabnas.Rule, ctx *tabnas.Context) bool {
					if r.N["dr_"+name] != 1 {
						return false
					}
					if rm.C != nil {
						return rm.C(r, ctx)
					}
					return true
				},
			)
			commaCRef := tabnas.FuncRef("@dr-close-ca-c-" + name + "-" + rn)
			ref[commaCRef] = tabnas.AltCond(
				func(r *tabnas.Rule, ctx *tabnas.Context) bool {
					return r.N["dr_"+name] == 1
				},
			)

			closeAlts := []*tabnas.GrammarAltSpec{
				{
					S: closeTN,
					C: string(closeCRef),
					B: 1,
					G: "end",
				},
				{
					S: "#CA " + closeTN,
					C: string(commaCRef),
					B: 1,
					G: "end,comma",
				},
			}

			r := ruleFor(rn)
			r.Close = closeAlts
		}
	}

	var dirOpen []*tabnas.GrammarAltSpec
	if hasClose {

		dirOpen = append(dirOpen, &tabnas.GrammarAltSpec{
			S: closeTN,
			B: 1,
		})
	}

	counters := map[string]int{}
	if hasClose {
		counters["dlist"] = 0
		counters["dmap"] = 0
	} else {
		counters["dlist"] = 1
		counters["dmap"] = 1
	}
	dirOpen = append(dirOpen, &tabnas.GrammarAltSpec{
		P: "val",
		N: counters,
	})

	var dirClose []*tabnas.GrammarAltSpec
	if hasClose {
		dirClose = []*tabnas.GrammarAltSpec{
			{S: closeTN},
			{S: "#CA " + closeTN},
		}
	}

	dr := ruleFor(name)
	dr.Open = dirOpen
	if len(dirClose) > 0 {
		dr.Close = dirClose
	}

	j.Rule(name, func(rs *tabnas.RuleSpec, _ *tabnas.Parser) {
		rs.Clear()
	})

	setting := &tabnas.GrammarSetting{
		Rule: &tabnas.GrammarSettingRule{
			Alt: &tabnas.GrammarSettingAlt{G: "directive"},
		},
	}
	if err := j.Grammar(gs, setting); err != nil {
		return err
	}

	if custom != nil {
		closeTin := tabnas.Tin(-1)
		if hasClose {
			closeTin = CLOSE
		}
		custom(j, DirectiveConfig{
			OPEN:  OPEN,
			CLOSE: closeTin,
			Name:  name,
		})
	}

	return nil
}

Directive is the tabnas plugin that adds directive syntax support. A directive defines a custom token sequence (open and optional close) that triggers an action callback to transform the parsed content.

It follows the standard tabnas plugin shape — a Plugin value that reads its configuration from the option map passed to j.Use. Recognised keys:

"name"   string      — directive/rule name (required)
"open"   string      — open token source (required)
"close"  string      — optional close token source
"action" Action      — content transform callback
"rules"  *RulesOption — rules to modify; omit for defaults
"custom" CustomFunc   — extra setup callback

Most callers use the typed Apply constructor rather than calling this directly.

Functions

func Apply

func Apply(j *tabnas.Tabnas, opts DirectiveOptions) (*tabnas.Tabnas, error)

Apply registers the Directive plugin on the given tabnas instance with typed options. It is the convenience constructor mirroring the TypeScript `j.use(Directive, options)` call; under the hood it forwards the options to j.Use as the plugin option map. It returns the tabnas instance (for chaining) together with any registration error — e.g. a duplicate open token or a grammar build failure. The plugin never panics: every failure path is reported through this error.

To register the raw plugin directly — e.g. from a JSON-driven config — call j.Use(directive.Directive, opts) with the same option keys ("name", "open", "close", "action", "rules", "custom").

Types

type Action

type Action func(rule *tabnas.Rule, ctx *tabnas.Context)

Action is called when a directive is processed. It receives the directive rule and parse context. The rule's Child.Node contains the parsed content between open (and optional close) tokens. Set rule.Node to the directive's result value.

type CustomFunc

type CustomFunc func(j *tabnas.Tabnas, config DirectiveConfig)

CustomFunc allows additional customization of the tabnas instance after the directive rule is created.

type DirectiveConfig

type DirectiveConfig struct {
	OPEN  tabnas.Tin
	CLOSE tabnas.Tin // -1 if no close token
	Name  string
}

DirectiveConfig holds the resolved token Tins for a directive, passed to CustomFunc callbacks.

type DirectiveOptions

type DirectiveOptions struct {
	// Name is the directive name, used as the rule name and token prefix.
	Name string

	// Open is the character sequence that starts the directive.
	// Must be unique (not already a registered fixed token).
	Open string

	// Close is the optional character sequence that ends the directive.
	// If empty, the directive consumes a single value after the open token.
	Close string

	// Action is called when the directive content has been parsed.
	Action Action

	// Rules controls which existing grammar rules are modified.
	// nil means use defaults: open="val", close="list,elem,map,pair".
	// Set to &RulesOption{} to override defaults with no rules.
	Rules *RulesOption

	// Custom allows additional tabnas customization after directive setup.
	Custom CustomFunc
}

DirectiveOptions configures the Directive plugin.

type RuleMod

type RuleMod struct {
	// C is an optional condition that must be true for the directive to match
	// within this rule.
	C tabnas.AltCond
}

RuleMod configures how a directive integrates with an existing grammar rule.

type RulesOption

type RulesOption struct {
	Open  map[string]*RuleMod
	Close map[string]*RuleMod
}

RulesOption configures which grammar rules are modified by the directive. Open rules detect the directive open token and push to the directive rule. Close rules detect the close token (if any) to end sibling parsing.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL