tree-sitter-validatetest 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,83 @@
1
+ # tree-sitter-validatetest
2
+
3
+ GStreamer ValidateTest grammar for [tree-sitter](https://tree-sitter.github.io/).
4
+
5
+ This grammar parses [`.validatetest` files](https://gstreamer.freedesktop.org/documentation/gst-devtools/gst-validate-test-file.html) used for testing GStreamer pipelines. These files are executed by `gst-validate-1.0`, `ges-launch-1.0`, or any GStreamer tool that supports the validate test format.
6
+
7
+ ## Features
8
+
9
+ - Full support for GstStructure serialization format
10
+ - Comments (`# ...`)
11
+ - Variables (`$(variable_name)`)
12
+ - Expressions (`expr(...)`)
13
+ - Type casts (`(type)value`)
14
+ - Property paths (`element.pad::property`)
15
+ - Arrays with nested structures (`[...]`)
16
+ - GstValueArray (`<...>`)
17
+ - Value blocks (`{...}`)
18
+
19
+ ## Installation
20
+
21
+ ### Neovim (with nvim-treesitter)
22
+
23
+ Add the following to your neovim configuration (e.g., `lua/plugins/validatetest.lua` for LazyVim):
24
+
25
+ ```lua
26
+ -- Register the filetype
27
+ vim.filetype.add({
28
+ extension = {
29
+ validatetest = "validatetest",
30
+ },
31
+ })
32
+
33
+ -- Register custom parser with nvim-treesitter
34
+ vim.api.nvim_create_autocmd("User", {
35
+ pattern = "TSUpdate",
36
+ callback = function()
37
+ require("nvim-treesitter.parsers").validatetest = {
38
+ install_info = {
39
+ url = "https://github.com/thiblahute/tree-sitter-validatetest",
40
+ files = { "src/parser.c" },
41
+ branch = "main",
42
+ },
43
+ }
44
+ end,
45
+ })
46
+
47
+ return {}
48
+ ```
49
+
50
+ Then restart neovim and run `:TSInstall validatetest`.
51
+
52
+ ### Rust
53
+
54
+ ```toml
55
+ [dependencies]
56
+ tree-sitter-validatetest = "0.1"
57
+ ```
58
+
59
+ ### Node.js
60
+
61
+ ```bash
62
+ npm install tree-sitter-validatetest
63
+ ```
64
+
65
+ ## Development
66
+
67
+ ```bash
68
+ # Install dependencies
69
+ npm install
70
+
71
+ # Generate the parser
72
+ npx tree-sitter generate
73
+
74
+ # Run tests
75
+ npx tree-sitter test
76
+
77
+ # Parse a file
78
+ npx tree-sitter parse path/to/file.validatetest
79
+ ```
80
+
81
+ ## License
82
+
83
+ MIT
package/binding.gyp ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "targets": [
3
+ {
4
+ "target_name": "tree_sitter_validatetest_binding",
5
+ "dependencies": [
6
+ "<!(node -p \"require('node-addon-api').targets\"):node_addon_api_except",
7
+ ],
8
+ "include_dirs": [
9
+ "src",
10
+ ],
11
+ "sources": [
12
+ "bindings/node/binding.cc",
13
+ "src/parser.c",
14
+ ],
15
+ "conditions": [
16
+ ["OS!='win'", {
17
+ "cflags_c": ["-std=c11"],
18
+ "cflags_cc": ["-std=c++17"]
19
+ }]
20
+ ],
21
+ "xcode_settings": {
22
+ "CLANG_CXX_LANGUAGE_STANDARD": "c++17",
23
+ "GCC_C_LANGUAGE_STANDARD": "c11"
24
+ }
25
+ }
26
+ ]
27
+ }
@@ -0,0 +1,20 @@
1
+ #include <napi.h>
2
+
3
+ typedef struct TSLanguage TSLanguage;
4
+
5
+ extern "C" TSLanguage *tree_sitter_validatetest();
6
+
7
+ // "tree-sitter", "currentABIVersion" heuristic
8
+ static Napi::Number CurrentABIVersion(const Napi::CallbackInfo& info) {
9
+ return Napi::Number::New(info.Env(), NAPI_VERSION);
10
+ }
11
+
12
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
13
+ exports["name"] = Napi::String::New(env, "validatetest");
14
+ auto language = Napi::External<TSLanguage>::New(env, tree_sitter_validatetest());
15
+ exports["language"] = language;
16
+ exports["currentABIVersion"] = Napi::Function::New(env, CurrentABIVersion);
17
+ return exports;
18
+ }
19
+
20
+ NODE_API_MODULE(tree_sitter_validatetest_binding, Init)
@@ -0,0 +1,11 @@
1
+ const root = require("path").join(__dirname, "..", "..");
2
+
3
+ module.exports =
4
+ typeof process.versions.bun === "string"
5
+ // Support `bun build --compile` by being statically analyzable enough to find the .node file at build-time
6
+ ? require(`../../prebuilds/${process.platform}-${process.arch}/tree-sitter-validatetest.node`)
7
+ : require("node-gyp-build")(root);
8
+
9
+ try {
10
+ module.exports.nodeTypeInfo = require("../../src/node-types.json");
11
+ } catch (_) {}
package/grammar.js ADDED
@@ -0,0 +1,259 @@
1
+ /**
2
+ * @file GStreamer ValidateTest grammar for tree-sitter
3
+ * @author Thibault Saunier
4
+ * @license MIT
5
+ *
6
+ * Grammar for .validatetest files used by gst-validate-1.0, ges-launch-1.0, etc.
7
+ * Based on GstStructure serialization format with additional constructs
8
+ * for variables, expressions, and comments.
9
+ */
10
+
11
+ /// <reference types="tree-sitter-cli/dsl" />
12
+ // @ts-check
13
+
14
+ module.exports = grammar({
15
+ name: "validatetest",
16
+
17
+ extras: ($) => [/\s/, $.line_continuation, $.comment],
18
+
19
+ conflicts: ($) => [
20
+ [$.array_structure],
21
+ [$.structure],
22
+ [$.structure, $.field_value],
23
+ [$.structure_name, $.array_value],
24
+ [$.structure_name, $.value],
25
+ [$.field_list],
26
+ ],
27
+
28
+ rules: {
29
+ // A file is a sequence of structures (comments handled by extras)
30
+ source_file: ($) => repeat($.structure),
31
+
32
+ // Comments start with # and go to end of line
33
+ comment: ($) => seq("#", /.*/),
34
+
35
+ // Line continuation with backslash
36
+ line_continuation: ($) => seq("\\", /\r?\n/),
37
+
38
+ // A structure is: name, field=value, field=value, ...
39
+ // Can end with semicolon, newline, or EOF
40
+ structure: ($) =>
41
+ seq($.structure_name, optional(seq(",", $.field_list)), optional(";")),
42
+
43
+ // Structure name (action type) - can be identifier or variable
44
+ structure_name: ($) => choice($.identifier, $.variable),
45
+
46
+ // Comma-separated list of fields (allows trailing comma)
47
+ field_list: ($) => seq(sep1($.field, ","), optional(",")),
48
+
49
+ // A field is: name = value
50
+ field: ($) =>
51
+ seq(field("name", $.field_name), "=", field("value", $.field_value)),
52
+
53
+ // Field name can be a simple identifier or a property path
54
+ field_name: ($) => choice($.property_path, $.identifier),
55
+
56
+ // Property path: element.pad::property or element::property or element::parent::parent::property
57
+ property_path: ($) =>
58
+ seq(
59
+ $.identifier,
60
+ optional(seq(".", $.identifier)),
61
+ repeat1(seq("::", $.identifier)),
62
+ ),
63
+
64
+ // Field value
65
+ field_value: ($) =>
66
+ choice(
67
+ $.typed_value,
68
+ $.value,
69
+ $.array,
70
+ $.angle_bracket_array,
71
+ $.nested_structure_block,
72
+ ),
73
+
74
+ // Typed value: (type)value or (type)[array] or (type)<array>
75
+ typed_value: ($) =>
76
+ seq("(", field("type", $.type_name), ")", field("value", choice($.value, $.array, $.angle_bracket_array))),
77
+
78
+ // Type name for casts
79
+ type_name: ($) => /[a-zA-Z_][a-zA-Z0-9_]*/,
80
+
81
+ // A value can be many things
82
+ // Order matters: more specific patterns first, unquoted_string last as fallback
83
+ value: ($) =>
84
+ choice(
85
+ $.string,
86
+ $.hex_number,
87
+ $.fraction,
88
+ $.number,
89
+ $.boolean,
90
+ $.variable,
91
+ $.expression,
92
+ prec(2, $.flags),
93
+ prec(2, $.namespaced_identifier),
94
+ $.cli_argument,
95
+ $.unquoted_string,
96
+ ),
97
+
98
+ // CLI arguments like -t, --videosink, +test-clip (used in args blocks)
99
+ cli_argument: ($) => /[-+][a-zA-Z][-a-zA-Z0-9_]*|--[a-zA-Z][-a-zA-Z0-9_]*/,
100
+
101
+ // Double-quoted string with escapes
102
+ // Expression and variable are matched first, then raw text
103
+ string: ($) =>
104
+ seq(
105
+ '"',
106
+ optional($.string_inner),
107
+ '"',
108
+ ),
109
+
110
+ // Inner content of a string (used for injections)
111
+ string_inner: ($) =>
112
+ repeat1(
113
+ choice(
114
+ $.escape_sequence,
115
+ $.expression,
116
+ $.variable,
117
+ $.string_content,
118
+ "$", // Lone $ that's not part of $(...)
119
+ ),
120
+ ),
121
+
122
+ // String content that's not a special sequence
123
+ // Excludes: " (end), \ (escape), $ (variable start), e (expr start)
124
+ string_content: ($) => /[^"\\$e]+|e/,
125
+
126
+ // Escape sequences
127
+ escape_sequence: ($) => /\\./,
128
+
129
+ // Variable: $(name) or $(name.subfield)
130
+ variable: ($) => seq("$(", /[a-zA-Z_][a-zA-Z0-9_]*(\.[a-zA-Z0-9_]+)*/, ")"),
131
+
132
+ // Expression: expr(...)
133
+ // Handle nested parentheses by matching balanced content
134
+ expression: ($) => token(seq(
135
+ "expr(",
136
+ repeat(choice(
137
+ /[^()]+/, // Non-paren characters
138
+ seq("(", /[^()]*/, ")"), // One level of nested parens
139
+ )),
140
+ ")"
141
+ )),
142
+
143
+ // Integer or float
144
+ number: ($) => {
145
+ const integer = /[+-]?[0-9]+/;
146
+ const float = /[+-]?[0-9]+\.[0-9]*/;
147
+ return choice(float, integer);
148
+ },
149
+
150
+ // Fraction: num/denom (e.g., 30/1 for framerate)
151
+ fraction: ($) => /[0-9]+\/[0-9]+/,
152
+
153
+ // Hexadecimal number
154
+ hex_number: ($) => /0x[0-9a-fA-F]+/,
155
+
156
+ // Boolean (true/false, yes/no, t/f - case insensitive)
157
+ // Note: 1/0 are parsed as numbers unless explicitly cast with (bool)
158
+ boolean: ($) => token(choice(
159
+ /[tT][rR][uU][eE]/,
160
+ /[fF][aA][lL][sS][eE]/,
161
+ /[yY][eE][sS]/,
162
+ /[nN][oO]/,
163
+ /[tT]/,
164
+ /[fF]/,
165
+ )),
166
+
167
+ // Flags: flag1+flag2+flag3
168
+ // Use token to match the whole flags expression as a single token
169
+ flags: ($) => token(seq(
170
+ /[a-zA-Z_][a-zA-Z0-9_-]*/,
171
+ repeat1(seq("+", /[a-zA-Z_][a-zA-Z0-9_-]*/))
172
+ )),
173
+
174
+ // Namespaced identifier: namespace::name
175
+ // Use token to match the whole namespaced identifier as a single token
176
+ namespaced_identifier: ($) => token(seq(
177
+ /[a-zA-Z_][a-zA-Z0-9_-]*/,
178
+ "::",
179
+ /[a-zA-Z_][a-zA-Z0-9_-]*/
180
+ )),
181
+
182
+ // Unquoted string (bare identifier or value)
183
+ // Using alias to make this distinct from identifier at parse level
184
+ unquoted_string: ($) => alias(/[a-zA-Z_][a-zA-Z0-9_\-.:/]*/, "unquoted_string"),
185
+
186
+ // Basic identifier (allows / for caps media types like video/x-raw)
187
+ identifier: ($) => /[a-zA-Z_][a-zA-Z0-9_\-/]*/,
188
+
189
+ // Array: [ item, item, ... ] or [ structure, structure, ... ]
190
+ // Allows trailing commas
191
+ // Uses repeat instead of sep1 because array_structure contains internal commas
192
+ array: ($) =>
193
+ seq(
194
+ "[",
195
+ repeat($.array_element),
196
+ "]",
197
+ ),
198
+
199
+ // Array element: either a structure with fields or a simple value
200
+ // Uses array_value instead of field_value to avoid ambiguity with bare identifiers
201
+ array_element: ($) =>
202
+ choice(
203
+ seq($.array_structure, optional(",")),
204
+ seq($.array_value, optional(",")),
205
+ ),
206
+
207
+ // Value types allowed directly in arrays (excludes bare identifiers to avoid ambiguity)
208
+ array_value: ($) =>
209
+ choice(
210
+ $.typed_value,
211
+ $.string,
212
+ $.hex_number,
213
+ $.fraction,
214
+ $.number,
215
+ $.boolean,
216
+ $.variable,
217
+ $.expression,
218
+ $.flags,
219
+ $.namespaced_identifier,
220
+ $.array,
221
+ $.angle_bracket_array,
222
+ $.nested_structure_block,
223
+ ),
224
+
225
+ // GstValueArray: < item, item, ... > (angle bracket array, allows trailing comma)
226
+ angle_bracket_array: ($) =>
227
+ seq(
228
+ "<",
229
+ optional(seq(sep1($.field_value, ","), optional(","))),
230
+ ">",
231
+ ),
232
+
233
+ // Structure inside an array (without the trailing semicolon rules)
234
+ // Fields are optional - [video/x-raw] is a valid structure with no fields
235
+ array_structure: ($) =>
236
+ seq($.structure_name, optional(seq(",", $.field_list))),
237
+
238
+ // Nested structure block: { structure, structure, ... } or { "string", "string", ... }
239
+ // Note: strings, arrays, and other values are captured via field_value
240
+ // Allows trailing commas (comments are handled automatically via extras)
241
+ nested_structure_block: ($) =>
242
+ seq(
243
+ "{",
244
+ repeat(seq(choice($.structure, $.field_value), optional(","))),
245
+ "}",
246
+ ),
247
+ },
248
+ });
249
+
250
+ /**
251
+ * Creates a rule to match one or more of the rule separated by the separator.
252
+ *
253
+ * @param {Rule} rule
254
+ * @param {Rule} sep
255
+ * @returns {SeqRule}
256
+ */
257
+ function sep1(rule, sep) {
258
+ return seq(rule, repeat(seq(sep, rule)));
259
+ }
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "tree-sitter-validatetest",
3
+ "version": "0.1.0",
4
+ "description": "GStreamer ValidateTest grammar for tree-sitter",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://github.com/thiblahute/tree-sitter-validatetest"
8
+ },
9
+ "license": "MIT",
10
+ "author": "Thibault Saunier",
11
+ "main": "bindings/node",
12
+ "types": "bindings/node",
13
+ "keywords": [
14
+ "gstreamer",
15
+ "validate",
16
+ "validatetest",
17
+ "tree-sitter",
18
+ "parser"
19
+ ],
20
+ "files": [
21
+ "grammar.js",
22
+ "binding.gyp",
23
+ "prebuilds/**",
24
+ "bindings/node/*",
25
+ "queries/*",
26
+ "src/**",
27
+ "*.wasm"
28
+ ],
29
+ "dependencies": {
30
+ "node-addon-api": "^8.3.0",
31
+ "node-gyp-build": "^4.8.4"
32
+ },
33
+ "devDependencies": {
34
+ "tree-sitter-cli": "^0.24.0",
35
+ "prebuildify": "^6.0.1"
36
+ },
37
+ "peerDependencies": {
38
+ "tree-sitter": "^0.21.1"
39
+ },
40
+ "peerDependenciesMeta": {
41
+ "tree-sitter": {
42
+ "optional": true
43
+ }
44
+ },
45
+ "scripts": {
46
+ "build": "tree-sitter generate && node-gyp build",
47
+ "build-wasm": "tree-sitter build --wasm",
48
+ "install": "node-gyp-build",
49
+ "prestart": "tree-sitter build --wasm",
50
+ "start": "tree-sitter playground",
51
+ "test": "tree-sitter test",
52
+ "prebuildify": "prebuildify --napi --strip"
53
+ },
54
+ "tree-sitter": [
55
+ {
56
+ "scope": "source.validatetest",
57
+ "injection-regex": "^validatetest$",
58
+ "file-types": [
59
+ "validatetest"
60
+ ],
61
+ "highlights": "queries/highlights.scm",
62
+ "injections": "queries/injections.scm"
63
+ }
64
+ ]
65
+ }
@@ -0,0 +1,85 @@
1
+ ; Highlights for GStreamer ValidateTest files
2
+
3
+ ; Comments
4
+ (comment) @comment
5
+
6
+ ; Structure/action names (function calls - actions are "called" with field arguments)
7
+ (structure_name
8
+ (identifier) @function.call)
9
+
10
+ (array_structure
11
+ (structure_name
12
+ (identifier) @function.call))
13
+
14
+ ; Field names (parameters to the action call)
15
+ (field_name
16
+ (identifier) @parameter)
17
+
18
+ (field_name
19
+ (property_path
20
+ (identifier) @parameter))
21
+
22
+ ; Type names in casts
23
+ (typed_value
24
+ (type_name) @type)
25
+
26
+ ; Strings (quoted)
27
+ (string) @string
28
+
29
+ ; Unquoted string values (use same as quoted strings)
30
+ (unquoted_string) @string
31
+
32
+ ; Escape sequences within strings
33
+ (escape_sequence) @string.escape
34
+
35
+ ; Numbers
36
+ (number) @number
37
+ (hex_number) @number
38
+ (fraction) @number
39
+
40
+ ; Booleans
41
+ (boolean) @boolean
42
+
43
+ ; Numbers inside (bool) or (boolean) typed values should be highlighted as booleans
44
+ ((typed_value
45
+ type: (type_name) @_type
46
+ value: (value (number) @boolean))
47
+ (#eq? @_type "bool"))
48
+
49
+ ((typed_value
50
+ type: (type_name) @_type
51
+ value: (value (number) @boolean))
52
+ (#eq? @_type "boolean"))
53
+
54
+ ; Variables like $(foo)
55
+ (variable) @variable
56
+
57
+ ; Expressions like expr(...)
58
+ (expression) @function.call
59
+
60
+ ; Flags (like flush+accurate)
61
+ (flags) @constant
62
+
63
+ ; Namespaced identifiers (like scenario::execution-error)
64
+ (namespaced_identifier) @module
65
+
66
+ ; CLI arguments (like -t, --videosink)
67
+ (cli_argument) @attribute
68
+
69
+ ; Operators and punctuation
70
+ "=" @operator
71
+ "::" @punctuation.delimiter
72
+
73
+ ; Brackets and braces
74
+ "[" @punctuation.bracket
75
+ "]" @punctuation.bracket
76
+ "{" @punctuation.bracket
77
+ "}" @punctuation.bracket
78
+ "(" @punctuation.bracket
79
+ ")" @punctuation.bracket
80
+ "<" @punctuation.bracket
81
+ ">" @punctuation.bracket
82
+
83
+ ; Separators
84
+ "," @punctuation.delimiter
85
+ ";" @punctuation.delimiter
@@ -0,0 +1,62 @@
1
+ ; Injections for GStreamer ValidateTest files
2
+ ; Re-parse embedded GstStructure syntax within strings
3
+
4
+ ; Strings in 'configs' field contain GstStructure syntax
5
+ ((field
6
+ name: (field_name (identifier) @_field_name)
7
+ value: (field_value
8
+ (nested_structure_block
9
+ (field_value (value (string (string_inner) @injection.content))))))
10
+ (#eq? @_field_name "configs")
11
+ (#set! injection.language "validatetest")
12
+ (#set! injection.include-children))
13
+
14
+ ; Strings in 'expected-issues' field contain GstStructure syntax
15
+ ((field
16
+ name: (field_name (identifier) @_field_name)
17
+ value: (field_value
18
+ (nested_structure_block
19
+ (field_value (value (string (string_inner) @injection.content))))))
20
+ (#eq? @_field_name "expected-issues")
21
+ (#set! injection.language "validatetest")
22
+ (#set! injection.include-children))
23
+
24
+ ; Typed GstCaps values contain GstStructure/caps syntax
25
+ ((typed_value
26
+ type: (type_name) @_type
27
+ value: (value (string (string_inner) @injection.content)))
28
+ (#eq? @_type "GstCaps")
29
+ (#set! injection.language "validatetest")
30
+ (#set! injection.include-children))
31
+
32
+ ; Typed caps values (lowercase alias) contain GstStructure/caps syntax
33
+ ((typed_value
34
+ type: (type_name) @_type
35
+ value: (value (string (string_inner) @injection.content)))
36
+ (#eq? @_type "caps")
37
+ (#set! injection.language "validatetest")
38
+ (#set! injection.include-children))
39
+
40
+ ; Typed GstStructure values contain GstStructure syntax
41
+ ((typed_value
42
+ type: (type_name) @_type
43
+ value: (value (string (string_inner) @injection.content)))
44
+ (#eq? @_type "GstStructure")
45
+ (#set! injection.language "validatetest")
46
+ (#set! injection.include-children))
47
+
48
+ ; Typed structure values (lowercase alias) contain GstStructure syntax
49
+ ((typed_value
50
+ type: (type_name) @_type
51
+ value: (value (string (string_inner) @injection.content)))
52
+ (#eq? @_type "structure")
53
+ (#set! injection.language "validatetest")
54
+ (#set! injection.include-children))
55
+
56
+ ; Field named 'caps' with string value contains caps syntax
57
+ ((field
58
+ name: (field_name (identifier) @_field_name)
59
+ value: (field_value (value (string (string_inner) @injection.content))))
60
+ (#eq? @_field_name "caps")
61
+ (#set! injection.language "validatetest")
62
+ (#set! injection.include-children))