|
1 | 1 | /// A tree sitter parsing library for Dart. |
| 2 | +// ignore_for_file: unused_element |
| 3 | + |
2 | 4 | library; |
3 | 5 |
|
| 6 | +import 'dart:convert'; |
4 | 7 | import 'dart:ffi'; |
5 | 8 | import 'dart:ffi' as ffi; |
6 | 9 |
|
7 | 10 | import 'package:ffi/ffi.dart'; |
8 | 11 | import 'package:tree_sitter/tree_sitter.dart'; |
9 | 12 |
|
| 13 | +import 'src/parser_generated_bindings.dart' as details; |
| 14 | +import 'src/utils.dart'; |
| 15 | + |
10 | 16 | export 'src/generated_bindings.dart'; |
11 | 17 |
|
12 | 18 | /// Exposes the tree sitter C API as a minimal dart ffi wrapper |
@@ -62,21 +68,235 @@ base class Parser implements Finalizable { |
62 | 68 | } |
63 | 69 | } |
64 | 70 |
|
| 71 | + details.TSLanguage get language => _language.cast<details.TSLanguage>().ref; |
| 72 | + |
65 | 73 | /// Parses out a tree from the given string |
66 | | - Pointer<TSTree> parse(String program) { |
| 74 | + Tree parse(String program, {int? encoding}) { |
| 75 | + _contents = program; |
67 | 76 | final pProgram = program.toNativeUtf8().cast<Char>(); |
68 | | - final tree = treeSitterApi.ts_parser_parse_string( |
69 | | - parser, nullptr, pProgram, program.length); |
70 | | - assert(tree != nullptr); |
71 | | - return tree; |
| 77 | + if (encoding == null) { |
| 78 | + return Tree(treeSitterApi.ts_parser_parse_string( |
| 79 | + parser, nullptr, pProgram, program.length)); |
| 80 | + } else { |
| 81 | + return Tree(treeSitterApi.ts_parser_parse_string_encoding( |
| 82 | + parser, nullptr, pProgram, program.length, encoding)); |
| 83 | + } |
| 84 | + } |
| 85 | + |
| 86 | + String? _contents; |
| 87 | + List<int> get contents => utf8.encode(_contents ?? ''); |
| 88 | + |
| 89 | + void reset() => treeSitterApi.ts_parser_reset(parser); |
| 90 | + |
| 91 | + set timeoutMicros(int timeout) => |
| 92 | + treeSitterApi.ts_parser_set_timeout_micros(parser, timeout); |
| 93 | + |
| 94 | + int get timeoutMicros => treeSitterApi.ts_parser_timeout_micros(parser); |
| 95 | + |
| 96 | + set cancellationFlag(CancelToken flag) => |
| 97 | + treeSitterApi.ts_parser_set_cancellation_flag(parser, flag._token); |
| 98 | + CancelToken get cancellationFlag => |
| 99 | + CancelToken.fromToken(treeSitterApi.ts_parser_cancellation_flag(parser)); |
| 100 | + |
| 101 | + String getText(TSNode namedChild) { |
| 102 | + final text = contents.sublist(namedChild.startByte, namedChild.endByte); |
| 103 | + return utf8.decode(text); |
72 | 104 | } |
73 | 105 | } |
74 | 106 |
|
75 | | -extension TOString on TSNode { |
| 107 | +final class CancelToken implements Finalizable { |
| 108 | + final Pointer<Size> _token; |
| 109 | + final _finalizer = NativeFinalizer(free.cast()); |
| 110 | + CancelToken() : _token = calloc<Size>(1) { |
| 111 | + _finalizer.attach(this, _token.cast(), detach: this); |
| 112 | + } |
| 113 | + CancelToken.fromToken(this._token) { |
| 114 | + _finalizer.attach(this, _token.cast(), detach: this); |
| 115 | + } |
| 116 | + void cancel() => _token.value = 1; |
| 117 | +} |
| 118 | + |
| 119 | +base class Tree implements Finalizable { |
| 120 | + final Pointer<TSTree> tree; |
| 121 | + final _finalizer = |
| 122 | + NativeFinalizer(treeSitterApi.addresses.ts_tree_delete.cast()); |
| 123 | + |
| 124 | + Tree(this.tree) { |
| 125 | + if (tree == nullptr) { |
| 126 | + throw Exception('Tree is null'); |
| 127 | + } |
| 128 | + _finalizer.attach(this, tree.cast(), detach: this); |
| 129 | + } |
| 130 | + |
| 131 | + TSNode get root => treeSitterApi.ts_tree_root_node(tree); |
| 132 | + |
| 133 | + Tree get copy => Tree(treeSitterApi.ts_tree_copy(tree)); |
| 134 | + |
| 135 | + details.TSLanguage get language => |
| 136 | + treeSitterApi.ts_tree_language(tree).cast<details.TSLanguage>().ref; |
| 137 | +} |
| 138 | + |
| 139 | +base class TreeCursor implements Finalizable { |
| 140 | + late final Pointer<TSTreeCursor> cursor = malloc<TSTreeCursor>(1); |
| 141 | + final _finalizer = |
| 142 | + NativeFinalizer(treeSitterApi.addresses.ts_tree_cursor_delete.cast()); |
| 143 | + final TSNode node; |
| 144 | + TreeCursor(this.node) { |
| 145 | + cursor.ref = treeSitterApi.ts_tree_cursor_new(node); |
| 146 | + _finalizer.attach(this, cursor.cast(), detach: this); |
| 147 | + } |
| 148 | +} |
| 149 | + |
| 150 | +base class Query implements Finalizable { |
| 151 | + late final Pointer<TSQuery> query; |
| 152 | + final _finalizer = |
| 153 | + NativeFinalizer(treeSitterApi.addresses.ts_query_delete.cast()); |
| 154 | + Query(this.query) { |
| 155 | + _finalizer.attach(this, query.cast(), detach: this); |
| 156 | + } |
| 157 | + |
| 158 | + Query.fromSource( |
| 159 | + {required Pointer<TSLanguage> language, required String source}) { |
| 160 | + final pSource = source.toNativeUtf8().cast<Char>(); |
| 161 | + final length = utf8.encode(source).length; |
| 162 | + using((alloc) { |
| 163 | + final errorOffset = alloc<Uint32>(1); |
| 164 | + final errorType = alloc<Int32>(1); |
| 165 | + query = treeSitterApi.ts_query_new( |
| 166 | + language, pSource, length, errorOffset, errorType); |
| 167 | + if (query == nullptr) { |
| 168 | + final errOff = errorOffset.value; |
| 169 | + final errType = errorType.value; |
| 170 | + throw Exception( |
| 171 | + 'Failed to create query from source "$source" ${errType.queryError} at offset $errOff]}'); |
| 172 | + } |
| 173 | + }); |
| 174 | + } |
| 175 | +} |
| 176 | + |
| 177 | +extension TSApiIntX on int { |
| 178 | + String get queryError => switch (this) { |
| 179 | + TSQueryError.TSQueryErrorNone => 'TSQueryErrorNone', |
| 180 | + TSQueryError.TSQueryErrorSyntax => 'TSQueryErrorSyntax', |
| 181 | + TSQueryError.TSQueryErrorNodeType => 'TSQueryErrorNodeType', |
| 182 | + TSQueryError.TSQueryErrorField => 'TSQueryErrorField', |
| 183 | + TSQueryError.TSQueryErrorCapture => 'TSQueryErrorCapture', |
| 184 | + TSQueryError.TSQueryErrorStructure => 'TSQueryErrorStructure', |
| 185 | + TSQueryError.TSQueryErrorLanguage => 'TSQueryErrorLanguage', |
| 186 | + _ => 'Unknown error code $this' |
| 187 | + }; |
| 188 | + |
| 189 | + String get queryPredicateStepType => switch (this) { |
| 190 | + TSQueryPredicateStepType.TSQueryPredicateStepTypeCapture => |
| 191 | + 'TSQueryPredicateStepTypeCapture', |
| 192 | + TSQueryPredicateStepType.TSQueryPredicateStepTypeString => |
| 193 | + 'TSQueryPredicateStepTypeString', |
| 194 | + TSQueryPredicateStepType.TSQueryPredicateStepTypeDone => |
| 195 | + 'TSQueryPredicateStepTypeDone', |
| 196 | + _ => 'Unknown predicate step type $this' |
| 197 | + }; |
| 198 | + |
| 199 | + String get quantifier => switch (this) { |
| 200 | + TSQuantifier.TSQuantifierZero => 'TSQuantifierZero', |
| 201 | + TSQuantifier.TSQuantifierZeroOrOne => 'TSQuantifierZeroOrOne', |
| 202 | + TSQuantifier.TSQuantifierZeroOrMore => 'TSQuantifierZeroOrMore', |
| 203 | + TSQuantifier.TSQuantifierOne => 'TSQuantifierOne', |
| 204 | + TSQuantifier.TSQuantifierOneOrMore => 'TSQuantifierOneOrMore', |
| 205 | + _ => 'Unknown predicate step type $this' |
| 206 | + }; |
| 207 | + |
| 208 | + String get symbolType => switch (this) { |
| 209 | + TSSymbolType.TSSymbolTypeRegular => 'TSSymbolTypeRegular', |
| 210 | + TSSymbolType.TSSymbolTypeAnonymous => 'TSSymbolTypeAnonymous', |
| 211 | + TSSymbolType.TSSymbolTypeAuxiliary => 'TSSymbolTypeAuxiliary', |
| 212 | + _ => 'Unknown symbol type $this' |
| 213 | + }; |
| 214 | + |
| 215 | + String get inputEncoding => switch (this) { |
| 216 | + TSInputEncoding.TSInputEncodingUTF8 => 'TSInputEncodingUTF8', |
| 217 | + TSInputEncoding.TSInputEncodingUTF16 => 'TSInputEncodingUTF16', |
| 218 | + _ => 'Unknown input encoding $this' |
| 219 | + }; |
| 220 | + |
| 221 | + String get logType => switch (this) { |
| 222 | + TSLogType.TSLogTypeParse => 'TSLogTypeParse', |
| 223 | + TSLogType.TSLogTypeLex => 'TSLogTypeLex', |
| 224 | + _ => 'Unknown log type $this' |
| 225 | + }; |
| 226 | +} |
| 227 | + |
| 228 | +base class QueryCursor implements Finalizable { |
| 229 | + final Pointer<TSQueryCursor> cursor = treeSitterApi.ts_query_cursor_new(); |
| 230 | + final _finalizer = |
| 231 | + NativeFinalizer(treeSitterApi.addresses.ts_query_delete.cast()); |
| 232 | + QueryCursor() { |
| 233 | + _finalizer.attach(this, cursor.cast(), detach: this); |
| 234 | + } |
| 235 | +} |
| 236 | + |
| 237 | +extension TSNodeX on TSNode { |
76 | 238 | String get string { |
77 | 239 | final root = treeSitterApi.ts_node_string(this); |
78 | 240 | final result = root.cast<Utf8>().toDartString(); |
79 | 241 | malloc.free(root); |
80 | 242 | return result; |
81 | 243 | } |
| 244 | + |
| 245 | + String get nodeType => |
| 246 | + treeSitterApi.ts_node_type(this).cast<Utf8>().toDartString(); |
| 247 | + |
| 248 | + int get symbol => treeSitterApi.ts_node_symbol(this); |
| 249 | + |
| 250 | + int get startByte => treeSitterApi.ts_node_start_byte(this); |
| 251 | + |
| 252 | + int get endByte => treeSitterApi.ts_node_end_byte(this); |
| 253 | + |
| 254 | + TSPoint get startPoint => treeSitterApi.ts_node_start_point(this); |
| 255 | + |
| 256 | + TSPoint get endPoint => treeSitterApi.ts_node_end_point(this); |
| 257 | + |
| 258 | + bool get isNull => treeSitterApi.ts_node_is_null(this); |
| 259 | + |
| 260 | + bool get isNamed => treeSitterApi.ts_node_is_named(this); |
| 261 | + |
| 262 | + bool get isMissing => treeSitterApi.ts_node_is_missing(this); |
| 263 | + |
| 264 | + bool get isExtra => treeSitterApi.ts_node_is_extra(this); |
| 265 | + |
| 266 | + bool get hasChanges => treeSitterApi.ts_node_has_changes(this); |
| 267 | + |
| 268 | + bool get hasError => treeSitterApi.ts_node_has_error(this); |
| 269 | + |
| 270 | + TSNode get parent => treeSitterApi.ts_node_parent(this); |
| 271 | + |
| 272 | + TSNode child(int childIndex) => treeSitterApi.ts_node_child(this, childIndex); |
| 273 | + |
| 274 | + String fieldNameForChild(int childIndex) => treeSitterApi |
| 275 | + .ts_node_field_name_for_child(this, childIndex) |
| 276 | + .cast<Utf8>() |
| 277 | + .toDartString(); |
| 278 | + |
| 279 | + int get childCount => treeSitterApi.ts_node_child_count(this); |
| 280 | + |
| 281 | + TSNode namedChild(int childIndex) => |
| 282 | + treeSitterApi.ts_node_named_child(this, childIndex); |
| 283 | + |
| 284 | + int get namedChildCount => treeSitterApi.ts_node_named_child_count(this); |
| 285 | + |
| 286 | + TSNode childByFieldName(String fieldName, int fieldNameLength) { |
| 287 | + final pFieldName = fieldName.toNativeUtf8().cast<Char>(); |
| 288 | + final result = treeSitterApi.ts_node_child_by_field_name( |
| 289 | + this, pFieldName, fieldNameLength); |
| 290 | + malloc.free(pFieldName); |
| 291 | + return result; |
| 292 | + } |
| 293 | + |
| 294 | + TSNode childByFieldId(int fieldId) => |
| 295 | + treeSitterApi.ts_node_child_by_field_id(this, fieldId); |
| 296 | + |
| 297 | + TSNode get nextSibling => treeSitterApi.ts_node_next_sibling(this); |
| 298 | + TSNode get prevSibling => treeSitterApi.ts_node_prev_sibling(this); |
| 299 | + |
| 300 | + TSNode get nextNamedSibling => treeSitterApi.ts_node_next_named_sibling(this); |
| 301 | + TSNode get prevNamedSibling => treeSitterApi.ts_node_prev_named_sibling(this); |
82 | 302 | } |
0 commit comments