@sshadows/tree-sitter-al 3.2.0 → 3.3.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/grammar.js +27 -0
- package/package.json +3 -4
- package/queries/folds.scm +4 -16
- package/queries/highlights.scm +307 -97
- package/queries/indents.scm +5 -5
- package/queries/locals.scm +28 -7
- package/queries/tags.scm +179 -68
- package/queries/textobjects.scm +67 -27
- package/src/grammar.json +16 -0
- package/src/node-types.json +10 -0
- package/src/parser.c +77021 -56029
- package/src/scanner.c +51 -38
- package/src/tree_sitter/array.h +23 -17
- package/tree-sitter-al.wasm +0 -0
- package/queries/bad_delete.scm.v1 +0 -30
package/src/scanner.c
CHANGED
|
@@ -85,44 +85,24 @@ static bool peek_keyword_ci(TSLexer *lexer, const char *keyword) {
|
|
|
85
85
|
return true;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
static
|
|
88
|
+
// Directives that grammar.js declares as `extras`. They are transparent to the
|
|
89
|
+
// parse tree, so a lookahead scanning for a structural directive must step over
|
|
90
|
+
// them rather than stop on them. Keep in sync with the `extras` array.
|
|
91
|
+
static const char *const TRANSPARENT_DIRECTIVES[] = {
|
|
92
|
+
"pragma", "endregion", "region", "define", "undef", NULL,
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
// Skip whitespace and any transparent-directive lines, then peek for a keyword.
|
|
96
|
+
//
|
|
97
|
+
// Used when scanning ahead for split-construct patterns (e.g.
|
|
98
|
+
// PREPROC_SPLIT_BEGIN checking for #endif). Consuming '#' is irreversible
|
|
99
|
+
// within one scan, so the directive word after '#' is read ONCE into a buffer
|
|
100
|
+
// and then classified — matching candidates one after another would burn the
|
|
101
|
+
// shared prefix of e.g. "endif"/"endregion" on the first failed attempt.
|
|
102
|
+
static bool peek_keyword_ci_skip_extras(TSLexer *lexer, const char *keyword) {
|
|
92
103
|
while (true) {
|
|
93
104
|
skip_whitespace(lexer);
|
|
94
|
-
|
|
95
|
-
if (lexer->lookahead == '#') {
|
|
96
|
-
lexer->advance(lexer, false);
|
|
97
|
-
// Check if 'pragma' follows
|
|
98
|
-
const char *pragma = "pragma";
|
|
99
|
-
bool is_pragma = true;
|
|
100
|
-
for (int i = 0; pragma[i] != '\0'; i++) {
|
|
101
|
-
if (towlower(lexer->lookahead) != pragma[i]) { is_pragma = false; break; }
|
|
102
|
-
lexer->advance(lexer, false);
|
|
103
|
-
}
|
|
104
|
-
if (is_pragma && (lexer->lookahead == ' ' || lexer->lookahead == '\t' ||
|
|
105
|
-
lexer->lookahead == '\r' || lexer->lookahead == '\n' ||
|
|
106
|
-
lexer->lookahead == '\0')) {
|
|
107
|
-
// Skip rest of this #pragma line
|
|
108
|
-
while (lexer->lookahead != '\0' && lexer->lookahead != '\n') {
|
|
109
|
-
lexer->advance(lexer, false);
|
|
110
|
-
}
|
|
111
|
-
continue; // loop back to skip more whitespace/#pragma lines
|
|
112
|
-
} else {
|
|
113
|
-
// '#' was followed by something other than 'pragma' — check for keyword
|
|
114
|
-
// We already consumed '#'; now match rest of keyword (which starts with '#')
|
|
115
|
-
// Since we consumed '#', match from position 1 of keyword
|
|
116
|
-
if (keyword[0] != '#') return false;
|
|
117
|
-
const char *rest = keyword + 1;
|
|
118
|
-
for (int i = 0; rest[i] != '\0'; i++) {
|
|
119
|
-
if (towlower(lexer->lookahead) != rest[i]) return false;
|
|
120
|
-
lexer->advance(lexer, false);
|
|
121
|
-
}
|
|
122
|
-
if (is_identifier_char(lexer->lookahead)) return false;
|
|
123
|
-
return true;
|
|
124
|
-
}
|
|
125
|
-
} else {
|
|
105
|
+
if (lexer->lookahead != '#') {
|
|
126
106
|
// Not '#' — try to match keyword directly
|
|
127
107
|
for (int i = 0; keyword[i] != '\0'; i++) {
|
|
128
108
|
if (towlower(lexer->lookahead) != keyword[i]) return false;
|
|
@@ -131,6 +111,38 @@ static bool peek_keyword_ci_skip_pragma(TSLexer *lexer, const char *keyword) {
|
|
|
131
111
|
if (is_identifier_char(lexer->lookahead)) return false;
|
|
132
112
|
return true;
|
|
133
113
|
}
|
|
114
|
+
|
|
115
|
+
lexer->advance(lexer, false);
|
|
116
|
+
// Horizontal whitespace only: '# pragma' is one directive, but '#' and a
|
|
117
|
+
// word on the NEXT line are not (matching the extras regexes' `[ \t]*`).
|
|
118
|
+
while (lexer->lookahead == ' ' || lexer->lookahead == '\t') {
|
|
119
|
+
lexer->advance(lexer, false);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Read the directive word. Longest AL directive is "endregion" (9).
|
|
123
|
+
char word[16];
|
|
124
|
+
size_t len = 0;
|
|
125
|
+
while (is_identifier_char(lexer->lookahead)) {
|
|
126
|
+
if (len < sizeof(word) - 1) word[len] = (char)towlower(lexer->lookahead);
|
|
127
|
+
len++;
|
|
128
|
+
lexer->advance(lexer, false);
|
|
129
|
+
}
|
|
130
|
+
if (len >= sizeof(word)) return false; // too long to be any directive
|
|
131
|
+
word[len] = '\0';
|
|
132
|
+
|
|
133
|
+
// The caller's keyword includes the '#' we just consumed.
|
|
134
|
+
if (keyword[0] == '#' && strcmp(word, keyword + 1) == 0) return true;
|
|
135
|
+
|
|
136
|
+
bool transparent = false;
|
|
137
|
+
for (int i = 0; TRANSPARENT_DIRECTIVES[i] != NULL; i++) {
|
|
138
|
+
if (strcmp(word, TRANSPARENT_DIRECTIVES[i]) == 0) { transparent = true; break; }
|
|
139
|
+
}
|
|
140
|
+
if (!transparent) return false;
|
|
141
|
+
|
|
142
|
+
// Skip the rest of this directive's line, then look again.
|
|
143
|
+
while (lexer->lookahead != '\0' && lexer->lookahead != '\n') {
|
|
144
|
+
lexer->advance(lexer, false);
|
|
145
|
+
}
|
|
134
146
|
}
|
|
135
147
|
}
|
|
136
148
|
|
|
@@ -223,12 +235,13 @@ bool tree_sitter_al_external_scanner_scan(
|
|
|
223
235
|
// including '#'). PREPROC_OPEN/CLOSE manually advance past '#' before calling
|
|
224
236
|
// read_keyword_ci("if"/"endif"). These are DIFFERENT conventions — do not mix.
|
|
225
237
|
//
|
|
226
|
-
// #pragma
|
|
238
|
+
// #pragma/#region/#define and friends are transparent extras — we skip them
|
|
239
|
+
// when scanning ahead for #endif (see TRANSPARENT_DIRECTIVES).
|
|
227
240
|
if (valid_symbols[PREPROC_SPLIT_BEGIN] && state->depth > 0) {
|
|
228
241
|
skip_whitespace(lexer);
|
|
229
242
|
if (read_keyword_ci(lexer, "begin")) {
|
|
230
243
|
lexer->mark_end(lexer); // token covers only 'begin'
|
|
231
|
-
if (
|
|
244
|
+
if (peek_keyword_ci_skip_extras(lexer, "#endif")) {
|
|
232
245
|
lexer->result_symbol = PREPROC_SPLIT_BEGIN;
|
|
233
246
|
return true;
|
|
234
247
|
}
|
package/src/tree_sitter/array.h
CHANGED
|
@@ -50,12 +50,18 @@ extern "C" {
|
|
|
50
50
|
/// memory allocated for the array's contents.
|
|
51
51
|
#define array_clear(self) ((self)->size = 0)
|
|
52
52
|
|
|
53
|
+
#ifdef __cplusplus
|
|
54
|
+
#define _array__cast(self, expr) (decltype((self)->contents))(expr)
|
|
55
|
+
#else
|
|
56
|
+
#define _array__cast(self, expr) (expr)
|
|
57
|
+
#endif
|
|
58
|
+
|
|
53
59
|
/// Reserve `new_capacity` elements of space in the array. If `new_capacity` is
|
|
54
60
|
/// less than the array's current capacity, this function has no effect.
|
|
55
|
-
#define array_reserve(self, new_capacity)
|
|
56
|
-
((self)->contents = _array__reserve(
|
|
57
|
-
(void *)(self)->contents, &(self)->capacity,
|
|
58
|
-
array_elem_size(self), new_capacity)
|
|
61
|
+
#define array_reserve(self, new_capacity) \
|
|
62
|
+
((self)->contents = _array__cast(self, _array__reserve( \
|
|
63
|
+
(void *)(self)->contents, &(self)->capacity, \
|
|
64
|
+
array_elem_size(self), new_capacity)) \
|
|
59
65
|
)
|
|
60
66
|
|
|
61
67
|
/// Free any memory allocated for this array. Note that this does not free any
|
|
@@ -71,10 +77,10 @@ extern "C" {
|
|
|
71
77
|
/// Push a new `element` onto the end of the array.
|
|
72
78
|
#define array_push(self, element) \
|
|
73
79
|
do { \
|
|
74
|
-
(self)->contents = _array__grow(
|
|
80
|
+
(self)->contents = _array__cast(self, _array__grow( \
|
|
75
81
|
(void *)(self)->contents, (self)->size, &(self)->capacity, \
|
|
76
82
|
1, array_elem_size(self) \
|
|
77
|
-
);
|
|
83
|
+
)); \
|
|
78
84
|
(self)->contents[(self)->size++] = (element); \
|
|
79
85
|
} while(0)
|
|
80
86
|
|
|
@@ -83,10 +89,10 @@ extern "C" {
|
|
|
83
89
|
#define array_grow_by(self, count) \
|
|
84
90
|
do { \
|
|
85
91
|
if ((count) == 0) break; \
|
|
86
|
-
(self)->contents = _array__grow(
|
|
92
|
+
(self)->contents = _array__cast(self, _array__grow( \
|
|
87
93
|
(self)->contents, (self)->size, &(self)->capacity, \
|
|
88
94
|
count, array_elem_size(self) \
|
|
89
|
-
);
|
|
95
|
+
)); \
|
|
90
96
|
memset((self)->contents + (self)->size, 0, (count) * array_elem_size(self)); \
|
|
91
97
|
(self)->size += (count); \
|
|
92
98
|
} while (0)
|
|
@@ -98,26 +104,26 @@ extern "C" {
|
|
|
98
104
|
/// Append `count` elements to the end of the array, reading their values from the
|
|
99
105
|
/// `contents` pointer.
|
|
100
106
|
#define array_extend(self, count, other_contents) \
|
|
101
|
-
(self)->contents = _array__splice(
|
|
107
|
+
((self)->contents = _array__cast(self, _array__splice( \
|
|
102
108
|
(void*)(self)->contents, &(self)->size, &(self)->capacity, \
|
|
103
109
|
array_elem_size(self), (self)->size, 0, count, other_contents \
|
|
104
|
-
)
|
|
110
|
+
)))
|
|
105
111
|
|
|
106
112
|
/// Remove `old_count` elements from the array starting at the given `index`. At
|
|
107
113
|
/// the same index, insert `new_count` new elements, reading their values from the
|
|
108
114
|
/// `new_contents` pointer.
|
|
109
115
|
#define array_splice(self, _index, old_count, new_count, new_contents) \
|
|
110
|
-
(self)->contents = _array__splice(
|
|
116
|
+
((self)->contents = _array__cast(self, _array__splice( \
|
|
111
117
|
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
|
112
118
|
array_elem_size(self), _index, old_count, new_count, new_contents \
|
|
113
|
-
)
|
|
119
|
+
)))
|
|
114
120
|
|
|
115
121
|
/// Insert one `element` into the array at the given `index`.
|
|
116
122
|
#define array_insert(self, _index, element) \
|
|
117
|
-
(self)->contents = _array__splice(
|
|
123
|
+
((self)->contents = _array__cast(self, _array__splice( \
|
|
118
124
|
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
|
119
125
|
array_elem_size(self), _index, 0, 1, &(element) \
|
|
120
|
-
)
|
|
126
|
+
)))
|
|
121
127
|
|
|
122
128
|
/// Remove one element from the array at the given `index`.
|
|
123
129
|
#define array_erase(self, _index) \
|
|
@@ -128,17 +134,17 @@ extern "C" {
|
|
|
128
134
|
|
|
129
135
|
/// Assign the contents of one array to another, reallocating if necessary.
|
|
130
136
|
#define array_assign(self, other) \
|
|
131
|
-
(self)->contents = _array__assign(
|
|
137
|
+
((self)->contents = _array__cast(self, _array__assign( \
|
|
132
138
|
(void *)(self)->contents, &(self)->size, &(self)->capacity, \
|
|
133
139
|
(const void *)(other)->contents, (other)->size, array_elem_size(self) \
|
|
134
|
-
)
|
|
140
|
+
)))
|
|
135
141
|
|
|
136
142
|
/// Swap one array with another
|
|
137
143
|
#define array_swap(self, other) \
|
|
138
144
|
do { \
|
|
139
145
|
void *_array_swap_tmp = (void *)(self)->contents; \
|
|
140
146
|
(self)->contents = (other)->contents; \
|
|
141
|
-
(other)->contents = _array_swap_tmp;
|
|
147
|
+
(other)->contents = _array__cast(other, _array_swap_tmp); \
|
|
142
148
|
_array__swap(&(self)->size, &(self)->capacity, \
|
|
143
149
|
&(other)->size, &(other)->capacity); \
|
|
144
150
|
} while (0)
|
package/tree-sitter-al.wasm
CHANGED
|
Binary file
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
; ── "OnBeforeDeleteEvent subscribers missing IsTemporary guard" ─────────────────────────────
|
|
2
|
-
(
|
|
3
|
-
; The attribute_item and procedure must be adjacent siblings (Rust-style attributes)
|
|
4
|
-
(attribute_item
|
|
5
|
-
attribute: (attribute_content
|
|
6
|
-
name: (identifier) @attr_name
|
|
7
|
-
arguments: (attribute_arguments
|
|
8
|
-
arguments: (expression_list
|
|
9
|
-
(qualified_enum_value) ; ObjectType::Table
|
|
10
|
-
[(integer) (database_reference)] ; Table ID or Database::"Table Name"
|
|
11
|
-
[(string_literal) (identifier)] @event_name ; Event name (with or without quotes)
|
|
12
|
-
(string_literal) ; Publisher object
|
|
13
|
-
(boolean) ; Publisher function
|
|
14
|
-
(boolean))))) ; Skip on missing
|
|
15
|
-
. ; Adjacent sibling operator
|
|
16
|
-
(procedure
|
|
17
|
-
name: (name) @proc_name
|
|
18
|
-
(parameter_list
|
|
19
|
-
(parameter
|
|
20
|
-
parameter_name: (name) @rec_param) ; var Rec: Record
|
|
21
|
-
(parameter
|
|
22
|
-
parameter_name: (name))) ; RunTrigger: Boolean
|
|
23
|
-
(var_section)? ; Optional var section
|
|
24
|
-
(code_block ; BEGIN … END;
|
|
25
|
-
(_) @first_statement ; Capture first statement (any type)
|
|
26
|
-
.)) ; Ensure it's the first
|
|
27
|
-
(#eq? @attr_name "EventSubscriber") ; Match EventSubscriber attribute
|
|
28
|
-
(#match? @event_name "OnBeforeDeleteEvent") ; Match OnBeforeDeleteEvent (with or without quotes)
|
|
29
|
-
(#not-match? @first_statement "IsTemporary"); First statement does NOT contain IsTemporary
|
|
30
|
-
)
|