diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 96ea06847..e21743d5d 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -9,7 +9,7 @@ permissions: contents: write jobs: test: - name: Unit tests with coverage + name: Build and release runs-on: ubuntu-latest steps: - name: Check out code diff --git a/kclvm/parser/src/parser/expr.rs b/kclvm/parser/src/parser/expr.rs index 8d4b608f4..ec5292f9a 100644 --- a/kclvm/parser/src/parser/expr.rs +++ b/kclvm/parser/src/parser/expr.rs @@ -892,6 +892,7 @@ impl<'a> Parser<'a> { false }; + let item_start_token = self.token; let items = self.parse_list_items(has_newline); let generators = self.parse_comp_clauses(); @@ -919,17 +920,41 @@ impl<'a> Parser<'a> { if !generators.is_empty() { if items.len() > 1 { - self.sess - .struct_span_error("list multiple items found", self.token.span) + self.sess.struct_span_error( + &format!( + "multiple list comp clause expression found: expected 1, got {}", + items.len() + ), + item_start_token.span, + ); + Box::new(Node::node( + Expr::ListComp(ListComp { + elt: items[0].clone(), + generators, + }), + self.sess.struct_token_loc(token, self.prev_token), + )) + } else if items.len() == 1 { + Box::new(Node::node( + Expr::ListComp(ListComp { + elt: items[0].clone(), + generators, + }), + self.sess.struct_token_loc(token, self.prev_token), + )) + } else { + self.sess.struct_span_error( + "missing list comp clause expression", + item_start_token.span, + ); + Box::new(Node::node( + Expr::List(ListExpr { + elts: items, + ctx: ExprContext::Load, + }), + self.sess.struct_token_loc(token, self.prev_token), + )) } - - Box::new(Node::node( - Expr::ListComp(ListComp { - elt: items[0].clone(), - generators, - }), - self.sess.struct_token_loc(token, self.prev_token), - )) } else { Box::new(Node::node( Expr::List(ListExpr { @@ -1208,6 +1233,7 @@ impl<'a> Parser<'a> { false }; + let item_start_token = self.token; let items = self.parse_config_entries(has_newline); let generators = self.parse_comp_clauses(); @@ -1235,17 +1261,38 @@ impl<'a> Parser<'a> { if !generators.is_empty() { if items.len() > 1 { - self.sess - .struct_span_error("config multiple entries found", self.token.span) + self.sess.struct_span_error( + &format!( + "multiple config comp clause expression found: expected 1, got {}", + items.len() + ), + item_start_token.span, + ); + Box::new(Node::node( + Expr::DictComp(DictComp { + entry: items[0].node.clone(), + generators, + }), + self.sess.struct_token_loc(token, self.prev_token), + )) + } else if items.len() == 1 { + Box::new(Node::node( + Expr::DictComp(DictComp { + entry: items[0].node.clone(), + generators, + }), + self.sess.struct_token_loc(token, self.prev_token), + )) + } else { + self.sess.struct_span_error( + "missing config comp clause expression", + item_start_token.span, + ); + Box::new(Node::node( + Expr::Config(ConfigExpr { items }), + self.sess.struct_token_loc(token, self.prev_token), + )) } - - Box::new(Node::node( - Expr::DictComp(DictComp { - entry: items[0].node.clone(), - generators, - }), - self.sess.struct_token_loc(token, self.prev_token), - )) } else { Box::new(Node::node( Expr::Config(ConfigExpr { items }), @@ -1384,6 +1431,7 @@ impl<'a> Parser<'a> { /// loop_variables: identifier (COMMA identifier)* fn parse_comp_clause(&mut self) -> NodeRef { let token = self.token; + // bump the `for` keyword. self.bump(); let mut targets = vec![self.parse_identifier()]; @@ -1848,7 +1896,7 @@ impl<'a> Parser<'a> { loop { if matches!( self.token.kind, - TokenKind::CloseDelim(DelimToken::Brace) | TokenKind::Dedent + TokenKind::CloseDelim(DelimToken::Brace) | TokenKind::Dedent | TokenKind::Eof ) { break; } diff --git a/kclvm/parser/src/tests/error_recovery.rs b/kclvm/parser/src/tests/error_recovery.rs index 4d2c067bf..e82199262 100644 --- a/kclvm/parser/src/tests/error_recovery.rs +++ b/kclvm/parser/src/tests/error_recovery.rs @@ -68,6 +68,13 @@ parse_expr_snapshot! { config_recovery_9, "{*a, **b}" } parse_expr_snapshot! { config_recovery_10, "{**a, *b}" } parse_expr_snapshot! { config_recovery_11, "{if True: a = , b = 2}" } parse_expr_snapshot! { config_recovery_12, "{if True: *a, b = 2}" } +parse_expr_snapshot! { comp_clause_recovery_0, "[i for i in [1,2,3]]" } +parse_expr_snapshot! { comp_clause_recovery_1, "[i, j for i in [1,2,3]]" } +parse_expr_snapshot! { comp_clause_recovery_2, "[for i in [1,2,3]]" } +parse_expr_snapshot! { comp_clause_recovery_3, "{i for i in [1,2,3]}" } +parse_expr_snapshot! { comp_clause_recovery_4, "{i: for i in [1,2,3]}" } +parse_expr_snapshot! { comp_clause_recovery_5, "{i: 1, j for i in [1,2,3]}" } +parse_expr_snapshot! { comp_clause_recovery_6, "{for i in [1,2,3]}" } parse_expr_snapshot! { unary_recovery_0, r#"!a"# } parse_expr_snapshot! { unary_recovery_1, r#"!!a"# } parse_expr_snapshot! { unary_recovery_2, r#"not (!a)"# } diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_0.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_0.snap new file mode 100644 index 000000000..2c3b84950 --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_0.snap @@ -0,0 +1,120 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 71 +expression: "crate::tests::parsing_expr_string(\"[i for i in [1,2,3]]\")" +--- +Node { + node: ListComp( + ListComp { + elt: Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 2, + }, + generators: [ + Node { + node: CompClause { + targets: [ + Node { + node: Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + filename: "", + line: 1, + column: 7, + end_line: 1, + end_column: 8, + }, + ], + iter: Node { + node: List( + ListExpr { + elts: [ + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 1, + ), + }, + ), + filename: "", + line: 1, + column: 13, + end_line: 1, + end_column: 14, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 2, + ), + }, + ), + filename: "", + line: 1, + column: 15, + end_line: 1, + end_column: 16, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 3, + ), + }, + ), + filename: "", + line: 1, + column: 17, + end_line: 1, + end_column: 18, + }, + ], + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 12, + end_line: 1, + end_column: 19, + }, + ifs: [], + }, + filename: "", + line: 1, + column: 3, + end_line: 1, + end_column: 19, + }, + ], + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 20, +} + diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_1.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_1.snap new file mode 100644 index 000000000..eb394815c --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_1.snap @@ -0,0 +1,120 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 72 +expression: "crate::tests::parsing_expr_string(\"[i, j for i in [1,2,3]]\")" +--- +Node { + node: ListComp( + ListComp { + elt: Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 2, + }, + generators: [ + Node { + node: CompClause { + targets: [ + Node { + node: Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + filename: "", + line: 1, + column: 10, + end_line: 1, + end_column: 11, + }, + ], + iter: Node { + node: List( + ListExpr { + elts: [ + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 1, + ), + }, + ), + filename: "", + line: 1, + column: 16, + end_line: 1, + end_column: 17, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 2, + ), + }, + ), + filename: "", + line: 1, + column: 18, + end_line: 1, + end_column: 19, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 3, + ), + }, + ), + filename: "", + line: 1, + column: 20, + end_line: 1, + end_column: 21, + }, + ], + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 15, + end_line: 1, + end_column: 22, + }, + ifs: [], + }, + filename: "", + line: 1, + column: 6, + end_line: 1, + end_column: 22, + }, + ], + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 23, +} + diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_2.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_2.snap new file mode 100644 index 000000000..ff66fec1f --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_2.snap @@ -0,0 +1,19 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 73 +expression: "crate::tests::parsing_expr_string(\"[for i in [1,2,3]]\")" +--- +Node { + node: List( + ListExpr { + elts: [], + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 18, +} + diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_3.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_3.snap new file mode 100644 index 000000000..0df1e55ad --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_3.snap @@ -0,0 +1,139 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 74 +expression: "crate::tests::parsing_expr_string(\"{i for i in [1,2,3]}\")" +--- +Node { + node: Config( + ConfigExpr { + items: [ + Node { + node: ConfigEntry { + key: Some( + Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 2, + }, + ), + value: Node { + node: Compare( + Compare { + left: Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 7, + end_line: 1, + end_column: 8, + }, + ops: [ + In, + ], + comparators: [ + Node { + node: List( + ListExpr { + elts: [ + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 1, + ), + }, + ), + filename: "", + line: 1, + column: 13, + end_line: 1, + end_column: 14, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 2, + ), + }, + ), + filename: "", + line: 1, + column: 15, + end_line: 1, + end_column: 16, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 3, + ), + }, + ), + filename: "", + line: 1, + column: 17, + end_line: 1, + end_column: 18, + }, + ], + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 12, + end_line: 1, + end_column: 19, + }, + ], + }, + ), + filename: "", + line: 1, + column: 7, + end_line: 1, + end_column: 19, + }, + operation: Override, + insert_index: -1, + }, + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 19, + }, + ], + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 20, +} + diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_4.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_4.snap new file mode 100644 index 000000000..d6f5f4cdb --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_4.snap @@ -0,0 +1,178 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 75 +expression: "crate::tests::parsing_expr_string(\"{i: for i in [1,2,3]}\")" +--- +Node { + node: Config( + ConfigExpr { + items: [ + Node { + node: ConfigEntry { + key: Some( + Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 2, + }, + ), + value: Node { + node: Identifier( + Identifier { + names: [ + "for", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 4, + end_line: 1, + end_column: 7, + }, + operation: Union, + insert_index: -1, + }, + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 7, + }, + Node { + node: ConfigEntry { + key: Some( + Node { + node: Compare( + Compare { + left: Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 8, + end_line: 1, + end_column: 9, + }, + ops: [ + In, + ], + comparators: [ + Node { + node: List( + ListExpr { + elts: [ + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 1, + ), + }, + ), + filename: "", + line: 1, + column: 14, + end_line: 1, + end_column: 15, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 2, + ), + }, + ), + filename: "", + line: 1, + column: 16, + end_line: 1, + end_column: 17, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 3, + ), + }, + ), + filename: "", + line: 1, + column: 18, + end_line: 1, + end_column: 19, + }, + ], + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 13, + end_line: 1, + end_column: 20, + }, + ], + }, + ), + filename: "", + line: 1, + column: 8, + end_line: 1, + end_column: 20, + }, + ), + value: Node { + node: Missing( + MissingExpr, + ), + filename: "", + line: 1, + column: 21, + end_line: 1, + end_column: 21, + }, + operation: Override, + insert_index: -1, + }, + filename: "", + line: 1, + column: 8, + end_line: 1, + end_column: 21, + }, + ], + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 21, +} + diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_5.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_5.snap new file mode 100644 index 000000000..0bf399bf3 --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_5.snap @@ -0,0 +1,183 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 76 +expression: "crate::tests::parsing_expr_string(\"{i: 1, j for i in [1,2,3]}\")" +--- +Node { + node: Config( + ConfigExpr { + items: [ + Node { + node: ConfigEntry { + key: Some( + Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 2, + }, + ), + value: Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 1, + ), + }, + ), + filename: "", + line: 1, + column: 4, + end_line: 1, + end_column: 5, + }, + operation: Union, + insert_index: -1, + }, + filename: "", + line: 1, + column: 1, + end_line: 1, + end_column: 5, + }, + Node { + node: ConfigEntry { + key: Some( + Node { + node: Identifier( + Identifier { + names: [ + "j", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 7, + end_line: 1, + end_column: 8, + }, + ), + value: Node { + node: Compare( + Compare { + left: Node { + node: Identifier( + Identifier { + names: [ + "i", + ], + pkgpath: "", + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 13, + end_line: 1, + end_column: 14, + }, + ops: [ + In, + ], + comparators: [ + Node { + node: List( + ListExpr { + elts: [ + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 1, + ), + }, + ), + filename: "", + line: 1, + column: 19, + end_line: 1, + end_column: 20, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 2, + ), + }, + ), + filename: "", + line: 1, + column: 21, + end_line: 1, + end_column: 22, + }, + Node { + node: NumberLit( + NumberLit { + binary_suffix: None, + value: Int( + 3, + ), + }, + ), + filename: "", + line: 1, + column: 23, + end_line: 1, + end_column: 24, + }, + ], + ctx: Load, + }, + ), + filename: "", + line: 1, + column: 18, + end_line: 1, + end_column: 25, + }, + ], + }, + ), + filename: "", + line: 1, + column: 13, + end_line: 1, + end_column: 25, + }, + operation: Override, + insert_index: -1, + }, + filename: "", + line: 1, + column: 7, + end_line: 1, + end_column: 25, + }, + ], + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 26, +} + diff --git a/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_6.snap b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_6.snap new file mode 100644 index 000000000..12c72821e --- /dev/null +++ b/kclvm/parser/src/tests/snapshots/kclvm_parser__tests__error_recovery__comp_clause_recovery_6.snap @@ -0,0 +1,18 @@ +--- +source: parser/src/tests/error_recovery.rs +assertion_line: 77 +expression: "crate::tests::parsing_expr_string(\"{for i in [1,2,3]}\")" +--- +Node { + node: Config( + ConfigExpr { + items: [], + }, + ), + filename: "", + line: 1, + column: 0, + end_line: 1, + end_column: 18, +} + diff --git a/kclvm/sema/src/resolver/node.rs b/kclvm/sema/src/resolver/node.rs index c938e821d..3a4fb6585 100644 --- a/kclvm/sema/src/resolver/node.rs +++ b/kclvm/sema/src/resolver/node.rs @@ -278,13 +278,27 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { if target.node.names.is_empty() { continue; } + if target.node.names.len() > 1 { + self.handler.add_compile_error( + "loop variables can only be ordinary identifiers", + target.get_pos(), + ); + } target_node = Some(target); let name = &target.node.names[0]; if i == 0 { key_name = Some(name.to_string()); - } - if i == 1 { + } else if i == 1 { val_name = Some(name.to_string()) + } else { + self.handler.add_compile_error( + &format!( + "the number of loop variables is {}, which can only be 1 or 2", + quant_expr.variables.len() + ), + target.get_pos(), + ); + break; } self.ctx.local_vars.push(name.to_string()); let (start, end) = target.get_span_pos(); @@ -702,13 +716,27 @@ impl<'ctx> MutSelfTypedResultWalker<'ctx> for Resolver<'ctx> { if target.node.names.is_empty() { continue; } + if target.node.names.len() > 1 { + self.handler.add_compile_error( + "loop variables can only be ordinary identifiers", + target.get_pos(), + ); + } target_node = Some(target); let name = &target.node.names[0]; if i == 0 { key_name = Some(name.to_string()); - } - if i == 1 { + } else if i == 1 { val_name = Some(name.to_string()) + } else { + self.handler.add_compile_error( + &format!( + "the number of loop variables is {}, which can only be 1 or 2", + comp_clause.targets.len() + ), + target.get_pos(), + ); + break; } self.ctx.local_vars.push(name.to_string()); let (start, end) = target.get_span_pos(); diff --git a/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_0.k b/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_0.k new file mode 100644 index 000000000..26e7e558d --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_0.k @@ -0,0 +1 @@ +data = [i for i, j, k in [1, 2, 3]] diff --git a/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_1.k b/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_1.k new file mode 100644 index 000000000..26e7e558d --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_1.k @@ -0,0 +1 @@ +data = [i for i, j, k in [1, 2, 3]] diff --git a/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_2.k b/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_2.k new file mode 100644 index 000000000..ce39d5851 --- /dev/null +++ b/kclvm/sema/src/resolver/test_fail_data/comp_clause_error_2.k @@ -0,0 +1,3 @@ +data = all i.j.k in [1, 2, 3] { + i > 0 +} diff --git a/kclvm/sema/src/resolver/tests.rs b/kclvm/sema/src/resolver/tests.rs index d4c6e9732..6dc42cafc 100644 --- a/kclvm/sema/src/resolver/tests.rs +++ b/kclvm/sema/src/resolver/tests.rs @@ -85,6 +85,9 @@ fn test_resolve_program_fail() { let cases = &[ "./src/resolver/test_fail_data/attr.k", "./src/resolver/test_fail_data/cannot_find_module.k", + "./src/resolver/test_fail_data/comp_clause_error_0.k", + "./src/resolver/test_fail_data/comp_clause_error_1.k", + "./src/resolver/test_fail_data/comp_clause_error_2.k", "./src/resolver/test_fail_data/config_expr.k", "./src/resolver/test_fail_data/module_optional_select.k", "./src/resolver/test_fail_data/mutable_error_0.k", diff --git a/scripts/docker/kclvm/Dockerfile b/scripts/docker/kclvm/Dockerfile index 9c8e360c6..058d59520 100644 --- a/scripts/docker/kclvm/Dockerfile +++ b/scripts/docker/kclvm/Dockerfile @@ -21,7 +21,6 @@ RUN chmod +x /kclvm/bin/kcl-test RUN chmod +x /kclvm/bin/kcl-vet RUN chmod +x /kclvm/bin/kclvm RUN chmod +x /kclvm/bin/kclvm_cli -RUN /kclvm/bin/kcl ENV PATH="/kclvm/bin:${PATH}" ENV LANG=en_US.utf8