@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/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
- // Skip whitespace and any #pragma lines, then peek for a keyword.
89
- // #pragma lines are transparent extras we must skip them when scanning ahead
90
- // for split-construct patterns (e.g., PREPROC_SPLIT_BEGIN checking for #endif).
91
- static bool peek_keyword_ci_skip_pragma(TSLexer *lexer, const char *keyword) {
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
- // Check for '#pragma' line — skip entire line
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 lines are transparent extras — we skip them when scanning ahead for #endif.
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 (peek_keyword_ci_skip_pragma(lexer, "#endif")) {
244
+ if (peek_keyword_ci_skip_extras(lexer, "#endif")) {
232
245
  lexer->result_symbol = PREPROC_SPLIT_BEGIN;
233
246
  return true;
234
247
  }
@@ -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)
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
- )