From d0958220fefdec17df0a07fde87dbbc4e8d6d1fe Mon Sep 17 00:00:00 2001 From: Adam Chalmers Date: Thu, 22 May 2025 22:13:27 -0500 Subject: [PATCH] KCL: End-exclusive ranges like [0..<10] (#7179) Closes https://github.com/KittyCAD/modeling-app/issues/6843 To clarify: `[1..10]` is 1, 2, ..., 8, 9, 10 `[1..<10]` is 1, 2, ... 8, 9 --- rust/kcl-lib/src/lsp/kcl/mod.rs | 1 + rust/kcl-lib/src/parsing/parser.rs | 27 +++- ..._parser__snapshot_tests__array_ranges.snap | 117 ++++++++++++++++++ rust/kcl-lib/src/parsing/token/mod.rs | 3 + rust/kcl-lib/src/parsing/token/tokeniser.rs | 12 +- 5 files changed, 155 insertions(+), 5 deletions(-) create mode 100644 rust/kcl-lib/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__array_ranges.snap diff --git a/rust/kcl-lib/src/lsp/kcl/mod.rs b/rust/kcl-lib/src/lsp/kcl/mod.rs index 6bea88a11..bed5cc5ab 100644 --- a/rust/kcl-lib/src/lsp/kcl/mod.rs +++ b/rust/kcl-lib/src/lsp/kcl/mod.rs @@ -1189,6 +1189,7 @@ impl LanguageServer for Backend { } async fn completion(&self, params: CompletionParams) -> RpcResult> { + // ADAM: This is the entrypoint. let mut completions = vec![CompletionItem { label: PIPE_OPERATOR.to_string(), label_details: None, diff --git a/rust/kcl-lib/src/parsing/parser.rs b/rust/kcl-lib/src/parsing/parser.rs index 0cca44a39..93af08b20 100644 --- a/rust/kcl-lib/src/parsing/parser.rs +++ b/rust/kcl-lib/src/parsing/parser.rs @@ -886,7 +886,7 @@ fn array_end_start(i: &mut TokenSlice) -> PResult> { ignore_whitespace(i); let start_element = expression.parse_next(i)?; ignore_whitespace(i); - double_period.parse_next(i)?; + let end_inclusive = alt((end_inclusive_range.map(|_| true), end_exclusive_range.map(|_| false))).parse_next(i)?; ignore_whitespace(i); let end_element = expression.parse_next(i)?; ignore_whitespace(i); @@ -895,7 +895,7 @@ fn array_end_start(i: &mut TokenSlice) -> PResult> { ArrayRangeExpression { start_element, end_element, - end_inclusive: true, + end_inclusive, digest: None, }, start, @@ -2705,7 +2705,7 @@ fn period(i: &mut TokenSlice) -> PResult<()> { Ok(()) } -fn double_period(i: &mut TokenSlice) -> PResult { +fn end_inclusive_range(i: &mut TokenSlice) -> PResult { any.try_map(|token: Token| { if matches!(token.token_type, TokenType::DoublePeriod) { Ok(token) @@ -2724,6 +2724,21 @@ fn double_period(i: &mut TokenSlice) -> PResult { .parse_next(i) } +fn end_exclusive_range(i: &mut TokenSlice) -> PResult { + any.try_map(|token: Token| { + if matches!(token.token_type, TokenType::DoublePeriodLessThan) { + Ok(token) + } else { + Err(CompilationError::fatal( + token.as_source_range(), + format!("expected a '..<' but found {}", token.value.as_str()), + )) + } + }) + .context(expected("the ..< operator, used for array ranges like [0..<10]")) + .parse_next(i) +} + fn colon(i: &mut TokenSlice) -> PResult { TokenType::Colon.parse_from(i) } @@ -5344,7 +5359,6 @@ mod snapshot_tests { ); snapshot_test!(aa, r#"sg = -scale"#); snapshot_test!(ab, "line(endAbsolute = [0, -1])"); - snapshot_test!(ac, "myArray = [0..10]"); snapshot_test!( ad, r#" @@ -5485,6 +5499,11 @@ my14 = 4 ^ 2 - 3 ^ 2 * 2 )"# ); snapshot_test!(kw_function_in_binary_op, r#"val = f(x = 1) + 1"#); + snapshot_test!( + array_ranges, + r#"incl = [1..10] + excl = [0..<10]"# + ); } #[allow(unused)] diff --git a/rust/kcl-lib/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__array_ranges.snap b/rust/kcl-lib/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__array_ranges.snap new file mode 100644 index 000000000..4af2ba212 --- /dev/null +++ b/rust/kcl-lib/src/parsing/snapshots/kcl_lib__parsing__parser__snapshot_tests__array_ranges.snap @@ -0,0 +1,117 @@ +--- +source: kcl-lib/src/parsing/parser.rs +expression: actual +--- +{ + "body": [ + { + "commentStart": 0, + "declaration": { + "commentStart": 0, + "end": 14, + "id": { + "commentStart": 0, + "end": 4, + "name": "incl", + "start": 0, + "type": "Identifier" + }, + "init": { + "commentStart": 7, + "end": 14, + "endElement": { + "commentStart": 11, + "end": 13, + "raw": "10", + "start": 11, + "type": "Literal", + "type": "Literal", + "value": { + "value": 10.0, + "suffix": "None" + } + }, + "endInclusive": true, + "start": 7, + "startElement": { + "commentStart": 8, + "end": 9, + "raw": "1", + "start": 8, + "type": "Literal", + "type": "Literal", + "value": { + "value": 1.0, + "suffix": "None" + } + }, + "type": "ArrayRangeExpression", + "type": "ArrayRangeExpression" + }, + "start": 0, + "type": "VariableDeclarator" + }, + "end": 14, + "kind": "const", + "start": 0, + "type": "VariableDeclaration", + "type": "VariableDeclaration" + }, + { + "commentStart": 23, + "declaration": { + "commentStart": 23, + "end": 38, + "id": { + "commentStart": 23, + "end": 27, + "name": "excl", + "start": 23, + "type": "Identifier" + }, + "init": { + "commentStart": 30, + "end": 38, + "endElement": { + "commentStart": 35, + "end": 37, + "raw": "10", + "start": 35, + "type": "Literal", + "type": "Literal", + "value": { + "value": 10.0, + "suffix": "None" + } + }, + "endInclusive": false, + "start": 30, + "startElement": { + "commentStart": 31, + "end": 32, + "raw": "0", + "start": 31, + "type": "Literal", + "type": "Literal", + "value": { + "value": 0.0, + "suffix": "None" + } + }, + "type": "ArrayRangeExpression", + "type": "ArrayRangeExpression" + }, + "start": 23, + "type": "VariableDeclarator" + }, + "end": 38, + "kind": "const", + "start": 23, + "type": "VariableDeclaration", + "type": "VariableDeclaration" + } + ], + "commentStart": 0, + "end": 38, + "start": 0 +} diff --git a/rust/kcl-lib/src/parsing/token/mod.rs b/rust/kcl-lib/src/parsing/token/mod.rs index 7d702998d..e12d47f1f 100644 --- a/rust/kcl-lib/src/parsing/token/mod.rs +++ b/rust/kcl-lib/src/parsing/token/mod.rs @@ -369,6 +369,8 @@ pub enum TokenType { Period, /// A double period: `..`. DoublePeriod, + /// A double period and a less than: `..<`. + DoublePeriodLessThan, /// A line comment. LineComment, /// A block comment. @@ -410,6 +412,7 @@ impl TryFrom for SemanticTokenType { | TokenType::DoubleColon | TokenType::Period | TokenType::DoublePeriod + | TokenType::DoublePeriodLessThan | TokenType::Hash | TokenType::Dollar | TokenType::At diff --git a/rust/kcl-lib/src/parsing/token/tokeniser.rs b/rust/kcl-lib/src/parsing/token/tokeniser.rs index 3ebdb2e84..c48ad3683 100644 --- a/rust/kcl-lib/src/parsing/token/tokeniser.rs +++ b/rust/kcl-lib/src/parsing/token/tokeniser.rs @@ -87,7 +87,7 @@ pub(super) fn token(i: &mut Input<'_>) -> PResult { '0'..='9' => number, ';' => semi_colon, ':' => alt((double_colon, colon)), - '.' => alt((number, double_period, period)), + '.' => alt((number, double_period_less_than, double_period, period)), '#' => hash, '$' => dollar, '!' => alt((operator, bang)), @@ -320,6 +320,16 @@ fn double_period(i: &mut Input<'_>) -> PResult { )) } +fn double_period_less_than(i: &mut Input<'_>) -> PResult { + let (value, range) = "..<".with_span().parse_next(i)?; + Ok(Token::from_range( + range, + i.state.module_id, + TokenType::DoublePeriodLessThan, + value.to_string(), + )) +} + /// Zero or more of either: /// 1. Any character except " or \ /// 2. Any character preceded by \