549 lines
19 KiB
Rust
549 lines
19 KiB
Rust
//! LScript language support for the Tree-sitter parsing library.
|
|
|
|
use tree_sitter_language::LanguageFn;
|
|
|
|
unsafe extern "C" {
|
|
fn tree_sitter_lscript() -> *const ();
|
|
}
|
|
|
|
/// The Tree-sitter language function for LScript.
|
|
pub const LANGUAGE: LanguageFn = unsafe { LanguageFn::from_raw(tree_sitter_lscript) };
|
|
|
|
/// Generated descriptions of the grammar's named node types.
|
|
pub const NODE_TYPES: &str = include_str!("../../src/node-types.json");
|
|
|
|
/// The default syntax-highlighting query.
|
|
pub const HIGHLIGHTS_QUERY: &str = include_str!("../../queries/highlights.scm");
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
fn parse(source: &str) -> tree_sitter::Tree {
|
|
let mut parser = tree_sitter::Parser::new();
|
|
parser
|
|
.set_language(&super::LANGUAGE.into())
|
|
.expect("generated LScript grammar must load");
|
|
parser
|
|
.parse(source, None)
|
|
.expect("parser must produce a tree")
|
|
}
|
|
|
|
#[test]
|
|
fn loads_and_parses_lscript() {
|
|
let tree = parse("answer :: 42\n");
|
|
assert!(!tree.root_node().has_error());
|
|
}
|
|
|
|
#[test]
|
|
fn exposes_stable_expression_fields_without_precedence_wrappers() {
|
|
let source = "answer := left + right * -value\n";
|
|
let tree = parse(source);
|
|
let declaration = tree.root_node().named_child(0).unwrap();
|
|
let addition = declaration.child_by_field_name("value").unwrap();
|
|
|
|
assert_eq!(addition.kind(), "binary_expression");
|
|
assert_eq!(
|
|
addition
|
|
.child_by_field_name("operator")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"+"
|
|
);
|
|
assert_eq!(
|
|
addition.child_by_field_name("left").unwrap().kind(),
|
|
"identifier"
|
|
);
|
|
|
|
let multiplication = addition.child_by_field_name("right").unwrap();
|
|
assert_eq!(multiplication.kind(), "binary_expression");
|
|
let negation = multiplication.child_by_field_name("right").unwrap();
|
|
assert_eq!(negation.kind(), "unary_expression");
|
|
assert_eq!(
|
|
negation.child_by_field_name("operand").unwrap().kind(),
|
|
"identifier"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exposes_meaningful_type_constructor_fields() {
|
|
let source = "Transform :: []{i32?} -> [str]i32 ! Error\n";
|
|
let tree = parse(source);
|
|
let declaration = tree.root_node().named_child(0).unwrap();
|
|
let definition = declaration.child_by_field_name("value").unwrap();
|
|
let function = definition.named_child(0).unwrap();
|
|
|
|
assert_eq!(function.kind(), "function_type");
|
|
assert_eq!(
|
|
function
|
|
.child_by_field_name("effect")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"->"
|
|
);
|
|
assert_eq!(
|
|
function.child_by_field_name("input").unwrap().kind(),
|
|
"array_type"
|
|
);
|
|
let result = function.child_by_field_name("result").unwrap();
|
|
assert_eq!(result.kind(), "result_type");
|
|
assert_eq!(
|
|
result.child_by_field_name("value").unwrap().kind(),
|
|
"map_type"
|
|
);
|
|
assert_eq!(
|
|
result.child_by_field_name("error").unwrap().kind(),
|
|
"identifier"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exposes_typed_function_headers() {
|
|
let source =
|
|
"named :: @ value: i32 => i32 { value }\nadd :: @ (a: i32, b: i32) { a + b }\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let named = tree.root_node().named_child(0).unwrap();
|
|
let function = named.child_by_field_name("value").unwrap();
|
|
assert_eq!(function.kind(), "function_literal");
|
|
let parameter = function.child_by_field_name("parameter").unwrap();
|
|
assert_eq!(parameter.kind(), "typed_parameter");
|
|
assert_eq!(
|
|
parameter
|
|
.child_by_field_name("pattern")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"value"
|
|
);
|
|
assert_eq!(
|
|
function
|
|
.child_by_field_name("effect")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"=>"
|
|
);
|
|
assert_eq!(
|
|
function.child_by_field_name("result").unwrap().kind(),
|
|
"primitive_type"
|
|
);
|
|
assert_eq!(
|
|
function.child_by_field_name("body").unwrap().kind(),
|
|
"block"
|
|
);
|
|
|
|
let tuple_declaration = tree.root_node().named_child(1).unwrap();
|
|
let tuple_function = tuple_declaration.child_by_field_name("value").unwrap();
|
|
let tuple_input = tuple_function.child_by_field_name("parameter").unwrap();
|
|
assert_eq!(tuple_input.kind(), "typed_tuple_pattern");
|
|
assert!(tuple_function.child_by_field_name("parameters").is_none());
|
|
assert_eq!(tuple_input.named_child_count(), 2);
|
|
for index in 0..2 {
|
|
assert_eq!(
|
|
tuple_input.named_child(index).unwrap().kind(),
|
|
"typed_tuple_component"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn distinguishes_whole_argument_annotation_from_tuple_component_annotations() {
|
|
let source = "whole :: @ (left, right): (i32, i32) { left + right }\n\
|
|
components :: @ (left: i32, right: i32) { left + right }\n\
|
|
product :: @ {value}: &{value: i32:} { value }\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let whole = tree.root_node().named_child(0).unwrap();
|
|
let whole_function = whole.child_by_field_name("value").unwrap();
|
|
let whole_input = whole_function.child_by_field_name("parameter").unwrap();
|
|
assert_eq!(whole_input.kind(), "typed_parameter");
|
|
assert_eq!(
|
|
whole_input.child_by_field_name("pattern").unwrap().kind(),
|
|
"tuple_pattern"
|
|
);
|
|
assert_eq!(
|
|
whole_input.child_by_field_name("type").unwrap().kind(),
|
|
"tuple_type"
|
|
);
|
|
|
|
let components = tree.root_node().named_child(1).unwrap();
|
|
let components_function = components.child_by_field_name("value").unwrap();
|
|
let component_input = components_function
|
|
.child_by_field_name("parameter")
|
|
.unwrap();
|
|
assert_eq!(component_input.kind(), "typed_tuple_pattern");
|
|
assert_eq!(component_input.named_child_count(), 2);
|
|
|
|
let product = tree.root_node().named_child(2).unwrap();
|
|
let product_function = product.child_by_field_name("value").unwrap();
|
|
let product_input = product_function.child_by_field_name("parameter").unwrap();
|
|
assert_eq!(product_input.kind(), "typed_parameter");
|
|
assert_eq!(
|
|
product_input.child_by_field_name("pattern").unwrap().kind(),
|
|
"product_pattern"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exposes_sum_spelled_optionals_and_untyped_inputs() {
|
|
let source = "present: i32?: 1->|.some\n\
|
|
double :: @ value { value * 2 }\n\
|
|
unwrap :: @ result { match result { |.ok inner => inner; |.err _ => 0 } }\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let present = tree.root_node().named_child(0).unwrap();
|
|
let wrapped = present.child_by_field_name("value").unwrap();
|
|
assert_eq!(wrapped.kind(), "pipeline_expression");
|
|
assert_eq!(
|
|
wrapped.child_by_field_name("input").unwrap().kind(),
|
|
"signed_numeric_literal"
|
|
);
|
|
let stage = wrapped.named_child(1).unwrap();
|
|
assert_eq!(stage.kind(), "direct_pipeline_stage");
|
|
let constructor = stage
|
|
.child_by_field_name("callable")
|
|
.unwrap()
|
|
.named_child(0)
|
|
.unwrap();
|
|
assert_eq!(constructor.kind(), "sum_constructor");
|
|
assert_eq!(
|
|
constructor
|
|
.child_by_field_name("variant")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"some"
|
|
);
|
|
|
|
let double = tree.root_node().named_child(1).unwrap();
|
|
let input = double
|
|
.child_by_field_name("value")
|
|
.unwrap()
|
|
.child_by_field_name("parameter")
|
|
.unwrap();
|
|
assert_eq!(input.kind(), "typed_parameter");
|
|
assert!(input.child_by_field_name("type").is_none());
|
|
|
|
let unwrap = tree.root_node().named_child(2).unwrap();
|
|
let function = unwrap.child_by_field_name("value").unwrap();
|
|
let body = function.child_by_field_name("body").unwrap();
|
|
let arm = body.named_child(0).unwrap().named_child(1).unwrap();
|
|
let pattern = arm.child_by_field_name("pattern").unwrap();
|
|
let variant = pattern.named_child(0).unwrap();
|
|
assert_eq!(variant.kind(), "sum_pattern");
|
|
assert_eq!(
|
|
variant
|
|
.child_by_field_name("variant")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"ok"
|
|
);
|
|
assert_eq!(
|
|
variant.child_by_field_name("payload").unwrap().kind(),
|
|
"identifier"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn binds_same_line_stages_tighter_than_operators() {
|
|
let source = "mixed :: -3->add_ten + 2 * 3->double - {1 + 1}->double\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let declaration = tree.root_node().named_child(0).unwrap();
|
|
let subtraction = declaration.child_by_field_name("value").unwrap();
|
|
assert_eq!(subtraction.kind(), "binary_expression");
|
|
assert_eq!(
|
|
subtraction
|
|
.child_by_field_name("operator")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"-"
|
|
);
|
|
|
|
let addition = subtraction.child_by_field_name("left").unwrap();
|
|
assert_eq!(addition.kind(), "binary_expression");
|
|
let negation = addition.child_by_field_name("left").unwrap();
|
|
assert_eq!(negation.kind(), "unary_expression");
|
|
let negated = negation.child_by_field_name("operand").unwrap();
|
|
assert_eq!(negated.kind(), "pipeline_expression");
|
|
assert_eq!(
|
|
negated
|
|
.child_by_field_name("input")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"3"
|
|
);
|
|
|
|
let product = addition.child_by_field_name("right").unwrap();
|
|
assert_eq!(product.kind(), "binary_expression");
|
|
assert_eq!(
|
|
product.child_by_field_name("right").unwrap().kind(),
|
|
"pipeline_expression"
|
|
);
|
|
|
|
let grouped = subtraction.child_by_field_name("right").unwrap();
|
|
assert_eq!(grouped.kind(), "pipeline_expression");
|
|
assert_eq!(
|
|
grouped.child_by_field_name("input").unwrap().kind(),
|
|
"block"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn applies_continuation_stages_to_the_whole_preceding_expression() {
|
|
let source = "flat := value->first\n->second\nwhole := a + b->f\n->g\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let flat = tree
|
|
.root_node()
|
|
.named_child(0)
|
|
.unwrap()
|
|
.child_by_field_name("value")
|
|
.unwrap();
|
|
assert_eq!(flat.kind(), "pipeline_expression");
|
|
assert_eq!(
|
|
flat.child_by_field_name("input").unwrap().kind(),
|
|
"identifier"
|
|
);
|
|
assert_eq!(flat.named_child(1).unwrap().kind(), "direct_pipeline_stage");
|
|
assert_eq!(
|
|
flat.named_child(2).unwrap().kind(),
|
|
"continuation_pipeline_stage"
|
|
);
|
|
assert_eq!(flat.named_child_count(), 3);
|
|
|
|
let whole = tree
|
|
.root_node()
|
|
.named_child(1)
|
|
.unwrap()
|
|
.child_by_field_name("value")
|
|
.unwrap();
|
|
assert_eq!(whole.kind(), "pipeline_expression");
|
|
let head = whole.child_by_field_name("input").unwrap();
|
|
assert_eq!(head.kind(), "binary_expression");
|
|
assert_eq!(
|
|
head.child_by_field_name("right").unwrap().kind(),
|
|
"pipeline_expression"
|
|
);
|
|
assert_eq!(
|
|
whole.named_child(1).unwrap().kind(),
|
|
"continuation_pipeline_stage"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exposes_generic_binders_constraints_and_output_types() {
|
|
let source = "Show :: <>{ to_str: str; ...Display }\n\
|
|
pick: <T: Show> (T, T) -> T: @ (first, second): (T, T) { first }\n\
|
|
Card :: &{ label: Output<make_label: Show>: }\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let show = tree.root_node().named_child(0).unwrap();
|
|
let definition = show.child_by_field_name("value").unwrap();
|
|
assert_eq!(definition.kind(), "type_definition");
|
|
let constraint = definition.named_child(0).unwrap();
|
|
assert_eq!(constraint.kind(), "constraint_body");
|
|
let entry = constraint.named_child(0).unwrap();
|
|
assert_eq!(entry.kind(), "constraint_entry");
|
|
assert_eq!(
|
|
entry
|
|
.child_by_field_name("name")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"to_str"
|
|
);
|
|
assert_eq!(
|
|
entry.child_by_field_name("output").unwrap().kind(),
|
|
"primitive_type"
|
|
);
|
|
let spread = constraint.named_child(1).unwrap();
|
|
assert_eq!(spread.kind(), "constraint_spread");
|
|
assert_eq!(
|
|
spread
|
|
.child_by_field_name("constraint")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"Display"
|
|
);
|
|
|
|
let pick = tree.root_node().named_child(1).unwrap();
|
|
let binder = pick.child_by_field_name("binder").unwrap();
|
|
assert_eq!(binder.kind(), "binder");
|
|
let parameter = binder.named_child(0).unwrap();
|
|
assert_eq!(parameter.kind(), "binder_entry");
|
|
assert_eq!(
|
|
parameter
|
|
.child_by_field_name("type")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"T"
|
|
);
|
|
assert_eq!(
|
|
parameter
|
|
.child_by_field_name("constraint")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"Show"
|
|
);
|
|
assert_eq!(
|
|
pick.child_by_field_name("type").unwrap().kind(),
|
|
"function_type"
|
|
);
|
|
assert_eq!(
|
|
pick.child_by_field_name("value").unwrap().kind(),
|
|
"function_literal"
|
|
);
|
|
|
|
let card = tree.root_node().named_child(2).unwrap();
|
|
let field = card
|
|
.child_by_field_name("value")
|
|
.unwrap()
|
|
.named_child(0)
|
|
.unwrap()
|
|
.named_child(0)
|
|
.unwrap();
|
|
assert_eq!(field.kind(), "product_field");
|
|
let output = field.child_by_field_name("type").unwrap();
|
|
assert_eq!(output.kind(), "output_type");
|
|
assert_eq!(
|
|
output
|
|
.child_by_field_name("function")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"make_label"
|
|
);
|
|
assert_eq!(
|
|
output
|
|
.child_by_field_name("output")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"Show"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exposes_import_filters() {
|
|
let source = "use math.{sqrt for f64, pi}\nuse .shapes.area for Circle\npub { area for Circle; sqrt }\n";
|
|
let tree = parse(source);
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
|
|
let group = tree.root_node().named_child(0).unwrap();
|
|
assert_eq!(group.kind(), "use_declaration");
|
|
let member = group.named_child(1).unwrap();
|
|
assert_eq!(member.kind(), "import_member");
|
|
let filter = member.named_child(1).unwrap();
|
|
assert_eq!(filter.kind(), "import_filter");
|
|
assert_eq!(
|
|
filter.child_by_field_name("type").unwrap().kind(),
|
|
"primitive_type"
|
|
);
|
|
assert_eq!(group.named_child(2).unwrap().named_child_count(), 1);
|
|
|
|
let single = tree.root_node().named_child(1).unwrap();
|
|
assert_eq!(single.named_child(1).unwrap().kind(), "import_filter");
|
|
|
|
let publication = tree.root_node().named_child(2).unwrap();
|
|
assert_eq!(publication.kind(), "pub_declaration");
|
|
let filtered = publication.named_child(0).unwrap();
|
|
assert_eq!(filtered.kind(), "pub_entry");
|
|
assert_eq!(
|
|
filtered
|
|
.child_by_field_name("name")
|
|
.unwrap()
|
|
.utf8_text(source.as_bytes())
|
|
.unwrap(),
|
|
"area"
|
|
);
|
|
assert_eq!(filtered.named_child(1).unwrap().kind(), "import_filter");
|
|
assert_eq!(publication.named_child(1).unwrap().named_child_count(), 1);
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_removed_wrapper_forms_and_reserved_words() {
|
|
for source in [
|
|
"present: i32?: some 1\n",
|
|
"old :: @ value: i32 -> i32 ! str { value! }\n",
|
|
"old :: @ value: str -> i32 ! str { !value }\n",
|
|
"context := 1\n",
|
|
] {
|
|
assert!(parse(source).root_node().has_error(), "accepted {source}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn rejects_removed_function_literal_forms() {
|
|
for source in [
|
|
"old: i32 -> i32: value@ value\n",
|
|
"old: i32 -> i32:\n@ value: i32 { value }\n",
|
|
"old :: @ (value:i32) { value }\n",
|
|
"old :: @ (value:i32,) { value }\n",
|
|
"old :: @ (\nvalue:i32\n) -> i32 { value }\n",
|
|
] {
|
|
assert!(parse(source).root_node().has_error(), "accepted {source}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn distinguishes_compact_ranges_floats_and_word_prefixes() {
|
|
let source = "value := (item.value, 0..<limit)->slice\n\
|
|
trailing_dot := 1.\n\
|
|
anchor := value\n\
|
|
android := value\n\
|
|
order: []Item = []\n\
|
|
ready := first\n\
|
|
and second\n\
|
|
or fallback\n";
|
|
let tree = parse(source);
|
|
|
|
assert!(
|
|
!tree.root_node().has_error(),
|
|
"{}",
|
|
tree.root_node().to_sexp()
|
|
);
|
|
}
|
|
}
|