Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 75 additions & 25 deletions grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const commaSeparatedList = (rule) =>
export default grammar({
name: 'wit',

word: $ => $.id,

extras: $ => [
/\s/,
$.line_comment,
Expand All @@ -31,6 +33,14 @@ export default grammar({
$._typedef_item,
],

conflicts: $ => [
// `import a: b`:
// * (`a` -> import target) (`b` -> interface `use_path`)
// * OR `a:b` may be the start of a `use_path` ex: `import a:b/c;`
[$._uri_head, $.import_item],
[$._uri_head, $.export_item],
],

rules: {
source_file: ($) =>
seq(
Expand All @@ -39,27 +49,46 @@ export default grammar({

_statement: $ => choice(
$.package_decl,
$.nested_package_definition,
$._package_items,
),

_package_items: $ => choice(
$.toplevel_use_item,
$.world_item,
$.interface_item,
),

nested_package_definition: $ =>
seq(
$.decl_head,
'{',
repeat($._package_items),
'}',
),


// URI patterns shared between `use_path` and `package_decl`
// URI patterns shared between `use_path` and `decl_head`.
_uri_head: $ => seq($.id, ':'),
_uri_tail: $ => seq('/', $.id),
_version: $ => seq('@', alias($._valid_semver, $.version)),

package_decl: ($) =>
// Shared header for both `package_decl` (terminated with `;`) and
// `nested_package_definition` (followed by a `{ ... }` block).
decl_head: $ =>
seq(
'package',
repeat1($._uri_head),
$.id,
repeat($._uri_tail),
optional($._version),
';',
),

// Semicolon is not explicitly tied to the EBNF grammar:
// https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#top-level-items
// wit-file ::= (package-decl ';')? (package-items | nested-package-definition)*
package_decl: ($) => seq($.decl_head, ';'),

toplevel_use_item: ($) =>
seq(
'use',
Expand All @@ -83,10 +112,21 @@ export default grammar({
id: ($) =>
/%?(([a-z][a-z0-9]*|[A-Z][A-Z0-9]*))(-([a-z][a-z0-9]*|[A-Z][A-Z0-9]*))*/,

// https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#string-literals
// WIT string literals are double-quoted and use the same text format as
// the core WebAssembly text format's [`name`](https://webassembly.github.io/spec/core/text/values.html#text-name) literal:
// - regular chars: any code point >= U+20 but NOT: U+7F, `"`, `\`
// - escapes: `\t` `\n` `\r` `\"` `\'` `\\`
// - byte escape: \XX (two hex digits)
// - unicode escape: \u{HEX+} < U+10FFFF
string_literal: _ =>
/"([^\x00-\x1F\x7F"\\]|\\[tnr"'\\]|\\[0-9A-Fa-f][0-9A-Fa-f]|\\u\{[0-9A-Fa-f]+\})*"/,

// As per https://semver.org/, this regex allows for trailing metadata after MAJOR.MINOR.PATCH
_valid_semver: ($) =>
/(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?(?:\+([0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*))?/,


world_item: ($) =>
seq(optional($._gate), 'world', field('name', $.id), alias($._world_body, $.body)),

Expand All @@ -106,18 +146,22 @@ export default grammar({

export_item: ($) =>
choice(
seq('export', field('name', $.id), ':', $.extern_type),
seq(optional($.external_id), 'export', field('name', $.id), ':', $.extern_type),
seq('export', $.use_path, ';'),
),

import_item: ($) =>
choice(
seq('import', field('name', $.id), ':', $.extern_type),
seq(optional($.external_id), 'import', field('name', $.id), ':', $.extern_type),
seq('import', $.use_path, ';'),
),

extern_type: ($) =>
choice(seq($.func_type, ';'), seq('interface', alias($._interface_body, $.body))),
choice(
seq($.func_type, ';'),
seq('interface', alias($._interface_body, $.body)),
seq($.use_path, ';'),
),

include_item: ($) =>
choice(
Expand All @@ -134,12 +178,12 @@ export default grammar({
seq('{', $._include_names_list, '}'),

_include_names_list: ($) =>
commaSeparatedList( $.include_names_item),
include_names_item: ($) => $.alias_item,
commaSeparatedList($.include_names_item),

include_names_item: ($) => seq(field('path', $.id), 'as', field('alias', $.id)),

alias_item: $ =>
seq(field('path', $.id), 'as', field('alias', $.id)),
include_names_item: ($) => seq($.id, 'as', $.id),

interface_item: ($) =>
seq(optional($._gate), 'interface', field('name', $.id), alias($._interface_body, $.body)),
Expand All @@ -151,9 +195,9 @@ export default grammar({
seq(
optional($._gate),
choice(
$._typedef_item,
$.use_item,
$.func_item,
seq(optional($.external_id), $._typedef_item),
seq(optional($.external_id), $.func_item),
)),

_typedef_item: ($) =>
Expand Down Expand Up @@ -234,43 +278,45 @@ export default grammar({
choice(';', optional(alias($._resource_body, $.body))),
),

_resource_body: ($) => seq('{', repeat($.resource_method), '}'),
_resource_body: ($) => seq('{', repeat(seq(optional($.external_id), $.resource_method)), '}'),

resource_method: ($) =>
choice(
$.func_item,
seq($.id, ':', 'static', $.func_type, ';'),
seq('constructor', $.param_list, ';'),
seq(field('name', $.id), ':', 'static', $.func_type, ';'),
seq('constructor', $.param_list, optional($.result_list), ';'),
),

// Primitive types shared between `ty` and `kt` ([map] key types).
// no floating points
_primitive_ty: _ => choice(
'u8', 'u16', 'u32', 'u64',
's8', 's16', 's32', 's64',
'char', 'bool', 'string',
),

ty: ($) =>
prec(
1,
choice(
'u8',
'u16',
'u32',
'u64',
's8',
's16',
's32',
's64',
$._primitive_ty,
'f32',
'f64',
'char',
'bool',
'string',
$.tuple,
$.list,
$.option,
$.result,
$.map,
$.id,
$.handle,
$.future,
$.stream,
),
),

// https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#types
kt: $ => $._primitive_ty,

tuple: ($) => seq('tuple', '<', $.tuple_list, '>'),

tuple_list: ($) => commaSeparatedList($.ty),
Expand All @@ -285,6 +331,8 @@ export default grammar({

option: ($) => seq('option', '<', $.ty, '>'),

map: ($) => seq('map', '<', field('key', $.kt), ',', field('value', $.ty), '>'),

result: ($) =>
seq(
'result',
Expand Down Expand Up @@ -358,6 +406,8 @@ export default grammar({
$.deprecated_gate,
),

external_id: $ => seq('@', 'external-id', '(', field('id', $.string_literal), ')'),

unstable_gate: $ => seq( '@', 'unstable', '(', $._feature_field, ')'),
_feature_field: $ => seq('feature', '=', field('feature', $.id)),

Expand Down
11 changes: 9 additions & 2 deletions queries/highlights.scm
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
(ty
(id)) @type

(package_decl
(decl_head
(id) @module)

(version) @string.special
Expand All @@ -12,7 +12,7 @@
"/"
] @punctuation.delimiter)

(package_decl
(decl_head
[
"@"
"/"
Expand Down Expand Up @@ -121,6 +121,12 @@

"func" @keyword.function

(external_id
"@" @punctuation.special
"external-id" @attribute.builtin
id: (string_literal) @string)


[
"type"
"interface"
Expand Down Expand Up @@ -168,6 +174,7 @@
"list"
"option"
"result"
"map"
"borrow"
"future"
"stream"
Expand Down
Loading
Loading