feat: add devtools (tree-sitter, neovim, formatter, CLI commands)

- Add tree-sitter grammar for syntax highlighting
- Add Neovim plugin with syntax, LSP integration, and commands
- Add code formatter (lux fmt) with check mode
- Add CLI commands: fmt, check, test, watch, init
- Add project initialization with lux.toml template

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-13 10:30:13 -05:00
parent 961a861822
commit f786d18182
15 changed files with 2578 additions and 7 deletions

View File

@@ -0,0 +1,599 @@
// Tree-sitter grammar for the Lux programming language
module.exports = grammar({
name: 'lux',
extras: $ => [
/\s/,
$.line_comment,
$.doc_comment,
],
conflicts: $ => [
[$.primary_expression, $.pattern],
[$.type_expression, $.primary_expression],
],
word: $ => $.identifier,
rules: {
source_file: $ => repeat($._declaration),
_declaration: $ => choice(
$.function_declaration,
$.let_declaration,
$.type_declaration,
$.effect_declaration,
$.handler_declaration,
$.trait_declaration,
$.impl_declaration,
$.import_declaration,
$.export_declaration,
),
// Comments
line_comment: $ => token(seq('//', /.*/)),
doc_comment: $ => token(seq('///', /.*/)),
// Function declaration
function_declaration: $ => seq(
'fn',
field('name', $.identifier),
optional($.type_parameters),
$.parameter_list,
':',
field('return_type', $.type_expression),
optional($.effect_clause),
optional($.property_clause),
'=',
field('body', $._expression),
),
parameter_list: $ => seq(
'(',
optional(seq(
$.parameter,
repeat(seq(',', $.parameter)),
optional(','),
)),
')',
),
parameter: $ => seq(
field('name', $.identifier),
':',
field('type', $.type_expression),
),
type_parameters: $ => seq(
'<',
$.identifier,
repeat(seq(',', $.identifier)),
optional(','),
'>',
),
effect_clause: $ => seq(
'with',
'{',
optional(seq(
$.identifier,
repeat(seq(',', $.identifier)),
optional(','),
)),
'}',
),
property_clause: $ => seq(
'is',
$.identifier,
repeat(seq(',', $.identifier)),
),
// Let declaration
let_declaration: $ => seq(
'let',
field('name', $.identifier),
optional(seq(':', field('type', $.type_expression))),
'=',
field('value', $._expression),
),
// Type declaration
type_declaration: $ => seq(
'type',
field('name', $.identifier),
optional($.type_parameters),
'=',
field('definition', $.type_definition),
),
type_definition: $ => choice(
$.enum_definition,
$.record_type,
$.type_expression,
),
enum_definition: $ => seq(
repeat1(seq('|', $.variant)),
),
variant: $ => seq(
field('name', $.identifier),
optional(choice(
$.tuple_fields,
$.record_fields,
)),
),
tuple_fields: $ => seq(
'(',
$.type_expression,
repeat(seq(',', $.type_expression)),
optional(','),
')',
),
record_fields: $ => seq(
'{',
$.record_field,
repeat(seq(',', $.record_field)),
optional(','),
'}',
),
record_field: $ => seq(
field('name', $.identifier),
':',
field('type', $.type_expression),
),
// Effect declaration
effect_declaration: $ => seq(
'effect',
field('name', $.identifier),
optional($.type_parameters),
'{',
repeat($.effect_operation),
'}',
),
effect_operation: $ => seq(
'fn',
field('name', $.identifier),
$.parameter_list,
':',
field('return_type', $.type_expression),
),
// Handler declaration
handler_declaration: $ => seq(
'handler',
field('name', $.identifier),
':',
field('effect', $.identifier),
'{',
repeat($.handler_operation),
'}',
),
handler_operation: $ => seq(
'fn',
field('name', $.identifier),
'(',
optional(seq(
$.identifier,
repeat(seq(',', $.identifier)),
optional(','),
)),
')',
'=',
field('body', $._expression),
),
// Trait declaration
trait_declaration: $ => seq(
'trait',
field('name', $.identifier),
optional($.type_parameters),
'{',
repeat($.trait_method),
'}',
),
trait_method: $ => seq(
'fn',
field('name', $.identifier),
$.parameter_list,
':',
field('return_type', $.type_expression),
),
// Impl declaration
impl_declaration: $ => seq(
'impl',
field('trait', $.identifier),
'for',
field('type', $.type_expression),
'{',
repeat($.function_declaration),
'}',
),
// Import/Export
import_declaration: $ => seq(
'import',
$.import_path,
optional($.import_clause),
),
import_path: $ => seq(
$.identifier,
repeat(seq('.', $.identifier)),
),
import_clause: $ => seq(
'{',
$.identifier,
repeat(seq(',', $.identifier)),
optional(','),
'}',
),
export_declaration: $ => seq(
'export',
'{',
$.identifier,
repeat(seq(',', $.identifier)),
optional(','),
'}',
),
// Type expressions
type_expression: $ => choice(
$.identifier,
$.generic_type,
$.function_type,
$.tuple_type,
$.record_type,
seq('(', $.type_expression, ')'),
),
generic_type: $ => seq(
$.identifier,
'<',
$.type_expression,
repeat(seq(',', $.type_expression)),
optional(','),
'>',
),
function_type: $ => seq(
'fn',
'(',
optional(seq(
$.type_expression,
repeat(seq(',', $.type_expression)),
optional(','),
)),
')',
':',
$.type_expression,
optional($.effect_clause),
),
tuple_type: $ => seq(
'(',
$.type_expression,
',',
$.type_expression,
repeat(seq(',', $.type_expression)),
optional(','),
')',
),
record_type: $ => seq(
'{',
optional(seq(
$.record_field,
repeat(seq(',', $.record_field)),
optional(','),
)),
'}',
),
// Expressions
_expression: $ => choice(
$.primary_expression,
$.binary_expression,
$.unary_expression,
$.call_expression,
$.member_expression,
$.index_expression,
$.if_expression,
$.match_expression,
$.block_expression,
$.lambda_expression,
$.run_expression,
$.resume_expression,
$.pipe_expression,
),
primary_expression: $ => choice(
$.identifier,
$.number,
$.string,
$.char,
$.boolean,
$.unit,
$.list_expression,
$.tuple_expression,
$.record_expression,
seq('(', $._expression, ')'),
),
// Literals
number: $ => choice(
$.integer,
$.float,
),
integer: $ => /\d+/,
float: $ => /\d+\.\d+/,
string: $ => choice(
$.simple_string,
$.interpolated_string,
),
simple_string: $ => seq(
'"',
repeat(choice(
$.string_content,
$.escape_sequence,
)),
'"',
),
interpolated_string: $ => seq(
'"',
repeat(choice(
$.string_content,
$.escape_sequence,
$.interpolation,
)),
'"',
),
string_content: $ => /[^"\\{]+/,
escape_sequence: $ => /\\[nrt\\'"0{}\}]/,
interpolation: $ => seq(
'{',
$._expression,
'}',
),
char: $ => seq(
"'",
choice(
/[^'\\]/,
$.escape_sequence,
),
"'",
),
boolean: $ => choice('true', 'false'),
unit: $ => seq('(', ')'),
list_expression: $ => seq(
'[',
optional(seq(
$._expression,
repeat(seq(',', $._expression)),
optional(','),
)),
']',
),
tuple_expression: $ => seq(
'(',
$._expression,
',',
$._expression,
repeat(seq(',', $._expression)),
optional(','),
')',
),
record_expression: $ => seq(
'{',
optional(seq(
$.field_assignment,
repeat(seq(',', $.field_assignment)),
optional(','),
)),
'}',
),
field_assignment: $ => seq(
field('name', $.identifier),
':',
field('value', $._expression),
),
// Binary expressions
binary_expression: $ => choice(
prec.left(1, seq($._expression, '||', $._expression)),
prec.left(2, seq($._expression, '&&', $._expression)),
prec.left(3, seq($._expression, choice('==', '!='), $._expression)),
prec.left(4, seq($._expression, choice('<', '>', '<=', '>='), $._expression)),
prec.left(5, seq($._expression, choice('+', '-'), $._expression)),
prec.left(6, seq($._expression, choice('*', '/', '%'), $._expression)),
),
unary_expression: $ => prec(7, choice(
seq('-', $._expression),
seq('!', $._expression),
)),
// Pipe expression
pipe_expression: $ => prec.left(0, seq(
$._expression,
'|>',
$._expression,
)),
// Call expression
call_expression: $ => prec(8, seq(
field('function', $._expression),
'(',
optional(seq(
$._expression,
repeat(seq(',', $._expression)),
optional(','),
)),
')',
)),
// Member expression
member_expression: $ => prec(9, seq(
field('object', $._expression),
'.',
field('member', $.identifier),
)),
// Index expression
index_expression: $ => prec(8, seq(
field('object', $._expression),
'[',
field('index', $._expression),
']',
)),
// If expression
if_expression: $ => prec.right(seq(
'if',
field('condition', $._expression),
'then',
field('then', $._expression),
'else',
field('else', $._expression),
)),
// Match expression
match_expression: $ => seq(
'match',
field('value', $._expression),
'{',
repeat1($.match_arm),
'}',
),
match_arm: $ => seq(
field('pattern', $.pattern),
'=>',
field('body', $._expression),
optional(','),
),
pattern: $ => choice(
$.identifier,
$.number,
$.string,
$.boolean,
'_',
$.constructor_pattern,
$.tuple_pattern,
$.record_pattern,
),
constructor_pattern: $ => seq(
field('name', $.identifier),
optional(seq(
'(',
$.pattern,
repeat(seq(',', $.pattern)),
optional(','),
')',
)),
),
tuple_pattern: $ => seq(
'(',
$.pattern,
',',
$.pattern,
repeat(seq(',', $.pattern)),
optional(','),
')',
),
record_pattern: $ => seq(
'{',
$.field_pattern,
repeat(seq(',', $.field_pattern)),
optional(','),
'}',
),
field_pattern: $ => seq(
field('name', $.identifier),
optional(seq(':', field('pattern', $.pattern))),
),
// Block expression
block_expression: $ => seq(
'{',
repeat($.statement),
optional($._expression),
'}',
),
statement: $ => seq(
$.let_declaration,
),
// Lambda expression
lambda_expression: $ => seq(
'fn',
$.parameter_list,
optional(seq(':', $.type_expression)),
'=>',
$._expression,
),
// Run expression
run_expression: $ => seq(
'run',
field('expression', $._expression),
'with',
'{',
optional(seq(
$.handler_binding,
repeat(seq(',', $.handler_binding)),
optional(','),
)),
'}',
),
handler_binding: $ => seq(
field('effect', $.identifier),
'=',
field('handler', $.identifier),
),
// Resume expression
resume_expression: $ => seq(
'resume',
'(',
$._expression,
')',
),
// Identifier
identifier: $ => /[a-zA-Z_][a-zA-Z0-9_]*/,
}
});