decoder

package module
v1.1.5 Latest Latest
Warning

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

Go to latest
Published: Nov 2, 2025 License: MIT Imports: 23 Imported by: 2

README

Decoder

Dynamic decoders based on inspector framework and vector parsers.

Retrospective

One of the major problems we ran into was a necessity to convert tons of different response formats from external services into an internal response format. The problem became harder due to new external services with their own response formats may appear at any time. Due to highload conditions, there is no way to use standard dynamic approaches like reflection - the convertation must work very fast, make zero allocations and support dynamic to avoid application deploys.

This package was developed as an answer to this challenge. It provides a possibility to describe decoding rules in Go-like meta-language with full dynamic support - registering new decoders (or edit an existing) may on the fly.

How it works

Decoders are similar to dyntpl package in opposite - dyntpl makes a text from structures but decoders parses text and assign data to structures.

Similar to dyntpl, decoding divides into two phases - parsing and decoding. The parsing phase builds from decoder's body a tree (like AST) and registers it in decoders registry by unique name afterward. This phase isn't intended to be used in highload conditions due to high pressure to cpu/mem. The second phase - decoding, against intended to use in highload.

Decoding phase required a preparation to pass data to the decoder. There is a special object Ctx, that collects variables to use in decoder. Each variable must have three params:

  • unique name
  • data - anything you need to use in decoder
  • inspector type

What is the inspector describes here, but need an extra explanation of how it works together with decoders. In general, decoding problem sounds like "grab an arbitrary data from one struct and write it to another struct as fast as it possible and with zero allocations". The first part of the problem was solved in dyntpl using inspectors, and it was a good decision to extend inspectors with possibility to write data to destination structs. Thus, the problem became like "using one inspector, read data from the source struct and, using another inspector, write it to the destination struct".

Usage

The typical usage of decoders looks like this:

package main

import (
	"github.com/koykov/decoder"
	"github.com/koykov/inspector/testobj"
	"github.com/koykov/inspector/testobj_ins"
	"github.com/koykov/jsonvector"
)

var (
	data     testobj.TestObject
	response = []byte(`{"identifier":"xf44e","person":{"full_name":"Marquis Warren","status":67},"finance":{"balance":"164.5962"","is_active":true}}`)
	decBody  = []byte(`data.Id = resp.identifier
data.Name = resp.person.full_name
data.Status = resp.person.status|default(-1)
data.Finance.Balance = atof(resp.finance.balance)`)
)

func init() {
	// Parse decoder body and register it.
	dec, _ := decoder.Parse(decBody)
	decoder.RegisterDecoderKey("myDecoder", dec)
}

func main() {
	// Prepare response as vector object.
	vec := jsonvector.Acquire()
	defer jsonvector.Release(vec)
	_ = vec.Parse(response)

	ctx := decoder.AcquireCtx()
	defer decoder.ReleaseCtx(ctx)
	
	// Prepare context.
	ctx.SetVector("resp", vec)
	ctx.Set("data", &data, testobj_ins.TestObjectInspector{})
	// Execute the decoder.
	err := decoder.Decode("myDecoder", ctx)
	println(err)                  // nil
	println(data.Id)              // xf44e
	println(data.Name)            // []byte("Marquis Warren")
	println(data.Status)          // 67
	println(data.Finance.Balance) // 164.5962
}

Content of init() function should be executed once (or periodically on the fly from some source, eg DB).

Content of main() function is how to use decoders in a general way in highload.

Syntax

Decoders inherits Go syntax, but provides an extra features like modifiers and coalesce operator (see below).

Assigning

The base decoding operation is assigning the data from source variable to destination variable. The syntax is typical lvalue.Field1 = rvalue.Field2. From example:

data.Id = resp.identifier
data.Name = resp.person.full_name

where data represents lvalue (source variable) and resp - rvalue (destination variable).

Coalesce operator

Decoders provide a possibility to read one-of-many fields when read nested fields from struct:

dst.Field = src.Nested.{Field1|Field2|Field3|...}

The first non-empty field between curly brackets will be read as data to assign. This syntax sugar allows to avoid tons of comparisons or build chain of default modifiers. Example of usage see here.

Modifiers

Decoders supports user-defined modifiers, which applies additional logic to data before assigning. It may be helpful for edge cases (no data, conditional assignment, etc.). Modifiers usage syntax is typical - after source of data, using | symbol, modifier calls as function call:

dst.Field = src.Field|modifier0(arg0, arg1, ...)|modifier1(arg0, arg1, ...)|...

Example:

data.Status = src.Nested.Blocked|ifThenElse(src.Nested.State, -1)
                                ^ simple modifier
data.Name = src.FullName|default("N\D")|toUpper()
                        ^ first mod    ^ second modifier

Modifiers may collect in chain with variadic length. In that case, each modifier will take to input the result of previous modifier. Each modifier may take an arbitrary count of arguments.

Modifier is a Go function with a special signature:

type ModFn func(ctx *Ctx, buf *any, val any, args []any) error

where:

  • ctx - context of the decoder
  • buf - pointer to return the result
  • val - value to pass to the modifier (value of varName in example varName|modifier())
  • args - list of all arguments

You should register your modifier using one of the functions:

  • RegisterModFn(name, alias string, mod ModFn)
  • RegisterModFnNS(namespace, name, alias string, mod ModFn)

They are the same, but NS version allows to specify the namespace of the function. In that case, you should specify namespace in modifiers call:

dst.Field = src.Field|namespaceName::modifier()
Conditions

Decoders supports classic syntax of conditions:

if leftVar [==|!=|>|>=|<|<=] rightVar {
    true branch
} else {
    false branch
}

Examples: 1, 2, 3.

Decoders can't handle complicated conditions containing more than one comparison, like:

if user.Id == 0 || user.Finance.Balance == 0 {...}

In the future this problem will be solved, but now you can make nested conditions or use conditions helpers - functions with signature:

type CondFn func(ctx *Ctx, args []any) bool

, where you may pass an arbitrary amount of arguments and these functions will return bool to choose the right execution branch. These functions are user-defined, like modifiers, and you may write your own and then register it using one of the functions:

func RegisterCondFn(name string, cond CondFn)
func RegisterCondFnNS(namespace, name string, cond CondFn) // namespace version

Then condition helper will be accessible inside decoders and you may use it using the name:

if helperName(user.Id, user.Finance.Balance) {...}

Decoders supports ternary operator for most primitive cases of assigning. Conditions like this:

if x.a == 123 {
    dst.Field1 = src.Field2
} else {
    dst.Field1 = "N/D
}

may be shortener using ternary operator:

dst.Field1 = x.a == 123 ? src.Field2 : "N/D"

Condition helpers also supported:

obj.Id = testns::check(obj.Id, 15.123, "foobar", false) ? 225 : src.{status|state}
switch

For multiple conditions, you can use switch statement, examples:

Loops

Decoders supports both types of loops:

  • counter loops, like for i:=0; i<5; i++ {...}
  • range-loop, like for k, v := range obj.Items {...}

Edge cases like for k < 2000 {...} or for ; i < 10 ; {...} isn't supported. Also, you can't make an infinite loop by using for {...}.

Loop breaking

Decoders supports default instructions break and continue to break loop/iteration, example:

for _, v := list
  if v.ID == 0 {
    continue
  }
  if v.Status == -1 {
    break
  }
}

These instructions works as intended, but they required condition a wrapper and that's bulky. Therefore, decoders provide combined break if and continue if that works the same:

for _, v := list {
  continue if v.ID == 0
  break if v.Status == -1
}

Both examples are equal, but the second is more compact.

Lazy breaks

Imagine the case - you've decided in the middle of iteration that loop requires a break, but the iteration must finish its work the end. For that case, decoders supports special instruction lazybreak. It breaks the loop but allows current iteration works till the end.

Extensions

Decoders may be extended by including modules in the project. Currently supported modules:

To enable necessary module just import it to the project, eg:

import (
	_ "https://github.com/koykov/decoder_vector"
)

and vector's features will be available inside decoders.

Feel free to develop your own extensions. Strongly recommend to register new modifiers using namespaces, like this.

Conclusion

Due to two phases (parsing and decoding) in using decoders it isn't handy to use in simple cases, especially outside highload. The good condition to use it is a highload project and dynamic support requirement. Use decoders in proper conditions and wish you happy decoding.

Documentation

Index

Constants

View Source
const (
	DocgenFormatMarkdown DocgenFormat = "markdown"
	DocgenFormatHTML                  = "html"
	DocgenFormatJSON                  = "json"
)

Variables

View Source
var (
	ErrDecoderNotFound = errors.New("decoder not found")
	ErrEmptyNode       = errors.New("provided node is empty")
	ErrModNoArgs       = errors.New("empty arguments list")
	ErrModPoorArgs     = errors.New("arguments list in modifier is too small")
	ErrCbPoorArgs      = errors.New("arguments list in callback is too small")
	ErrGetterPoorArgs  = errors.New("arguments list in getter callback is too small")

	ErrUnbalancedCtl   = errors.New("unbalanced control structures found")
	ErrUnexpectedClose = errors.New("unexpected close bracket")

	ErrWrongLoopLim  = errors.New("wrong count loop limit argument")
	ErrWrongLoopCond = errors.New("wrong loop condition operation")
	ErrWrongLoopOp   = errors.New("wrong loop operation")
	ErrBreakLoop     = errors.New("break loop")
	ErrLBreakLoop    = errors.New("lazybreak loop")
	ErrContLoop      = errors.New("continue loop")

	ErrSenselessCond   = errors.New("comparison of two static args")
	ErrCondHlpNotFound = errors.New("condition helper not found")

	ErrUnknownPool = errors.New("unknown pool")
)

Functions

func AssignVectorNode

func AssignVectorNode(dst, src any, _ inspector.AccumulativeBuffer) (ok bool)

AssignVectorNode implements assign callback to convert vector.Node to destination with arbitrary type.

func Decode

func Decode(key string, ctx *Ctx) error

Decode applies decoder rules using given id.

ctx should contain all variables mentioned in the decoder's body.

func DecodeByID added in v1.1.5

func DecodeByID(id int, ctx *Ctx) error

DecodeByID applies decoder rules using given id.

func DecodeFallback added in v1.1.5

func DecodeFallback(key, fbKey string, ctx *Ctx) error

DecodeFallback applies decoder rules using one of keys: key or fallback key.

Using this func you can handle cases when some objects have custom decoders and all other should use default decoders. Example: decoder registry: * decoderUser * decoderUser-15 user object with id 15 Call of decoder.DecoderFallback("decUser-15", "decUser", ctx) will take decoder decUser-15 from registry. In other case, for user #4: call of decoder.DecoderFallback("decUser-4", "decUser", ctx) will take default decoder decUser from registry.

func DecodeRuleset

func DecodeRuleset(ruleset Ruleset, ctx *Ctx) (err error)

DecodeRuleset applies decoder ruleset without using id.

func Docgen added in v1.1.4

func Docgen(format DocgenFormat) ([]byte, error)

func RegisterDecoder

func RegisterDecoder(id int, key string, tree *Tree)

RegisterDecoder saves decoder by ID and key in the registry.

You may use to access to the decoder both ID or key. This function can be used in any time to register new decoders or overwrite existing to provide dynamics.

func RegisterDecoderID added in v1.1.5

func RegisterDecoderID(id int, tree *Tree)

RegisterDecoderID saves decoder using only ID.

See RegisterDecoder().

func RegisterDecoderKey added in v1.1.5

func RegisterDecoderKey(key string, tree *Tree)

RegisterDecoderKey saves decoder using only key.

See RegisterDecoder().

func RegisterPool added in v1.1.0

func RegisterPool(key string, pool Pool) error

RegisterPool adds new internal pool to the registry by given key.

func ReleaseCtx

func ReleaseCtx(ctx *Ctx)

ReleaseCtx puts object back to default pool.

func WriteDocgen added in v1.1.4

func WriteDocgen(w io.Writer, format DocgenFormat) error

Types

type CallbackFn

type CallbackFn func(ctx *Ctx, args []any) error

CallbackFn represents the signature of callback function.

args contains list of all arguments you passed in decoder node.

func GetCallbackFn

func GetCallbackFn(name string) CallbackFn

GetCallbackFn returns callback function from the registry.

type CallbackFnTuple added in v1.1.4

type CallbackFnTuple struct {
	// contains filtered or unexported fields
}

func RegisterCallbackFn

func RegisterCallbackFn(name, alias string, cb CallbackFn) *CallbackFnTuple

RegisterCallbackFn registers new callback to the registry.

func RegisterCallbackFnNS added in v1.1.0

func RegisterCallbackFnNS(namespace, name, alias string, cb CallbackFn) *CallbackFnTuple

RegisterCallbackFnNS registers new callback in given namespace.

func (*CallbackFnTuple) WithDescription added in v1.1.4

func (t *CallbackFnTuple) WithDescription(desc string) *docgen

func (*CallbackFnTuple) WithExample added in v1.1.4

func (t *CallbackFnTuple) WithExample(example string) *docgen

func (*CallbackFnTuple) WithNote added in v1.1.4

func (t *CallbackFnTuple) WithNote(note string) *docgen

func (*CallbackFnTuple) WithParam added in v1.1.4

func (t *CallbackFnTuple) WithParam(param, desc string) *docgen

func (*CallbackFnTuple) WithType added in v1.1.4

func (t *CallbackFnTuple) WithType(typ string) *docgen

type CondFn added in v1.1.5

type CondFn func(ctx *Ctx, args []any) bool

CondFn describes helper func signature.

func GetCondFn added in v1.1.5

func GetCondFn(name string) CondFn

GetCondFn returns condition helper from the registry.

type CondFnTuple added in v1.1.5

type CondFnTuple struct {
	// contains filtered or unexported fields
}

func RegisterCondFn added in v1.1.5

func RegisterCondFn(name string, cond CondFn) *CondFnTuple

RegisterCondFn registers new condition helper.

func RegisterCondFnNS added in v1.1.5

func RegisterCondFnNS(namespace, name string, cond CondFn) *CondFnTuple

RegisterCondFnNS registers new condition helper in given namespace.

func (*CondFnTuple) WithDescription added in v1.1.5

func (t *CondFnTuple) WithDescription(desc string) *docgen

func (*CondFnTuple) WithExample added in v1.1.5

func (t *CondFnTuple) WithExample(example string) *docgen

func (*CondFnTuple) WithNote added in v1.1.5

func (t *CondFnTuple) WithNote(note string) *docgen

func (*CondFnTuple) WithParam added in v1.1.5

func (t *CondFnTuple) WithParam(param, desc string) *docgen

func (*CondFnTuple) WithType added in v1.1.5

func (t *CondFnTuple) WithType(typ string) *docgen

type CondOKFn added in v1.1.5

type CondOKFn func(ctx *Ctx, v *any, ok *bool, args []any)

CondOKFn describes helper func signature.

func GetCondOKFn added in v1.1.5

func GetCondOKFn(name string) CondOKFn

GetCondOKFn returns condition-OK helper from the registry.

type CondOKTuple added in v1.1.5

type CondOKTuple struct {
	// contains filtered or unexported fields
}

func RegisterCondOKFn added in v1.1.5

func RegisterCondOKFn(name string, cond CondOKFn) *CondOKTuple

RegisterCondOKFn registers new condition-OK helper.

func RegisterCondOKFnNS added in v1.1.5

func RegisterCondOKFnNS(namespace, name string, cond CondOKFn) *CondOKTuple

RegisterCondOKFnNS registers new condition-OK helper in given namespace.

func (*CondOKTuple) WithDescription added in v1.1.5

func (t *CondOKTuple) WithDescription(desc string) *docgen

func (*CondOKTuple) WithExample added in v1.1.5

func (t *CondOKTuple) WithExample(example string) *docgen

func (*CondOKTuple) WithNote added in v1.1.5

func (t *CondOKTuple) WithNote(note string) *docgen

func (*CondOKTuple) WithParam added in v1.1.5

func (t *CondOKTuple) WithParam(param, desc string) *docgen

func (*CondOKTuple) WithType added in v1.1.5

func (t *CondOKTuple) WithType(typ string) *docgen

type Ctx

type Ctx struct {
	BufT time.Time

	// External buffers to use in modifier and condition helpers.
	BufAcc bytebuf.Accumulative
	// todo remove as unused later
	Buf, Buf1, Buf2 bytebuf.Chain

	Err error
	// contains filtered or unexported fields
}

Ctx represents decoder context object.

Contains list of variables that can be used as source or destination.

func AcquireCtx

func AcquireCtx() *Ctx

AcquireCtx returns object from the default context pool.

func NewCtx

func NewCtx() *Ctx

NewCtx makes new context object.

func (*Ctx) AcquireBytes

func (ctx *Ctx) AcquireBytes() []byte

AcquireBytes returns accumulative buffer.

func (*Ctx) AcquireFrom added in v1.1.0

func (ctx *Ctx) AcquireFrom(pool string) (any, error)

AcquireFrom receives new variable from given pool and register it to return batch after finish decoder processing.

func (*Ctx) Bufferize added in v1.0.2

func (ctx *Ctx) Bufferize(p []byte) []byte

func (*Ctx) BufferizeString added in v1.0.2

func (ctx *Ctx) BufferizeString(s string) string

func (*Ctx) Get

func (ctx *Ctx) Get(path string) any

Get arbitrary value from the context by path.

See Ctx.get(). Path syntax: <ctxVrName>[.<Field>[.<NestedField0>[....<NestedFieldN>]]] Examples: * user.Bio.Birthday * staticVar

func (*Ctx) ReleaseBytes

func (ctx *Ctx) ReleaseBytes(p []byte)

ReleaseBytes updates accumulative buffer with p.

func (*Ctx) Reset

func (ctx *Ctx) Reset()

Reset the context.

Made to use together with pools.

func (*Ctx) Set

func (ctx *Ctx) Set(key string, val any, ins inspector.Inspector)

Set the variable to context. Inspector ins should be corresponded to variable val.

func (*Ctx) SetStatic

func (ctx *Ctx) SetStatic(key string, val any)

SetStatic registers static variable in context.

func (*Ctx) SetVector

func (ctx *Ctx) SetVector(key string, vec vector.Interface)

SetVector directly register vector in context.

func (*Ctx) SetVectorNode

func (ctx *Ctx) SetVectorNode(key string, node *vector.Node) error

SetVectorNode directly registers vector's node in context under given key.

type CtxPool

type CtxPool struct {
	// contains filtered or unexported fields
}

CtxPool represents context pool.

var (
	// CP is a default instance of context pool.
	// You may use it directly as decoder.CP.Get()/Put() or using functions AcquireCtx()/ReleaseCtx().
	CP CtxPool
)

func (*CtxPool) Get

func (p *CtxPool) Get() *Ctx

Get context object from the pool or make new object if pool is empty.

func (*CtxPool) Put

func (p *CtxPool) Put(ctx *Ctx)

Put the object to the pool.

type Decoder

type Decoder struct {
	ID  int
	Key string
	// contains filtered or unexported fields
}

Decoder represents main decoder object. Decoder contains only parsed ruleset. All temporary and intermediate data should be store in context logic to make using of decoders thread-safe.

type DocgenFormat added in v1.1.4

type DocgenFormat string

type GetterFn

type GetterFn func(ctx *Ctx, buf *any, args []any) error

GetterFn represents signature of getter callback function.

args contains list of all arguments you passed in decoder node.

func GetGetterFn

func GetGetterFn(name string) GetterFn

GetGetterFn returns getter callback function from the registry.

type GetterFnTuple added in v1.1.4

type GetterFnTuple struct {
	// contains filtered or unexported fields
}

func RegisterGetterFn

func RegisterGetterFn(name, alias string, cb GetterFn) *GetterFnTuple

RegisterGetterFn registers new getter callback to the registry.

func RegisterGetterFnNS added in v1.1.0

func RegisterGetterFnNS(namespace, name, alias string, cb GetterFn) *GetterFnTuple

RegisterGetterFnNS registers new getter callback in given namespace.

func (*GetterFnTuple) WithDescription added in v1.1.4

func (t *GetterFnTuple) WithDescription(desc string) *docgen

func (*GetterFnTuple) WithExample added in v1.1.4

func (t *GetterFnTuple) WithExample(example string) *docgen

func (*GetterFnTuple) WithNote added in v1.1.4

func (t *GetterFnTuple) WithNote(note string) *docgen

func (*GetterFnTuple) WithParam added in v1.1.4

func (t *GetterFnTuple) WithParam(param, desc string) *docgen

func (*GetterFnTuple) WithType added in v1.1.4

func (t *GetterFnTuple) WithType(typ string) *docgen

type Global added in v1.1.5

type Global any

Global describes value of global variable.

func GetGlobal added in v1.1.5

func GetGlobal(name string) Global

GetGlobal returns global variable by given name.

type GlobalTuple added in v1.1.5

type GlobalTuple struct {
	// contains filtered or unexported fields
}

func RegisterGlobal added in v1.1.5

func RegisterGlobal(name, alias string, val Global) *GlobalTuple

RegisterGlobal registers new global variable.

Caution! Globals registered after template parsing will take no effect.

func RegisterGlobalNS added in v1.1.5

func RegisterGlobalNS(namespace, name, alias string, val Global) *GlobalTuple

RegisterGlobalNS registers new global variable in given namespace.

func (*GlobalTuple) WithDescription added in v1.1.5

func (t *GlobalTuple) WithDescription(desc string) *docgen

func (*GlobalTuple) WithExample added in v1.1.5

func (t *GlobalTuple) WithExample(example string) *docgen

func (*GlobalTuple) WithNote added in v1.1.5

func (t *GlobalTuple) WithNote(note string) *docgen

func (*GlobalTuple) WithParam added in v1.1.5

func (t *GlobalTuple) WithParam(param, desc string) *docgen

func (*GlobalTuple) WithType added in v1.1.5

func (t *GlobalTuple) WithType(typ string) *docgen

type ModFn

type ModFn func(ctx *Ctx, buf *any, val any, args []any) error

ModFn represents signature of the modifier functions.

Arguments description: * ctx provides access to additional variables and various buffers to reduce allocations. * buf is a storage for final result after finishing modifier work. * val is a left side variable that preceded to call of modifier func, example: {%= val|mod(...) %} * args is a list of all arguments listed on modifier call.

func GetModFn

func GetModFn(name string) ModFn

GetModFn returns modifier from the registry.

type ModFnTuple added in v1.1.4

type ModFnTuple struct {
	// contains filtered or unexported fields
}

func RegisterModFn

func RegisterModFn(name, alias string, mod ModFn) *ModFnTuple

RegisterModFn registers new modifier function.

func RegisterModFnNS added in v1.1.0

func RegisterModFnNS(namespace, name, alias string, mod ModFn) *ModFnTuple

RegisterModFnNS registers new modifier function in given namespace.

func (*ModFnTuple) WithDescription added in v1.1.4

func (t *ModFnTuple) WithDescription(desc string) *docgen

func (*ModFnTuple) WithExample added in v1.1.4

func (t *ModFnTuple) WithExample(example string) *docgen

func (*ModFnTuple) WithNote added in v1.1.4

func (t *ModFnTuple) WithNote(note string) *docgen

func (*ModFnTuple) WithParam added in v1.1.4

func (t *ModFnTuple) WithParam(param, desc string) *docgen

func (*ModFnTuple) WithType added in v1.1.4

func (t *ModFnTuple) WithType(typ string) *docgen

type Pool added in v1.1.0

type Pool interface {
	Get() any
	Put(any)
	// Reset cleanups data before putting to the pool.
	Reset(any)
}

Pool represents internal pool. In addition to native sync.Pool requires Reset() method.

type RangeLoop added in v1.1.5

type RangeLoop struct {
	// contains filtered or unexported fields
}

RangeLoop is a object that injects to inspector to perform range loop execution.

func NewRangeLoop added in v1.1.5

func NewRangeLoop(r *node, nodes []node, ctx *Ctx) *RangeLoop

NewRangeLoop makes new RL.

func (*RangeLoop) Iterate added in v1.1.5

func (rl *RangeLoop) Iterate() inspector.LoopCtl

Iterate performs the iteration.

func (*RangeLoop) RequireKey added in v1.1.5

func (rl *RangeLoop) RequireKey() bool

RequireKey checks if node requires a key to store in the context.

func (*RangeLoop) Reset added in v1.1.5

func (rl *RangeLoop) Reset()

Reset clears all data in the list of RL.

func (*RangeLoop) SetKey added in v1.1.5

func (rl *RangeLoop) SetKey(val any, ins inspector.Inspector)

SetKey saves key to the context.

func (*RangeLoop) SetVal added in v1.1.5

func (rl *RangeLoop) SetVal(val any, ins inspector.Inspector)

SetVal saves value to the context.

type Ruleset

type Ruleset []node

type Tree added in v1.1.5

type Tree struct {
	// contains filtered or unexported fields
}

Tree represents list of nodes.

func Parse

func Parse(src []byte) (*Tree, error)

Parse parses the decoder rules.

func ParseFile

func ParseFile(fileName string) (tree *Tree, err error)

ParseFile parses the file.

func (*Tree) HumanReadable added in v1.1.5

func (t *Tree) HumanReadable() []byte

HumanReadable builds human-readable view of the nodes list.

func (*Tree) Ruleset added in v1.1.5

func (t *Tree) Ruleset() Ruleset

Ruleset returns list of root nodes as old ruleset. Implements to support old types.