Compare commits

...

3 Commits

Author SHA1 Message Date
7b46656c0f Update docs 2024-08-02 21:07:41 -04:00
6c79b15adf Update existing tests 2024-08-02 21:07:41 -04:00
b45aa89d16 Require variable declaration for pipe expressions 2024-08-02 19:36:19 -04:00
8 changed files with 68 additions and 23 deletions

View File

@ -15,7 +15,7 @@ close(sketch_group: SketchGroup, tag?: TagDeclarator) -> SketchGroup
### Examples ### Examples
```js ```js
startSketchOn('XZ') const exampleSketch = startSketchOn('XZ')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([10, 10], %) |> line([10, 10], %)
|> line([10, 0], %) |> line([10, 0], %)

View File

@ -81459,7 +81459,7 @@
"unpublished": false, "unpublished": false,
"deprecated": false, "deprecated": false,
"examples": [ "examples": [
"startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([10, 0], %)\n |> close(%)\n |> extrude(10, %)", "const exampleSketch = startSketchOn('XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 10], %)\n |> line([10, 0], %)\n |> close(%)\n |> extrude(10, %)",
"const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)" "const exampleSketch = startSketchOn('-XZ')\n |> startProfileAt([0, 0], %)\n |> line([10, 0], %)\n |> line([0, 10], %)\n |> close(%)\n\nconst example = extrude(10, exampleSketch)"
] ]
}, },

View File

@ -3994,7 +3994,7 @@ mod tests {
|> startProfileAt([0.0000000000, 5.0000000000], %) |> startProfileAt([0.0000000000, 5.0000000000], %)
|> line([0.4900857016, -0.0240763666], %) |> line([0.4900857016, -0.0240763666], %)
startSketchOn('XY') let s1 = startSketchOn('XY')
|> startProfileAt([0.0000000000, 5.0000000000], %) |> startProfileAt([0.0000000000, 5.0000000000], %)
|> line([0.4900857016, -0.0240763666], %) |> line([0.4900857016, -0.0240763666], %)
@ -4025,7 +4025,7 @@ ghi("things")
assert_eq!(folding_ranges[1].end_line, 254); assert_eq!(folding_ranges[1].end_line, 254);
assert_eq!( assert_eq!(
folding_ranges[1].collapsed_text, folding_ranges[1].collapsed_text,
Some("startSketchOn('XY')".to_string()) Some("let s1 = startSketchOn('XY')".to_string())
); );
assert_eq!(folding_ranges[2].start_line, 390); assert_eq!(folding_ranges[2].start_line, 390);
assert_eq!(folding_ranges[2].end_line, 403); assert_eq!(folding_ranges[2].end_line, 403);
@ -5264,7 +5264,7 @@ fn ghi = (part001) => {
#[test] #[test]
fn test_recast_trailing_comma() { fn test_recast_trailing_comma() {
let some_program_string = r#"startSketchOn('XY') let some_program_string = r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> arc({ |> arc({
radius: 1, radius: 1,
@ -5278,7 +5278,7 @@ fn ghi = (part001) => {
let recasted = program.recast(&Default::default(), 0); let recasted = program.recast(&Default::default(), 0);
assert_eq!( assert_eq!(
recasted, recasted,
r#"startSketchOn('XY') r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> arc({ |> arc({
radius: 1, radius: 1,
@ -5858,7 +5858,7 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_parse_tag_named_std_lib() { async fn test_parse_tag_named_std_lib() {
let some_program_string = r#"startSketchOn('XY') let some_program_string = r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([5, 5], %, $xLine) |> line([5, 5], %, $xLine)
"#; "#;
@ -5869,13 +5869,13 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err().to_string(), result.unwrap_err().to_string(),
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([76, 82])], message: "Cannot assign a tag to a reserved keyword: xLine" }"# r#"syntax: KclErrorDetails { source_ranges: [SourceRange([84, 90])], message: "Cannot assign a tag to a reserved keyword: xLine" }"#
); );
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_parse_empty_tag() { async fn test_parse_empty_tag() {
let some_program_string = r#"startSketchOn('XY') let some_program_string = r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([5, 5], %, $) |> line([5, 5], %, $)
"#; "#;
@ -5886,13 +5886,13 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.unwrap_err().to_string(), result.unwrap_err().to_string(),
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([57, 59])], message: "Unexpected token" }"# r#"syntax: KclErrorDetails { source_ranges: [SourceRange([65, 67])], message: "Unexpected token" }"#
); );
} }
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_parse_digest() { async fn test_parse_digest() {
let prog1_string = r#"startSketchOn('XY') let prog1_string = r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([5, 5], %) |> line([5, 5], %)
"#; "#;
@ -5900,7 +5900,7 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
let prog1_parser = crate::parser::Parser::new(prog1_tokens); let prog1_parser = crate::parser::Parser::new(prog1_tokens);
let prog1_digest = prog1_parser.ast().unwrap().compute_digest(); let prog1_digest = prog1_parser.ast().unwrap().compute_digest();
let prog2_string = r#"startSketchOn('XY') let prog2_string = r#"let s = startSketchOn('XY')
|> startProfileAt([0, 2], %) |> startProfileAt([0, 2], %)
|> line([5, 5], %) |> line([5, 5], %)
"#; "#;
@ -5910,7 +5910,7 @@ const thickness = sqrt(distance * p * FOS * 6 / (sigmaAllow * width))"#;
assert!(prog1_digest != prog2_digest); assert!(prog1_digest != prog2_digest);
let prog3_string = r#"startSketchOn('XY') let prog3_string = r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %) |> startProfileAt([0, 0], %)
|> line([5, 5], %) |> line([5, 5], %)
"#; "#;

View File

@ -2086,6 +2086,20 @@ const newVar = myVar + 1"#;
); );
} }
#[tokio::test(flavor = "multi_thread")]
async fn test_execute_top_level_pipe_without_variable() {
let ast = r#"startSketchOn('XY')
|> startProfileAt([0, 0], %)
|> lineTo([2, 2], %, $yo)
"#;
let result = parse_execute(ast).await;
assert!(result.is_err());
assert_eq!(
result.unwrap_err().to_string(),
r#"syntax: KclErrorDetails { source_ranges: [SourceRange([0, 78])], message: "A top-level pipe expression must be assigned to a new variable declaration" }"#.to_owned()
);
}
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn test_execute_angled_line_that_intersects() { async fn test_execute_angled_line_that_intersects() {
let ast_fn = |offset: &str| -> String { let ast_fn = |offset: &str| -> String {

View File

@ -1354,7 +1354,7 @@ async fn test_kcl_lsp_formatting() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"startSketchOn('XY') text: r#"let s = startSketchOn('XY')
|> startProfileAt([0,0], %)"# |> startProfileAt([0,0], %)"#
.to_string(), .to_string(),
}, },
@ -1385,7 +1385,7 @@ async fn test_kcl_lsp_formatting() {
assert_eq!(formatting.len(), 1); assert_eq!(formatting.len(), 1);
assert_eq!( assert_eq!(
formatting[0].new_text, formatting[0].new_text,
r#"startSketchOn('XY') r#"let s = startSketchOn('XY')
|> startProfileAt([0, 0], %)"# |> startProfileAt([0, 0], %)"#
); );
} }
@ -2901,7 +2901,7 @@ async fn test_kcl_lsp_folding() {
uri: "file:///test.kcl".try_into().unwrap(), uri: "file:///test.kcl".try_into().unwrap(),
language_id: "kcl".to_string(), language_id: "kcl".to_string(),
version: 1, version: 1,
text: r#"startSketchOn('XY') text: r#"let s = startSketchOn('XY')
|> startProfileAt([0,0], %)"# |> startProfileAt([0,0], %)"#
.to_string(), .to_string(),
}, },
@ -2926,12 +2926,12 @@ async fn test_kcl_lsp_folding() {
assert_eq!( assert_eq!(
folding.first().unwrap().clone(), folding.first().unwrap().clone(),
tower_lsp::lsp_types::FoldingRange { tower_lsp::lsp_types::FoldingRange {
start_line: 19, start_line: 27,
start_character: None, start_character: None,
end_line: 67, end_line: 75,
end_character: None, end_character: None,
kind: Some(tower_lsp::lsp_types::FoldingRangeKind::Region), kind: Some(tower_lsp::lsp_types::FoldingRangeKind::Region),
collapsed_text: Some("startSketchOn('XY')".to_string()) collapsed_text: Some("let s = startSketchOn('XY')".to_string())
} }
); );
} }

View File

@ -50,6 +50,37 @@ fn program(i: TokenSlice) -> PResult<Program> {
// Once this is merged and stable, consider changing this as I think it's more accurate // Once this is merged and stable, consider changing this as I think it's more accurate
// without the -1. // without the -1.
out.end -= 1; out.end -= 1;
// Prevent top-level pipe expressions without a variable declaration, giving
// a good error message that will help users fix their code. This is a
// band-aid until we can use the artifact graph.
let source_ranges = out
.body
.iter()
.filter_map(|item| {
if let BodyItem::ExpressionStatement(ExpressionStatement {
expression: Value::PipeExpression(_),
start,
end,
..
}) = item
{
Some(SourceRange([*start, *end]))
} else {
None
}
})
.collect::<Vec<_>>();
if !source_ranges.is_empty() {
return Err(ErrMode::Cut(
KclError::Syntax(KclErrorDetails {
source_ranges,
message: "A top-level pipe expression must be assigned to a new variable declaration".to_owned(),
})
.into(),
));
}
Ok(out) Ok(out)
} }

View File

@ -1395,7 +1395,7 @@ pub async fn close(args: Args) -> Result<MemoryItem, KclError> {
/// Close the current sketch. /// Close the current sketch.
/// ///
/// ```no_run /// ```no_run
/// startSketchOn('XZ') /// const exampleSketch = startSketchOn('XZ')
/// |> startProfileAt([0, 0], %) /// |> startProfileAt([0, 0], %)
/// |> line([10, 10], %) /// |> line([10, 10], %)
/// |> line([10, 0], %) /// |> line([10, 0], %)

View File

@ -484,7 +484,7 @@ const part = roundedRectangle([0, 0], 20, 20, 4)
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_top_level_expression() { async fn kcl_test_top_level_expression() {
let code = r#"startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#; let code = r#"let c1 = startSketchOn('XY') |> circle([0,0], 22, %) |> extrude(14, %)"#;
let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap(); let result = execute_and_snapshot(code, UnitLength::Mm).await.unwrap();
assert_out("top_level_expression", &result); assert_out("top_level_expression", &result);
@ -2117,7 +2117,7 @@ async fn kcl_test_extrude_custom_plane() {
#[tokio::test(flavor = "multi_thread")] #[tokio::test(flavor = "multi_thread")]
async fn kcl_test_arc_error_same_start_end() { async fn kcl_test_arc_error_same_start_end() {
let code = r#"startSketchOn('XY') let code = r#"let x = startSketchOn('XY')
|> startProfileAt([10, 0], %) |> startProfileAt([10, 0], %)
|> arc({ |> arc({
angle_start: 180, angle_start: 180,
@ -2137,7 +2137,7 @@ async fn kcl_test_arc_error_same_start_end() {
assert!(result.is_err()); assert!(result.is_err());
assert_eq!( assert_eq!(
result.err().unwrap().to_string(), result.err().unwrap().to_string(),
r#"type: KclErrorDetails { source_ranges: [SourceRange([57, 140])], message: "Arc start and end angles must be different" }"# r#"type: KclErrorDetails { source_ranges: [SourceRange([65, 148])], message: "Arc start and end angles must be different" }"#
); );
} }