tree-sitter-scpi 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 +132 -0
- package/binding.gyp +19 -0
- package/bindings/node/binding.cc +28 -0
- package/bindings/node/index.js +19 -0
- package/grammar.js +128 -0
- package/package.json +45 -0
- package/src/grammar.json +505 -0
- package/src/node-types.json +250 -0
- package/src/parser.c +1691 -0
- package/src/tree_sitter/parser.h +224 -0
package/README.md
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
# tree-sitter-scpi
|
|
2
|
+
|
|
3
|
+
Tree-sitter grammar for SCPI (Standard Commands for Programmable Instruments).
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This grammar provides syntax parsing for SCPI commands used in programmable instruments. It supports:
|
|
8
|
+
|
|
9
|
+
- **Common commands**: Asterisk-prefixed commands like `*IDN?`, `*RST`
|
|
10
|
+
- **Hierarchical commands**: Colon-separated command paths like `:MEASure:VOLTage?`
|
|
11
|
+
- **Query commands**: Commands ending with `?`
|
|
12
|
+
- **Command parameters**: Numeric values, strings, and enumerated values
|
|
13
|
+
- **Command chaining**: Multiple commands separated by semicolons
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npm install
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Building
|
|
22
|
+
|
|
23
|
+
Generate the parser from the grammar:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm run generate
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
Or using tree-sitter CLI directly:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
tree-sitter generate
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Testing
|
|
36
|
+
|
|
37
|
+
Run the test suite:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
npm test
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Or using tree-sitter CLI directly:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
tree-sitter test
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Grammar Structure
|
|
50
|
+
|
|
51
|
+
### Command Types
|
|
52
|
+
|
|
53
|
+
#### Common Commands
|
|
54
|
+
Common commands start with an asterisk (`*`) and may include a query marker (`?`):
|
|
55
|
+
|
|
56
|
+
```
|
|
57
|
+
*IDN?
|
|
58
|
+
*RST
|
|
59
|
+
*OPC?
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Hierarchical Commands
|
|
63
|
+
Hierarchical commands use colons (`:`) to separate command nodes. The leading colon is optional:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
:MEASure:VOLTage?
|
|
67
|
+
MEASure:VOLTage?
|
|
68
|
+
:SYSTem:COMMunicate:SERial:BAUD 9600
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Parameters
|
|
72
|
+
|
|
73
|
+
Commands can include parameters separated by commas:
|
|
74
|
+
|
|
75
|
+
- **Numbers**: `5.0`, `-10`, `1.5e3`, `0.001`
|
|
76
|
+
- **Strings**: `"192.168.1.100"`, `"AUTO"`
|
|
77
|
+
- **Enumerations**: `EXTernal`, `INTernal`, `AUTO`
|
|
78
|
+
|
|
79
|
+
Example:
|
|
80
|
+
```
|
|
81
|
+
:MEASure:VOLTage:DC? 10,0.001
|
|
82
|
+
:SYSTem:COMMunicate:LAN:IPADdress "192.168.1.100"
|
|
83
|
+
:TRIGger:SOURce EXTernal
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### Command Chaining
|
|
87
|
+
|
|
88
|
+
Multiple commands can be chained on a single line using semicolons:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
*IDN?;*RST
|
|
92
|
+
:MEASure:VOLTage?;:SOURce:VOLTage 5.0
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
## Example Usage
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
const Parser = require('tree-sitter');
|
|
99
|
+
const SCPI = require('tree-sitter-scpi');
|
|
100
|
+
|
|
101
|
+
const parser = new Parser();
|
|
102
|
+
parser.setLanguage(SCPI);
|
|
103
|
+
|
|
104
|
+
const sourceCode = `*IDN?
|
|
105
|
+
:MEASure:VOLTage?
|
|
106
|
+
:SOURce:VOLTage 5.0`;
|
|
107
|
+
|
|
108
|
+
const tree = parser.parse(sourceCode);
|
|
109
|
+
console.log(tree.rootNode.toString());
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
## Grammar Rules
|
|
113
|
+
|
|
114
|
+
The grammar defines the following main rules:
|
|
115
|
+
|
|
116
|
+
- `program`: Root node containing one or more commands
|
|
117
|
+
- `command`: Individual SCPI command (common or hierarchical)
|
|
118
|
+
- `common_command`: Asterisk-prefixed commands
|
|
119
|
+
- `hierarchical_command`: Colon-separated command path
|
|
120
|
+
- `command_path`: Sequence of command nodes separated by colons
|
|
121
|
+
- `command_node`: Individual node in hierarchical path
|
|
122
|
+
- `query_marker`: Optional `?` at end of commands
|
|
123
|
+
- `parameter_list`: Optional parameters following command
|
|
124
|
+
- `parameter`: Individual parameter (number, string, or enumeration)
|
|
125
|
+
- `number`: Numeric values (integers, floats, scientific notation)
|
|
126
|
+
- `string`: Quoted string parameters
|
|
127
|
+
- `enumeration`: Enumerated values (keywords)
|
|
128
|
+
|
|
129
|
+
## License
|
|
130
|
+
|
|
131
|
+
MIT
|
|
132
|
+
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "tree_sitter_scpi_binding",
|
|
5
|
+
"include_dirs": [
|
|
6
|
+
"<!(node -e \"require('nan')\")",
|
|
7
|
+
"src"
|
|
8
|
+
],
|
|
9
|
+
"sources": [
|
|
10
|
+
"bindings/node/binding.cc",
|
|
11
|
+
"src/parser.c",
|
|
12
|
+
# If your language uses an external scanner, add it here.
|
|
13
|
+
],
|
|
14
|
+
"cflags_c": [
|
|
15
|
+
"-std=c99",
|
|
16
|
+
]
|
|
17
|
+
}
|
|
18
|
+
]
|
|
19
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#include "tree_sitter/parser.h"
|
|
2
|
+
#include <node.h>
|
|
3
|
+
#include "nan.h"
|
|
4
|
+
|
|
5
|
+
using namespace v8;
|
|
6
|
+
|
|
7
|
+
extern "C" TSLanguage * tree_sitter_scpi();
|
|
8
|
+
|
|
9
|
+
namespace {
|
|
10
|
+
|
|
11
|
+
NAN_METHOD(New) {}
|
|
12
|
+
|
|
13
|
+
void Init(Local<Object> exports, Local<Object> module) {
|
|
14
|
+
Local<FunctionTemplate> tpl = Nan::New<FunctionTemplate>(New);
|
|
15
|
+
tpl->SetClassName(Nan::New("Language").ToLocalChecked());
|
|
16
|
+
tpl->InstanceTemplate()->SetInternalFieldCount(1);
|
|
17
|
+
|
|
18
|
+
Local<Function> constructor = Nan::GetFunction(tpl).ToLocalChecked();
|
|
19
|
+
Local<Object> instance = constructor->NewInstance(Nan::GetCurrentContext()).ToLocalChecked();
|
|
20
|
+
Nan::SetInternalFieldPointer(instance, 0, tree_sitter_scpi());
|
|
21
|
+
|
|
22
|
+
Nan::Set(instance, Nan::New("name").ToLocalChecked(), Nan::New("scpi").ToLocalChecked());
|
|
23
|
+
Nan::Set(module, Nan::New("exports").ToLocalChecked(), instance);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
NODE_MODULE(tree_sitter_scpi_binding, Init)
|
|
27
|
+
|
|
28
|
+
} // namespace
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
try {
|
|
2
|
+
module.exports = require("../../build/Release/tree_sitter_scpi_binding");
|
|
3
|
+
} catch (error1) {
|
|
4
|
+
if (error1.code !== 'MODULE_NOT_FOUND') {
|
|
5
|
+
throw error1;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
module.exports = require("../../build/Debug/tree_sitter_scpi_binding");
|
|
9
|
+
} catch (error2) {
|
|
10
|
+
if (error2.code !== 'MODULE_NOT_FOUND') {
|
|
11
|
+
throw error2;
|
|
12
|
+
}
|
|
13
|
+
throw error1
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
module.exports.nodeTypeInfo = require("../../src/node-types.json");
|
|
19
|
+
} catch (_) {}
|
package/grammar.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
module.exports = grammar({
|
|
2
|
+
name: 'scpi',
|
|
3
|
+
|
|
4
|
+
extras: $ => [
|
|
5
|
+
/[ \t]/,
|
|
6
|
+
$.comment
|
|
7
|
+
],
|
|
8
|
+
|
|
9
|
+
rules: {
|
|
10
|
+
scpi_command: $ => repeat($._statement),
|
|
11
|
+
|
|
12
|
+
_statement: $ => seq(
|
|
13
|
+
optional($.program_message),
|
|
14
|
+
$._terminator
|
|
15
|
+
),
|
|
16
|
+
|
|
17
|
+
_terminator: $ => /[\r\n]+/,
|
|
18
|
+
|
|
19
|
+
program_message: $ => seq(
|
|
20
|
+
$._program_message_unit,
|
|
21
|
+
repeat(seq(';', $._program_message_unit))
|
|
22
|
+
),
|
|
23
|
+
|
|
24
|
+
_program_message_unit: $ => seq(
|
|
25
|
+
choice(
|
|
26
|
+
$.command_header,
|
|
27
|
+
$.common_command_header
|
|
28
|
+
),
|
|
29
|
+
optional(seq(
|
|
30
|
+
alias($._separator, 'separator'),
|
|
31
|
+
$.parameter_list
|
|
32
|
+
))
|
|
33
|
+
),
|
|
34
|
+
|
|
35
|
+
command_header: $ => seq(
|
|
36
|
+
optional(':'),
|
|
37
|
+
$.mnemonic,
|
|
38
|
+
repeat(seq(':', $.mnemonic)),
|
|
39
|
+
optional('?')
|
|
40
|
+
),
|
|
41
|
+
|
|
42
|
+
common_command_header: $ => seq(
|
|
43
|
+
'*',
|
|
44
|
+
$.mnemonic,
|
|
45
|
+
optional('?')
|
|
46
|
+
),
|
|
47
|
+
|
|
48
|
+
mnemonic: $ => /[a-zA-Z][a-zA-Z0-9_]*/,
|
|
49
|
+
|
|
50
|
+
parameter_list: $ => seq(
|
|
51
|
+
$._parameter,
|
|
52
|
+
repeat(seq(',', $._parameter))
|
|
53
|
+
),
|
|
54
|
+
|
|
55
|
+
_parameter: $ => choice(
|
|
56
|
+
$._numeric_entry,
|
|
57
|
+
$.string_literal,
|
|
58
|
+
$.boolean_literal,
|
|
59
|
+
$.character_data,
|
|
60
|
+
$.channel_list
|
|
61
|
+
// $.block_data
|
|
62
|
+
),
|
|
63
|
+
|
|
64
|
+
_numeric_entry: $ => seq(
|
|
65
|
+
$.numeric_literal,
|
|
66
|
+
optional($.suffix)
|
|
67
|
+
),
|
|
68
|
+
|
|
69
|
+
suffix: $ => /[a-zA-Z]+(\/[a-zA-Z]+)*/,
|
|
70
|
+
|
|
71
|
+
channel_list: $ => seq(
|
|
72
|
+
'(@',
|
|
73
|
+
$._channel_spec_list,
|
|
74
|
+
')'
|
|
75
|
+
),
|
|
76
|
+
|
|
77
|
+
_channel_spec_list: $ => seq(
|
|
78
|
+
$._channel_item,
|
|
79
|
+
repeat(seq(',', $._channel_item))
|
|
80
|
+
),
|
|
81
|
+
|
|
82
|
+
_channel_item: $ => choice(
|
|
83
|
+
$.channel_range,
|
|
84
|
+
$.channel_spec
|
|
85
|
+
),
|
|
86
|
+
|
|
87
|
+
channel_range: $ => seq(
|
|
88
|
+
$.channel_spec,
|
|
89
|
+
':',
|
|
90
|
+
$.channel_spec
|
|
91
|
+
),
|
|
92
|
+
|
|
93
|
+
channel_spec: $ => seq(
|
|
94
|
+
$.integer,
|
|
95
|
+
repeat(seq('!', $.integer))
|
|
96
|
+
),
|
|
97
|
+
|
|
98
|
+
integer: $ => /\d+/,
|
|
99
|
+
|
|
100
|
+
numeric_literal: $ => token(choice(
|
|
101
|
+
// Decimal with optional exponent
|
|
102
|
+
/[+-]?(\d+(\.\d*)?|\.\d+)([eE][+-]?\d+)?/,
|
|
103
|
+
// Hexadecimal
|
|
104
|
+
/#H[0-9a-fA-F]+/,
|
|
105
|
+
// Octal
|
|
106
|
+
/#Q[0-7]+/,
|
|
107
|
+
// Binary
|
|
108
|
+
/#B[01]+/
|
|
109
|
+
)),
|
|
110
|
+
|
|
111
|
+
string_literal: $ => choice(
|
|
112
|
+
seq('"', repeat(choice(/[^"\n]/, '""')), '"'),
|
|
113
|
+
seq("'", repeat(choice(/[^'\n]/, "''")), "'")
|
|
114
|
+
),
|
|
115
|
+
|
|
116
|
+
boolean_literal: $ => choice('ON', 'OFF'),
|
|
117
|
+
|
|
118
|
+
character_data: $ => /[a-zA-Z][a-zA-Z0-9_]*/,
|
|
119
|
+
|
|
120
|
+
comment: $ => token(seq('//', /.*/)),
|
|
121
|
+
|
|
122
|
+
_separator: $ => /[ \t]+/
|
|
123
|
+
},
|
|
124
|
+
|
|
125
|
+
conflicts: $ => [
|
|
126
|
+
[$.boolean_literal, $.character_data]
|
|
127
|
+
]
|
|
128
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "tree-sitter-scpi",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Tree-sitter grammar for SCPI (Standard Commands for Programmable Instruments)",
|
|
5
|
+
"main": "bindings/node",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"scpi",
|
|
8
|
+
"tree-sitter",
|
|
9
|
+
"parser",
|
|
10
|
+
"grammar",
|
|
11
|
+
"instrumentation"
|
|
12
|
+
],
|
|
13
|
+
"author": "Isaac Guo <isaacgy@gmail.com>",
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "https://github.com/isaacguo/tree-sitter-scpi.git"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"grammar.js",
|
|
21
|
+
"src/",
|
|
22
|
+
"bindings/node/",
|
|
23
|
+
"binding.gyp",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"nan": "^2.17.0"
|
|
28
|
+
},
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"tree-sitter-cli": "^0.20.0"
|
|
31
|
+
},
|
|
32
|
+
"scripts": {
|
|
33
|
+
"generate": "tree-sitter generate",
|
|
34
|
+
"test": "tree-sitter test"
|
|
35
|
+
},
|
|
36
|
+
"tree-sitter": [
|
|
37
|
+
{
|
|
38
|
+
"scope": "source.scpi",
|
|
39
|
+
"file-types": [
|
|
40
|
+
"scpi"
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}
|
|
45
|
+
|