ruby_tree_sitter 1.1.0-x86_64-linux → 1.3.0-x86_64-linux
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.
- checksums.yaml +4 -4
 - data/README.md +11 -7
 - data/ext/tree_sitter/language.c +1 -1
 - data/ext/tree_sitter/logger.c +2 -2
 - data/ext/tree_sitter/node.c +9 -4
 - data/ext/tree_sitter/parser.c +1 -1
 - data/ext/tree_sitter/query.c +7 -0
 - data/ext/tree_sitter/query_cursor.c +18 -2
 - data/lib/tree_sitter/helpers.rb +23 -0
 - data/lib/tree_sitter/node.rb +19 -51
 - data/lib/tree_sitter/query.rb +191 -0
 - data/lib/tree_sitter/query_captures.rb +28 -0
 - data/lib/tree_sitter/query_cursor.rb +27 -0
 - data/lib/tree_sitter/query_match.rb +100 -0
 - data/lib/tree_sitter/query_matches.rb +25 -0
 - data/lib/tree_sitter/query_predicate.rb +14 -0
 - data/lib/tree_sitter/text_predicate_capture.rb +37 -0
 - data/lib/tree_sitter/tree_sitter.so +0 -0
 - data/lib/tree_sitter/version.rb +1 -1
 - data/lib/tree_sitter.rb +8 -1
 - data/lib/tree_stand/config.rb +8 -2
 - data/lib/tree_stand/node.rb +153 -18
 - data/lib/tree_stand/parser.rb +161 -5
 - metadata +10 -2
 
    
        checksums.yaml
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            ---
         
     | 
| 
       2 
2 
     | 
    
         
             
            SHA256:
         
     | 
| 
       3 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       4 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 3 
     | 
    
         
            +
              metadata.gz: a0f7c51b289e769ad5701be586b28d61f41f0979ee6fd3c08f87481ae307f146
         
     | 
| 
      
 4 
     | 
    
         
            +
              data.tar.gz: 988052e5b5b5c41e9a9d974398c6c6cae401f2068a2ad4013d80198f3437c029
         
     | 
| 
       5 
5 
     | 
    
         
             
            SHA512:
         
     | 
| 
       6 
     | 
    
         
            -
              metadata.gz:  
     | 
| 
       7 
     | 
    
         
            -
              data.tar.gz:  
     | 
| 
      
 6 
     | 
    
         
            +
              metadata.gz: 9ef62c5ea3bd4451b03b43e6efc2c3501191ffcd9ea96dfe12d2e66b249d1f16f58e82d6127d833a6c8105e3f910dda2ba898a138d5e6d2756615bf69fcb2e05
         
     | 
| 
      
 7 
     | 
    
         
            +
              data.tar.gz: 5bd46cc66e845c9091005a5eadeccb3db2984e0bbc5bf6d12e9fc4e3fd126f9ab6f1d37970fbe6760eee096ec2c011d4d328d5610a9e873ed313df3842caceb3
         
     | 
    
        data/README.md
    CHANGED
    
    | 
         @@ -1,6 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # Ruby tree-sitter bindings
         
     | 
| 
       2 
2 
     | 
    
         | 
| 
       3 
     | 
    
         
            -
            [](https://faveod.github.io/ruby-tree-sitter)
         
     | 
| 
      
 4 
     | 
    
         
            +
            [](https://rubygems.org/gems/ruby_tree_sitter)
         
     | 
| 
      
 5 
     | 
    
         
            +
            [](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/ci.yml)
         
     | 
| 
      
 6 
     | 
    
         
            +
            <!--
         
     | 
| 
      
 7 
     | 
    
         
            +
            [](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/valgrind.yml)
         
     | 
| 
      
 8 
     | 
    
         
            +
            [](https://github.com/Faveod/ruby-tree-sitter/actions/workflows/asan.yml)
         
     | 
| 
      
 9 
     | 
    
         
            +
            -->
         
     | 
| 
       4 
10 
     | 
    
         | 
| 
       5 
11 
     | 
    
         
             
            Ruby bindings for [tree-sitter](https://github.com/tree-sitter/tree-sitter).
         
     | 
| 
       6 
12 
     | 
    
         | 
| 
         @@ -63,8 +69,7 @@ This gem is a binding for `tree-sitter`. It doesn't have a version of 
     | 
|
| 
       63 
69 
     | 
    
         | 
| 
       64 
70 
     | 
    
         
             
            You must install `tree-sitter` and make sure that their dynamic library is
         
     | 
| 
       65 
71 
     | 
    
         
             
            accessible from `$PATH`, or build the gem with `--disable-sys-libs`, which will
         
     | 
| 
       66 
     | 
    
         
            -
            download the latest tagged `tree-sitter` and build against it (see [Build from
         
     | 
| 
       67 
     | 
    
         
            -
            source](docs/Contributing.md#build-from-source) .)
         
     | 
| 
      
 72 
     | 
    
         
            +
            download the latest tagged `tree-sitter` and build against it (see [Build from source](docs/Contributing.md#build-from-source) .)
         
     | 
| 
       68 
73 
     | 
    
         | 
| 
       69 
74 
     | 
    
         
             
            You can either install `tree-sitter` from source or through your go-to package manager.
         
     | 
| 
       70 
75 
     | 
    
         | 
| 
         @@ -133,8 +138,7 @@ bundle config set build.ruby_tree_sitter --disable-sys-libs 
     | 
|
| 
       133 
138 
     | 
    
         
             
            If you don't want to install from `rubygems`, `git`, or if you don't want to
         
     | 
| 
       134 
139 
     | 
    
         
             
            compile on install, then download a native gem from this repository's
         
     | 
| 
       135 
140 
     | 
    
         
             
            [releases](https://github.com/Faveod/ruby-tree-sitter/releases), or you can
         
     | 
| 
       136 
     | 
    
         
            -
            compile it yourself (see [Build from
         
     | 
| 
       137 
     | 
    
         
            -
            source](docs/Contributing.md#build-from-source) .)
         
     | 
| 
      
 141 
     | 
    
         
            +
            compile it yourself (see [Build from source](docs/Contributing.md#build-from-source) .)
         
     | 
| 
       138 
142 
     | 
    
         | 
| 
       139 
143 
     | 
    
         
             
            In that case, you'd have to point your `Gemfile` to the `gem` as such:
         
     | 
| 
       140 
144 
     | 
    
         | 
| 
         @@ -171,7 +175,7 @@ See `examples` directory. 
     | 
|
| 
       171 
175 
     | 
    
         | 
| 
       172 
176 
     | 
    
         
             
            ## Development
         
     | 
| 
       173 
177 
     | 
    
         | 
| 
       174 
     | 
    
         
            -
            See [`docs/ 
     | 
| 
      
 178 
     | 
    
         
            +
            See [`docs/Contributing.md`](docs/Contributing.md).
         
     | 
| 
       175 
179 
     | 
    
         | 
| 
       176 
180 
     | 
    
         
             
            ## 🚧 👷♀️ Notes 👷 🚧
         
     | 
| 
       177 
181 
     | 
    
         | 
| 
         @@ -186,7 +190,7 @@ don't copy them left and right, and then expect them to work without 
     | 
|
| 
       186 
190 
     | 
    
         
             
            `SEGFAULT`ing and creating a black-hole in your living-room.  Assume that you
         
     | 
| 
       187 
191 
     | 
    
         
             
            have to work locally with them. If you get a `SEGFAULT`, you can debug the
         
     | 
| 
       188 
192 
     | 
    
         
             
            native `C` code using `gdb`.  You can read more on `SEGFAULT`s
         
     | 
| 
       189 
     | 
    
         
            -
            [here](docs/SIGSEGV.md), and debugging [here](docs/Contributing.md 
     | 
| 
      
 193 
     | 
    
         
            +
            [here](docs/SIGSEGV.md), and debugging [here](docs/Contributing#Debugging.md).
         
     | 
| 
       190 
194 
     | 
    
         | 
| 
       191 
195 
     | 
    
         
             
            That said, we do aim at providing an idiomatic `Ruby` interface.  It should also
         
     | 
| 
       192 
196 
     | 
    
         
             
            provide a _safer_ interface, where you don't have to worry about when and how
         
     | 
    
        data/ext/tree_sitter/language.c
    CHANGED
    
    | 
         @@ -32,7 +32,7 @@ VALUE new_language(const TSLanguage *language) { 
     | 
|
| 
       32 
32 
     | 
    
         
             
             * with this gem.
         
     | 
| 
       33 
33 
     | 
    
         
             
             *
         
     | 
| 
       34 
34 
     | 
    
         
             
             * @param name [String] the parser's name.
         
     | 
| 
       35 
     | 
    
         
            -
             * @param path [String] the parser's shared library (so, dylib) path on disk.
         
     | 
| 
      
 35 
     | 
    
         
            +
             * @param path [String, Pathname] the parser's shared library (so, dylib) path on disk.
         
     | 
| 
       36 
36 
     | 
    
         
             
             *
         
     | 
| 
       37 
37 
     | 
    
         
             
             * @return [Language]
         
     | 
| 
       38 
38 
     | 
    
         
             
             */
         
     | 
    
        data/ext/tree_sitter/logger.c
    CHANGED
    
    | 
         @@ -154,9 +154,9 @@ static void logger_initialize_stderr(logger_t *logger) { 
     | 
|
| 
       154 
154 
     | 
    
         
             
             *
         
     | 
| 
       155 
155 
     | 
    
         
             
             * You can provide your proper backend. You have to make sure that it
         
     | 
| 
       156 
156 
     | 
    
         
             
             * exposes a +printf+, +puts+, or +write+ (lookup is done in that specific
         
     | 
| 
       157 
     | 
    
         
            -
             * order). {StringIO} is a perfect candidate.
         
     | 
| 
      
 157 
     | 
    
         
            +
             * order). {::StringIO} is a perfect candidate.
         
     | 
| 
       158 
158 
     | 
    
         
             
             *
         
     | 
| 
       159 
     | 
    
         
            -
             * You can also provide a format ({String}) if your backend supports a +printf+.
         
     | 
| 
      
 159 
     | 
    
         
            +
             * You can also provide a format ({::String}) if your backend supports a +printf+.
         
     | 
| 
       160 
160 
     | 
    
         
             
             *
         
     | 
| 
       161 
161 
     | 
    
         
             
             * @example
         
     | 
| 
       162 
162 
     | 
    
         
             
             *   backend = StringIO.new
         
     | 
    
        data/ext/tree_sitter/node.c
    CHANGED
    
    | 
         @@ -166,14 +166,19 @@ static VALUE node_child_by_field_id(VALUE self, VALUE field_id) { 
     | 
|
| 
       166 
166 
     | 
    
         
             
            /**
         
     | 
| 
       167 
167 
     | 
    
         
             
             * Get the node's child with the given field name.
         
     | 
| 
       168 
168 
     | 
    
         
             
             *
         
     | 
| 
       169 
     | 
    
         
            -
             * @param field_name [String]
         
     | 
| 
      
 169 
     | 
    
         
            +
             * @param field_name [String, Symbol]
         
     | 
| 
       170 
170 
     | 
    
         
             
             *
         
     | 
| 
       171 
171 
     | 
    
         
             
             * @return [Node]
         
     | 
| 
       172 
172 
     | 
    
         
             
             */
         
     | 
| 
       173 
173 
     | 
    
         
             
            static VALUE node_child_by_field_name(VALUE self, VALUE field_name) {
         
     | 
| 
       174 
     | 
    
         
            -
               
     | 
| 
       175 
     | 
    
         
            -
             
     | 
| 
       176 
     | 
    
         
            -
             
     | 
| 
      
 174 
     | 
    
         
            +
              if (Qtrue == rb_funcall(self, rb_intern("field?"), 1, field_name)) {
         
     | 
| 
      
 175 
     | 
    
         
            +
                VALUE field_str = rb_funcall(field_name, rb_intern("to_s"), 0);
         
     | 
| 
      
 176 
     | 
    
         
            +
                const char *name = StringValuePtr(field_str);
         
     | 
| 
      
 177 
     | 
    
         
            +
                uint32_t length = (uint32_t)RSTRING_LEN(field_str);
         
     | 
| 
      
 178 
     | 
    
         
            +
                return new_node_by_val(ts_node_child_by_field_name(SELF, name, length));
         
     | 
| 
      
 179 
     | 
    
         
            +
              } else {
         
     | 
| 
      
 180 
     | 
    
         
            +
                return Qnil;
         
     | 
| 
      
 181 
     | 
    
         
            +
              }
         
     | 
| 
       177 
182 
     | 
    
         
             
            }
         
     | 
| 
       178 
183 
     | 
    
         | 
| 
       179 
184 
     | 
    
         
             
            /**
         
     | 
    
        data/ext/tree_sitter/parser.c
    CHANGED
    
    | 
         @@ -208,7 +208,7 @@ static VALUE parser_set_logger(VALUE self, VALUE logger) { 
     | 
|
| 
       208 
208 
     | 
    
         
             
             *    same arguments. Or you can start parsing from scratch by first calling
         
     | 
| 
       209 
209 
     | 
    
         
             
             *    {Parser#reset}.
         
     | 
| 
       210 
210 
     | 
    
         
             
             * 3. Parsing was cancelled using a cancellation flag that was set by an
         
     | 
| 
       211 
     | 
    
         
            -
             *    earlier call to { 
     | 
| 
      
 211 
     | 
    
         
            +
             *    earlier call to {Parser#cancellation_flag=}. You can resume parsing
         
     | 
| 
       212 
212 
     | 
    
         
             
             *    from where the parser left out by calling {Parser#parse} again with
         
     | 
| 
       213 
213 
     | 
    
         
             
             *    the same arguments.
         
     | 
| 
       214 
214 
     | 
    
         
             
             *
         
     | 
    
        data/ext/tree_sitter/query.c
    CHANGED
    
    | 
         @@ -140,6 +140,13 @@ static VALUE query_initialize(VALUE self, VALUE language, VALUE source) { 
     | 
|
| 
       140 
140 
     | 
    
         
             
                SELF = res;
         
     | 
| 
       141 
141 
     | 
    
         
             
              }
         
     | 
| 
       142 
142 
     | 
    
         | 
| 
      
 143 
     | 
    
         
            +
              rb_iv_set(self, "@text_predicates", rb_ary_new());
         
     | 
| 
      
 144 
     | 
    
         
            +
              rb_iv_set(self, "@property_predicates", rb_ary_new());
         
     | 
| 
      
 145 
     | 
    
         
            +
              rb_iv_set(self, "@property_settings", rb_ary_new());
         
     | 
| 
      
 146 
     | 
    
         
            +
              rb_iv_set(self, "@general_predicates", rb_ary_new());
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
              rb_funcall(self, rb_intern("process"), 1, source);
         
     | 
| 
      
 149 
     | 
    
         
            +
             
     | 
| 
       143 
150 
     | 
    
         
             
              return self;
         
     | 
| 
       144 
151 
     | 
    
         
             
            }
         
     | 
| 
       145 
152 
     | 
    
         | 
| 
         @@ -49,7 +49,7 @@ DATA_FROM_VALUE(TSQueryCursor *, query_cursor) 
     | 
|
| 
       49 
49 
     | 
    
         
             
             *
         
     | 
| 
       50 
50 
     | 
    
         
             
             * @return [QueryCursor]
         
     | 
| 
       51 
51 
     | 
    
         
             
             */
         
     | 
| 
       52 
     | 
    
         
            -
            static VALUE  
     | 
| 
      
 52 
     | 
    
         
            +
            static VALUE query_cursor_exec_static(VALUE self, VALUE query, VALUE node) {
         
     | 
| 
       53 
53 
     | 
    
         
             
              VALUE res = query_cursor_allocate(cQueryCursor);
         
     | 
| 
       54 
54 
     | 
    
         
             
              query_cursor_t *query_cursor = unwrap(res);
         
     | 
| 
       55 
55 
     | 
    
         
             
              ts_query_cursor_exec(query_cursor->data, value_to_query(query),
         
     | 
| 
         @@ -57,6 +57,21 @@ static VALUE query_cursor_exec(VALUE self, VALUE query, VALUE node) { 
     | 
|
| 
       57 
57 
     | 
    
         
             
              return res;
         
     | 
| 
       58 
58 
     | 
    
         
             
            }
         
     | 
| 
       59 
59 
     | 
    
         | 
| 
      
 60 
     | 
    
         
            +
            /**
         
     | 
| 
      
 61 
     | 
    
         
            +
             * Start running a given query on a given node.
         
     | 
| 
      
 62 
     | 
    
         
            +
             *
         
     | 
| 
      
 63 
     | 
    
         
            +
             * @param query [Query]
         
     | 
| 
      
 64 
     | 
    
         
            +
             * @param node  [Node]
         
     | 
| 
      
 65 
     | 
    
         
            +
             *
         
     | 
| 
      
 66 
     | 
    
         
            +
             * @return [QueryCursor]
         
     | 
| 
      
 67 
     | 
    
         
            +
             */
         
     | 
| 
      
 68 
     | 
    
         
            +
            static VALUE query_cursor_exec(VALUE self, VALUE query, VALUE node) {
         
     | 
| 
      
 69 
     | 
    
         
            +
              query_cursor_t *query_cursor = unwrap(self);
         
     | 
| 
      
 70 
     | 
    
         
            +
              ts_query_cursor_exec(query_cursor->data, value_to_query(query),
         
     | 
| 
      
 71 
     | 
    
         
            +
                                   value_to_node(node));
         
     | 
| 
      
 72 
     | 
    
         
            +
              return self;
         
     | 
| 
      
 73 
     | 
    
         
            +
            }
         
     | 
| 
      
 74 
     | 
    
         
            +
             
     | 
| 
       60 
75 
     | 
    
         
             
            /**
         
     | 
| 
       61 
76 
     | 
    
         
             
             * Manage the maximum number of in-progress matches allowed by this query
         
     | 
| 
       62 
77 
     | 
    
         
             
             * cursor.
         
     | 
| 
         @@ -190,13 +205,14 @@ void init_query_cursor(void) { 
     | 
|
| 
       190 
205 
     | 
    
         
             
              rb_define_alloc_func(cQueryCursor, query_cursor_allocate);
         
     | 
| 
       191 
206 
     | 
    
         | 
| 
       192 
207 
     | 
    
         
             
              /* Module methods */
         
     | 
| 
       193 
     | 
    
         
            -
              rb_define_module_function(cQueryCursor, "exec",  
     | 
| 
      
 208 
     | 
    
         
            +
              rb_define_module_function(cQueryCursor, "exec", query_cursor_exec_static, 2);
         
     | 
| 
       194 
209 
     | 
    
         | 
| 
       195 
210 
     | 
    
         
             
              /* Class methods */
         
     | 
| 
       196 
211 
     | 
    
         
             
              // Accessors
         
     | 
| 
       197 
212 
     | 
    
         
             
              DECLARE_ACCESSOR(cQueryCursor, query_cursor, match_limit)
         
     | 
| 
       198 
213 
     | 
    
         | 
| 
       199 
214 
     | 
    
         
             
              // Other
         
     | 
| 
      
 215 
     | 
    
         
            +
              rb_define_method(cQueryCursor, "exec", query_cursor_exec, 2);
         
     | 
| 
       200 
216 
     | 
    
         
             
              rb_define_method(cQueryCursor, "exceed_match_limit?",
         
     | 
| 
       201 
217 
     | 
    
         
             
                               query_cursor_did_exceed_match_limit, 0);
         
     | 
| 
       202 
218 
     | 
    
         
             
              rb_define_method(cQueryCursor, "match_limit", query_cursor_get_match_limit,
         
     | 
| 
         @@ -0,0 +1,23 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            # splits an array like [rust](https://doc.rust-lang.org/std/primitive.slice.html#method.split)
         
     | 
| 
      
 4 
     | 
    
         
            +
            def array_split_like_rust(array, &block)
         
     | 
| 
      
 5 
     | 
    
         
            +
              return enum_for(__method__, array) if !block_given?
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
              return [] if array.empty?
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
              result = []
         
     | 
| 
      
 10 
     | 
    
         
            +
              current_slice = []
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
              array.each do |element|
         
     | 
| 
      
 13 
     | 
    
         
            +
                if yield(element)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  result << current_slice
         
     | 
| 
      
 15 
     | 
    
         
            +
                  current_slice = []
         
     | 
| 
      
 16 
     | 
    
         
            +
                else
         
     | 
| 
      
 17 
     | 
    
         
            +
                  current_slice << element
         
     | 
| 
      
 18 
     | 
    
         
            +
                end
         
     | 
| 
      
 19 
     | 
    
         
            +
              end
         
     | 
| 
      
 20 
     | 
    
         
            +
             
     | 
| 
      
 21 
     | 
    
         
            +
              result << current_slice
         
     | 
| 
      
 22 
     | 
    
         
            +
              result
         
     | 
| 
      
 23 
     | 
    
         
            +
            end
         
     | 
    
        data/lib/tree_sitter/node.rb
    CHANGED
    
    | 
         @@ -13,28 +13,17 @@ module TreeSitter 
     | 
|
| 
       13 
13 
     | 
    
         
             
                    @fields << name.to_sym if name
         
     | 
| 
       14 
14 
     | 
    
         
             
                  end
         
     | 
| 
       15 
15 
     | 
    
         | 
| 
       16 
     | 
    
         
            -
                  @fields
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @fields.to_a
         
     | 
| 
       17 
17 
     | 
    
         
             
                end
         
     | 
| 
       18 
18 
     | 
    
         | 
| 
      
 19 
     | 
    
         
            +
                # @param field [String, Symbol]
         
     | 
| 
       19 
20 
     | 
    
         
             
                def field?(field)
         
     | 
| 
       20 
     | 
    
         
            -
                  fields.include?(field)
         
     | 
| 
      
 21 
     | 
    
         
            +
                  fields.include?(field.to_sym)
         
     | 
| 
       21 
22 
     | 
    
         
             
                end
         
     | 
| 
       22 
23 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                # FIXME: These APIs (`[]` and `fetch`) need absolute fixing.
         
     | 
| 
       24 
     | 
    
         
            -
                # 1. The documentation with the table doesn't work.
         
     | 
| 
       25 
     | 
    
         
            -
                # 1. The APIs are very confusing! Make them act similarly to Hash's
         
     | 
| 
       26 
     | 
    
         
            -
                #    `fetch` and `[]`.
         
     | 
| 
       27 
     | 
    
         
            -
                #    1. `[]` should take a single input and return nil if nothing found
         
     | 
| 
       28 
     | 
    
         
            -
                #       (no exceptions).
         
     | 
| 
       29 
     | 
    
         
            -
                #    1. `fetch` should should accept a single argument, potentially a
         
     | 
| 
       30 
     | 
    
         
            -
                #       default, and raise exception if no default was provided.
         
     | 
| 
       31 
     | 
    
         
            -
                #       Also allow for the `all:` kwarg.
         
     | 
| 
       32 
     | 
    
         
            -
                #    1. `values_at` takes many arguments.
         
     | 
| 
       33 
     | 
    
         
            -
                # And I don't think we can move to 1.0 without adressing them.
         
     | 
| 
       34 
     | 
    
         
            -
                #
         
     | 
| 
       35 
24 
     | 
    
         
             
                # Access node's named children.
         
     | 
| 
       36 
25 
     | 
    
         
             
                #
         
     | 
| 
       37 
     | 
    
         
            -
                # It's similar to {#fetch}, but  
     | 
| 
      
 26 
     | 
    
         
            +
                # It's similar to {#fetch}, but differs in input type, return values, and
         
     | 
| 
       38 
27 
     | 
    
         
             
                # the internal implementation.
         
     | 
| 
       39 
28 
     | 
    
         
             
                #
         
     | 
| 
       40 
29 
     | 
    
         
             
                # Both of these methods exist for separate use cases, but also because
         
     | 
| 
         @@ -60,15 +49,15 @@ module TreeSitter 
     | 
|
| 
       60 
49 
     | 
    
         
             
                # @return [Node | Array<Node>]
         
     | 
| 
       61 
50 
     | 
    
         
             
                def [](*keys)
         
     | 
| 
       62 
51 
     | 
    
         
             
                  case keys.length
         
     | 
| 
       63 
     | 
    
         
            -
                  when 0 then raise "#{self.class.name}##{__method__} requires a key."
         
     | 
| 
      
 52 
     | 
    
         
            +
                  when 0 then raise ArgumentError, "#{self.class.name}##{__method__} requires a key."
         
     | 
| 
       64 
53 
     | 
    
         
             
                  when 1
         
     | 
| 
       65 
54 
     | 
    
         
             
                    case k = keys.first
         
     | 
| 
       66 
     | 
    
         
            -
                    when  
     | 
| 
      
 55 
     | 
    
         
            +
                    when Integer then named_child(k)
         
     | 
| 
       67 
56 
     | 
    
         
             
                    when String, Symbol
         
     | 
| 
       68 
     | 
    
         
            -
                      raise "Cannot find field #{k}" unless fields.include?(k.to_sym)
         
     | 
| 
      
 57 
     | 
    
         
            +
                      raise IndexError, "Cannot find field #{k}. Available: #{fields}" unless fields.include?(k.to_sym)
         
     | 
| 
       69 
58 
     | 
    
         | 
| 
       70 
59 
     | 
    
         
             
                      child_by_field_name(k.to_s)
         
     | 
| 
       71 
     | 
    
         
            -
                    else raise <<~ERR
         
     | 
| 
      
 60 
     | 
    
         
            +
                    else raise ArgumentError, <<~ERR
         
     | 
| 
       72 
61 
     | 
    
         
             
                      #{self.class.name}##{__method__} accepts Integer and returns named child at given index,
         
     | 
| 
       73 
62 
     | 
    
         
             
                          or a (String | Symbol) and returns the child by given field name.
         
     | 
| 
       74 
63 
     | 
    
         
             
                    ERR
         
     | 
| 
         @@ -139,7 +128,7 @@ module TreeSitter 
     | 
|
| 
       139 
128 
     | 
    
         | 
| 
       140 
129 
     | 
    
         
             
                # Access node's named children.
         
     | 
| 
       141 
130 
     | 
    
         
             
                #
         
     | 
| 
       142 
     | 
    
         
            -
                # It's similar to {# 
     | 
| 
      
 131 
     | 
    
         
            +
                # It's similar to {#[]}, but differs in input type, return values, and
         
     | 
| 
       143 
132 
     | 
    
         
             
                # the internal implementation.
         
     | 
| 
       144 
133 
     | 
    
         
             
                #
         
     | 
| 
       145 
134 
     | 
    
         
             
                # Both of these methods exist for separate use cases, but also because
         
     | 
| 
         @@ -159,39 +148,18 @@ module TreeSitter 
     | 
|
| 
       159 
148 
     | 
    
         
             
                # uses         named_child                   | field_name_for_child
         
     | 
| 
       160 
149 
     | 
    
         
             
                #              child_by_field_name           |   via each_node
         
     | 
| 
       161 
150 
     | 
    
         
             
                #              ------------------------------+----------------------
         
     | 
| 
       162 
     | 
    
         
            -
                # 
     | 
| 
       163 
     | 
    
         
            -
                #  
     | 
| 
       164 
     | 
    
         
            -
                 
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
     | 
    
         
            -
             
     | 
| 
       167 
     | 
    
         
            -
             
     | 
| 
       168 
     | 
    
         
            -
                   
     | 
| 
       169 
     | 
    
         
            -
             
     | 
| 
       170 
     | 
    
         
            -
                    dict[k.to_s] = i
         
     | 
| 
       171 
     | 
    
         
            -
                  end
         
     | 
| 
      
 151 
     | 
    
         
            +
                #
         
     | 
| 
      
 152 
     | 
    
         
            +
                # See {#[]}.
         
     | 
| 
      
 153 
     | 
    
         
            +
                def fetch(*keys)
         
     | 
| 
      
 154 
     | 
    
         
            +
                  keys = keys.map(&:to_s)
         
     | 
| 
      
 155 
     | 
    
         
            +
                  key_set = keys.to_set
         
     | 
| 
      
 156 
     | 
    
         
            +
                  fields = {}
         
     | 
| 
      
 157 
     | 
    
         
            +
                  each_field do |f, _c|
         
     | 
| 
      
 158 
     | 
    
         
            +
                    fields[f] = self[f] if key_set.delete(f)
         
     | 
| 
       172 
159 
     | 
    
         | 
| 
       173 
     | 
    
         
            -
             
     | 
| 
       174 
     | 
    
         
            -
                  each_field do |f, c|
         
     | 
| 
       175 
     | 
    
         
            -
                    if dict.key?(f)
         
     | 
| 
       176 
     | 
    
         
            -
                      res[f] = c
         
     | 
| 
       177 
     | 
    
         
            -
                      dict.delete(f)
         
     | 
| 
       178 
     | 
    
         
            -
                    end
         
     | 
| 
       179 
     | 
    
         
            -
                    break if dict.empty?
         
     | 
| 
      
 160 
     | 
    
         
            +
                    break if key_set.empty?
         
     | 
| 
       180 
161 
     | 
    
         
             
                  end
         
     | 
| 
       181 
     | 
    
         
            -
             
     | 
| 
       182 
     | 
    
         
            -
                  res = keys.uniq.map { |k| res[k.to_s] }
         
     | 
| 
       183 
     | 
    
         
            -
                  res = res.compact if !all
         
     | 
| 
       184 
     | 
    
         
            -
                  res
         
     | 
| 
       185 
     | 
    
         
            -
                end
         
     | 
| 
       186 
     | 
    
         
            -
             
     | 
| 
       187 
     | 
    
         
            -
                # Access all named children of a node, returning `nil` for missing ones.
         
     | 
| 
       188 
     | 
    
         
            -
                #
         
     | 
| 
       189 
     | 
    
         
            -
                # Equivalent to `fetch(…, all: true)`.
         
     | 
| 
       190 
     | 
    
         
            -
                #
         
     | 
| 
       191 
     | 
    
         
            -
                # See {#fetch}.
         
     | 
| 
       192 
     | 
    
         
            -
                def fetch_all(*keys, **kwargs)
         
     | 
| 
       193 
     | 
    
         
            -
                  kwargs[:all] = true
         
     | 
| 
       194 
     | 
    
         
            -
                  fetch(*keys, **kwargs)
         
     | 
| 
      
 162 
     | 
    
         
            +
                  fields.values_at(*keys)
         
     | 
| 
       195 
163 
     | 
    
         
             
                end
         
     | 
| 
       196 
164 
     | 
    
         
             
              end
         
     | 
| 
       197 
165 
     | 
    
         
             
            end
         
     | 
| 
         @@ -0,0 +1,191 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'helpers'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 6 
     | 
    
         
            +
              # Query is a wrapper around a tree-sitter query.
         
     | 
| 
      
 7 
     | 
    
         
            +
              class Query
         
     | 
| 
      
 8 
     | 
    
         
            +
                attr_reader :capture_names
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_reader :capture_quantifiers
         
     | 
| 
      
 10 
     | 
    
         
            +
                attr_reader :text_predicates
         
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :property_predicates
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :property_settings
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :general_predicates
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                private
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Called from query.c on initialize.
         
     | 
| 
      
 18 
     | 
    
         
            +
                #
         
     | 
| 
      
 19 
     | 
    
         
            +
                # Prepares all the predicates so we could process them in places like
         
     | 
| 
      
 20 
     | 
    
         
            +
                # {QueryMatch#satisfies_text_predicate?}.
         
     | 
| 
      
 21 
     | 
    
         
            +
                #
         
     | 
| 
      
 22 
     | 
    
         
            +
                # This is translation from the [rust bindings](https://github.com/tree-sitter/tree-sitter/blob/e553578696fe86071846ed612ee476d0167369c1/lib/binding_rust/lib.rs#L1860)
         
     | 
| 
      
 23 
     | 
    
         
            +
                # Because it's a direct translation, it's way too long and we need to shut up rubocop.
         
     | 
| 
      
 24 
     | 
    
         
            +
                # TODO: refactor + simplify when stable.
         
     | 
| 
      
 25 
     | 
    
         
            +
                def process(source) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
         
     | 
| 
      
 26 
     | 
    
         
            +
                  string_count = self.string_count
         
     | 
| 
      
 27 
     | 
    
         
            +
                  capture_count = self.capture_count
         
     | 
| 
      
 28 
     | 
    
         
            +
                  pattern_count = self.pattern_count
         
     | 
| 
      
 29 
     | 
    
         
            +
             
     | 
| 
      
 30 
     | 
    
         
            +
                  # Build a vector of strings to store the capture names.
         
     | 
| 
      
 31 
     | 
    
         
            +
                  capture_names = capture_count.times.map { |i| capture_name_for_id(i) }
         
     | 
| 
      
 32 
     | 
    
         
            +
             
     | 
| 
      
 33 
     | 
    
         
            +
                  # Build a vector to store capture qunatifiers.
         
     | 
| 
      
 34 
     | 
    
         
            +
                  capture_quantifiers =
         
     | 
| 
      
 35 
     | 
    
         
            +
                    pattern_count.times.map do |i|
         
     | 
| 
      
 36 
     | 
    
         
            +
                      capture_count.times.map do |j|
         
     | 
| 
      
 37 
     | 
    
         
            +
                        capture_quantifier_for_id(i, j)
         
     | 
| 
      
 38 
     | 
    
         
            +
                      end
         
     | 
| 
      
 39 
     | 
    
         
            +
                    end
         
     | 
| 
      
 40 
     | 
    
         
            +
             
     | 
| 
      
 41 
     | 
    
         
            +
                  # Build a vector of strings to represent literal values used in predicates.
         
     | 
| 
      
 42 
     | 
    
         
            +
                  string_values = string_count.times.map { |i| string_value_for_id(i) }
         
     | 
| 
      
 43 
     | 
    
         
            +
             
     | 
| 
      
 44 
     | 
    
         
            +
                  # Build a vector of predicates for each pattern.
         
     | 
| 
      
 45 
     | 
    
         
            +
                  pattern_count.times do |i| # rubocop:disable Metrics/BlockLength
         
     | 
| 
      
 46 
     | 
    
         
            +
                    predicate_steps = predicates_for_pattern(i)
         
     | 
| 
      
 47 
     | 
    
         
            +
                    byte_offset = start_byte_for_pattern(i)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    row =
         
     | 
| 
      
 49 
     | 
    
         
            +
                      source.chars.map.with_index
         
     | 
| 
      
 50 
     | 
    
         
            +
                        .take_while { |_, i| i < byte_offset } # rubocop:disable Lint/ShadowingOuterLocalVariable
         
     | 
| 
      
 51 
     | 
    
         
            +
                        .filter { |c, _| c == "\n" }
         
     | 
| 
      
 52 
     | 
    
         
            +
                        .size
         
     | 
| 
      
 53 
     | 
    
         
            +
                    text_predicates = []
         
     | 
| 
      
 54 
     | 
    
         
            +
                    property_predicates = []
         
     | 
| 
      
 55 
     | 
    
         
            +
                    property_settings = []
         
     | 
| 
      
 56 
     | 
    
         
            +
                    general_predicates = []
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                    array_split_like_rust(predicate_steps) { |s| s.type == QueryPredicateStep::DONE } # rubocop:disable Metrics/BlockLength
         
     | 
| 
      
 59 
     | 
    
         
            +
                      .each do |p|
         
     | 
| 
      
 60 
     | 
    
         
            +
                        next if p.empty?
         
     | 
| 
      
 61 
     | 
    
         
            +
             
     | 
| 
      
 62 
     | 
    
         
            +
                        if p[0] == QueryPredicateStep::STRING
         
     | 
| 
      
 63 
     | 
    
         
            +
                          cap = capture_names[p[0].value_id]
         
     | 
| 
      
 64 
     | 
    
         
            +
                          raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 65 
     | 
    
         
            +
                            L#{row}: Expected predicate to start with a function name. Got @#{cap}.
         
     | 
| 
      
 66 
     | 
    
         
            +
                          MSG
         
     | 
| 
      
 67 
     | 
    
         
            +
                        end
         
     | 
| 
      
 68 
     | 
    
         
            +
             
     | 
| 
      
 69 
     | 
    
         
            +
                        # Build a predicate for each of the known predicate function names.
         
     | 
| 
      
 70 
     | 
    
         
            +
                        operator_name = string_values[p[0].value_id]
         
     | 
| 
      
 71 
     | 
    
         
            +
             
     | 
| 
      
 72 
     | 
    
         
            +
                        case operator_name
         
     | 
| 
      
 73 
     | 
    
         
            +
                        in 'any-eq?' | 'any-not-eq?' | 'eq?' | 'not-eq?'
         
     | 
| 
      
 74 
     | 
    
         
            +
                          if p.size != 3
         
     | 
| 
      
 75 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 76 
     | 
    
         
            +
                              L#{row}: Wrong number of arguments to ##{operator_name} predicate. Expected 2, got #{p.size - 1}.
         
     | 
| 
      
 77 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 78 
     | 
    
         
            +
                          end
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                          if p[1].type != QueryPredicateStep::CAPTURE
         
     | 
| 
      
 81 
     | 
    
         
            +
                            lit = string_values[p[1].value_id]
         
     | 
| 
      
 82 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 83 
     | 
    
         
            +
                              L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
         
     | 
| 
      
 84 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 85 
     | 
    
         
            +
                          end
         
     | 
| 
      
 86 
     | 
    
         
            +
             
     | 
| 
      
 87 
     | 
    
         
            +
                          is_positive = %w[eq? any-eq?].include?(operator_name)
         
     | 
| 
      
 88 
     | 
    
         
            +
                          match_all = %w[eq? not-eq?].include?(operator_name)
         
     | 
| 
      
 89 
     | 
    
         
            +
                          # NOTE: in the rust impl, match_all can hit an unreachable! but I am simplifying
         
     | 
| 
      
 90 
     | 
    
         
            +
                          # for readability. Same applies for the other `in` branches.
         
     | 
| 
      
 91 
     | 
    
         
            +
                          text_predicates <<
         
     | 
| 
      
 92 
     | 
    
         
            +
                            if p[2].type == QueryPredicateStep::CAPTURE
         
     | 
| 
      
 93 
     | 
    
         
            +
                              TextPredicateCapture.eq_capture(p[1].value_id, p[2].value_id, is_positive, match_all)
         
     | 
| 
      
 94 
     | 
    
         
            +
                            else
         
     | 
| 
      
 95 
     | 
    
         
            +
                              TextPredicateCapture.eq_string(p[1].value_id, string_values[p[2].value_id], is_positive, match_all)
         
     | 
| 
      
 96 
     | 
    
         
            +
                            end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                        in 'match?' | 'not-match?' | 'any-match?' | 'any-not-match?'
         
     | 
| 
      
 99 
     | 
    
         
            +
                          if p.size != 3
         
     | 
| 
      
 100 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 101 
     | 
    
         
            +
                              L#{row}: Wrong number of arguments to ##{operator_name} predicate. Expected 2, got #{p.size - 1}.
         
     | 
| 
      
 102 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 103 
     | 
    
         
            +
                          end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                          if p[1].type != QueryPredicateStep::CAPTURE
         
     | 
| 
      
 106 
     | 
    
         
            +
                            lit = string_values[p[1].value_id]
         
     | 
| 
      
 107 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 108 
     | 
    
         
            +
                              L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
         
     | 
| 
      
 109 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 110 
     | 
    
         
            +
                          end
         
     | 
| 
      
 111 
     | 
    
         
            +
             
     | 
| 
      
 112 
     | 
    
         
            +
                          if p[2].type == QueryPredicateStep::CAPTURE
         
     | 
| 
      
 113 
     | 
    
         
            +
                            cap = capture_names[p[2].value_id]
         
     | 
| 
      
 114 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 115 
     | 
    
         
            +
                              L#{row}: First argument to ##{operator_name} predicate must be a literal. Got capture @#{cap}".
         
     | 
| 
      
 116 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 117 
     | 
    
         
            +
                          end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                          is_positive = %w[match? any-match?].include?(operator_name)
         
     | 
| 
      
 120 
     | 
    
         
            +
                          match_all = %w[match? not-match?].include?(operator_name)
         
     | 
| 
      
 121 
     | 
    
         
            +
                          regex = /#{string_values[p[2].value_id]}/
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                          text_predicates << TextPredicateCapture.match_string(p[1].value_id, regex, is_positive, match_all)
         
     | 
| 
      
 124 
     | 
    
         
            +
             
     | 
| 
      
 125 
     | 
    
         
            +
                        in 'set!'
         
     | 
| 
      
 126 
     | 
    
         
            +
                          property_settings << 'todo!'
         
     | 
| 
      
 127 
     | 
    
         
            +
             
     | 
| 
      
 128 
     | 
    
         
            +
                        in 'is?' | 'is-not?'
         
     | 
| 
      
 129 
     | 
    
         
            +
                          property_predicates << 'todo!'
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                        in 'any-of?' | 'not-any-of?'
         
     | 
| 
      
 132 
     | 
    
         
            +
                          if p.size < 2
         
     | 
| 
      
 133 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 134 
     | 
    
         
            +
                              L#{row}: Wrong number of arguments to ##{operator_name} predicate. Expected at least 1, got #{p.size - 1}.
         
     | 
| 
      
 135 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 136 
     | 
    
         
            +
                          end
         
     | 
| 
      
 137 
     | 
    
         
            +
             
     | 
| 
      
 138 
     | 
    
         
            +
                          if p[1].type != QueryPredicateStep::CAPTURE
         
     | 
| 
      
 139 
     | 
    
         
            +
                            lit = string_values[p[1].value_id]
         
     | 
| 
      
 140 
     | 
    
         
            +
                            raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 141 
     | 
    
         
            +
                              L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
         
     | 
| 
      
 142 
     | 
    
         
            +
                            MSG
         
     | 
| 
      
 143 
     | 
    
         
            +
                          end
         
     | 
| 
      
 144 
     | 
    
         
            +
             
     | 
| 
      
 145 
     | 
    
         
            +
                          is_positive = operator_name == 'any_of'
         
     | 
| 
      
 146 
     | 
    
         
            +
                          values = []
         
     | 
| 
      
 147 
     | 
    
         
            +
             
     | 
| 
      
 148 
     | 
    
         
            +
                          p[2..].each do |arg|
         
     | 
| 
      
 149 
     | 
    
         
            +
                            if arg.type == QueryPredicateStep::CAPTURE
         
     | 
| 
      
 150 
     | 
    
         
            +
                              lit = string_values[arg.value_id]
         
     | 
| 
      
 151 
     | 
    
         
            +
                              raise ArgumentError, <<~MSG.chomp
         
     | 
| 
      
 152 
     | 
    
         
            +
                                L#{row}: First argument to ##{operator_name} predicate must be a capture name. Got literal "#{lit}".
         
     | 
| 
      
 153 
     | 
    
         
            +
                              MSG
         
     | 
| 
      
 154 
     | 
    
         
            +
                            end
         
     | 
| 
      
 155 
     | 
    
         
            +
                            values << string_values[arg.value_id]
         
     | 
| 
      
 156 
     | 
    
         
            +
                          end
         
     | 
| 
      
 157 
     | 
    
         
            +
             
     | 
| 
      
 158 
     | 
    
         
            +
                          # TODO: is the map to to_s necessary in ruby?
         
     | 
| 
      
 159 
     | 
    
         
            +
                          text_predicates <<
         
     | 
| 
      
 160 
     | 
    
         
            +
                            TextPredicateCapture.any_string(p[1].value_id, values.map(&:to_s), is_positive, match_all)
         
     | 
| 
      
 161 
     | 
    
         
            +
                        else
         
     | 
| 
      
 162 
     | 
    
         
            +
                          general_predicates <<
         
     | 
| 
      
 163 
     | 
    
         
            +
                            QueryPredicate.new(
         
     | 
| 
      
 164 
     | 
    
         
            +
                              operator_name,
         
     | 
| 
      
 165 
     | 
    
         
            +
                              p[1..].map do |a|
         
     | 
| 
      
 166 
     | 
    
         
            +
                                if a.type == QueryPredicateStep::CAPTURE
         
     | 
| 
      
 167 
     | 
    
         
            +
                                  { capture: a.value_id }
         
     | 
| 
      
 168 
     | 
    
         
            +
                                else
         
     | 
| 
      
 169 
     | 
    
         
            +
                                  { string: string_values[a.value_id] }
         
     | 
| 
      
 170 
     | 
    
         
            +
                                end
         
     | 
| 
      
 171 
     | 
    
         
            +
                              end,
         
     | 
| 
      
 172 
     | 
    
         
            +
                            )
         
     | 
| 
      
 173 
     | 
    
         
            +
                        end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                        @text_predicates << text_predicates
         
     | 
| 
      
 176 
     | 
    
         
            +
                        @property_predicates << property_predicates
         
     | 
| 
      
 177 
     | 
    
         
            +
                        @property_settings << property_settings
         
     | 
| 
      
 178 
     | 
    
         
            +
                        @general_predicates << general_predicates
         
     | 
| 
      
 179 
     | 
    
         
            +
                      end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                    @capture_names = capture_names
         
     | 
| 
      
 182 
     | 
    
         
            +
                    @capture_quantifiers = capture_quantifiers
         
     | 
| 
      
 183 
     | 
    
         
            +
                  end
         
     | 
| 
      
 184 
     | 
    
         
            +
                end
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
      
 186 
     | 
    
         
            +
                # TODO
         
     | 
| 
      
 187 
     | 
    
         
            +
                def parse_property
         
     | 
| 
      
 188 
     | 
    
         
            +
                  # todo
         
     | 
| 
      
 189 
     | 
    
         
            +
                end
         
     | 
| 
      
 190 
     | 
    
         
            +
              end
         
     | 
| 
      
 191 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,28 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A sequence of {TreeSitter::QueryCapture} associated with a given {TreeSitter::QueryCursor}.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class QueryCaptures
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(cursor, query, src)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @cursor = cursor
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @query = query
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @src = src
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # Iterator over captures.
         
     | 
| 
      
 13 
     | 
    
         
            +
                #
         
     | 
| 
      
 14 
     | 
    
         
            +
                # @yieldparam match [TreeSitter::QueryMatch]
         
     | 
| 
      
 15 
     | 
    
         
            +
                # @yieldparam capture_index [Integer]
         
     | 
| 
      
 16 
     | 
    
         
            +
                def each
         
     | 
| 
      
 17 
     | 
    
         
            +
                  return enum_for __method__ if !block_given?
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
                  while (capture_index, match = @cursor.next_capture)
         
     | 
| 
      
 20 
     | 
    
         
            +
                    next if !match.is_a?(TreeSitter::QueryMatch)
         
     | 
| 
      
 21 
     | 
    
         
            +
             
     | 
| 
      
 22 
     | 
    
         
            +
                    if match.satisfies_text_predicate?(@query, @src)
         
     | 
| 
      
 23 
     | 
    
         
            +
                      yield [match, capture_index]
         
     | 
| 
      
 24 
     | 
    
         
            +
                    end
         
     | 
| 
      
 25 
     | 
    
         
            +
                  end
         
     | 
| 
      
 26 
     | 
    
         
            +
                end
         
     | 
| 
      
 27 
     | 
    
         
            +
              end
         
     | 
| 
      
 28 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,27 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A Cursor for {Query}.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class QueryCursor
         
     | 
| 
      
 6 
     | 
    
         
            +
                # Iterate over all of the matches in the order that they were found.
         
     | 
| 
      
 7 
     | 
    
         
            +
                #
         
     | 
| 
      
 8 
     | 
    
         
            +
                # Each match contains the index of the pattern that matched, and a list of
         
     | 
| 
      
 9 
     | 
    
         
            +
                # captures. Because multiple patterns can match the same set of nodes,
         
     | 
| 
      
 10 
     | 
    
         
            +
                # one match may contain captures that appear *before* some of the
         
     | 
| 
      
 11 
     | 
    
         
            +
                # captures from a previous match.
         
     | 
| 
      
 12 
     | 
    
         
            +
                def matches(query, node, src)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  self.exec(query, node)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  QueryMatches.new(self, query, src)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
             
     | 
| 
      
 17 
     | 
    
         
            +
                # Iterate over all of the individual captures in the order that they
         
     | 
| 
      
 18 
     | 
    
         
            +
                # appear.
         
     | 
| 
      
 19 
     | 
    
         
            +
                #
         
     | 
| 
      
 20 
     | 
    
         
            +
                # This is useful if you don't care about which pattern matched, and just
         
     | 
| 
      
 21 
     | 
    
         
            +
                # want a single, ordered sequence of captures.
         
     | 
| 
      
 22 
     | 
    
         
            +
                def captures(query, node, src)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  self.exec(query, node)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  QueryCaptures.new(self, query, src)
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,100 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            require_relative 'query_captures'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 6 
     | 
    
         
            +
              # A match for a {Query}.
         
     | 
| 
      
 7 
     | 
    
         
            +
              class QueryMatch
         
     | 
| 
      
 8 
     | 
    
         
            +
                # All nodes at a given capture index.
         
     | 
| 
      
 9 
     | 
    
         
            +
                #
         
     | 
| 
      
 10 
     | 
    
         
            +
                # @param index [Integer]
         
     | 
| 
      
 11 
     | 
    
         
            +
                #
         
     | 
| 
      
 12 
     | 
    
         
            +
                # @return [TreeSitter::Node]
         
     | 
| 
      
 13 
     | 
    
         
            +
                def nodes_for_capture_index(index) = captures.filter_map { |capture| capture.node if capture.index == index }
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # Whether the {QueryMatch} satisfies the text predicates in the query.
         
     | 
| 
      
 16 
     | 
    
         
            +
                #
         
     | 
| 
      
 17 
     | 
    
         
            +
                # This is a translation from the [rust bindings](https://github.com/tree-sitter/tree-sitter/blob/e553578696fe86071846ed612ee476d0167369c1/lib/binding_rust/lib.rs#L2502).
         
     | 
| 
      
 18 
     | 
    
         
            +
                # Because it's a direct translation, it's way too long and we need to shut up rubocop.
         
     | 
| 
      
 19 
     | 
    
         
            +
                # TODO: refactor + simplify when satable.
         
     | 
| 
      
 20 
     | 
    
         
            +
                def satisfies_text_predicate?(query, src) # rubocop:disable Metrics/AbcSize,Metrics/CyclomaticComplexity,Metrics/MethodLength,Metrics/PerceivedComplexity
         
     | 
| 
      
 21 
     | 
    
         
            +
                  return true if query.text_predicates[pattern_index].nil?
         
     | 
| 
      
 22 
     | 
    
         
            +
             
     | 
| 
      
 23 
     | 
    
         
            +
                  query # rubocop:disable Metrics/BlockLength
         
     | 
| 
      
 24 
     | 
    
         
            +
                    .text_predicates[pattern_index]
         
     | 
| 
      
 25 
     | 
    
         
            +
                    .all? do |predicate|
         
     | 
| 
      
 26 
     | 
    
         
            +
                      case predicate.type
         
     | 
| 
      
 27 
     | 
    
         
            +
                      in TextPredicateCapture::EQ_CAPTURE
         
     | 
| 
      
 28 
     | 
    
         
            +
                        fst_nodes = nodes_for_capture_index(predicate.fst)
         
     | 
| 
      
 29 
     | 
    
         
            +
                        snd_nodes = nodes_for_capture_index(predicate.snd)
         
     | 
| 
      
 30 
     | 
    
         
            +
                        res = nil
         
     | 
| 
      
 31 
     | 
    
         
            +
                        consumed = 0
         
     | 
| 
      
 32 
     | 
    
         
            +
                        fst_nodes.zip(snd_nodes).each do |node1, node2|
         
     | 
| 
      
 33 
     | 
    
         
            +
                          text1 = node_text(node1, src)
         
     | 
| 
      
 34 
     | 
    
         
            +
                          text2 = node_text(node2, src)
         
     | 
| 
      
 35 
     | 
    
         
            +
                          if (text1 == text2) != predicate.positive? && predicate.match_all?
         
     | 
| 
      
 36 
     | 
    
         
            +
                            res = false
         
     | 
| 
      
 37 
     | 
    
         
            +
                            break
         
     | 
| 
      
 38 
     | 
    
         
            +
                          end
         
     | 
| 
      
 39 
     | 
    
         
            +
                          if (text1 == text2) == predicate.positive? && !predicate.match_all?
         
     | 
| 
      
 40 
     | 
    
         
            +
                            res = true
         
     | 
| 
      
 41 
     | 
    
         
            +
                            break
         
     | 
| 
      
 42 
     | 
    
         
            +
                          end
         
     | 
| 
      
 43 
     | 
    
         
            +
                          consumed += 1
         
     | 
| 
      
 44 
     | 
    
         
            +
                        end
         
     | 
| 
      
 45 
     | 
    
         
            +
                        (res.nil? && consumed == fst_nodes.length && consumed == snd_nodes.length) \
         
     | 
| 
      
 46 
     | 
    
         
            +
                          || res
         
     | 
| 
      
 47 
     | 
    
         
            +
             
     | 
| 
      
 48 
     | 
    
         
            +
                      in TextPredicateCapture::EQ_STRING
         
     | 
| 
      
 49 
     | 
    
         
            +
                        nodes = nodes_for_capture_index(predicate.fst)
         
     | 
| 
      
 50 
     | 
    
         
            +
                        res = true
         
     | 
| 
      
 51 
     | 
    
         
            +
                        nodes.each do |node|
         
     | 
| 
      
 52 
     | 
    
         
            +
                          text = node_text(node, src)
         
     | 
| 
      
 53 
     | 
    
         
            +
                          if (predicate.snd == text) != predicate.positive? && predicate.match_all?
         
     | 
| 
      
 54 
     | 
    
         
            +
                            res = false
         
     | 
| 
      
 55 
     | 
    
         
            +
                            break
         
     | 
| 
      
 56 
     | 
    
         
            +
                          end
         
     | 
| 
      
 57 
     | 
    
         
            +
                          if (predicate.snd == text) == predicate.positive? && !predicate.match_all?
         
     | 
| 
      
 58 
     | 
    
         
            +
                            res = true
         
     | 
| 
      
 59 
     | 
    
         
            +
                            break
         
     | 
| 
      
 60 
     | 
    
         
            +
                          end
         
     | 
| 
      
 61 
     | 
    
         
            +
                        end
         
     | 
| 
      
 62 
     | 
    
         
            +
                        res
         
     | 
| 
      
 63 
     | 
    
         
            +
             
     | 
| 
      
 64 
     | 
    
         
            +
                      in TextPredicateCapture::MATCH_STRING
         
     | 
| 
      
 65 
     | 
    
         
            +
                        nodes = nodes_for_capture_index(predicate.fst)
         
     | 
| 
      
 66 
     | 
    
         
            +
                        res = true
         
     | 
| 
      
 67 
     | 
    
         
            +
                        nodes.each do |node|
         
     | 
| 
      
 68 
     | 
    
         
            +
                          text = node_text(node, src)
         
     | 
| 
      
 69 
     | 
    
         
            +
                          if predicate.snd.match?(text) != predicate.positive? && predicate.match_all?
         
     | 
| 
      
 70 
     | 
    
         
            +
                            res = false
         
     | 
| 
      
 71 
     | 
    
         
            +
                            break
         
     | 
| 
      
 72 
     | 
    
         
            +
                          end
         
     | 
| 
      
 73 
     | 
    
         
            +
                          if predicate.snd.match?(text) == predicate.positive? && !predicate.match_all?
         
     | 
| 
      
 74 
     | 
    
         
            +
                            res = true
         
     | 
| 
      
 75 
     | 
    
         
            +
                            break
         
     | 
| 
      
 76 
     | 
    
         
            +
                          end
         
     | 
| 
      
 77 
     | 
    
         
            +
                        end
         
     | 
| 
      
 78 
     | 
    
         
            +
                        res
         
     | 
| 
      
 79 
     | 
    
         
            +
             
     | 
| 
      
 80 
     | 
    
         
            +
                      in TextPredicateCapture::ANY_STRING
         
     | 
| 
      
 81 
     | 
    
         
            +
                        nodes = nodes_for_capture_index(predicate.fst)
         
     | 
| 
      
 82 
     | 
    
         
            +
                        res = true
         
     | 
| 
      
 83 
     | 
    
         
            +
                        nodes.each do |node|
         
     | 
| 
      
 84 
     | 
    
         
            +
                          text = node_text(node, src)
         
     | 
| 
      
 85 
     | 
    
         
            +
                          if predicate.snd.any? { |v| v == text } != predicate.positive?
         
     | 
| 
      
 86 
     | 
    
         
            +
                            res = false
         
     | 
| 
      
 87 
     | 
    
         
            +
                            break
         
     | 
| 
      
 88 
     | 
    
         
            +
                          end
         
     | 
| 
      
 89 
     | 
    
         
            +
                        end
         
     | 
| 
      
 90 
     | 
    
         
            +
                        res
         
     | 
| 
      
 91 
     | 
    
         
            +
             
     | 
| 
      
 92 
     | 
    
         
            +
                      end
         
     | 
| 
      
 93 
     | 
    
         
            +
                    end
         
     | 
| 
      
 94 
     | 
    
         
            +
                end
         
     | 
| 
      
 95 
     | 
    
         
            +
             
     | 
| 
      
 96 
     | 
    
         
            +
                private
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
                def node_text(node, text) = text.byteslice(node.start_byte...node.end_byte)
         
     | 
| 
      
 99 
     | 
    
         
            +
              end
         
     | 
| 
      
 100 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A sequence of {QueryMatch} associated with a given {QueryCursor}.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class QueryMatches
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(cursor, query, src)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @cursor = cursor
         
     | 
| 
      
 8 
     | 
    
         
            +
                  @query = query
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @src = src
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
                # Iterator over matches.
         
     | 
| 
      
 13 
     | 
    
         
            +
                #
         
     | 
| 
      
 14 
     | 
    
         
            +
                # @yieldparam match [TreeSitter::QueryMatch]
         
     | 
| 
      
 15 
     | 
    
         
            +
                def each
         
     | 
| 
      
 16 
     | 
    
         
            +
                  return enum_for __method__ if !block_given?
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
                  while match = @cursor.next_match
         
     | 
| 
      
 19 
     | 
    
         
            +
                    if match.satisfies_text_predicate?(@query, @src)
         
     | 
| 
      
 20 
     | 
    
         
            +
                      yield match
         
     | 
| 
      
 21 
     | 
    
         
            +
                    end
         
     | 
| 
      
 22 
     | 
    
         
            +
                  end
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,14 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A {Query} predicate generic representation.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class QueryPredicate
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_accessor :operator
         
     | 
| 
      
 7 
     | 
    
         
            +
                attr_accessor :args
         
     | 
| 
      
 8 
     | 
    
         
            +
             
     | 
| 
      
 9 
     | 
    
         
            +
                def initialize(operator, args)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @operator = operator
         
     | 
| 
      
 11 
     | 
    
         
            +
                  @args = args
         
     | 
| 
      
 12 
     | 
    
         
            +
                end
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,37 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            module TreeSitter
         
     | 
| 
      
 4 
     | 
    
         
            +
              # A representation for text predicates.
         
     | 
| 
      
 5 
     | 
    
         
            +
              class TextPredicateCapture
         
     | 
| 
      
 6 
     | 
    
         
            +
                EQ_CAPTURE = 0   # Equality Capture
         
     | 
| 
      
 7 
     | 
    
         
            +
                EQ_STRING = 1    # Equality String
         
     | 
| 
      
 8 
     | 
    
         
            +
                MATCH_STRING = 2 # Match String
         
     | 
| 
      
 9 
     | 
    
         
            +
                ANY_STRING = 3   # Any String
         
     | 
| 
      
 10 
     | 
    
         
            +
             
     | 
| 
      
 11 
     | 
    
         
            +
                attr_reader :fst
         
     | 
| 
      
 12 
     | 
    
         
            +
                attr_reader :snd
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :type
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                # Create a TextPredicateCapture for {EQ_CAPTURE}.
         
     | 
| 
      
 16 
     | 
    
         
            +
                def self.eq_capture(...) = new(EQ_CAPTURE, ...)
         
     | 
| 
      
 17 
     | 
    
         
            +
                # Create a TextPredicateCapture for {EQ_STRING}.
         
     | 
| 
      
 18 
     | 
    
         
            +
                def self.eq_string(...) = new(EQ_STRING, ...)
         
     | 
| 
      
 19 
     | 
    
         
            +
                # Create a TextPredicateCapture for {MATCH_STRING}.
         
     | 
| 
      
 20 
     | 
    
         
            +
                def self.match_string(...) = new(MATCH_STRING, ...)
         
     | 
| 
      
 21 
     | 
    
         
            +
                # Create a TextPredicateCapture for {ANY_STRING}.
         
     | 
| 
      
 22 
     | 
    
         
            +
                def self.any_string(...) = new(ANY_STRING, ...)
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
                def initialize(type, fst, snd, positive, match_all)
         
     | 
| 
      
 25 
     | 
    
         
            +
                  @type = type
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @fst = fst
         
     | 
| 
      
 27 
     | 
    
         
            +
                  @snd = snd
         
     | 
| 
      
 28 
     | 
    
         
            +
                  @positive = positive
         
     | 
| 
      
 29 
     | 
    
         
            +
                  @match_all = match_all
         
     | 
| 
      
 30 
     | 
    
         
            +
                end
         
     | 
| 
      
 31 
     | 
    
         
            +
             
     | 
| 
      
 32 
     | 
    
         
            +
                # `#eq` is positive, `#not-eq` is not.
         
     | 
| 
      
 33 
     | 
    
         
            +
                def positive? = @positive
         
     | 
| 
      
 34 
     | 
    
         
            +
                # `#any-` means don't match all.
         
     | 
| 
      
 35 
     | 
    
         
            +
                def match_all? = @match_all
         
     | 
| 
      
 36 
     | 
    
         
            +
              end
         
     | 
| 
      
 37 
     | 
    
         
            +
            end
         
     | 
| 
         Binary file 
     | 
    
        data/lib/tree_sitter/version.rb
    CHANGED
    
    
    
        data/lib/tree_sitter.rb
    CHANGED
    
    | 
         @@ -6,9 +6,16 @@ end 
     | 
|
| 
       6 
6 
     | 
    
         | 
| 
       7 
7 
     | 
    
         
             
            require 'set'
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
      
 9 
     | 
    
         
            +
            require 'tree_sitter/tree_sitter'
         
     | 
| 
       9 
10 
     | 
    
         
             
            require 'tree_sitter/version'
         
     | 
| 
       10 
11 
     | 
    
         | 
| 
       11 
     | 
    
         
            -
            require 'tree_sitter/tree_sitter'
         
     | 
| 
       12 
12 
     | 
    
         
             
            require 'tree_sitter/node'
         
     | 
| 
      
 13 
     | 
    
         
            +
            require 'tree_sitter/query'
         
     | 
| 
      
 14 
     | 
    
         
            +
            require 'tree_sitter/query_captures'
         
     | 
| 
      
 15 
     | 
    
         
            +
            require 'tree_sitter/query_cursor'
         
     | 
| 
      
 16 
     | 
    
         
            +
            require 'tree_sitter/query_match'
         
     | 
| 
      
 17 
     | 
    
         
            +
            require 'tree_sitter/query_matches'
         
     | 
| 
      
 18 
     | 
    
         
            +
            require 'tree_sitter/query_predicate'
         
     | 
| 
      
 19 
     | 
    
         
            +
            require 'tree_sitter/text_predicate_capture'
         
     | 
| 
       13 
20 
     | 
    
         | 
| 
       14 
21 
     | 
    
         
             
            ObjectSpace.define_finalizer(TreeSitter::Tree.class, proc { TreeSitter::Tree.finalizer })
         
     | 
    
        data/lib/tree_stand/config.rb
    CHANGED
    
    | 
         @@ -1,13 +1,19 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         
             
            # typed: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
      
 4 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       4 
6 
     | 
    
         
             
            module TreeStand
         
     | 
| 
       5 
7 
     | 
    
         
             
              # Global configuration for the gem.
         
     | 
| 
       6 
8 
     | 
    
         
             
              # @api private
         
     | 
| 
       7 
9 
     | 
    
         
             
              class Config
         
     | 
| 
       8 
10 
     | 
    
         
             
                extend T::Sig
         
     | 
| 
       9 
11 
     | 
    
         | 
| 
       10 
     | 
    
         
            -
                sig { returns( 
     | 
| 
       11 
     | 
    
         
            -
                 
     | 
| 
      
 12 
     | 
    
         
            +
                sig { returns(T.nilable(Pathname)) }
         
     | 
| 
      
 13 
     | 
    
         
            +
                attr_reader :parser_path
         
     | 
| 
      
 14 
     | 
    
         
            +
             
     | 
| 
      
 15 
     | 
    
         
            +
                def parser_path=(path)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @parser_path = Pathname(path)
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
       12 
18 
     | 
    
         
             
              end
         
     | 
| 
       13 
19 
     | 
    
         
             
            end
         
     | 
    
        data/lib/tree_stand/node.rb
    CHANGED
    
    | 
         @@ -11,16 +11,60 @@ module TreeStand 
     | 
|
| 
       11 
11 
     | 
    
         
             
                extend Forwardable
         
     | 
| 
       12 
12 
     | 
    
         
             
                include Enumerable
         
     | 
| 
       13 
13 
     | 
    
         | 
| 
      
 14 
     | 
    
         
            +
                # @!method changed?
         
     | 
| 
      
 15 
     | 
    
         
            +
                #   @return [Boolean] true if a syntax node has been edited.
         
     | 
| 
      
 16 
     | 
    
         
            +
                # @!method child_count
         
     | 
| 
      
 17 
     | 
    
         
            +
                #   @return [Integer] the number of child nodes.
         
     | 
| 
      
 18 
     | 
    
         
            +
                # @!method extra?
         
     | 
| 
      
 19 
     | 
    
         
            +
                #   @return [Boolean] true if the node is *extra* (e.g. comments).
         
     | 
| 
      
 20 
     | 
    
         
            +
                # @!method has_error?
         
     | 
| 
      
 21 
     | 
    
         
            +
                #   @return [Boolean] true if the node is a syntax error or contains any syntax errors.
         
     | 
| 
      
 22 
     | 
    
         
            +
                # @!method missing?
         
     | 
| 
      
 23 
     | 
    
         
            +
                #   @return [Boolean] true if the parser inserted that node to recover from error.
         
     | 
| 
      
 24 
     | 
    
         
            +
                # @!method named?
         
     | 
| 
      
 25 
     | 
    
         
            +
                #   @return [Boolean] true if the node is not a literal in the grammar.
         
     | 
| 
      
 26 
     | 
    
         
            +
                # @!method named_child_count
         
     | 
| 
      
 27 
     | 
    
         
            +
                #   @return [Integer] the number of *named* children.
         
     | 
| 
       14 
28 
     | 
    
         
             
                # @!method type
         
     | 
| 
       15 
29 
     | 
    
         
             
                #   @return [Symbol] the type of the node in the tree-sitter grammar.
         
     | 
| 
       16 
30 
     | 
    
         
             
                # @!method error?
         
     | 
| 
       17 
31 
     | 
    
         
             
                #   @return [bool] true if the node is an error node.
         
     | 
| 
       18 
32 
     | 
    
         
             
                def_delegators(
         
     | 
| 
       19 
33 
     | 
    
         
             
                  :@ts_node,
         
     | 
| 
       20 
     | 
    
         
            -
                  : 
     | 
| 
      
 34 
     | 
    
         
            +
                  :changed?,
         
     | 
| 
      
 35 
     | 
    
         
            +
                  :child_count,
         
     | 
| 
       21 
36 
     | 
    
         
             
                  :error?,
         
     | 
| 
      
 37 
     | 
    
         
            +
                  :extra?,
         
     | 
| 
      
 38 
     | 
    
         
            +
                  :has_error?,
         
     | 
| 
      
 39 
     | 
    
         
            +
                  :missing?,
         
     | 
| 
      
 40 
     | 
    
         
            +
                  :named?,
         
     | 
| 
      
 41 
     | 
    
         
            +
                  :named_child_count,
         
     | 
| 
      
 42 
     | 
    
         
            +
                  :type,
         
     | 
| 
       22 
43 
     | 
    
         
             
                )
         
     | 
| 
       23 
44 
     | 
    
         | 
| 
      
 45 
     | 
    
         
            +
                # These are methods defined in {TreeStand::Node} but map to something
         
     | 
| 
      
 46 
     | 
    
         
            +
                # in {TreeSitter::Node}, because we want a more idiomatic API.
         
     | 
| 
      
 47 
     | 
    
         
            +
                THINLY_REMAPPED_METHODS = {
         
     | 
| 
      
 48 
     | 
    
         
            +
                  '[]': :[],
         
     | 
| 
      
 49 
     | 
    
         
            +
                  fetch: :fetch,
         
     | 
| 
      
 50 
     | 
    
         
            +
                  field: :child_by_field_name,
         
     | 
| 
      
 51 
     | 
    
         
            +
                  next: :next_sibling,
         
     | 
| 
      
 52 
     | 
    
         
            +
                  prev: :prev_sibling,
         
     | 
| 
      
 53 
     | 
    
         
            +
                  next_named: :next_named_sibling,
         
     | 
| 
      
 54 
     | 
    
         
            +
                  prev_named: :prev_named_sibling,
         
     | 
| 
      
 55 
     | 
    
         
            +
                  field_names: :fields,
         
     | 
| 
      
 56 
     | 
    
         
            +
                }.freeze
         
     | 
| 
      
 57 
     | 
    
         
            +
             
     | 
| 
      
 58 
     | 
    
         
            +
                # These are methods from {TreeSitter} that are thinly wrapped to create
         
     | 
| 
      
 59 
     | 
    
         
            +
                # {TreeStand::Node} instead.
         
     | 
| 
      
 60 
     | 
    
         
            +
                THINLY_WRAPPED_METHODS = (
         
     | 
| 
      
 61 
     | 
    
         
            +
                  %i[
         
     | 
| 
      
 62 
     | 
    
         
            +
                    child
         
     | 
| 
      
 63 
     | 
    
         
            +
                    named_child
         
     | 
| 
      
 64 
     | 
    
         
            +
                    parent
         
     | 
| 
      
 65 
     | 
    
         
            +
                  ] + THINLY_REMAPPED_METHODS.keys
         
     | 
| 
      
 66 
     | 
    
         
            +
                ).freeze
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
       24 
68 
     | 
    
         
             
                sig { returns(TreeStand::Tree) }
         
     | 
| 
       25 
69 
     | 
    
         
             
                attr_reader :tree
         
     | 
| 
       26 
70 
     | 
    
         | 
| 
         @@ -32,7 +76,6 @@ module TreeStand 
     | 
|
| 
       32 
76 
     | 
    
         
             
                def initialize(tree, ts_node)
         
     | 
| 
       33 
77 
     | 
    
         
             
                  @tree = tree
         
     | 
| 
       34 
78 
     | 
    
         
             
                  @ts_node = ts_node
         
     | 
| 
       35 
     | 
    
         
            -
                  @fields = @ts_node.each_field.to_a.map(&:first)
         
     | 
| 
       36 
79 
     | 
    
         
             
                end
         
     | 
| 
       37 
80 
     | 
    
         | 
| 
       38 
81 
     | 
    
         
             
                # TreeSitter uses a `TreeSitter::Cursor` to iterate over matches by calling
         
     | 
| 
         @@ -131,6 +174,74 @@ module TreeStand 
     | 
|
| 
       131 
174 
     | 
    
         
             
                  enumerator
         
     | 
| 
       132 
175 
     | 
    
         
             
                end
         
     | 
| 
       133 
176 
     | 
    
         | 
| 
      
 177 
     | 
    
         
            +
                # Enumerate named children.
         
     | 
| 
      
 178 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 179 
     | 
    
         
            +
                #   node.text # => "3 * 4"
         
     | 
| 
      
 180 
     | 
    
         
            +
                #
         
     | 
| 
      
 181 
     | 
    
         
            +
                # @example Iterate over the child nodes
         
     | 
| 
      
 182 
     | 
    
         
            +
                #   node.each_named do |child|
         
     | 
| 
      
 183 
     | 
    
         
            +
                #     print child.text
         
     | 
| 
      
 184 
     | 
    
         
            +
                #   end
         
     | 
| 
      
 185 
     | 
    
         
            +
                #   # prints: 34
         
     | 
| 
      
 186 
     | 
    
         
            +
                #
         
     | 
| 
      
 187 
     | 
    
         
            +
                # @example Enumerable methods
         
     | 
| 
      
 188 
     | 
    
         
            +
                #   node.each_named.map(&:text) # => ["3", "4"]
         
     | 
| 
      
 189 
     | 
    
         
            +
                #
         
     | 
| 
      
 190 
     | 
    
         
            +
                # @yieldparam child [TreeStand::Node]
         
     | 
| 
      
 191 
     | 
    
         
            +
                sig do
         
     | 
| 
      
 192 
     | 
    
         
            +
                  params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
         
     | 
| 
      
 193 
     | 
    
         
            +
                    .returns(T::Enumerator[TreeStand::Node])
         
     | 
| 
      
 194 
     | 
    
         
            +
                end
         
     | 
| 
      
 195 
     | 
    
         
            +
                def each_named(&block)
         
     | 
| 
      
 196 
     | 
    
         
            +
                  enumerator = Enumerator.new do |yielder|
         
     | 
| 
      
 197 
     | 
    
         
            +
                    @ts_node.each_named do |child|
         
     | 
| 
      
 198 
     | 
    
         
            +
                      yielder << TreeStand::Node.new(@tree, child)
         
     | 
| 
      
 199 
     | 
    
         
            +
                    end
         
     | 
| 
      
 200 
     | 
    
         
            +
                  end
         
     | 
| 
      
 201 
     | 
    
         
            +
                  enumerator.each(&block) if block_given?
         
     | 
| 
      
 202 
     | 
    
         
            +
                  enumerator
         
     | 
| 
      
 203 
     | 
    
         
            +
                end
         
     | 
| 
      
 204 
     | 
    
         
            +
             
     | 
| 
      
 205 
     | 
    
         
            +
                # Iterate of (field, child).
         
     | 
| 
      
 206 
     | 
    
         
            +
                #
         
     | 
| 
      
 207 
     | 
    
         
            +
                # @example
         
     | 
| 
      
 208 
     | 
    
         
            +
                #   node.text # => "3 * 4"
         
     | 
| 
      
 209 
     | 
    
         
            +
                #
         
     | 
| 
      
 210 
     | 
    
         
            +
                # @example Iterate over the child nodes
         
     | 
| 
      
 211 
     | 
    
         
            +
                #   node.each_field do |field, child|
         
     | 
| 
      
 212 
     | 
    
         
            +
                #     puts "#{field}: #{child.text}"
         
     | 
| 
      
 213 
     | 
    
         
            +
                #   end
         
     | 
| 
      
 214 
     | 
    
         
            +
                #   # prints:
         
     | 
| 
      
 215 
     | 
    
         
            +
                #   #  left: 3
         
     | 
| 
      
 216 
     | 
    
         
            +
                #   #  right: 4
         
     | 
| 
      
 217 
     | 
    
         
            +
                #
         
     | 
| 
      
 218 
     | 
    
         
            +
                # @example Enumerable methods
         
     | 
| 
      
 219 
     | 
    
         
            +
                #   node.each_field.map { |f, c| "#{f}: #{c}" } # => ["left: 3", "right: 4"]
         
     | 
| 
      
 220 
     | 
    
         
            +
                #
         
     | 
| 
      
 221 
     | 
    
         
            +
                # @yieldparam field [Symbol]
         
     | 
| 
      
 222 
     | 
    
         
            +
                # @yieldparam child [TreeStand::Node]
         
     | 
| 
      
 223 
     | 
    
         
            +
                sig do
         
     | 
| 
      
 224 
     | 
    
         
            +
                  params(block: T.nilable(T.proc.params(node: TreeStand::Node).returns(BasicObject)))
         
     | 
| 
      
 225 
     | 
    
         
            +
                    .returns(T::Enumerator[[Symbol, TreeStand::Node]])
         
     | 
| 
      
 226 
     | 
    
         
            +
                end
         
     | 
| 
      
 227 
     | 
    
         
            +
                def each_field(&block)
         
     | 
| 
      
 228 
     | 
    
         
            +
                  enumerator = Enumerator.new do |yielder|
         
     | 
| 
      
 229 
     | 
    
         
            +
                    @ts_node.each_field do |field, child|
         
     | 
| 
      
 230 
     | 
    
         
            +
                      yielder << [field.to_sym, TreeStand::Node.new(@tree, child)]
         
     | 
| 
      
 231 
     | 
    
         
            +
                    end
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
      
 233 
     | 
    
         
            +
                  enumerator.each(&block) if block_given?
         
     | 
| 
      
 234 
     | 
    
         
            +
                  enumerator
         
     | 
| 
      
 235 
     | 
    
         
            +
                end
         
     | 
| 
      
 236 
     | 
    
         
            +
             
     | 
| 
      
 237 
     | 
    
         
            +
                # @example Enumerable methods
         
     | 
| 
      
 238 
     | 
    
         
            +
                #   node.named.map(&:text) # => ["3", "4"]
         
     | 
| 
      
 239 
     | 
    
         
            +
                alias_method :named, :each_named
         
     | 
| 
      
 240 
     | 
    
         
            +
             
     | 
| 
      
 241 
     | 
    
         
            +
                # @example Enumerable methods
         
     | 
| 
      
 242 
     | 
    
         
            +
                #   node.fields.map { |f, c| "#{f}: #{c}" } # => ["left: 3", "right: 4"]
         
     | 
| 
      
 243 
     | 
    
         
            +
                alias_method :fields, :each_field
         
     | 
| 
      
 244 
     | 
    
         
            +
             
     | 
| 
       134 
245 
     | 
    
         
             
                # (see TreeStand::Visitors::TreeWalker)
         
     | 
| 
       135 
246 
     | 
    
         
             
                # Backed by {TreeStand::Visitors::TreeWalker}.
         
     | 
| 
       136 
247 
     | 
    
         
             
                #
         
     | 
| 
         @@ -154,15 +265,6 @@ module TreeStand 
     | 
|
| 
       154 
265 
     | 
    
         
             
                  enumerator
         
     | 
| 
       155 
266 
     | 
    
         
             
                end
         
     | 
| 
       156 
267 
     | 
    
         | 
| 
       157 
     | 
    
         
            -
                # @example
         
     | 
| 
       158 
     | 
    
         
            -
                #   node.text # => "4"
         
     | 
| 
       159 
     | 
    
         
            -
                #   node.parent.text # => "3 * 4"
         
     | 
| 
       160 
     | 
    
         
            -
                #   node.parent.parent.text # => "1 + 3 * 4"
         
     | 
| 
       161 
     | 
    
         
            -
                sig { returns(TreeStand::Node) }
         
     | 
| 
       162 
     | 
    
         
            -
                def parent
         
     | 
| 
       163 
     | 
    
         
            -
                  TreeStand::Node.new(@tree, @ts_node.parent)
         
     | 
| 
       164 
     | 
    
         
            -
                end
         
     | 
| 
       165 
     | 
    
         
            -
             
     | 
| 
       166 
268 
     | 
    
         
             
                # @example
         
     | 
| 
       167 
269 
     | 
    
         
             
                #   node.text # => "3 * 4"
         
     | 
| 
       168 
270 
     | 
    
         
             
                #   node.to_a.map(&:text) # => ["3", "*", "4"]
         
     | 
| 
         @@ -174,7 +276,7 @@ module TreeStand 
     | 
|
| 
       174 
276 
     | 
    
         
             
                # wraps the parent {TreeStand::Tree #tree} and has access to the source document.
         
     | 
| 
       175 
277 
     | 
    
         
             
                sig { returns(String) }
         
     | 
| 
       176 
278 
     | 
    
         
             
                def text
         
     | 
| 
       177 
     | 
    
         
            -
                  T.must(@tree.document 
     | 
| 
      
 279 
     | 
    
         
            +
                  T.must(@tree.document.byteslice(@ts_node.start_byte...@ts_node.end_byte))
         
     | 
| 
       178 
280 
     | 
    
         
             
                end
         
     | 
| 
       179 
281 
     | 
    
         | 
| 
       180 
282 
     | 
    
         
             
                # This class overrides the `method_missing` method to delegate to the
         
     | 
| 
         @@ -193,10 +295,20 @@ module TreeStand 
     | 
|
| 
       193 
295 
     | 
    
         
             
                #
         
     | 
| 
       194 
296 
     | 
    
         
             
                # @overload method_missing(method_name, *args, &block)
         
     | 
| 
       195 
297 
     | 
    
         
             
                #   @raise [NoMethodError]
         
     | 
| 
       196 
     | 
    
         
            -
                def method_missing(method, *args, &block)
         
     | 
| 
       197 
     | 
    
         
            -
                   
     | 
| 
       198 
     | 
    
         
            -
             
     | 
| 
       199 
     | 
    
         
            -
             
     | 
| 
      
 298 
     | 
    
         
            +
                def method_missing(method, *args, **kwargs, &block)
         
     | 
| 
      
 299 
     | 
    
         
            +
                  if thinly_wrapped?(method)
         
     | 
| 
      
 300 
     | 
    
         
            +
                    from(
         
     | 
| 
      
 301 
     | 
    
         
            +
                      T.unsafe(@ts_node)
         
     | 
| 
      
 302 
     | 
    
         
            +
                        .public_send(
         
     | 
| 
      
 303 
     | 
    
         
            +
                          THINLY_REMAPPED_METHODS[method] || method,
         
     | 
| 
      
 304 
     | 
    
         
            +
                          *args,
         
     | 
| 
      
 305 
     | 
    
         
            +
                          **kwargs,
         
     | 
| 
      
 306 
     | 
    
         
            +
                          &block
         
     | 
| 
      
 307 
     | 
    
         
            +
                        ),
         
     | 
| 
      
 308 
     | 
    
         
            +
                    )
         
     | 
| 
      
 309 
     | 
    
         
            +
                  else
         
     | 
| 
      
 310 
     | 
    
         
            +
                    super
         
     | 
| 
      
 311 
     | 
    
         
            +
                  end
         
     | 
| 
       200 
312 
     | 
    
         
             
                end
         
     | 
| 
       201 
313 
     | 
    
         | 
| 
       202 
314 
     | 
    
         
             
                sig { params(other: Object).returns(T::Boolean) }
         
     | 
| 
         @@ -217,8 +329,31 @@ module TreeStand 
     | 
|
| 
       217 
329 
     | 
    
         | 
| 
       218 
330 
     | 
    
         
             
                private
         
     | 
| 
       219 
331 
     | 
    
         | 
| 
       220 
     | 
    
         
            -
                def respond_to_missing?(method, *)
         
     | 
| 
       221 
     | 
    
         
            -
                   
     | 
| 
      
 332 
     | 
    
         
            +
                def respond_to_missing?(method, *_args, **_kwargs)
         
     | 
| 
      
 333 
     | 
    
         
            +
                  thinly_wrapped?(method) || super
         
     | 
| 
      
 334 
     | 
    
         
            +
                end
         
     | 
| 
      
 335 
     | 
    
         
            +
             
     | 
| 
      
 336 
     | 
    
         
            +
                def thinly_wrapped?(method)
         
     | 
| 
      
 337 
     | 
    
         
            +
                  @ts_node.fields.include?(method) || THINLY_WRAPPED_METHODS.include?(method)
         
     | 
| 
      
 338 
     | 
    
         
            +
                end
         
     | 
| 
      
 339 
     | 
    
         
            +
             
     | 
| 
      
 340 
     | 
    
         
            +
                # FIXME: Make more generic if needed in other classes.
         
     | 
| 
      
 341 
     | 
    
         
            +
             
     | 
| 
      
 342 
     | 
    
         
            +
                # 1 instance of {TreeStand} from a {TreeSitter} equivalent.
         
     | 
| 
      
 343 
     | 
    
         
            +
                def from_a(node)
         
     | 
| 
      
 344 
     | 
    
         
            +
                  node.is_a?(TreeSitter::Node) ? TreeStand::Node.new(@tree, node) : node
         
     | 
| 
      
 345 
     | 
    
         
            +
                end
         
     | 
| 
      
 346 
     | 
    
         
            +
             
     | 
| 
      
 347 
     | 
    
         
            +
                # {TreeSitter} instance, or a collection ({Array, Hash})
         
     | 
| 
      
 348 
     | 
    
         
            +
                def from(obj)
         
     | 
| 
      
 349 
     | 
    
         
            +
                  case obj
         
     | 
| 
      
 350 
     | 
    
         
            +
                  when Array
         
     | 
| 
      
 351 
     | 
    
         
            +
                    obj.map { |n| from(n) }
         
     | 
| 
      
 352 
     | 
    
         
            +
                  when Hash
         
     | 
| 
      
 353 
     | 
    
         
            +
                    obj.to_h { |k, v| [from(k), from(v)] }
         
     | 
| 
      
 354 
     | 
    
         
            +
                  else
         
     | 
| 
      
 355 
     | 
    
         
            +
                    from_a(obj)
         
     | 
| 
      
 356 
     | 
    
         
            +
                  end
         
     | 
| 
       222 
357 
     | 
    
         
             
                end
         
     | 
| 
       223 
358 
     | 
    
         
             
              end
         
     | 
| 
       224 
359 
     | 
    
         
             
            end
         
     | 
    
        data/lib/tree_stand/parser.rb
    CHANGED
    
    | 
         @@ -1,6 +1,8 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
       2 
2 
     | 
    
         
             
            # typed: true
         
     | 
| 
       3 
3 
     | 
    
         | 
| 
      
 4 
     | 
    
         
            +
            require 'pathname'
         
     | 
| 
      
 5 
     | 
    
         
            +
             
     | 
| 
       4 
6 
     | 
    
         
             
            module TreeStand
         
     | 
| 
       5 
7 
     | 
    
         
             
              # Wrapper around the TreeSitter parser. It looks up the parser by filename in
         
     | 
| 
       6 
8 
     | 
    
         
             
              # the configured parsers directory.
         
     | 
| 
         @@ -14,6 +16,14 @@ module TreeStand 
     | 
|
| 
       14 
16 
     | 
    
         
             
              #
         
     | 
| 
       15 
17 
     | 
    
         
             
              #   # Looks for a parser in `path/to/parser/folder/ruby.{so,dylib}`
         
     | 
| 
       16 
18 
     | 
    
         
             
              #   ruby_parser = TreeStand::Parser.new("ruby")
         
     | 
| 
      
 19 
     | 
    
         
            +
              #
         
     | 
| 
      
 20 
     | 
    
         
            +
              # If no {TreeStand::Config#parser_path} is setup, {TreeStand} will lookup in a
         
     | 
| 
      
 21 
     | 
    
         
            +
              # set of default paths.  You can always override any configuration by passing
         
     | 
| 
      
 22 
     | 
    
         
            +
              # the environment variable `TREE_SITTER_PARSERS` (colon-separated).
         
     | 
| 
      
 23 
     | 
    
         
            +
              #
         
     | 
| 
      
 24 
     | 
    
         
            +
              # @see language
         
     | 
| 
      
 25 
     | 
    
         
            +
              # @see search_for_lib
         
     | 
| 
      
 26 
     | 
    
         
            +
              # @see LIBDIRS
         
     | 
| 
       17 
27 
     | 
    
         
             
              class Parser
         
     | 
| 
       18 
28 
     | 
    
         
             
                extend T::Sig
         
     | 
| 
       19 
29 
     | 
    
         | 
| 
         @@ -23,14 +33,160 @@ module TreeStand 
     | 
|
| 
       23 
33 
     | 
    
         
             
                sig { returns(TreeSitter::Parser) }
         
     | 
| 
       24 
34 
     | 
    
         
             
                attr_reader :ts_parser
         
     | 
| 
       25 
35 
     | 
    
         | 
| 
      
 36 
     | 
    
         
            +
                # A colon-seprated list of paths pointing to directories that can contain parsers.
         
     | 
| 
      
 37 
     | 
    
         
            +
                # Order matters.
         
     | 
| 
      
 38 
     | 
    
         
            +
                # Takes precedence over default lookup paths.
         
     | 
| 
      
 39 
     | 
    
         
            +
                ENV_PARSERS =
         
     | 
| 
      
 40 
     | 
    
         
            +
                  ENV['TREE_SITTER_PARSERS']
         
     | 
| 
      
 41 
     | 
    
         
            +
                    &.split(':')
         
     | 
| 
      
 42 
     | 
    
         
            +
                    &.map { |v| Pathname(v) }
         
     | 
| 
      
 43 
     | 
    
         
            +
                    .freeze
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
                # The default paths we use to lookup parsers when no specific
         
     | 
| 
      
 46 
     | 
    
         
            +
                # {TreeStand::Config#parser_path} is `nil`.
         
     | 
| 
      
 47 
     | 
    
         
            +
                # Order matters.
         
     | 
| 
      
 48 
     | 
    
         
            +
                LIBDIRS = [
         
     | 
| 
      
 49 
     | 
    
         
            +
                  'tree-sitter-parsers',
         
     | 
| 
      
 50 
     | 
    
         
            +
                  '/opt/local/lib',
         
     | 
| 
      
 51 
     | 
    
         
            +
                  '/opt/lib',
         
     | 
| 
      
 52 
     | 
    
         
            +
                  '/usr/local/lib',
         
     | 
| 
      
 53 
     | 
    
         
            +
                  '/usr/lib',
         
     | 
| 
      
 54 
     | 
    
         
            +
                ].map { |p| Pathname(p) }.freeze
         
     | 
| 
      
 55 
     | 
    
         
            +
             
     | 
| 
      
 56 
     | 
    
         
            +
                # The library directories we need to look into.
         
     | 
| 
      
 57 
     | 
    
         
            +
                #
         
     | 
| 
      
 58 
     | 
    
         
            +
                # @return [Array<Pathname>] the list of candidate places to use when searching for parsers.
         
     | 
| 
      
 59 
     | 
    
         
            +
                #
         
     | 
| 
      
 60 
     | 
    
         
            +
                # @see ENV_PARSERS
         
     | 
| 
      
 61 
     | 
    
         
            +
                # @see LIBDIRS
         
     | 
| 
      
 62 
     | 
    
         
            +
                sig { returns(T::Array[Pathname]) }
         
     | 
| 
      
 63 
     | 
    
         
            +
                def self.lib_dirs = [
         
     | 
| 
      
 64 
     | 
    
         
            +
                  *ENV_PARSERS,
         
     | 
| 
      
 65 
     | 
    
         
            +
                  *(TreeStand.config.parser_path ? [TreeStand.config.parser_path] : LIBDIRS),
         
     | 
| 
      
 66 
     | 
    
         
            +
                ].compact
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
                # The platform-specific extension of the parser.
         
     | 
| 
      
 69 
     | 
    
         
            +
                # @return [String] `dylib` or `so` for mac or linux.
         
     | 
| 
      
 70 
     | 
    
         
            +
                sig { returns(String) }
         
     | 
| 
      
 71 
     | 
    
         
            +
                def self.ext
         
     | 
| 
      
 72 
     | 
    
         
            +
                  case Gem::Platform.local.os
         
     | 
| 
      
 73 
     | 
    
         
            +
                  in   /darwin/ then 'dylib'
         
     | 
| 
      
 74 
     | 
    
         
            +
                  else               'so'
         
     | 
| 
      
 75 
     | 
    
         
            +
                  end
         
     | 
| 
      
 76 
     | 
    
         
            +
                end
         
     | 
| 
      
 77 
     | 
    
         
            +
             
     | 
| 
      
 78 
     | 
    
         
            +
                # Lookup a parser by name.
         
     | 
| 
      
 79 
     | 
    
         
            +
                #
         
     | 
| 
      
 80 
     | 
    
         
            +
                # Precedence:
         
     | 
| 
      
 81 
     | 
    
         
            +
                # 1. `Env['TREE_SITTER_PARSERS]`
         
     | 
| 
      
 82 
     | 
    
         
            +
                # 2. {TreeStand::Config#parser_path}
         
     | 
| 
      
 83 
     | 
    
         
            +
                # 3. {LIBDIRS}
         
     | 
| 
      
 84 
     | 
    
         
            +
                #
         
     | 
| 
      
 85 
     | 
    
         
            +
                # If a {TreeStand::Config#parser_path} is `nil`, {LIBDIRS} is used.
         
     | 
| 
      
 86 
     | 
    
         
            +
                # If a {TreeStand::Config#parser_path} is a {::Pathname}, {LIBDIRS} is ignored.
         
     | 
| 
      
 87 
     | 
    
         
            +
                sig { params(name: String).returns(T.nilable(Pathname)) }
         
     | 
| 
      
 88 
     | 
    
         
            +
                def self.search_for_lib(name)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  files = [
         
     | 
| 
      
 90 
     | 
    
         
            +
                    name,
         
     | 
| 
      
 91 
     | 
    
         
            +
                    "tree-sitter-#{name}",
         
     | 
| 
      
 92 
     | 
    
         
            +
                    "libtree-sitter-#{name}",
         
     | 
| 
      
 93 
     | 
    
         
            +
                  ].map { |v| "#{v}.#{ext}" }
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     | 
| 
      
 95 
     | 
    
         
            +
                  res = lib_dirs
         
     | 
| 
      
 96 
     | 
    
         
            +
                    .product(files)
         
     | 
| 
      
 97 
     | 
    
         
            +
                    .find do |dir, so|
         
     | 
| 
      
 98 
     | 
    
         
            +
                      path = dir / so
         
     | 
| 
      
 99 
     | 
    
         
            +
                      path = dir / name / so if !path.exist?
         
     | 
| 
      
 100 
     | 
    
         
            +
                      break path.expand_path if path.exist?
         
     | 
| 
      
 101 
     | 
    
         
            +
                    end
         
     | 
| 
      
 102 
     | 
    
         
            +
                  res.is_a?(Array) ? nil : res
         
     | 
| 
      
 103 
     | 
    
         
            +
                end
         
     | 
| 
      
 104 
     | 
    
         
            +
             
     | 
| 
      
 105 
     | 
    
         
            +
                # Generates a string message on where parser lookup happens.
         
     | 
| 
      
 106 
     | 
    
         
            +
                #
         
     | 
| 
      
 107 
     | 
    
         
            +
                # @return [String] A pretty message.
         
     | 
| 
      
 108 
     | 
    
         
            +
                sig { returns(String) }
         
     | 
| 
      
 109 
     | 
    
         
            +
                def self.search_lib_message
         
     | 
| 
      
 110 
     | 
    
         
            +
                  indent = 2
         
     | 
| 
      
 111 
     | 
    
         
            +
                  pretty = ->(arr) {
         
     | 
| 
      
 112 
     | 
    
         
            +
                    if arr
         
     | 
| 
      
 113 
     | 
    
         
            +
                      arr
         
     | 
| 
      
 114 
     | 
    
         
            +
                        .compact
         
     | 
| 
      
 115 
     | 
    
         
            +
                        .map { |v| "#{' ' * indent}#{v.expand_path}" }
         
     | 
| 
      
 116 
     | 
    
         
            +
                        .join("\n")
         
     | 
| 
      
 117 
     | 
    
         
            +
                    end
         
     | 
| 
      
 118 
     | 
    
         
            +
                  }
         
     | 
| 
      
 119 
     | 
    
         
            +
                  <<~MSG
         
     | 
| 
      
 120 
     | 
    
         
            +
                    From ENV['TREE_SITTER_PARSERS']:
         
     | 
| 
      
 121 
     | 
    
         
            +
                    #{pretty.call(ENV_PARSERS)}
         
     | 
| 
      
 122 
     | 
    
         
            +
             
     | 
| 
      
 123 
     | 
    
         
            +
                    From TreeStand.config.parser_path:
         
     | 
| 
      
 124 
     | 
    
         
            +
                    #{pretty.call([TreeStand.config.parser_path])}
         
     | 
| 
      
 125 
     | 
    
         
            +
             
     | 
| 
      
 126 
     | 
    
         
            +
                    From Defaults:
         
     | 
| 
      
 127 
     | 
    
         
            +
                    #{pretty.call(LIBDIRS)}
         
     | 
| 
      
 128 
     | 
    
         
            +
                  MSG
         
     | 
| 
      
 129 
     | 
    
         
            +
                end
         
     | 
| 
      
 130 
     | 
    
         
            +
             
     | 
| 
      
 131 
     | 
    
         
            +
                # Load a language from configuration or default lookup paths.
         
     | 
| 
      
 132 
     | 
    
         
            +
                #
         
     | 
| 
      
 133 
     | 
    
         
            +
                # @example Load java from default paths
         
     | 
| 
      
 134 
     | 
    
         
            +
                #    # This will look for:
         
     | 
| 
      
 135 
     | 
    
         
            +
                #    #
         
     | 
| 
      
 136 
     | 
    
         
            +
                #    #   tree-sitter-parsers/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 137 
     | 
    
         
            +
                #    #   /opt/local/lib/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 138 
     | 
    
         
            +
                #    #   /opt/lib/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 139 
     | 
    
         
            +
                #    #   /usr/local/lib/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 140 
     | 
    
         
            +
                #    #   /usr/lib/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 141 
     | 
    
         
            +
                #    #
         
     | 
| 
      
 142 
     | 
    
         
            +
                #    java = TreeStand::Parser.language('java')
         
     | 
| 
      
 143 
     | 
    
         
            +
                #
         
     | 
| 
      
 144 
     | 
    
         
            +
                # @example Load java from a configured path
         
     | 
| 
      
 145 
     | 
    
         
            +
                #    # This will look for:
         
     | 
| 
      
 146 
     | 
    
         
            +
                #    #
         
     | 
| 
      
 147 
     | 
    
         
            +
                #    #   /my/path/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 148 
     | 
    
         
            +
                #    #
         
     | 
| 
      
 149 
     | 
    
         
            +
                #    TreeStand.config.parser_path = '/my/path'
         
     | 
| 
      
 150 
     | 
    
         
            +
                #    java = TreeStand::Parser.language('java')
         
     | 
| 
      
 151 
     | 
    
         
            +
                #
         
     | 
| 
      
 152 
     | 
    
         
            +
                # @example Load java from environment variables
         
     | 
| 
      
 153 
     | 
    
         
            +
                #    # This will look for:
         
     | 
| 
      
 154 
     | 
    
         
            +
                #    #
         
     | 
| 
      
 155 
     | 
    
         
            +
                #    #   /my/forced/env/path/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 156 
     | 
    
         
            +
                #    #   /my/path/(java/)?(libtree-sitter-)?java.{ext}
         
     | 
| 
      
 157 
     | 
    
         
            +
                #    #
         
     | 
| 
      
 158 
     | 
    
         
            +
                #    # … and the same works for the default paths if `TreeStand.config.parser_path`
         
     | 
| 
      
 159 
     | 
    
         
            +
                #    # was `nil`
         
     | 
| 
      
 160 
     | 
    
         
            +
                #    ENV['TREE_SITTER_PARSERS'] = '/my/forced/env/path'
         
     | 
| 
      
 161 
     | 
    
         
            +
                #    TreeStand.config.parser_path = '/my/path'
         
     | 
| 
      
 162 
     | 
    
         
            +
                #    java = TreeStand::Parser.language('java')
         
     | 
| 
      
 163 
     | 
    
         
            +
                #
         
     | 
| 
      
 164 
     | 
    
         
            +
                # @param name [String] the name of the parser.
         
     | 
| 
      
 165 
     | 
    
         
            +
                #   This name is used to load the symbol from the compiled parser, replacing `-` with `_`.
         
     | 
| 
      
 166 
     | 
    
         
            +
                # @return [TreeSitter:language] a language object to use in your parsers.
         
     | 
| 
      
 167 
     | 
    
         
            +
                # @raise [RuntimeError] if the parser was not found.
         
     | 
| 
      
 168 
     | 
    
         
            +
                # @see search_for_lib
         
     | 
| 
      
 169 
     | 
    
         
            +
                sig { params(name: String).returns(TreeSitter::Language) }
         
     | 
| 
      
 170 
     | 
    
         
            +
                def self.language(name)
         
     | 
| 
      
 171 
     | 
    
         
            +
                  lib = search_for_lib(name)
         
     | 
| 
      
 172 
     | 
    
         
            +
             
     | 
| 
      
 173 
     | 
    
         
            +
                  if lib.nil?
         
     | 
| 
      
 174 
     | 
    
         
            +
                    raise <<~MSG
         
     | 
| 
      
 175 
     | 
    
         
            +
                      Failed to load a parser for #{name}.
         
     | 
| 
      
 176 
     | 
    
         
            +
             
     | 
| 
      
 177 
     | 
    
         
            +
                      #{search_lib_message}
         
     | 
| 
      
 178 
     | 
    
         
            +
                    MSG
         
     | 
| 
      
 179 
     | 
    
         
            +
                  end
         
     | 
| 
      
 180 
     | 
    
         
            +
             
     | 
| 
      
 181 
     | 
    
         
            +
                  # We know that the bindings will accept `lib`, but I don't know how to tell sorbet
         
     | 
| 
      
 182 
     | 
    
         
            +
                  # the types in ext/tree_sitter where `load` is defined.
         
     | 
| 
      
 183 
     | 
    
         
            +
                  TreeSitter::Language.load(name.tr('-', '_'), T.unsafe(lib))
         
     | 
| 
      
 184 
     | 
    
         
            +
                end
         
     | 
| 
      
 185 
     | 
    
         
            +
             
     | 
| 
       26 
186 
     | 
    
         
             
                # @param language [String]
         
     | 
| 
       27 
187 
     | 
    
         
             
                sig { params(language: String).void }
         
     | 
| 
       28 
188 
     | 
    
         
             
                def initialize(language)
         
     | 
| 
       29 
     | 
    
         
            -
                  @ 
     | 
| 
       30 
     | 
    
         
            -
                  @ts_language = TreeSitter::Language.load(
         
     | 
| 
       31 
     | 
    
         
            -
                    language,
         
     | 
| 
       32 
     | 
    
         
            -
                    "#{TreeStand.config.parser_path}/#{language}.so",
         
     | 
| 
       33 
     | 
    
         
            -
                  )
         
     | 
| 
      
 189 
     | 
    
         
            +
                  @ts_language = Parser.language(language)
         
     | 
| 
       34 
190 
     | 
    
         
             
                  @ts_parser = TreeSitter::Parser.new.tap do |parser|
         
     | 
| 
       35 
191 
     | 
    
         
             
                    parser.language = @ts_language
         
     | 
| 
       36 
192 
     | 
    
         
             
                  end
         
     | 
    
        metadata
    CHANGED
    
    | 
         @@ -1,7 +1,7 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            --- !ruby/object:Gem::Specification
         
     | 
| 
       2 
2 
     | 
    
         
             
            name: ruby_tree_sitter
         
     | 
| 
       3 
3 
     | 
    
         
             
            version: !ruby/object:Gem::Version
         
     | 
| 
       4 
     | 
    
         
            -
              version: 1. 
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.3.0
         
     | 
| 
       5 
5 
     | 
    
         
             
            platform: x86_64-linux
         
     | 
| 
       6 
6 
     | 
    
         
             
            authors:
         
     | 
| 
       7 
7 
     | 
    
         
             
            - Firas al-Khalil
         
     | 
| 
         @@ -9,7 +9,7 @@ authors: 
     | 
|
| 
       9 
9 
     | 
    
         
             
            autorequire: 
         
     | 
| 
       10 
10 
     | 
    
         
             
            bindir: bin
         
     | 
| 
       11 
11 
     | 
    
         
             
            cert_chain: []
         
     | 
| 
       12 
     | 
    
         
            -
            date: 2024- 
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2024-06-26 00:00:00.000000000 Z
         
     | 
| 
       13 
13 
     | 
    
         
             
            dependencies:
         
     | 
| 
       14 
14 
     | 
    
         
             
            - !ruby/object:Gem::Dependency
         
     | 
| 
       15 
15 
     | 
    
         
             
              name: sorbet-runtime
         
     | 
| 
         @@ -74,7 +74,15 @@ files: 
     | 
|
| 
       74 
74 
     | 
    
         
             
            - ext/tree_sitter/tree_sitter.c
         
     | 
| 
       75 
75 
     | 
    
         
             
            - ext/tree_sitter/tree_sitter.h
         
     | 
| 
       76 
76 
     | 
    
         
             
            - lib/tree_sitter.rb
         
     | 
| 
      
 77 
     | 
    
         
            +
            - lib/tree_sitter/helpers.rb
         
     | 
| 
       77 
78 
     | 
    
         
             
            - lib/tree_sitter/node.rb
         
     | 
| 
      
 79 
     | 
    
         
            +
            - lib/tree_sitter/query.rb
         
     | 
| 
      
 80 
     | 
    
         
            +
            - lib/tree_sitter/query_captures.rb
         
     | 
| 
      
 81 
     | 
    
         
            +
            - lib/tree_sitter/query_cursor.rb
         
     | 
| 
      
 82 
     | 
    
         
            +
            - lib/tree_sitter/query_match.rb
         
     | 
| 
      
 83 
     | 
    
         
            +
            - lib/tree_sitter/query_matches.rb
         
     | 
| 
      
 84 
     | 
    
         
            +
            - lib/tree_sitter/query_predicate.rb
         
     | 
| 
      
 85 
     | 
    
         
            +
            - lib/tree_sitter/text_predicate_capture.rb
         
     | 
| 
       78 
86 
     | 
    
         
             
            - lib/tree_sitter/tree_sitter.so
         
     | 
| 
       79 
87 
     | 
    
         
             
            - lib/tree_sitter/version.rb
         
     | 
| 
       80 
88 
     | 
    
         
             
            - lib/tree_stand.rb
         
     |