diff --git a/README.md b/README.md index e387a4d..fc02cb1 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,62 @@ This repository contains a parser definition of the [ReScript](https://rescript-lang.org/) language for the [Tree-sitter](https://tree-sitter.github.io/tree-sitter/) parser generator tool. -Athough Tree-sitter has many applications, the main intent of this parser is powering the [`nvim-treesitter-rescript`](https://github.com/nkrkv/nvim-tree-sitter-rescript/) NeoVim plugin which may be used to improve development experience in the NeoVim + ReScript combo. - Queries for text objects are also included which help you to navigate, select, and modify ReScript code syntactically. For NeoVim, the [`nvim-treesitter-textobjects`](https://github.com/nvim-treesitter/nvim-treesitter-textobjects) plugin is required to use Tree-sitter text objects. ## Installation -- If you want ReScript Tree-sitter in NeoVim, refer to [`nvim-treesitter-rescript`](https://github.com/nkrkv/nvim-tree-sitter-rescript/) installation notes; +### Neovim + +If you want ReScript Tree-sitter in NeoVim, you will first need to register a new parser for it like so: + +```lua +local parser_config = require("nvim-treesitter.parsers").get_parser_configs() +parser_config.rescript = { + install_info = { + url = "https://github.com/rescript-lang/tree-sitter-rescript", + branch = "main", + files = { "src/scanner.c" }, + generate_requires_npm = false, + requires_generate_from_grammar = true, + use_makefile = true, -- macOS specific instruction + }, +} +``` + +This will make `TSInstall rescript` globally available. For more persistent approach you should add this parser to your Lua configuration. + +Default configuration detects `.res` and `.resi` files. You can confirm that it's correctly installed by invoking `:InspectTree` when you are in the ReScript file. + +- Notice that by default you will not see the highlighting! To enable highlighting, you will need to install this package either as a dependency or directly. + +If you are using `lazy.nvim` example configuration will look like so: + +```lua + { + "nvim-treesitter/nvim-treesitter", + dependencies = { + "rescript-lang/tree-sitter-rescript" + }, + opts = function(_, opts) -- this is needed so you won't override your default nvim-treesitter configuration + vim.list_extend(opts.ensure_installed, { + "rescript", + }) + + local parser_config = require("nvim-treesitter.parsers").get_parser_configs() + parser_config.rescript = { + install_info = { + url = "https://github.com/rescript-lang/tree-sitter-rescript", + branch = "main", + files = { "src/scanner.c" }, + generate_requires_npm = false, + requires_generate_from_grammar = true, + use_makefile = true, -- macOS specific instruction + }, + } + end, + } +``` + - If you want it for other purposes, you probably know what to do. ## Contributing diff --git a/grammar.js b/grammar.js index 10b537d..e75fdc3 100644 --- a/grammar.js +++ b/grammar.js @@ -1,3 +1,5 @@ +/// + module.exports = grammar({ name: 'rescript', @@ -105,7 +107,9 @@ module.exports = grammar({ [$._reserved_identifier, $.function], [$.exception_pattern, $.or_pattern], [$.type_binding, $._inline_type], - [$._module_structure, $.parenthesized_module_expression] + [$._module_structure, $.parenthesized_module_expression], + [$.record_type_field, $.object_type_field], + [$._record_type_member, $._object_type_member], ], rules: { @@ -174,6 +178,7 @@ module.exports = grammar({ )), optional(seq( '=', + optional('await'), field('definition', $._module_definition), )), )), @@ -314,6 +319,7 @@ module.exports = grammar({ $.module_pack, $.unit, $.polymorphic_type, + alias($._as_aliasing_non_function_inline_type, $.as_aliasing_type) ), polymorphic_type: $ => seq( @@ -377,37 +383,48 @@ module.exports = grammar({ record_type: $ => seq( '{', - commaSept($.record_type_field), + commaSept($._record_type_member), '}', ), - record_type_field: $ => seq( - optional('mutable'), - alias($.value_identifier, $.property_identifier), - optional('?'), - $.type_annotation, + record_type_field: $ => + seq( + optional('mutable'), + alias($.value_identifier, $.property_identifier), + optional('?'), + $.type_annotation, + ), + + type_spread: $ => + seq('...', choice($.type_identifier, $.generic_type, $.type_identifier_path)), + + _record_type_member: $ => choice( + $.record_type_field, + $.type_spread ), object_type: $ => prec.left(seq( '{', choice( - commaSep1t($._object_type_field), - seq('.', commaSept($._object_type_field)), - seq('..', commaSept($._object_type_field)), + commaSep1t($._object_type_member), + seq('.', commaSept($._object_type_member)), + seq('..', commaSept($._object_type_member)), ), '}', )), - _object_type_field: $ => alias($.object_type_field, $.field), + _object_type_member: $ => + choice( + alias($.object_type_field, $.field), + $.type_spread + ), object_type_field: $ => choice( - seq('...', choice($.type_identifier, $.type_identifier_path)), seq( alias($.string, $.property_identifier), ':', $._type, ), - ), generic_type: $ => prec.left(seq( @@ -521,6 +538,7 @@ module.exports = grammar({ $.module_pack, $.extension_expression, $.lazy_expression, + $._jsx_element ), parenthesized_expression: $ => seq( @@ -698,6 +716,9 @@ module.exports = grammar({ as_aliasing_type: $ => seq($._type, 'as', $.type_identifier), + _as_aliasing_non_function_inline_type: $ => + prec(2, seq($._non_function_inline_type, 'as', $.type_identifier)), + assert_expression: $ => prec.left(seq('assert', $.expression)), call_expression: $ => prec('call', seq( @@ -726,6 +747,7 @@ module.exports = grammar({ '(', optional($.uncurry), optional(commaSep1t($._call_argument)), + optional($.partial_application_spread), ')' ), @@ -737,6 +759,8 @@ module.exports = grammar({ $.labeled_argument, ), + partial_application_spread: $ => "...", + labeled_argument: $ => seq( '~', field('label', $.value_identifier), @@ -876,6 +900,7 @@ module.exports = grammar({ record_pattern: $ => seq( '{', commaSep1t(seq( + optional("?"), choice( $.value_identifier, $.value_identifier_path, @@ -1331,7 +1356,7 @@ module.exports = grammar({ const bigint_literal = seq(choice(hex_literal, binary_literal, octal_literal, decimal_digits), 'n') const decimal_integer_literal = choice( - repeat('0'), + repeat1('0'), seq(repeat('0'), /[1-9]/, optional(seq(optional('_'), decimal_digits))) ) diff --git a/package.json b/package.json index 6a9bbbf..8980c64 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,19 @@ "res", "resi" ], - "injection-regex": "rescript" + "injection-regex": "rescript", + "highlights": [ + "queries/rescript/highlights.scm" + ], + "locals": [ + "queries/rescript/locals.scm" + ], + "injections": [ + "queries/rescript/injections.scm" + ], + "textobjects": [ + "queries/rescript/textobjects.scm" + ] } ] } diff --git a/queries/injections.scm b/queries/injections.scm deleted file mode 100644 index ad135cb..0000000 --- a/queries/injections.scm +++ /dev/null @@ -1,21 +0,0 @@ -(comment) @comment - -; %re -(extension_expression - (extension_identifier) @_name - (#eq? @_name "re") - (expression_statement (_) @regex)) - -; %raw -(extension_expression - (extension_identifier) @_name - (#eq? @_name "raw") - (expression_statement - (_ (_) @javascript))) - -; %graphql -(extension_expression - (extension_identifier) @_name - (#eq? @_name "graphql") - (expression_statement - (_ (_) @graphql))) diff --git a/queries/highlights.scm b/queries/rescript/highlights.scm similarity index 100% rename from queries/highlights.scm rename to queries/rescript/highlights.scm diff --git a/queries/rescript/injections.scm b/queries/rescript/injections.scm new file mode 100644 index 0000000..67f530d --- /dev/null +++ b/queries/rescript/injections.scm @@ -0,0 +1,29 @@ +((comment) @injection.content (#set! injection.language "comment")) + +; %re +(extension_expression + (extension_identifier) @_name + (#eq? @_name "re") + (expression_statement (_) @injection.content (#set! injection.language "regex"))) + +; %raw +(extension_expression + (extension_identifier) @_name + (#eq? @_name "raw") + (expression_statement + (_ (_) @injection.content (#set! injection.language "javascript")))) + +; %graphql +(extension_expression + (extension_identifier) @_name + (#eq? @_name "graphql") + (expression_statement + (_ (_) @injection.content (#set! injection.language "graphql")))) + +; %relay +(extension_expression + (extension_identifier) @_name + (#eq? @_name "relay") + (expression_statement + (_ (_) @injection.content (#set! injection.language "graphql") ))) + diff --git a/queries/locals.scm b/queries/rescript/locals.scm similarity index 100% rename from queries/locals.scm rename to queries/rescript/locals.scm diff --git a/queries/textobjects.scm b/queries/rescript/textobjects.scm similarity index 100% rename from queries/textobjects.scm rename to queries/rescript/textobjects.scm diff --git a/src/scanner.c b/src/scanner.c index 4c20192..3e80a79 100644 --- a/src/scanner.c +++ b/src/scanner.c @@ -113,7 +113,7 @@ static bool scan_comment(TSLexer *lexer) { // Single-line comment do { advance(lexer); - } while (lexer->lookahead != '\n'); + } while (lexer->lookahead != '\n' && !lexer->eof(lexer)); return true; case '*': diff --git a/test/corpus/expressions.txt b/test/corpus/expressions.txt index 2764643..fc20174 100644 --- a/test/corpus/expressions.txt +++ b/test/corpus/expressions.txt @@ -111,6 +111,7 @@ blocky( ~third={3}, ) f(raise) +f(1, ...) -------------------------------------------------------------------------------- @@ -191,7 +192,13 @@ f(raise) (call_expression function: (value_identifier) arguments: (arguments - (value_identifier))))) + (value_identifier)))) + (expression_statement + (call_expression + function: (value_identifier) + arguments: (arguments + (number) + (partial_application_spread))))) ================================================================================ Pipe @@ -755,6 +762,7 @@ switch person { }) => 20 | Student({status: Sick}) => 30 | Student({name}) => 40 +| Student({?age}) => 50 } -------------------------------------------------------------------------------- @@ -818,7 +826,16 @@ switch person { (value_identifier)))) (sequence_expression (expression_statement - (number))))))) + (number)))) + (switch_match + (variant_pattern + (variant_identifier) + (formal_parameters + (record_pattern + (value_identifier)))) + (sequence_expression + (expression_statement + (number))))))) ================================================================================ Switch of lists @@ -1149,6 +1166,8 @@ Math operators - 1 + 2 / 3 -. 1.0 +. 2.0 /. 3.0 2.0 ** 3.0 +-0l +-ln -------------------------------------------------------------------------------- @@ -1170,7 +1189,12 @@ Math operators (expression_statement (binary_expression (number) - (number)))) + (number))) + (expression_statement + (number)) + (expression_statement + (unary_expression + (value_identifier)))) ================================================================================ Boolean operators diff --git a/test/corpus/jsx.txt b/test/corpus/jsx.txt index 477c076..f736275 100644 --- a/test/corpus/jsx.txt +++ b/test/corpus/jsx.txt @@ -310,3 +310,20 @@ Spread props (value_identifier)))) (jsx_closing_element (jsx_identifier))))) + +================================================================================ +Elements in pipes +================================================================================ + +->Some + +-------------------------------------------------------------------------------- + +(source_file + (expression_statement + (pipe_expression + (jsx_self_closing_element + (jsx_identifier)) + (variant + (variant_identifier))))) + diff --git a/test/corpus/let_bindings.txt b/test/corpus/let_bindings.txt index 54ae990..901b5d6 100644 --- a/test/corpus/let_bindings.txt +++ b/test/corpus/let_bindings.txt @@ -101,6 +101,8 @@ Record destructuring let {bar, baz} = foo let {bar, baz: qux} = foo let {Bar.Bar.bar: bar} = foo +let {?Bar.bar} = foo +let {?bar} = foo -------------------------------------------------------------------------------- @@ -127,6 +129,18 @@ let {Bar.Bar.bar: bar} = foo (module_identifier)) (value_identifier)) (value_identifier)) + (value_identifier))) + (let_declaration + (let_binding + (record_pattern + (value_identifier_path + (module_identifier) + (value_identifier))) + (value_identifier))) + (let_declaration + (let_binding + (record_pattern + (value_identifier)) (value_identifier)))) ================================================================================ diff --git a/test/corpus/modules.txt b/test/corpus/modules.txt index e42782c..3054a4b 100644 --- a/test/corpus/modules.txt +++ b/test/corpus/modules.txt @@ -746,3 +746,18 @@ module M = (Na: N, Nb: N): ( (type_identifier) (type_identifier))))) body: (block))))) + +================================================================================ +Dynamic imports +================================================================================ + +module LazyUtils: UtilsType = await Utils + +-------------------------------------------------------------------------------- + +(source_file + (module_declaration + (module_binding + (module_identifier) + (module_identifier) + (module_identifier)))) diff --git a/test/corpus/type_declarations.txt b/test/corpus/type_declarations.txt index 10f91d0..652e048 100644 --- a/test/corpus/type_declarations.txt +++ b/test/corpus/type_declarations.txt @@ -99,13 +99,23 @@ type t = { mutable x: int, - opt?: string + opt?: string, + + env: {..} as 'env } type t = Mod.t = {a: int} type t = {} +type t = { + ...a, + ...b, + ...Mod.t, + ...Mod.t, + other: int, +} + -------------------------------------------------------------------------------- (source_file @@ -132,7 +142,13 @@ type t = {} (record_type_field (property_identifier) (type_annotation - (type_identifier)))))) + (type_identifier))) + (record_type_field + (property_identifier) + (type_annotation + (as_aliasing_type + (object_type) + (type_identifier))))))) (type_declaration (type_binding (type_identifier) @@ -147,7 +163,33 @@ type t = {} (type_declaration (type_binding (type_identifier) - (record_type)))) + (record_type))) + (type_declaration + (type_binding + (type_identifier) + (record_type + (type_spread + (type_identifier)) + (type_spread + (generic_type + (type_identifier) + (type_arguments + (type_identifier)))) + (type_spread + (generic_type + (type_identifier_path + (module_identifier) + (type_identifier)) + (type_arguments + (type_identifier)))) + (type_spread + (type_identifier_path + (module_identifier) + (type_identifier))) + (record_type_field + (property_identifier) + (type_annotation + (type_identifier))))))) ================================================================================ Extensible Variant @@ -436,7 +478,7 @@ type t<'a> = {.."name": string} as 'a (property_identifier (string_fragment)) (type_identifier)) - (field + (type_spread (type_identifier))))) (type_declaration (type_binding