Skip to content

Commit 398af69

Browse files
committed
add some idiomatic code
1 parent b09a863 commit 398af69

File tree

7 files changed

+249
-17
lines changed

7 files changed

+249
-17
lines changed

tree_sitter/CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1-
## 0.1.0
1+
## 0.1.1
2+
- Added some idiomatic dart apis
23

4+
## 0.1.0
35
- Initial version.

tree_sitter/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,13 +22,14 @@ void main() {
2222
final parser =
2323
Parser(sharedLibrary: 'libdart.dylib', entryPoint: 'tree_sitter_dart');
2424
final program = "class A {}";
25-
final result = parser.parse(program);
26-
malloc.free(result);
25+
final tree = parser.parse(program);
26+
print(tree.root.string);
2727
}
2828
```
2929

3030
You can access other apis via the top level `treeSitterApi` ffi wrapper
3131

32-
Or you can help contribute to an idiomatic dart api on the Parser class
32+
Or you can help contribute to an idiomatic dart api on top of the ffi wrapper.
33+
Many of the apis are started but not complete.
3334

3435
Expect breaking changes while we figure out the best api

tree_sitter/api_config.yaml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,8 @@ headers:
99
functions:
1010
symbol-address:
1111
include:
12-
- 'ts_parser_delete'
12+
- 'ts_parser_delete'
13+
- 'ts_tree_delete'
14+
- 'ts_tree_cursor_delete'
15+
- 'ts_query_delete'
16+
- 'ts_query_cursor_delete'
Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import 'package:ffi/ffi.dart';
21
import 'package:tree_sitter/tree_sitter.dart';
32

43
void main() {
54
final parser =
65
Parser(sharedLibrary: 'libdart.dylib', entryPoint: 'tree_sitter_dart');
76
final program = "class A {}";
87
final tree = parser.parse(program);
9-
print(treeSitterApi.ts_tree_root_node(tree).string);
10-
malloc.free(tree);
8+
print(tree.root.child(0).namedChild(0).string);
9+
print(parser.getText(tree.root.child(0).namedChild(0)));
1110
}

tree_sitter/lib/src/generated_bindings.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2184,6 +2184,14 @@ class _SymbolAddresses {
21842184
_SymbolAddresses(this._library);
21852185
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSParser>)>>
21862186
get ts_parser_delete => _library._ts_parser_deletePtr;
2187+
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSTree>)>>
2188+
get ts_tree_delete => _library._ts_tree_deletePtr;
2189+
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSTreeCursor>)>>
2190+
get ts_tree_cursor_delete => _library._ts_tree_cursor_deletePtr;
2191+
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSQuery>)>>
2192+
get ts_query_delete => _library._ts_query_deletePtr;
2193+
ffi.Pointer<ffi.NativeFunction<ffi.Void Function(ffi.Pointer<TSQueryCursor>)>>
2194+
get ts_query_cursor_delete => _library._ts_query_cursor_deletePtr;
21872195
}
21882196

21892197
final class TSLanguage extends ffi.Opaque {}

tree_sitter/lib/tree_sitter.dart

Lines changed: 226 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
/// A tree sitter parsing library for Dart.
2+
// ignore_for_file: unused_element
3+
24
library;
35

6+
import 'dart:convert';
47
import 'dart:ffi';
58
import 'dart:ffi' as ffi;
69

710
import 'package:ffi/ffi.dart';
811
import 'package:tree_sitter/tree_sitter.dart';
912

13+
import 'src/parser_generated_bindings.dart' as details;
14+
import 'src/utils.dart';
15+
1016
export 'src/generated_bindings.dart';
1117

1218
/// Exposes the tree sitter C API as a minimal dart ffi wrapper
@@ -62,21 +68,235 @@ base class Parser implements Finalizable {
6268
}
6369
}
6470

71+
details.TSLanguage get language => _language.cast<details.TSLanguage>().ref;
72+
6573
/// Parses out a tree from the given string
66-
Pointer<TSTree> parse(String program) {
74+
Tree parse(String program, {int? encoding}) {
75+
_contents = program;
6776
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);
72104
}
73105
}
74106

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 {
76238
String get string {
77239
final root = treeSitterApi.ts_node_string(this);
78240
final result = root.cast<Utf8>().toDartString();
79241
malloc.free(root);
80242
return result;
81243
}
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);
82302
}
Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import 'package:ffi/ffi.dart';
21
import 'package:test/test.dart';
32
import 'package:tree_sitter/tree_sitter.dart';
43

@@ -8,7 +7,6 @@ void main() {
87
Parser(sharedLibrary: 'libdart.dylib', entryPoint: 'tree_sitter_dart');
98
final program = "class A {}";
109
final tree = parser.parse(program);
11-
print(treeSitterApi.ts_tree_root_node(tree).string);
12-
malloc.free(tree);
10+
print(tree.root.string);
1311
});
1412
}

0 commit comments

Comments
 (0)