@willbooster/tree-sitter-haskell 8.1.0 → 8.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/grammar/data.js +4 -4
- package/grammar/decl.js +37 -40
- package/grammar/id.js +2 -0
- package/grammar/lexeme.js +7 -7
- package/grammar/literal.js +5 -2
- package/grammar.js +2 -2
- package/hsc/grammar.js +69 -0
- package/hsc/queries/highlights.scm +1 -0
- package/hsc/queries/injections.scm +2 -0
- package/hsc/src/node-types.json +6941 -0
- package/package.json +5 -2
- package/tree-sitter-haskell.wasm +0 -0
- package/tree-sitter-hsc.wasm +0 -0
- package/tree-sitter.json +20 -1
package/README.md
CHANGED
|
@@ -79,6 +79,8 @@ parser.setLanguage(await Language.load(haskell));
|
|
|
79
79
|
|
|
80
80
|
The package also ships `grammar.js` with the files in `grammar/` it loads, the queries in `queries/`, and the node types in `src/node-types.json`.
|
|
81
81
|
|
|
82
|
+
For [hsc2hs](https://github.com/haskell/hsc2hs) source in `.hsc` files, load `tree-sitter-hsc.wasm` instead. This dialect parses C directives and the doubled hashes that hsc2hs turns into literal hashes. Its node schema is `hsc/src/node-types.json`. Combine the ordinary queries with the matching suffixes in `hsc/queries/` for directive highlighting and C-body injections.
|
|
83
|
+
|
|
82
84
|
For code navigation, use `queries/tags.scm` (or `TAGS_QUERY` in Rust). It indexes module headers, top-level named functions and bindings, named types and classes, class and instance methods, and syntactic variable/operator calls. A signature and each equation have separate definition locations. Call references identify the called name without resolving its binding.
|
|
83
85
|
|
|
84
86
|
Declaration names and variable/operator callees remain indexed through nested parentheses. Class and instance equations retain their method roles; grouping a value without applying it does not create a call reference. Destructuring bindings contribute their explicit names at every nesting depth, including as-pattern binders and record puns. Local pattern bindings, names bound by patterns inside declaration quotes and constructors (including pattern synonyms) are not indexed.
|
|
@@ -96,7 +98,7 @@ fuzzed with:
|
|
|
96
98
|
```toml
|
|
97
99
|
[dependencies]
|
|
98
100
|
tree-sitter = { package = "willbooster-tree-sitter", version = "7.0.5" }
|
|
99
|
-
tree-sitter-haskell = { package = "willbooster-tree-sitter-haskell", version = "
|
|
101
|
+
tree-sitter-haskell = { package = "willbooster-tree-sitter-haskell", version = "8" }
|
|
100
102
|
```
|
|
101
103
|
|
|
102
104
|
```rust
|
|
@@ -104,6 +106,8 @@ let mut parser = tree_sitter::Parser::new();
|
|
|
104
106
|
parser.set_language(&tree_sitter_haskell::LANGUAGE.into())?;
|
|
105
107
|
```
|
|
106
108
|
|
|
109
|
+
For `.hsc` files in Rust, select `HSC_LANGUAGE` and `HSC_NODE_TYPES`. Use `HSC_HIGHLIGHTS_QUERY` and `HSC_INJECTIONS_QUERY` for the combined queries; `TAGS_QUERY` and `LOCALS_QUERY` are shared with ordinary Haskell.
|
|
110
|
+
|
|
107
111
|
## Development
|
|
108
112
|
|
|
109
113
|
```sh
|
|
@@ -125,7 +129,7 @@ well (e.g. `script/tree-sitter parse file.hs`).
|
|
|
125
129
|
`bun run generate` records a fresh generation profile from the applicable `test/corpus` cases and Git-tracked
|
|
126
130
|
files in `examples/`, then generates compact parser tables. The parser also embeds metadata from `tree-sitter.json`. After changing a grammar file,
|
|
127
131
|
`tree-sitter.json`, a corpus case, or a tracked example,
|
|
128
|
-
regenerate and commit `src/`. Stage added or removed examples with `git add -A examples` before generation so the profile uses the intended file list.
|
|
132
|
+
regenerate and commit `src/` and `hsc/src/`. Stage added or removed examples with `git add -A examples` before generation so the profile uses the intended file list.
|
|
129
133
|
Profiles in `.tmp/generation-profiles/` are temporary and must not be committed. `bun run build-wasm`, `bun run build/ci`,
|
|
130
134
|
and the release build regenerate the parsers before compiling them.
|
|
131
135
|
|
package/grammar/data.js
CHANGED
|
@@ -70,7 +70,7 @@ module.exports = {
|
|
|
70
70
|
*/
|
|
71
71
|
gadt_constructor: $ => seq(
|
|
72
72
|
choice(
|
|
73
|
-
field('name', $.
|
|
73
|
+
field('name', $._decl_con),
|
|
74
74
|
field('names', alias($._con_binding_list, $.binding_list)),
|
|
75
75
|
),
|
|
76
76
|
$._colon2,
|
|
@@ -97,7 +97,7 @@ module.exports = {
|
|
|
97
97
|
_field_type: $ => choice($.strict_field, $.lazy_field, $.type),
|
|
98
98
|
|
|
99
99
|
_datacon_prefix: $ => seq(
|
|
100
|
-
field('name', $.
|
|
100
|
+
field('name', $._decl_con),
|
|
101
101
|
repeat(prec('patterns', field('field', $._field_type))),
|
|
102
102
|
),
|
|
103
103
|
|
|
@@ -109,7 +109,7 @@ module.exports = {
|
|
|
109
109
|
)),
|
|
110
110
|
|
|
111
111
|
_datacon_record: $ => seq(
|
|
112
|
-
field('name', $.
|
|
112
|
+
field('name', $._decl_constructor),
|
|
113
113
|
field('fields', alias($._record_fields, $.fields)),
|
|
114
114
|
),
|
|
115
115
|
|
|
@@ -169,7 +169,7 @@ module.exports = {
|
|
|
169
169
|
_newtype_con_field: $ => $.type,
|
|
170
170
|
|
|
171
171
|
newtype_constructor: $ => seq(
|
|
172
|
-
field('name', $.
|
|
172
|
+
field('name', $._decl_con),
|
|
173
173
|
field('field', choice(
|
|
174
174
|
alias($._newtype_con_field, $.field),
|
|
175
175
|
alias($._record_fields, $.record),
|
package/grammar/decl.js
CHANGED
|
@@ -5,6 +5,8 @@ const {
|
|
|
5
5
|
layout,
|
|
6
6
|
} = require('./util.js')
|
|
7
7
|
|
|
8
|
+
const foreignMacro = /[A-Z][A-Za-z_0-9]*/
|
|
9
|
+
|
|
8
10
|
const binding = ($, pattern) => prec('bind', seq(
|
|
9
11
|
choice(
|
|
10
12
|
field('pattern', pattern),
|
|
@@ -17,10 +19,6 @@ const binding = ($, pattern) => prec('bind', seq(
|
|
|
17
19
|
|
|
18
20
|
module.exports = {
|
|
19
21
|
|
|
20
|
-
// ------------------------------------------------------------------------
|
|
21
|
-
// fixity
|
|
22
|
-
// ------------------------------------------------------------------------
|
|
23
|
-
|
|
24
22
|
_fun_arrow_prec: _ => seq('-', '1'),
|
|
25
23
|
|
|
26
24
|
// GHC.Types special decl
|
|
@@ -39,10 +37,6 @@ module.exports = {
|
|
|
39
37
|
),
|
|
40
38
|
),
|
|
41
39
|
|
|
42
|
-
// ------------------------------------------------------------------------
|
|
43
|
-
// signature
|
|
44
|
-
// ------------------------------------------------------------------------
|
|
45
|
-
|
|
46
40
|
_con_binding_list: $ => sep2(',', field('name', $._con)),
|
|
47
41
|
|
|
48
42
|
_var_binding_list: $ => sep2(',', field('name', $._var)),
|
|
@@ -55,10 +49,6 @@ module.exports = {
|
|
|
55
49
|
$._type_annotation,
|
|
56
50
|
),
|
|
57
51
|
|
|
58
|
-
// ------------------------------------------------------------------------
|
|
59
|
-
// function and pattern bind
|
|
60
|
-
// ------------------------------------------------------------------------
|
|
61
|
-
|
|
62
52
|
_simple_bind_match: $ => seq('=', field('expression', $._exp)),
|
|
63
53
|
|
|
64
54
|
_bind_match: $ => seq(
|
|
@@ -91,14 +81,6 @@ module.exports = {
|
|
|
91
81
|
$._function_head_parens,
|
|
92
82
|
),
|
|
93
83
|
|
|
94
|
-
/**
|
|
95
|
-
* The difference between a `function` with an `infix` head and a `bind` with `pat_infix` is that the former is for
|
|
96
|
-
* _declaring_ a `varop` and the latter uses a `conop` to pattern match on the rhs expression.
|
|
97
|
-
* The former may not have a type annotation, while the latter may.
|
|
98
|
-
*
|
|
99
|
-
* > a <> b = undefined
|
|
100
|
-
* > h : t :: [Int] = undefined
|
|
101
|
-
*/
|
|
102
84
|
_function_head_infix: $ => seq(
|
|
103
85
|
field('left_operand', $.pattern),
|
|
104
86
|
optional($._cond_no_section_op),
|
|
@@ -118,10 +100,6 @@ module.exports = {
|
|
|
118
100
|
|
|
119
101
|
_binding_name_parens: $ => parens($, choice($._var, $._binding_name_parens)),
|
|
120
102
|
|
|
121
|
-
/**
|
|
122
|
-
* The `implicit_variable` here is for:
|
|
123
|
-
* g = let ?par = Impy 5 in f
|
|
124
|
-
*/
|
|
125
103
|
bind: $ => binding($, $._pat),
|
|
126
104
|
|
|
127
105
|
_module_bind: $ => binding($, $._binding_pat),
|
|
@@ -147,48 +125,67 @@ module.exports = {
|
|
|
147
125
|
|
|
148
126
|
_where_binds: $ => seq($._where, optional(field('binds', $.local_binds))),
|
|
149
127
|
|
|
150
|
-
|
|
151
|
-
// foreign
|
|
152
|
-
// ------------------------------------------------------------------------
|
|
153
|
-
|
|
154
|
-
calling_convention: _ => token(choice(
|
|
128
|
+
_foreign_calling_keyword: _ => token(choice(
|
|
155
129
|
'ccall',
|
|
156
130
|
'stdcall',
|
|
157
131
|
'capi',
|
|
158
132
|
'prim',
|
|
159
133
|
'javascript',
|
|
160
|
-
/[A-Z_]+/, // It's common in GHC to use a cpp #define for this
|
|
161
134
|
)),
|
|
162
135
|
|
|
163
|
-
|
|
136
|
+
_foreign_safety_keyword: _ => token(choice(
|
|
164
137
|
'unsafe',
|
|
165
138
|
'safe',
|
|
166
139
|
'interruptible',
|
|
167
140
|
)),
|
|
168
141
|
|
|
142
|
+
_foreign_macro: _ => token(prec(-1, foreignMacro)),
|
|
143
|
+
|
|
144
|
+
_foreign_underscore_identifier: _ => token(/_[A-Za-z_0-9]+/),
|
|
145
|
+
|
|
146
|
+
_foreign_calling_convention: $ => choice(
|
|
147
|
+
alias('_', $.calling_convention),
|
|
148
|
+
alias($._foreign_calling_keyword, $.calling_convention),
|
|
149
|
+
alias($._foreign_macro, $.calling_convention),
|
|
150
|
+
alias($._foreign_underscore_identifier, $.calling_convention),
|
|
151
|
+
),
|
|
152
|
+
|
|
153
|
+
_foreign_safety: $ => choice(
|
|
154
|
+
alias('_', $.safety),
|
|
155
|
+
alias($._foreign_safety_keyword, $.safety),
|
|
156
|
+
alias($._foreign_macro, $.safety),
|
|
157
|
+
alias($._foreign_underscore_identifier, $.safety),
|
|
158
|
+
),
|
|
159
|
+
|
|
160
|
+
_foreign_underscore_signature: $ => seq(
|
|
161
|
+
field('name', alias($._foreign_underscore_identifier, $.variable)),
|
|
162
|
+
$._type_annotation,
|
|
163
|
+
),
|
|
164
|
+
|
|
165
|
+
_foreign_signature: $ => choice(
|
|
166
|
+
$.signature,
|
|
167
|
+
alias($._foreign_underscore_signature, $.signature),
|
|
168
|
+
),
|
|
169
|
+
|
|
169
170
|
entity: $ => $.string,
|
|
170
171
|
|
|
171
172
|
foreign_import: $ => seq(
|
|
172
173
|
'foreign',
|
|
173
174
|
'import',
|
|
174
|
-
field('calling_convention', $.
|
|
175
|
-
optional(field('safety', $.
|
|
175
|
+
field('calling_convention', $._foreign_calling_convention),
|
|
176
|
+
optional(field('safety', $._foreign_safety)),
|
|
176
177
|
optional(field('entity', $.entity)),
|
|
177
|
-
field('signature', $.
|
|
178
|
+
field('signature', $._foreign_signature),
|
|
178
179
|
),
|
|
179
180
|
|
|
180
181
|
foreign_export: $ => seq(
|
|
181
182
|
'foreign',
|
|
182
183
|
'export',
|
|
183
|
-
field('calling_convention', $.
|
|
184
|
+
field('calling_convention', $._foreign_calling_convention),
|
|
184
185
|
optional(field('entity', $.entity)),
|
|
185
|
-
field('signature', $.
|
|
186
|
+
field('signature', $._foreign_signature),
|
|
186
187
|
),
|
|
187
188
|
|
|
188
|
-
// ------------------------------------------------------------------------
|
|
189
|
-
// default
|
|
190
|
-
// ------------------------------------------------------------------------
|
|
191
|
-
|
|
192
189
|
default_types: $ => seq('default', parens($, optional(sep1(',', field('type', $._ktype))))),
|
|
193
190
|
|
|
194
191
|
}
|
package/grammar/id.js
CHANGED
|
@@ -37,11 +37,13 @@ module.exports = {
|
|
|
37
37
|
// ------------------------------------------------------------------------
|
|
38
38
|
|
|
39
39
|
_constructor: $ => alias($.name, $.constructor),
|
|
40
|
+
_decl_constructor: $ => $._constructor,
|
|
40
41
|
_qualified_constructor: $ => qualified($, $._constructor),
|
|
41
42
|
_qconid: $ => alias($._qualified_constructor, $.qualified),
|
|
42
43
|
_conids: $ => choice($._qconid, $._constructor),
|
|
43
44
|
|
|
44
45
|
_con: $ => choice($._constructor, $._pconsym),
|
|
46
|
+
_decl_con: $ => $._con,
|
|
45
47
|
_qcon: $ => choice($._qconid, $._pqconsym),
|
|
46
48
|
_cons: $ => choice(prec('con', $._con), $._qcon),
|
|
47
49
|
|
package/grammar/lexeme.js
CHANGED
|
@@ -6,15 +6,15 @@ const varid_start_char = /[_\p{Ll}\p{Lo}]/
|
|
|
6
6
|
|
|
7
7
|
const conid_start_char = /[\p{Lu}\p{Lt}]/
|
|
8
8
|
|
|
9
|
-
module.exports = {
|
|
9
|
+
module.exports = (hash = '#') => ({
|
|
10
10
|
|
|
11
|
-
variable: _ => token(seq(varid_start_char, id_char, /#*/)),
|
|
11
|
+
variable: _ => token(seq(varid_start_char, id_char, hash === '#' ? /#*/ : /(##)*/)),
|
|
12
12
|
|
|
13
13
|
implicit_variable: _ => token(seq('?', varid_start_char, id_char)),
|
|
14
14
|
|
|
15
|
-
name: _ => token(seq(conid_start_char, id_char, /#*/)),
|
|
15
|
+
name: _ => token(seq(conid_start_char, id_char, hash === '#' ? /#*/ : /(##)*/)),
|
|
16
16
|
|
|
17
|
-
label: _ => token(seq(
|
|
17
|
+
label: _ => token(seq(hash, choice(/[\pL\p{Mn}\pN_']+/, quoted_string(
|
|
18
18
|
/[^\p{C}\p{Zl}\p{Zp}\\"]/,
|
|
19
19
|
/\\(?:[abfnrtv\\"'&]|\^[@-_]|[0-9]+|o[0-7]+|x[0-9a-fA-F]+|NUL|SOH|STX|ETX|EOT|ENQ|ACK|BEL|BS|HT|LF|VT|FF|CR|SO|SI|DLE|DC[1-4]|NAK|SYN|ETB|CAN|EM|SUB|ESC|FS|GS|RS|US|SP|DEL)/,
|
|
20
20
|
)))),
|
|
@@ -52,12 +52,12 @@ module.exports = {
|
|
|
52
52
|
_bracket_open: $ => seq('[', $._cmd_texp_start),
|
|
53
53
|
_bracket_close: $ => seq(']', $._cmd_texp_end),
|
|
54
54
|
|
|
55
|
-
_unboxed_open: $ => seq(optional($._unboxed_label_start), alias(seq($._paren_open, token.immediate(
|
|
56
|
-
_unboxed_close: $ => seq(
|
|
55
|
+
_unboxed_open: $ => seq(optional($._unboxed_label_start), alias(seq($._paren_open, token.immediate(hash)), '(#')),
|
|
56
|
+
_unboxed_close: $ => seq(`${hash})`, $._cmd_texp_end),
|
|
57
57
|
_unboxed_bar: _ => choice('|', token.immediate('|')),
|
|
58
58
|
|
|
59
59
|
_where: $ => seq(optional($._phantom_where), 'where'),
|
|
60
60
|
|
|
61
61
|
_bar: $ => seq(optional($._phantom_bar), '|'),
|
|
62
62
|
|
|
63
|
-
}
|
|
63
|
+
})
|
package/grammar/literal.js
CHANGED
|
@@ -9,9 +9,11 @@ const {
|
|
|
9
9
|
const decimal = /[0-9][0-9_]*/
|
|
10
10
|
const exponent = /[eE][+-]?[0-9_]+/
|
|
11
11
|
const hex_exponent = /[pP][+-]?[0-9a-fA-F_]+/
|
|
12
|
-
const magic_hash = rule => token(seq(rule, optional(token.immediate(/##?/))))
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
|
|
14
|
+
module.exports = (hashSuffix = /##?/) => {
|
|
15
|
+
const magic_hash = rule => token(seq(rule, optional(token.immediate(hashSuffix))))
|
|
16
|
+
return {
|
|
15
17
|
|
|
16
18
|
float: _ => magic_hash(
|
|
17
19
|
seq(
|
|
@@ -100,3 +102,4 @@ module.exports = {
|
|
|
100
102
|
),
|
|
101
103
|
|
|
102
104
|
}
|
|
105
|
+
}
|
package/grammar.js
CHANGED
|
@@ -9,8 +9,8 @@ const
|
|
|
9
9
|
general = require('./grammar/general.js'),
|
|
10
10
|
id = require('./grammar/id.js'),
|
|
11
11
|
inline = require('./grammar/inline.js'),
|
|
12
|
-
lexeme = require('./grammar/lexeme.js'),
|
|
13
|
-
literal = require('./grammar/literal.js'),
|
|
12
|
+
lexeme = require('./grammar/lexeme.js')(),
|
|
13
|
+
literal = require('./grammar/literal.js')(),
|
|
14
14
|
module_ = require('./grammar/module.js'),
|
|
15
15
|
operator = require('./grammar/operator.js'),
|
|
16
16
|
pat = require('./grammar/pat.js'),
|
package/hsc/grammar.js
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const haskell = require('../grammar.js')
|
|
2
|
+
const literal = require('../grammar/literal.js')
|
|
3
|
+
const lexeme = require('../grammar/lexeme.js')
|
|
4
|
+
|
|
5
|
+
const braced = ($, close) => seq(
|
|
6
|
+
$._cond_hsc_hash,
|
|
7
|
+
'#',
|
|
8
|
+
optional($._hsc_name_space),
|
|
9
|
+
token.immediate('{'),
|
|
10
|
+
$._cmd_hsc_brace_open,
|
|
11
|
+
optional($._hsc_braced_space),
|
|
12
|
+
field('name', $.hsc_directive_name),
|
|
13
|
+
field('body', alias($.hsc_args_nested, $.hsc_args)),
|
|
14
|
+
close,
|
|
15
|
+
$._cmd_brace_close,
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
module.exports = grammar(haskell, {
|
|
19
|
+
name: 'hsc',
|
|
20
|
+
|
|
21
|
+
externals: ($, previous) => [
|
|
22
|
+
...previous.slice(0, -1),
|
|
23
|
+
$._cmd_hsc_brace_open,
|
|
24
|
+
$._cond_hsc_hash,
|
|
25
|
+
$.hsc_args_nested,
|
|
26
|
+
$.hsc_args_newline,
|
|
27
|
+
previous[previous.length - 1],
|
|
28
|
+
],
|
|
29
|
+
|
|
30
|
+
conflicts: ($, previous) => [
|
|
31
|
+
...previous,
|
|
32
|
+
[$._foreign_safety, $.entity],
|
|
33
|
+
],
|
|
34
|
+
|
|
35
|
+
rules: {
|
|
36
|
+
...literal(/(?:##){1,2}/),
|
|
37
|
+
...lexeme('##'),
|
|
38
|
+
|
|
39
|
+
_operator_hash_head: _ => seq(
|
|
40
|
+
choice('##', token.immediate('##')),
|
|
41
|
+
optional(choice(token.immediate('##'), token.immediate('|'))),
|
|
42
|
+
),
|
|
43
|
+
|
|
44
|
+
_foreign_calling_convention: ($, previous) => choice(previous, $.hsc),
|
|
45
|
+
_foreign_safety: ($, previous) => choice(previous, $.hsc),
|
|
46
|
+
_ie_entity: ($, previous) => choice(previous, $.hsc),
|
|
47
|
+
entity: ($, previous) => choice(previous, $.hsc),
|
|
48
|
+
literal: ($, previous) => choice(previous, $._hsc_literal),
|
|
49
|
+
_var: ($, previous) => choice(previous, $.hsc),
|
|
50
|
+
_tycon: ($, previous) => choice(previous, $.hsc),
|
|
51
|
+
_decl_con: ($, previous) => choice(previous, $.hsc),
|
|
52
|
+
_decl_constructor: ($, previous) => choice(previous, $.hsc),
|
|
53
|
+
|
|
54
|
+
hsc: $ => choice(
|
|
55
|
+
braced($, '}'),
|
|
56
|
+
seq(
|
|
57
|
+
$._cond_hsc_hash,
|
|
58
|
+
'#',
|
|
59
|
+
optional($._hsc_name_space),
|
|
60
|
+
field('name', $.hsc_directive_name),
|
|
61
|
+
field('body', alias($.hsc_args_newline, $.hsc_args)),
|
|
62
|
+
),
|
|
63
|
+
),
|
|
64
|
+
_hsc_literal: $ => braced($, choice('}##', '}####')),
|
|
65
|
+
_hsc_name_space: _ => token.immediate(/(?:[\t\v\f\r\p{Zs}\x85\u2028\u2029]|\\\r?\n)+/),
|
|
66
|
+
_hsc_braced_space: _ => token.immediate(/(?:\p{White_Space}|\\\r?\n)+/),
|
|
67
|
+
hsc_directive_name: _ => token.immediate(prec(-1, /[\pL_](?:[\pL\pN_]|\\\r?\n)*/)),
|
|
68
|
+
},
|
|
69
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(hsc_directive_name) @function.macro
|