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_]*/,
}
});

View File

@@ -0,0 +1,38 @@
{
"name": "tree-sitter-lux",
"version": "0.1.0",
"description": "Tree-sitter grammar for the Lux programming language",
"main": "bindings/node",
"keywords": [
"tree-sitter",
"parser",
"lux"
],
"repository": {
"type": "git",
"url": "https://github.com/your-org/lux"
},
"license": "MIT",
"dependencies": {
"nan": "^2.17.0"
},
"devDependencies": {
"tree-sitter-cli": "^0.20.8"
},
"scripts": {
"build": "tree-sitter generate && node-gyp rebuild",
"test": "tree-sitter test",
"parse": "tree-sitter parse"
},
"tree-sitter": [
{
"scope": "source.lux",
"injection-regex": "lux",
"file-types": [
"lux"
],
"highlights": "queries/highlights.scm",
"locals": "queries/locals.scm"
}
]
}

View File

@@ -0,0 +1,141 @@
; Syntax highlighting queries for Lux
; Comments
(line_comment) @comment
(doc_comment) @comment.documentation
; Keywords
[
"fn"
"let"
"type"
"effect"
"handler"
"trait"
"impl"
"for"
"import"
"export"
"match"
"if"
"then"
"else"
"with"
"run"
"resume"
"is"
] @keyword
; Operators
[
"+"
"-"
"*"
"/"
"%"
"=="
"!="
"<"
">"
"<="
">="
"&&"
"||"
"!"
"="
"=>"
"|>"
":"
"|"
"."
","
] @operator
; Punctuation
[
"("
")"
"{"
"}"
"["
"]"
"<"
">"
] @punctuation.bracket
; Literals
(integer) @number
(float) @number.float
(string) @string
(char) @character
(boolean) @boolean
(unit) @constant.builtin
; String interpolation
(interpolation
"{" @punctuation.special
"}" @punctuation.special)
(escape_sequence) @string.escape
; Types
(type_expression (identifier) @type)
(generic_type (identifier) @type)
(type_declaration name: (identifier) @type.definition)
(type_parameters (identifier) @type.parameter)
; Built-in types
((identifier) @type.builtin
(#match? @type.builtin "^(Int|Float|Bool|String|Char|Unit|Option|Result|List)$"))
; Functions
(function_declaration name: (identifier) @function.definition)
(call_expression function: (identifier) @function.call)
(call_expression function: (member_expression member: (identifier) @function.method))
; Effect operations
(effect_declaration name: (identifier) @type.definition)
(effect_operation name: (identifier) @function.definition)
(handler_declaration name: (identifier) @function.definition)
(handler_declaration effect: (identifier) @type)
(handler_operation name: (identifier) @function.definition)
; Traits
(trait_declaration name: (identifier) @type.definition)
(trait_method name: (identifier) @function.definition)
(impl_declaration trait: (identifier) @type)
; Parameters
(parameter name: (identifier) @variable.parameter)
; Variables
(let_declaration name: (identifier) @variable.definition)
(identifier) @variable
; Patterns
(pattern (identifier) @variable)
(constructor_pattern name: (identifier) @constructor)
(field_pattern name: (identifier) @property)
; Record fields
(field_assignment name: (identifier) @property)
(record_field name: (identifier) @property)
; Member access
(member_expression member: (identifier) @property)
; Modules (capitalized identifiers used as module access)
(member_expression
object: (identifier) @module
(#match? @module "^[A-Z]"))
; Variants/Constructors (capitalized identifiers)
((identifier) @constructor
(#match? @constructor "^[A-Z][a-zA-Z0-9]*$"))
; Special identifiers
((identifier) @constant.builtin
(#match? @constant.builtin "^(None|Some|Ok|Err|true|false)$"))
; Property annotations
(property_clause (identifier) @attribute)

View File

@@ -0,0 +1,22 @@
; Indent rules for Lux
[
(block_expression)
(match_expression)
(if_expression)
(record_expression)
(list_expression)
(effect_declaration)
(handler_declaration)
(trait_declaration)
(impl_declaration)
(enum_definition)
] @indent
[
"}"
"]"
")"
] @outdent
(match_arm "=>" @indent)

View File

@@ -0,0 +1,21 @@
; Scopes
(function_declaration) @scope
(block_expression) @scope
(lambda_expression) @scope
(match_arm) @scope
(handler_declaration) @scope
; Definitions
(function_declaration name: (identifier) @definition.function)
(let_declaration name: (identifier) @definition.var)
(parameter name: (identifier) @definition.parameter)
(type_declaration name: (identifier) @definition.type)
(effect_declaration name: (identifier) @definition.type)
(trait_declaration name: (identifier) @definition.type)
(handler_declaration name: (identifier) @definition.function)
; Pattern bindings
(pattern (identifier) @definition.var)
; References
(identifier) @reference