This document holds the target Go API of transit: what the runtime package
exports, what a generated grammar package exports, and what the module
styles exports. It comes from the
working C example of phase 1 (D10), in _samples/example/, which uses the
upstream runtime at the base commit and a real grammar.
The API follows the Rust binding, in idiomatic Go (D24, D25). A lookup that can
find no node returns the node and a bool (D56). Ken accepted this document
on 2026-10-01, which ended phase 1 (D103). A change to it is a decision first. The choices that the
example raised are decided: #lua-match? (D55, which waits for the tier 1
grammars, D69), the form of a lookup (D56)
and StatesAt (D57). The API of the package inject is decided too (D72),
and so is the form of QueryMatch::remove (D89).
_samples/example/build.sh builds the upstream tool at the base commit,
generates the SQL grammar of DerekStride/tree-sitter-sql at the tag
v0.3.11 with it, compiles example.c with the upstream runtime, and runs it.
Downloads and build output go to $XDG_CACHE_HOME/transit, outside the
repository.
The example does eight steps, each for a need of rline or usql:
- It parses a statement of three rows and prints the tree.
- It highlights the statement with
queries/highlights.scmof the grammar, and it evaluates the query predicates as the Rust binding does. - It types a statement one key at a time. After each key, it edits the old tree, parses again with it, reads the changed ranges, and highlights again.
- It finds the node at a cursor offset, its parent and its field.
- It parses three statements that are not finished, and prints the
ERRORandMISSINGnodes. - It lists the symbols that the parser can accept at the cursor.
- It highlights only the second row, with a byte range.
- It times each step.
These numbers are from one run on the development machine on 2026-09-29. They are the first C baseline for the benchmarks of phase 3 (D37).
| Step | Time |
|---|---|
| first parse of 68 bytes | 36 µs |
compile of highlights.scm, 445 rows and 24 patterns |
6,727 µs |
| highlight of the statement, 19 captures | 50 µs |
| one key: edit, parse again, changed ranges and highlight, mean of 49 keys | 12.2 µs |
| one key, the worst of 49 keys | 33.9 µs |
The SQL grammar has 729 symbols, 53 fields and 17,329 parse states.
- A query costs much more to compile than to run. A consumer compiles each query once, and shares it (D52).
- The highlight query of the SQL grammar uses
#match?with the patterns^[-+]?%d+$and^[-+]?%d*\.%d*$. These are patterns of Lua, which Neovim uses, and not regular expressions. In a regular expression,%dis the two characters%andd, so both patterns never match, and42and1.5fall through to the capturestring. The Rust binding has the same result. transit adds#lua-match?, which Neovim queries use for such patterns (D55). - The generic SQL grammar parses
SELECT id, FROM userswith no error. It readsFROMas a column andusersas its alias. A consumer cannot find every mistake fromERRORnodes. ts_node_next_parse_stategives 0 for a token that the parser lexed before a reduce, such asFROMinSELECT id FROM u. The token keeps the state in which the parser lexed it, and the action of the token in that state is a reduce. The state before the token is the state of its parent, when it is the first child, or the next state of the sibling before it. From there the lookahead lists 11 symbols afterFROM, such asrelation,identifierandsubquery. Each of the two methods fails in a case where the other works.- Inside an error, both methods give state 0, the state of the recovery, and the lookahead lists 375 symbols. This is why transit adds an API for the states before the recovery (D28). "The API that upstream does not have" below proposes it.
This is the surface of the root package. The bodies are ports of the
upstream functions that the table below names. Each enum type, such as
SymbolType, Encoding and LogType, also has a String method, which the
list leaves out.
package transit
// Symbol, FieldID and StateID are the numbers of the tables of a language.
type (
Symbol uint16
FieldID uint16
StateID uint16
)
// SymbolType is the kind of a symbol.
type SymbolType int
const (
SymbolRegular SymbolType = iota
SymbolAnonymous
SymbolSupertype
SymbolAuxiliary
)
// Point is a position as a row and a column. Both count from zero, and the
// column counts bytes.
type Point struct {
Row int
Column int
}
// Range is a range of the text, in bytes and in points.
type Range struct {
StartByte int
EndByte int
StartPoint Point
EndPoint Point
}
// InputEdit describes one edit of the text.
type InputEdit struct {
StartByte int
OldEndByte int
NewEndByte int
StartPoint Point
OldEndPoint Point
NewEndPoint Point
}
func (e InputEdit) EditPoint(p *Point, byteOffset *int)
func (e InputEdit) EditRange(r *Range)
// Language is the tables of one grammar. A grammar package returns it. It is
// safe to share between goroutines.
type Language struct{ /* unexported */ }
// NewLanguage builds a language from its tables. Only code under
// github.com/xo/transit/, such as a grammar package, can build an
// *abi.Language, because the package abi is internal (D63).
func NewLanguage(tables *abi.Language) *Language
func (l *Language) Name() string
func (l *Language) ABIVersion() int
func (l *Language) Metadata() (LanguageMetadata, bool)
func (l *Language) SymbolCount() int
func (l *Language) SymbolName(s Symbol) string
func (l *Language) SymbolForName(name string, named bool) (Symbol, bool)
func (l *Language) SymbolType(s Symbol) SymbolType
func (l *Language) Supertypes() []Symbol
func (l *Language) Subtypes(supertype Symbol) []Symbol
func (l *Language) FieldCount() int
func (l *Language) FieldName(f FieldID) string
func (l *Language) FieldForName(name string) (FieldID, bool)
func (l *Language) StateCount() int
func (l *Language) NextState(s StateID, sym Symbol) StateID
func (l *Language) LookaheadIterator(s StateID) (*LookaheadIterator, bool)
// LanguageMetadata is the version of a grammar.
type LanguageMetadata struct {
Major, Minor, Patch int
}
// NodeType is one entry of node-types.json, which the NodeTypes function of
// a grammar package returns. The JSON keys are the keys of the file.
type NodeType struct {
Kind string // "type"
Named bool // "named"
Root bool // "root"
Extra bool // "extra"
Fields map[string]FieldInfo // "fields"
Children *FieldInfo // "children", or nil
Subtypes []NodeKind // "subtypes", for a supertype
}
// FieldInfo says which nodes a field, or the children of a node, can hold.
type FieldInfo struct {
Multiple bool
Required bool
Types []NodeKind
}
// NodeKind names a node type: its kind, and whether it is named.
type NodeKind struct {
Kind string
Named bool
}
// Parser builds trees. It belongs to one goroutine at a time.
type Parser struct{ /* unexported */ }
func NewParser() *Parser
func (p *Parser) SetLanguage(l *Language) error
func (p *Parser) Language() *Language
func (p *Parser) SetIncludedRanges(ranges []Range) error
func (p *Parser) IncludedRanges() []Range
func (p *Parser) SetLogger(fn func(LogType, string))
func (p *Parser) PrintDotGraphs(w io.Writer)
func (p *Parser) Parse(ctx context.Context, src []byte, old *Tree) (*Tree, error)
func (p *Parser) ParseInput(ctx context.Context, in Input, enc Encoding, old *Tree) (*Tree, error)
func (p *Parser) ParseCustomEncoding(ctx context.Context, in Input, decode func(b []byte) (rune, int), old *Tree) (*Tree, error)
func (p *Parser) Reset()
// Input gives the text in chunks, as TSInput does.
type Input interface {
ReadAt(offset int, at Point) []byte
}
// Encoding is the encoding of the text.
type Encoding int
const (
EncodingUTF8 Encoding = iota
EncodingUTF16LE
EncodingUTF16BE
)
// LogType says whether a log message comes from the parser or the lexer.
type LogType int
const (
LogParse LogType = iota
LogLex
)
// Tree is a syntax tree. It is safe for reads from many goroutines. Edit
// changes it, so call Copy first to keep a version. Close is optional. After
// Close, the program must not use the tree or its nodes (D91).
type Tree struct{ /* unexported */ }
func (t *Tree) Close()
func (t *Tree) RootNode() Node
func (t *Tree) RootNodeWithOffset(offset int, at Point) Node
func (t *Tree) Language() *Language
func (t *Tree) Edit(e InputEdit)
func (t *Tree) Copy() *Tree
func (t *Tree) ChangedRanges(newTree *Tree) []Range
func (t *Tree) IncludedRanges() []Range
func (t *Tree) Walk() *TreeCursor
func (t *Tree) PrintDotGraph(w io.Writer)
// Node is a node of a tree. It is a small value, and two nodes compare with ==,
// which also compares their positions. Equal is the comparison of C (D68).
type Node struct{ /* unexported */ }
func (n Node) Kind() string
func (n Node) KindID() Symbol
func (n Node) GrammarKind() string
func (n Node) GrammarID() Symbol
func (n Node) Language() *Language
func (n Node) IsNamed() bool
func (n Node) IsExtra() bool
func (n Node) IsMissing() bool
func (n Node) IsError() bool
func (n Node) HasError() bool
func (n Node) HasChanges() bool
func (n Node) StartByte() int
func (n Node) EndByte() int
func (n Node) StartPoint() Point
func (n Node) EndPoint() Point
func (n Node) Range() Range
func (n Node) Text(src []byte) string
func (n Node) String() string
func (n Node) Equal(other Node) bool
func (n Node) ParseState() StateID
func (n Node) NextParseState() StateID
func (n Node) Parent() (Node, bool)
func (n Node) ChildWithDescendant(descendant Node) (Node, bool)
func (n Node) ChildCount() int
func (n Node) Child(i int) (Node, bool)
func (n Node) NamedChildCount() int
func (n Node) NamedChild(i int) (Node, bool)
func (n Node) Children() iter.Seq[Node]
func (n Node) NamedChildren() iter.Seq[Node]
func (n Node) ChildByFieldName(name string) (Node, bool)
func (n Node) ChildByFieldID(f FieldID) (Node, bool)
func (n Node) ChildrenByFieldName(name string) iter.Seq[Node]
func (n Node) FieldNameForChild(i int) string
func (n Node) FieldNameForNamedChild(i int) string
func (n Node) NextSibling() (Node, bool)
func (n Node) PrevSibling() (Node, bool)
func (n Node) NextNamedSibling() (Node, bool)
func (n Node) PrevNamedSibling() (Node, bool)
func (n Node) FirstChildForByte(offset int) (Node, bool)
func (n Node) FirstNamedChildForByte(offset int) (Node, bool)
func (n Node) DescendantCount() int
func (n Node) DescendantForByteRange(start, end int) (Node, bool)
func (n Node) NamedDescendantForByteRange(start, end int) (Node, bool)
func (n Node) DescendantForPointRange(start, end Point) (Node, bool)
func (n Node) NamedDescendantForPointRange(start, end Point) (Node, bool)
func (n *Node) Edit(e InputEdit)
func (n Node) Walk() *TreeCursor
// TreeCursor walks a tree. It belongs to one goroutine at a time.
type TreeCursor struct{ /* unexported */ }
func (c *TreeCursor) Node() Node
func (c *TreeCursor) FieldName() string
func (c *TreeCursor) FieldID() FieldID
func (c *TreeCursor) Depth() int
func (c *TreeCursor) DescendantIndex() int
func (c *TreeCursor) GotoFirstChild() bool
func (c *TreeCursor) GotoLastChild() bool
func (c *TreeCursor) GotoParent() bool
func (c *TreeCursor) GotoNextSibling() bool
func (c *TreeCursor) GotoPreviousSibling() bool
func (c *TreeCursor) GotoDescendant(index int)
func (c *TreeCursor) GotoFirstChildForByte(offset int) (int, bool)
func (c *TreeCursor) GotoFirstChildForPoint(at Point) (int, bool)
func (c *TreeCursor) Reset(n Node)
func (c *TreeCursor) ResetTo(other *TreeCursor)
func (c *TreeCursor) Copy() *TreeCursor
// Query is a compiled query. It is safe to share between goroutines, after
// any call to DisablePattern or DisableCapture.
type Query struct{ /* unexported */ }
func NewQuery(l *Language, source string) (*Query, error) // the error is a *QueryError
func (q *Query) PatternCount() int
func (q *Query) CaptureNames() []string
func (q *Query) CaptureIndexForName(name string) (int, bool)
func (q *Query) CaptureQuantifiers(pattern int) []Quantifier
func (q *Query) StartByteForPattern(pattern int) int
func (q *Query) EndByteForPattern(pattern int) int
func (q *Query) PropertySettings(pattern int) []QueryProperty
func (q *Query) PropertyPredicates(pattern int) []QueryPropertyPredicate
func (q *Query) GeneralPredicates(pattern int) []QueryPredicate
func (q *Query) IsPatternRooted(pattern int) bool
func (q *Query) IsPatternNonLocal(pattern int) bool
func (q *Query) IsPatternGuaranteedAtStep(offset int) bool
func (q *Query) DisablePattern(pattern int)
func (q *Query) DisableCapture(name string)
func (q *Query) Copy() *Query
// QueryError says where a query fails to compile.
type QueryError struct {
Offset int
Row int
Column int
Kind QueryErrorKind
Message string
}
func (e *QueryError) Error() string
// QueryErrorKind is the kind of a QueryError. QueryErrorPredicate comes from
// the Rust binding, and the others from TSQueryError.
type QueryErrorKind int
const (
QueryErrorNone QueryErrorKind = iota
QueryErrorSyntax
QueryErrorNodeType
QueryErrorField
QueryErrorCapture
QueryErrorStructure
QueryErrorLanguage
QueryErrorPredicate
)
// Quantifier says how many times a capture can occur in a pattern.
type Quantifier int
const (
QuantifierZero Quantifier = iota
QuantifierZeroOrOne
QuantifierZeroOrMore
QuantifierOne
QuantifierOneOrMore
)
// QueryProperty is a key and a value of #set!, #is? or #is-not?. CaptureID is
// -1 when the predicate names no capture.
type QueryProperty struct {
Key string
Value string
HasValue bool
CaptureID int
}
// QueryPropertyPredicate is a property of #is?, which is positive, or of
// #is-not?.
type QueryPropertyPredicate struct {
Property QueryProperty
Positive bool
}
// QueryPredicate is a predicate that the query does not evaluate, such as a
// predicate of Neovim, with its arguments.
type QueryPredicate struct {
Operator string
Args []QueryPredicateArg
}
// QueryPredicateArg is a capture or a string.
type QueryPredicateArg struct {
IsCapture bool
Capture int
Value string
}
// QueryCursor runs a query on a tree. It belongs to one goroutine at a time.
// Matches and Captures evaluate the text predicates of the query (D27).
// The predicates of Neovim, such as #lua-match?, wait for the tier 1 grammars
// (D69), and until then each one is a general predicate, as in the Rust
// binding. docs/NEOVIM.md lists them.
type QueryCursor struct{ /* unexported */ }
func NewQueryCursor() *QueryCursor
func (c *QueryCursor) SetMatchLimit(limit int)
func (c *QueryCursor) MatchLimit() int
func (c *QueryCursor) DidExceedMatchLimit() bool
func (c *QueryCursor) SetMaxStartDepth(depth int)
func (c *QueryCursor) SetByteRange(start, end int)
func (c *QueryCursor) SetPointRange(start, end Point)
func (c *QueryCursor) SetContainingByteRange(start, end int)
func (c *QueryCursor) SetContainingPointRange(start, end Point)
func (c *QueryCursor) Matches(ctx context.Context, q *Query, n Node, src []byte) iter.Seq[QueryMatch]
func (c *QueryCursor) Captures(ctx context.Context, q *Query, n Node, src []byte) iter.Seq2[QueryMatch, int]
// QueryMatch is one match of a pattern. Captures is valid until the sequence
// moves on, because the cursor reuses it, as the Rust binding does.
type QueryMatch struct {
PatternIndex int
Captures []QueryCapture
// unexported: the cursor and the id of the match
}
// Remove removes the match from its cursor, so that the sequence gives no
// more captures of it. It is QueryMatch::remove of the Rust binding (D89).
func (m QueryMatch) Remove()
// QueryCapture is one captured node. Index is its place in CaptureNames.
type QueryCapture struct {
Node Node
Index int
}
// LookaheadIterator lists the symbols that the parser can accept in a state.
// It belongs to one goroutine at a time.
type LookaheadIterator struct{ /* unexported */ }
func (it *LookaheadIterator) Language() *Language
func (it *LookaheadIterator) Symbols() iter.Seq[Symbol]
func (it *LookaheadIterator) Names() iter.Seq[string]
func (it *LookaheadIterator) Reset(l *Language, s StateID) bool
func (it *LookaheadIterator) ResetState(s StateID) bool
// Error is an error of the runtime.
type Error string
func (e Error) Error() string
const (
ErrIncompatibleLanguage Error = "incompatible language version"
ErrInvalidRanges Error = "invalid included ranges"
ErrNoLanguage Error = "parser has no language"
ErrLanguageMismatch Error = "old tree has another language"
ErrInvalidInput Error = "invalid input"
)ErrNoLanguage, ErrLanguageMismatch and ErrInvalidInput came with the
port of the parser. The C parser returns NULL for a parse with no language,
with an old tree of another language, or with no input, and Go names each
case. A parse whose context ends keeps its state, as a parse whose progress
callback stops does in C, and the next parse goes on from it.
Parse returns the error of its context when the context ends, wrapped with
%w. ParseCustomEncoding reads a text in an encoding that the runtime does
not know. The parser calls decode for each character, with the text from
the character to the end of the chunk. decode returns the code point and
the number of bytes of the character, and it returns -1 as the code point
when the text is not valid. A nil decode gives ErrInvalidInput.
(*Query).Copy gives a query that DisablePattern and DisableCapture
change apart from the first one. EditPoint and EditRange change a point
and its byte offset, or a range, so that they stay at the same place in the
text after an edit, with no tree and no node. D119 adds these four.
Captures gives each match with the index of the capture in the match,
in the order of the text, which is the order that highlighting needs. The
highlighter of upstream removes the match of an injection with
QueryMatch::remove while it reads the captures. The package inject calls
Remove in the same place (D89). Remove is valid only while the sequence
that gave the match runs, because the next run of the cursor gives the same
ids to other matches.
When the context of Matches or Captures ends, the cursor halts, as a
query cursor halts in C when its progress callback returns true. The cursor
drops the matches in progress. Captures still gives the captures of the
matches that finished before, and then its sequence ends.
The example calls these C functions. Each row names the Go form.
| C | Go |
|---|---|
ts_parser_new, ts_parser_delete |
NewParser. The garbage collector frees it |
ts_parser_set_language |
(*Parser).SetLanguage, which returns ErrIncompatibleLanguage |
ts_parser_parse_string |
(*Parser).Parse(ctx, src, old) |
ts_tree_root_node |
(*Tree).RootNode |
ts_tree_edit |
(*Tree).Edit |
ts_tree_get_changed_ranges |
(*Tree).ChangedRanges, which returns a slice |
ts_tree_delete |
(*Tree).Close, which is optional. The garbage collector frees a tree that is not closed (D91) |
ts_node_string |
Node.String |
ts_node_type, ts_node_grammar_symbol |
Node.Kind, Node.GrammarID |
ts_node_start_byte, ts_node_end_byte, ts_node_start_point |
Node.StartByte, Node.EndByte, Node.StartPoint, as int |
ts_node_parent, ts_node_prev_sibling |
Node.Parent, Node.PrevSibling, each with a bool |
ts_node_child_count, ts_node_child |
Node.ChildCount, Node.Child, or Node.Children as an iter.Seq |
ts_node_descendant_for_byte_range |
Node.DescendantForByteRange |
ts_node_named_descendant_for_byte_range |
Node.NamedDescendantForByteRange |
ts_node_is_error, ts_node_is_missing, ts_node_has_error |
Node.IsError, Node.IsMissing, Node.HasError |
ts_node_parse_state, ts_node_next_parse_state |
Node.ParseState, Node.NextParseState |
ts_node_eq |
Node.Equal. == also compares the position (D68) |
ts_tree_cursor_new, ts_tree_cursor_delete |
Node.Walk |
ts_tree_cursor_goto_first_child, ts_tree_cursor_goto_next_sibling |
(*TreeCursor).GotoFirstChild, GotoNextSibling |
ts_tree_cursor_current_node, ts_tree_cursor_current_field_name |
(*TreeCursor).Node, FieldName |
ts_query_new |
NewQuery, which returns a *QueryError |
ts_query_pattern_count, ts_query_capture_count |
(*Query).PatternCount, len(q.CaptureNames()) |
ts_query_capture_name_for_id |
q.CaptureNames()[i] |
ts_query_predicates_for_pattern, ts_query_string_value_for_id |
evaluated inside Matches and Captures (D27). GeneralPredicates gives the rest |
ts_query_cursor_new, ts_query_cursor_delete |
NewQueryCursor |
ts_query_cursor_exec, ts_query_cursor_next_capture |
(*QueryCursor).Captures |
ts_query_cursor_remove_match |
QueryMatch.Remove. Captures also calls it for a match whose predicates fail |
ts_query_cursor_set_byte_range |
(*QueryCursor).SetByteRange |
ts_language_next_state, ts_language_symbol_type |
(*Language).NextState, SymbolType |
ts_lookahead_iterator_new, _next, _current_symbol_name |
(*Language).LookaheadIterator, Names |
ts_language_name, ts_language_abi_version |
(*Language).Name, ABIVersion |
The example does not call these C functions. D119 gives them a Go form, so that the tests of upstream that use them are ported:
| C | Go |
|---|---|
ts_parser_parse with TSInputEncodingCustom and a decode function |
(*Parser).ParseCustomEncoding(ctx, in, decode, old), which is parse_custom_encoding of the Rust binding |
ts_query_copy |
(*Query).Copy, which is deep_clone of the Rust binding |
ts_point_edit |
InputEdit.EditPoint(&p, &byteOffset), which is edit_point of the Rust binding |
ts_range_edit |
InputEdit.EditRange(&r), which is edit_range of the Rust binding |
These upstream functions have no Go form:
- The functions that free memory or count references: each
_deleteother thants_tree_delete,ts_language_copyandts_set_allocator(D24, D91). - The WebAssembly functions:
ts_language_is_wasm,ts_parser_set_wasm_store,ts_parser_take_wasm_storeand eachts_wasm_store_function (D1). ts_node_is_null, because a lookup returns aboolwith the node.ts_parser_parse_string_encodingandts_parser_parse_with_options, whose jobsParseInput,ParseCustomEncodingand the context do.ts_language_is_parseable, which a language that Go generated always is.
D28 allows an API that only adds, in files that port no upstream file, with a decision for each. The first one is for completion (D57):
// StatesAt parses src up to offset, and returns the parse state of each
// stack version at offset, before the parser recovers from an error. A
// consumer lists the symbols that can come next with the lookahead iterator
// of each state.
func (p *Parser) StatesAt(ctx context.Context, src []byte, offset int, old *Tree) ([]StateID, error)states_at.go holds it. Each stack version stops before the first token
that ends after the offset, or before the end of the text. The lexer reads
all of src, so the tokens before the offset are the tokens of a parse of
all of it. When the offset is inside a word, the states are the states before
the word. A word that ends at the offset is handled, so to complete a word, a
consumer gives the offset of its start. D70 records these rules.
The test module measures it. For SELECT * FROM , the SQL grammar of the C
example gives state 9144, which accepts 14 symbols, such as identifier,
object_reference and subquery. The state after FROM in
SELECT id FROM u is the same state. The tree has an error there, and state
0 accepts 408 symbols. On the corpora of the 17 fixture grammars, a state that
StatesAt gives accepts the next token at each of 13,664 offsets.
The second one is the predicates #lua-match? and #not-lua-match?, which
match the text of a capture with a Lua pattern, as Neovim does (D55). They
wait until the tier 1 grammars need them (D69), with the rest of the Neovim
dialect that NEOVIM.md lists.
The third one is NodeType, FieldInfo and NodeKind, the form of
node-types.json in Go, which the NodeTypes function of a grammar package
returns. node_type.go holds them. Their names follow NodeInfoJSON,
FieldInfoJSON and NodeTypeJSON of crates/generate/src/node_types.rs,
and Kind is the key type, as Node.Kind is the type of a node. D76
records them.
The fourth one is inject.WithReplacer, which replaces the text of nodes
before a layer is parsed (D101). The fifth one is inject.Layer.Text and
inject.Layer.StatesAt, which give the text that a layer parses and the
parse states of the layer at an offset (D111). "The package inject" below
gives them.
The package inject finds the injections of a text and parses each layer
(D27). An injection is a range of the text that another grammar parses, such
as a <script> element of HTML, or a SQL statement in the input of usql
(D13). A layer is one tree of one language over the ranges of its
injections. It ports the part of crates/highlight that does this, without
the highlighting. Upstream has no public API for it, so D72 sets this one:
package inject
// Config is the injection part of HighlightConfiguration: a language, its
// name and its injection query.
type Config struct{ /* unexported */ }
func NewConfig(language *transit.Language, name, injectionQuery string) (*Config, error)
func (c *Config) Name() string
func (c *Config) Language() *transit.Language
// Layer is one tree of one language over the ranges of its injections.
type Layer struct {
Name string // the language name that the query gave, such as "js"
Config *Config
Tree *transit.Tree
Ranges []transit.Range
Depth int // 0 for the root layer
Text []byte // the text that the layer parses, after the replacements
}
// StatesAt returns the parse states of the layer at offset, as
// transit.Parser.StatesAt gives them. It is an API that upstream does not
// have.
func (l Layer) StatesAt(ctx context.Context, p *transit.Parser, offset int) ([]transit.StateID, error)
// Layers parses src with c, finds every injection, and parses the layer of
// each. lookup gives the configuration of an injected language name.
func (c *Config) Layers(ctx context.Context, p *transit.Parser, src []byte,
lookup func(name string) (*Config, bool), opts ...Option) ([]Layer, error)
// Option changes what Layers does. It is an API that upstream does not have.
type Option func(*options)
// Replacer gives the text that takes the place of the node n of a parent
// layer in the text of the injected layer of the language name, and true,
// or false for a node whose text stays. The text has the length of the node.
type Replacer func(name string, n transit.Node, src []byte) ([]byte, bool)
// WithReplacer makes Layers ask r about the nodes of the parent layer inside
// the ranges of an injected layer, before it parses that layer.
func WithReplacer(r Replacer) Optioninject takes UTF-8 only, and a Rust oracle in the test module compares its
layers with the layers of crates/highlight (D71). The layers come sorted by
the start of their first range, then by depth, so the root layer comes first.
D72 holds the rest of the rules: the skips, the errors and the faults of
upstream that the port keeps.
usql finds the layer at the cursor, and asks it for the node there:
layers, err := usqlConfig.Layers(ctx, parser, src, lookup)
for _, l := range slices.Backward(layers) {
if holds(l.Ranges, cursor) {
node, _ := l.Tree.RootNode().DescendantForByteRange(cursor, cursor)
// ...
}
}holds is code of usql that reports whether one of the ranges holds the
offset.
WithReplacer is an API that upstream does not have (D28). replace.go
holds it, and the options of Layers. A variable of the input of usql, such
as :id, is an error in a SQL grammar. So usql replaces each variable with a
placeholder of the same length before the SQL layer is parsed, and every
offset of the layer stays the offset of the input (D101). Layers walks the
nodes of the parent tree inside the ranges of the layer, from the root down,
and asks the replacer about each one. The replacer does not see the children
of a node that it replaces. The root layer has no parent, so its text stays.
The queries match the text of src, and only the parse of the layer reads
the new text. A text whose length is not the length of its node is an error
of Layers.
The test module shows it with the grammar usql and the SQL grammar of
DerekStride. select * from :tbl where id = :id gives a SQL layer with an
error, and with this replacer the layer has no error:
func placeholder(_ string, n transit.Node, src []byte) ([]byte, bool) {
if n.Kind() != "variable" {
return nil, false
}
name, _ := n.ChildByFieldName("name")
text := src[n.StartByte():n.EndByte()]
id := "_" + string(src[name.StartByte():name.EndByte()])
switch {
case bytes.HasPrefix(text, []byte(":{?")): // TRUE, then spaces
return append([]byte("TRUE"), bytes.Repeat([]byte(" "), len(text)-4)...), true
case bytes.HasPrefix(text, []byte(":'")): // a string
return []byte("'" + id + "'"), true
case bytes.HasPrefix(text, []byte(`:"`)): // a quoted identifier
return []byte(`"` + id + `"`), true
}
return []byte(id), true // an identifier
}
layers, err := usqlConfig.Layers(ctx, parser, src, lookup, inject.WithReplacer(placeholder))psql gives TRUE or FALSE for :{?name}, so its placeholder is TRUE
with spaces after it. The name of a variable needs at least one character,
so each placeholder fits.
Layer.Text and Layer.StatesAt are an API that upstream does not have
(D28, D111). Text is the text that the layer parses: the text of
Layers, with the replacements of WithReplacer for that layer. It has the
length and the offsets of the text of Layers. When nothing is replaced,
it is the text of Layers and not a copy.
To complete in a layer, usql runs StatesAt on Text with the language of
the layer. A layer whose ranges do not cover the whole text also needs its
ranges on the parser. Without them, the parser reads the text of the other
layers too. For example, the quote of \echo 'it before a statement starts
a string in SQL that holds the statement. Layer.StatesAt sets the language
and the included ranges of the layer on the parser, and it gives the tree of
the layer as the old tree. It clears the included ranges before it returns,
as Layers does:
for _, l := range slices.Backward(layers) {
if holds(l.Ranges, cursor) {
states, err := l.StatesAt(ctx, parser, cursor)
// ...
}
}The test module parses select * from :tbl where id = :id; after a meta
command, with usql.LanguageFor and the options of PostgreSQL, and with the
package postgres for the statement. At the start of :tbl, the states
accept table_ref and relation_expr. After where , they accept a_expr
and columnref. At both offsets, the states are the states of a parse of
select * from _tbl where id = _id; alone.
The Go backend writes one package for each grammar (D26, D31). For the SQL
grammar, the package sql exports:
package sql
// Language returns the tables of the SQL grammar.
func Language() *transit.Language
// A constant for each symbol and each field.
const (
SymKeywordSelect transit.Symbol = 2
SymKeywordFrom transit.Symbol = 3
// ...
SymRelation transit.Symbol = 431
)
const (
FieldAlias transit.FieldID = 1
FieldName transit.FieldID = 25
// ...
)
// Queries holds queries/ of the grammar: its own queries, and in
// queries/<grammar>/ the queries of another grammar that its
// tree-sitter.json lists (D84).
var Queries embed.FS
// Keywords returns the names of the keywords of the grammar: the symbols
// that the keyword lex table accepts, and the reserved words (D77).
func Keywords() []string
// NodeTypes returns the node types of node-types.json: the fields and the
// children of each node, and the supertypes.
func NodeTypes() []transit.NodeTypeThe numbers in this sketch are examples. The generator writes the real ones.
transit.NodeType is a type of the runtime that NodeTypes needs, and it is
in the list above.
The name of each constant comes from the C name that render.rs gives the
symbol or the field. The prefix sym_ becomes Sym, anon_sym_ becomes
AnonSym, aux_sym_ becomes AuxSym, alias_sym_ becomes AliasSym,
anon_alias_sym_ becomes AnonAliasSym, field_ becomes Field, and
ts_builtin_sym_end becomes BuiltinSymEnd. Each part of the rest between
two underscores starts with an upper case letter, and the rest of the part
stays as it is. So sym_keyword_select becomes SymKeywordSelect, and
anon_sym_LBRACE becomes AnonSymLBRACE. When two C names give the same
Go name, such as sym_value and sym__value, the later one gets the number
2 at its end, as render.rs does for two C names. The comment of each
constant is the name of the symbol in the grammar. D77 holds these rules, and
the rule that the grammar go gets the package golang.
A grammar that has an external scanner gets its scanner from the function
newScanner of scanner.go, which a person ports (GRAMMAR.md). It
returns a value that implements the scanner of the tables, so Language
wires Scan, Serialize and Deserialize. The runtime gives Scan the
valid external tokens of each state from the tables.
The package github.com/xo/transit/grammars/usql holds the grammar of the
input of usql, which xo writes (D13, D42). It is one language for every SQL
dialect, and its scanner takes the options of the dialect (D108). A person
writes this API in options.go, beside the generated files:
package usql
// Options are the options of the syntax of a SQL dialect that the external
// scanner reads. Each field but BeginEndBlocks and Batches is a flag of the
// type Syntax of dbmeta, with the same name.
type Options struct {
DollarQuotes bool // $tag$ ... $tag$ and $$ ... $$ are a string
BlockComments bool // /* ... */ is a comment
SlashComments bool // // starts a comment
HashComments bool // # starts a comment
Backticks bool // `...` is a quoted identifier
BeginEndBlocks bool // a stored program with BEGIN ... END is one statement
Batches bool // a batch of CQL, BEGIN BATCH ... APPLY BATCH, is one statement
}
// LanguageFor returns the language with an external scanner that reads
// opts. Every set of options uses the tables of Language.
func LanguageFor(opts Options) *transit.LanguageLanguage gives the language with dollar quotes and block comments, the
options of PostgreSQL, because the client lexer of PostgreSQL is the
reference of the grammar (D101). LanguageFor gives Language for these
options, and for other options it builds a language once and keeps it. A
new option is a new field and a new flag of the scanner, and no new grammar
(D108). The package reads the tables of Language with abi.TablesOf, a
function of the internal package abi that the root package sets. So the
root package exports nothing new for it.
BeginEndBlocks is the option of D112, and it is off by default. With it
on, a statement that starts with CREATE ... PROCEDURE, FUNCTION,
TRIGGER or EVENT keeps its BEGIN ... END body, and a ; inside the
body does not end the statement. docs/USQL.md says which words the
scanner reads.
Batches keeps a batch of CQL in one statement, and it is off by default.
With it on, a statement that starts with BEGIN BATCH, BEGIN UNLOGGED BATCH or BEGIN COUNTER BATCH runs to the first ; after APPLY BATCH.
The package github.com/xo/transit/styles holds the styles of transit (D65).
A style gives a color and a font to each capture name of a highlight query.
The package is a module of its own, in styles/ (D99). It imports only the
standard library, and it does not require the root module. It exports:
package styles
// Style is a style: an entry for each capture name, and the default entry.
type Style struct {
Name string // such as "monokai"
Source string // where the colors come from, and the license
Default Entry // the entry of text that no capture names
Captures map[string]Entry // the entry of each capture name
}
// Get returns the bundled style of a name. Names returns the names of the
// bundled styles, sorted.
func Get(name string) (*Style, bool)
func Names() []string
// Parse reads a style file of the consumer, in the form of the bundled ones.
func Parse(data []byte) (*Style, error)
// Lookup tries the whole capture name, then each shorter prefix by dots,
// then the default entry without its background. Background returns the
// background of the default entry, which is the background of the style.
func (s *Style) Lookup(capture string) Entry
func (s *Style) Background() Color
// Entry is a whole entry: a field that it leaves out does not come from
// another entry.
type Entry struct {
Fg, Bg Color
Bold, Italic, Underline bool
}
func ParseEntry(s string) (Entry, error)
func (e Entry) String() string
// Color keeps the kind that the file writes.
type Color struct {
Kind ColorKind // ColorNone, ColorANSI, Color256 or ColorRGB
Value uint32 // 0 to 15, 0 to 255, or 0xrrggbb
}
func (c Color) IsSet() bool
func (c Color) String() string
func (c Color) RGB() (r, g, b uint8)
func (c Color) To256() Color
func (c Color) To16() Color
type Error string
const ErrSyntax Error = "invalid style"A style is one JSON file with the keys name, source, default and
captures, and //go:embed holds the files. styles/chroma/ holds the 74
styles of chroma v2.27.0, which the command test/cmd/chromastyles
converts. styles/themes/ holds the styles that a person makes by hand. A
value is a style string. Its words are bold, italic, underline, their
no forms, a color, and bg: before the background color. A color is
#rgb or #rrggbb, a name of chroma for one of the 16 ANSI colors, such as
#ansired, or #ansi and the number of one of the 256 colors, such as
#ansi208.
The package does not look at the terminal. RGB gives an ANSI color and one
of the 256 colors in the default palette of xterm. To256 reduces an RGB
color to the nearest of the colors 16 to 255, and To16 reduces a color to
the nearest ANSI color. A line editor keeps the background of the terminal,
so Lookup gives no background for text that no capture names. An entry of
a capture has a background only when the style sets one for it, such as for
an error.
rline draws a capture of the highlight query like this:
style, _ := styles.Get("monokai")
e := style.Lookup(capture.Name)
fg := e.Fg.To256() // for a terminal of 256 colorsstyles/captures.txt lists the capture names of the highlight queries of
the grammar set. The test TestCaptures makes sure that each name reaches an
entry of each bundled style through its prefixes, without the default entry.
rline highlights with Parse, Edit, NewQuery once, and Captures with a
byte range for the rows that it shows. For a language with injections, it
takes the layers from inject and runs its highlight query on each tree.
It takes the entry of each capture from styles. usql takes the layers of
its input from inject. On the layer at the cursor, it completes with
DescendantForByteRange, Parent, FieldNameForChild, StatesAt and
LookaheadIterator. It decides what kind of name goes at the cursor from
the symbols that come back and from its own queries (D6).
RLINE.md and USQL.md hold the details.
The sample programs of D53 make these calls. _example/highlight highlights
the input of usql as rline will, and _example/complete completes at a
cursor as usql will. They live in the module github.com/xo/transit/_example,
so the root module still requires no other module.