Implement dynamic ranges (#4151)
Closes #4021 Allows array ranges (e.g., `[0..10]`) to take expression instead of just numeric literals as their start and end values. Both expressions are required (we don't support `[0..]`, etc.). I've created a new kind of expression in the AST. The alternative was to represent the internals of an array as some kind of pattern which could initially be fully explicit or ranges. I figured the chosen version was simpler and easier to extend to open ranges, whereas the latter would be easier to extend to mixed ranges or other patterns. I chose simpler, it'll be easy enough to refactor if necessary. Parsing is tested implicitly by the tests of execution and unparsing. --------- Signed-off-by: Nick Cameron <nrc@ncameron.org> Co-authored-by: Adam Chalmers <adam.chalmers@zoo.dev>
This commit is contained in:
File diff suppressed because one or more lines are too long
@ -32,7 +32,7 @@ reduce(array: [KclValue], start: KclValue, reduce_fn: FunctionParam) -> KclValue
|
||||
fn decagon = (radius) => {
|
||||
step = 1 / 10 * tau()
|
||||
sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])
|
||||
return reduce([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], sketch001, (i, sg) => {
|
||||
return reduce([1..10], sketch001, (i, sg) => {
|
||||
x = cos(step * i) * radius
|
||||
y = sin(step * i) * radius
|
||||
return lineTo([x, y], sg)
|
||||
|
@ -83233,6 +83233,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -86831,6 +86881,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -90433,6 +90533,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -91704,8 +91854,8 @@
|
||||
"unpublished": false,
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"r = 10 // radius\nfn drawCircle = (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1, 2, 3], drawCircle)",
|
||||
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1, 2, 3], (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n})"
|
||||
"r = 10 // radius\nfn drawCircle = (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n}\n\n// Call `drawCircle`, passing in each element of the array.\n// The outputs from each `drawCircle` form a new array,\n// which is the return value from `map`.\ncircles = map([1..3], drawCircle)",
|
||||
"r = 10 // radius\n// Call `map`, using an anonymous function instead of a named one.\ncircles = map([1..3], (id) => {\n return startSketchOn(\"XY\")\n |> circle({ center: [id * 2 * r, 0], radius: r }, %)\n})"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -114887,6 +115037,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -118878,6 +119078,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -122476,6 +122726,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -126072,6 +126372,56 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"end",
|
||||
"endElement",
|
||||
"endInclusive",
|
||||
"start",
|
||||
"startElement",
|
||||
"type"
|
||||
],
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"ArrayRangeExpression"
|
||||
]
|
||||
},
|
||||
"start": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"end": {
|
||||
"type": "integer",
|
||||
"format": "uint",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"startElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endElement": {
|
||||
"$ref": "#/components/schemas/Expr"
|
||||
},
|
||||
"endInclusive": {
|
||||
"description": "Is the `end_element` included in the range?",
|
||||
"type": "boolean"
|
||||
},
|
||||
"digest": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "integer",
|
||||
"format": "uint8",
|
||||
"minimum": 0.0
|
||||
},
|
||||
"maxItems": 32,
|
||||
"minItems": 32,
|
||||
"nullable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
@ -127739,7 +128089,7 @@
|
||||
"unpublished": false,
|
||||
"deprecated": false,
|
||||
"examples": [
|
||||
"fn decagon = (radius) => {\n step = 1 / 10 * tau()\n sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])\n return reduce([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], sketch001, (i, sg) => {\n x = cos(step * i) * radius\n y = sin(step * i) * radius\n return lineTo([x, y], sg)\n})\n}\ndecagon(5.0)\n |> close(%)",
|
||||
"fn decagon = (radius) => {\n step = 1 / 10 * tau()\n sketch001 = startSketchAt([cos(0) * radius, sin(0) * radius])\n return reduce([1..10], sketch001, (i, sg) => {\n x = cos(step * i) * radius\n y = sin(step * i) * radius\n return lineTo([x, y], sg)\n})\n}\ndecagon(5.0)\n |> close(%)",
|
||||
"array = [1, 2, 3]\nsum = reduce(array, 0, (i, result_so_far) => {\n return i + result_so_far\n})\nassertEqual(sum, 6, 0.00001, \"1 + 2 + 3 summed is 6\")",
|
||||
"fn add = (a, b) => {\n return a + b\n}\nfn sum = (array) => {\n return reduce(array, 0, add)\n}\nassertEqual(sum([1, 2, 3]), 6, 0.00001, \"1 + 2 + 3 summed is 6\")"
|
||||
]
|
||||
|
@ -197,6 +197,27 @@ An expression can be evaluated to yield a single KCL value.
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|----------|------|-------------|----------|
|
||||
| `type` |enum: `ArrayRangeExpression`| | No |
|
||||
| `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)| | No |
|
||||
| `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No |
|
||||
| `endInclusive` |`boolean`| Is the `end_element` included in the range? | No |
|
||||
| `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`| | No |
|
||||
|
||||
|
||||
----
|
||||
|
||||
**Type:** `object`
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Type | Description | Required |
|
||||
|
@ -513,6 +513,7 @@ pub enum Expr {
|
||||
PipeExpression(Box<PipeExpression>),
|
||||
PipeSubstitution(Box<PipeSubstitution>),
|
||||
ArrayExpression(Box<ArrayExpression>),
|
||||
ArrayRangeExpression(Box<ArrayRangeExpression>),
|
||||
ObjectExpression(Box<ObjectExpression>),
|
||||
MemberExpression(Box<MemberExpression>),
|
||||
UnaryExpression(Box<UnaryExpression>),
|
||||
@ -532,6 +533,7 @@ impl Expr {
|
||||
Expr::PipeExpression(pe) => pe.compute_digest(),
|
||||
Expr::PipeSubstitution(ps) => ps.compute_digest(),
|
||||
Expr::ArrayExpression(ae) => ae.compute_digest(),
|
||||
Expr::ArrayRangeExpression(are) => are.compute_digest(),
|
||||
Expr::ObjectExpression(oe) => oe.compute_digest(),
|
||||
Expr::MemberExpression(me) => me.compute_digest(),
|
||||
Expr::UnaryExpression(ue) => ue.compute_digest(),
|
||||
@ -569,6 +571,7 @@ impl Expr {
|
||||
match self {
|
||||
Expr::BinaryExpression(_bin_exp) => None,
|
||||
Expr::ArrayExpression(_array_exp) => None,
|
||||
Expr::ArrayRangeExpression(_array_exp) => None,
|
||||
Expr::ObjectExpression(_obj_exp) => None,
|
||||
Expr::MemberExpression(_mem_exp) => None,
|
||||
Expr::Literal(_literal) => None,
|
||||
@ -593,6 +596,7 @@ impl Expr {
|
||||
match self {
|
||||
Expr::BinaryExpression(ref mut bin_exp) => bin_exp.replace_value(source_range, new_value),
|
||||
Expr::ArrayExpression(ref mut array_exp) => array_exp.replace_value(source_range, new_value),
|
||||
Expr::ArrayRangeExpression(ref mut array_range) => array_range.replace_value(source_range, new_value),
|
||||
Expr::ObjectExpression(ref mut obj_exp) => obj_exp.replace_value(source_range, new_value),
|
||||
Expr::MemberExpression(_) => {}
|
||||
Expr::Literal(_) => {}
|
||||
@ -619,6 +623,7 @@ impl Expr {
|
||||
Expr::PipeExpression(pipe_expression) => pipe_expression.start(),
|
||||
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.start(),
|
||||
Expr::ArrayExpression(array_expression) => array_expression.start(),
|
||||
Expr::ArrayRangeExpression(array_range) => array_range.start(),
|
||||
Expr::ObjectExpression(object_expression) => object_expression.start(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.start(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.start(),
|
||||
@ -638,6 +643,7 @@ impl Expr {
|
||||
Expr::PipeExpression(pipe_expression) => pipe_expression.end(),
|
||||
Expr::PipeSubstitution(pipe_substitution) => pipe_substitution.end(),
|
||||
Expr::ArrayExpression(array_expression) => array_expression.end(),
|
||||
Expr::ArrayRangeExpression(array_range) => array_range.end(),
|
||||
Expr::ObjectExpression(object_expression) => object_expression.end(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.end(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.end(),
|
||||
@ -657,6 +663,7 @@ impl Expr {
|
||||
Expr::CallExpression(call_expression) => call_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::PipeExpression(pipe_expression) => pipe_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::ArrayExpression(array_expression) => array_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::ArrayRangeExpression(array_range) => array_range.get_hover_value_for_position(pos, code),
|
||||
Expr::ObjectExpression(object_expression) => object_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_hover_value_for_position(pos, code),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_hover_value_for_position(pos, code),
|
||||
@ -685,6 +692,7 @@ impl Expr {
|
||||
Expr::PipeExpression(ref mut pipe_expression) => pipe_expression.rename_identifiers(old_name, new_name),
|
||||
Expr::PipeSubstitution(_) => {}
|
||||
Expr::ArrayExpression(ref mut array_expression) => array_expression.rename_identifiers(old_name, new_name),
|
||||
Expr::ArrayRangeExpression(ref mut array_range) => array_range.rename_identifiers(old_name, new_name),
|
||||
Expr::ObjectExpression(ref mut object_expression) => {
|
||||
object_expression.rename_identifiers(old_name, new_name)
|
||||
}
|
||||
@ -712,6 +720,7 @@ impl Expr {
|
||||
source_ranges: vec![pipe_substitution.into()],
|
||||
},
|
||||
Expr::ArrayExpression(array_expression) => array_expression.get_constraint_level(),
|
||||
Expr::ArrayRangeExpression(array_range) => array_range.get_constraint_level(),
|
||||
Expr::ObjectExpression(object_expression) => object_expression.get_constraint_level(),
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_constraint_level(),
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_constraint_level(),
|
||||
@ -2182,6 +2191,114 @@ impl ArrayExpression {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase", tag = "type")]
|
||||
pub struct ArrayRangeExpression {
|
||||
pub start: usize,
|
||||
pub end: usize,
|
||||
pub start_element: Box<Expr>,
|
||||
pub end_element: Box<Expr>,
|
||||
/// Is the `end_element` included in the range?
|
||||
pub end_inclusive: bool,
|
||||
// TODO (maybe) comments on range components?
|
||||
pub digest: Option<Digest>,
|
||||
}
|
||||
|
||||
impl_value_meta!(ArrayRangeExpression);
|
||||
|
||||
impl From<ArrayRangeExpression> for Expr {
|
||||
fn from(array_expression: ArrayRangeExpression) -> Self {
|
||||
Expr::ArrayRangeExpression(Box::new(array_expression))
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayRangeExpression {
|
||||
pub fn new(start_element: Box<Expr>, end_element: Box<Expr>) -> Self {
|
||||
Self {
|
||||
start: 0,
|
||||
end: 0,
|
||||
start_element,
|
||||
end_element,
|
||||
end_inclusive: true,
|
||||
digest: None,
|
||||
}
|
||||
}
|
||||
|
||||
compute_digest!(|slf, hasher| {
|
||||
hasher.update(slf.start_element.compute_digest());
|
||||
hasher.update(slf.end_element.compute_digest());
|
||||
});
|
||||
|
||||
pub fn replace_value(&mut self, source_range: SourceRange, new_value: Expr) {
|
||||
self.start_element.replace_value(source_range, new_value.clone());
|
||||
self.end_element.replace_value(source_range, new_value.clone());
|
||||
}
|
||||
|
||||
pub fn get_constraint_level(&self) -> ConstraintLevel {
|
||||
let mut constraint_levels = ConstraintLevels::new();
|
||||
constraint_levels.push(self.start_element.get_constraint_level());
|
||||
constraint_levels.push(self.end_element.get_constraint_level());
|
||||
|
||||
constraint_levels.get_constraint_level(self.into())
|
||||
}
|
||||
|
||||
/// Returns a hover value that includes the given character position.
|
||||
pub fn get_hover_value_for_position(&self, pos: usize, code: &str) -> Option<Hover> {
|
||||
for element in [&*self.start_element, &*self.end_element] {
|
||||
let element_source_range: SourceRange = element.into();
|
||||
if element_source_range.contains(pos) {
|
||||
return element.get_hover_value_for_position(pos, code);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
#[async_recursion::async_recursion]
|
||||
pub async fn execute(&self, exec_state: &mut ExecState, ctx: &ExecutorContext) -> Result<KclValue, KclError> {
|
||||
let metadata = Metadata::from(&*self.start_element);
|
||||
let start = ctx
|
||||
.execute_expr(&self.start_element, exec_state, &metadata, StatementKind::Expression)
|
||||
.await?
|
||||
.get_json_value()?;
|
||||
let start = parse_json_number_as_u64(&start, (&*self.start_element).into())?;
|
||||
let metadata = Metadata::from(&*self.end_element);
|
||||
let end = ctx
|
||||
.execute_expr(&self.end_element, exec_state, &metadata, StatementKind::Expression)
|
||||
.await?
|
||||
.get_json_value()?;
|
||||
let end = parse_json_number_as_u64(&end, (&*self.end_element).into())?;
|
||||
|
||||
if end < start {
|
||||
return Err(KclError::Semantic(KclErrorDetails {
|
||||
source_ranges: vec![self.into()],
|
||||
message: format!("Range start is greater than range end: {start} .. {end}"),
|
||||
}));
|
||||
}
|
||||
|
||||
let range: Vec<_> = if self.end_inclusive {
|
||||
(start..=end).map(JValue::from).collect()
|
||||
} else {
|
||||
(start..end).map(JValue::from).collect()
|
||||
};
|
||||
|
||||
Ok(KclValue::UserVal(UserVal {
|
||||
value: range.into(),
|
||||
meta: vec![Metadata {
|
||||
source_range: self.into(),
|
||||
}],
|
||||
}))
|
||||
}
|
||||
|
||||
/// Rename all identifiers that have the old name to the new given name.
|
||||
fn rename_identifiers(&mut self, old_name: &str, new_name: &str) {
|
||||
self.start_element.rename_identifiers(old_name, new_name);
|
||||
self.end_element.rename_identifiers(old_name, new_name);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, ts_rs::TS, JsonSchema, Bake)]
|
||||
#[databake(path = kcl_lib::ast::types)]
|
||||
#[ts(export)]
|
||||
@ -2817,6 +2934,22 @@ pub fn parse_json_number_as_f64(j: &serde_json::Value, source_range: SourceRange
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_json_number_as_u64(j: &serde_json::Value, source_range: SourceRange) -> Result<u64, KclError> {
|
||||
if let serde_json::Value::Number(n) = &j {
|
||||
n.as_u64().ok_or_else(|| {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![source_range],
|
||||
message: format!("Invalid integer: {}", j),
|
||||
})
|
||||
})
|
||||
} else {
|
||||
Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges: vec![source_range],
|
||||
message: format!("Invalid integer: {}", j),
|
||||
}))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_json_value_as_string(j: &serde_json::Value) -> Option<String> {
|
||||
if let serde_json::Value::String(n) = &j {
|
||||
Some(n.clone())
|
||||
@ -3208,6 +3341,7 @@ async fn inner_execute_pipe_body(
|
||||
| Expr::PipeExpression(_)
|
||||
| Expr::PipeSubstitution(_)
|
||||
| Expr::ArrayExpression(_)
|
||||
| Expr::ArrayRangeExpression(_)
|
||||
| Expr::ObjectExpression(_)
|
||||
| Expr::MemberExpression(_)
|
||||
| Expr::UnaryExpression(_)
|
||||
|
@ -784,6 +784,9 @@ fn test_generate_stdlib_markdown_docs() {
|
||||
|
||||
#[test]
|
||||
fn test_generate_stdlib_json_schema() {
|
||||
// If this test fails and you've modified the AST or something else which affects the json repr
|
||||
// of stdlib functions, you should rerun the test with `EXPECTORATE=overwrite` to create new
|
||||
// test data, then check `/docs/kcl/std.json` to ensure the changes are expected.
|
||||
let stdlib = StdLib::new();
|
||||
let combined = stdlib.combined();
|
||||
|
||||
|
@ -2139,6 +2139,7 @@ impl ExecutorContext {
|
||||
},
|
||||
},
|
||||
Expr::ArrayExpression(array_expression) => array_expression.execute(exec_state, self).await?,
|
||||
Expr::ArrayRangeExpression(range_expression) => range_expression.execute(exec_state, self).await?,
|
||||
Expr::ObjectExpression(object_expression) => object_expression.execute(exec_state, self).await?,
|
||||
Expr::MemberExpression(member_expression) => member_expression.get_result(exec_state)?,
|
||||
Expr::UnaryExpression(unary_expression) => unary_expression.get_result(exec_state, self).await?,
|
||||
|
@ -10,12 +10,12 @@ use winnow::{
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, CommentStyle, ElseIf,
|
||||
Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier, IfExpression, Literal,
|
||||
LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta, NonCodeNode, NonCodeValue,
|
||||
ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution, Program, ReturnStatement,
|
||||
TagDeclarator, UnaryExpression, UnaryOperator, ValueMeta, VariableDeclaration, VariableDeclarator,
|
||||
VariableKind,
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
CommentStyle, ElseIf, Expr, ExpressionStatement, FnArgPrimitive, FnArgType, FunctionExpression, Identifier,
|
||||
IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject, NonCodeMeta,
|
||||
NonCodeNode, NonCodeValue, ObjectExpression, ObjectProperty, Parameter, PipeExpression, PipeSubstitution,
|
||||
Program, ReturnStatement, TagDeclarator, UnaryExpression, UnaryOperator, ValueMeta, VariableDeclaration,
|
||||
VariableDeclarator, VariableKind,
|
||||
},
|
||||
errors::{KclError, KclErrorDetails},
|
||||
executor::SourceRange,
|
||||
@ -336,6 +336,7 @@ fn operand(i: TokenSlice) -> PResult<BinaryPart> {
|
||||
| Expr::PipeExpression(_)
|
||||
| Expr::PipeSubstitution(_)
|
||||
| Expr::ArrayExpression(_)
|
||||
| Expr::ArrayRangeExpression(_)
|
||||
| Expr::ObjectExpression(_) => {
|
||||
return Err(KclError::Syntax(KclErrorDetails {
|
||||
source_ranges,
|
||||
@ -466,8 +467,13 @@ pub enum NonCodeOr<T> {
|
||||
}
|
||||
|
||||
/// Parse a KCL array of elements.
|
||||
fn array(i: TokenSlice) -> PResult<ArrayExpression> {
|
||||
alt((array_empty, array_elem_by_elem, array_end_start)).parse_next(i)
|
||||
fn array(i: TokenSlice) -> PResult<Expr> {
|
||||
alt((
|
||||
array_empty.map(Box::new).map(Expr::ArrayExpression),
|
||||
array_elem_by_elem.map(Box::new).map(Expr::ArrayExpression),
|
||||
array_end_start.map(Box::new).map(Expr::ArrayRangeExpression),
|
||||
))
|
||||
.parse_next(i)
|
||||
}
|
||||
|
||||
/// Match an empty array.
|
||||
@ -539,42 +545,26 @@ pub(crate) fn array_elem_by_elem(i: TokenSlice) -> PResult<ArrayExpression> {
|
||||
})
|
||||
}
|
||||
|
||||
fn array_end_start(i: TokenSlice) -> PResult<ArrayExpression> {
|
||||
fn array_end_start(i: TokenSlice) -> PResult<ArrayRangeExpression> {
|
||||
let start = open_bracket(i)?.start;
|
||||
ignore_whitespace(i);
|
||||
let elements = integer_range
|
||||
.context(expected("array contents, a numeric range (like 0..10)"))
|
||||
.parse_next(i)?;
|
||||
let start_element = Box::new(expression.parse_next(i)?);
|
||||
ignore_whitespace(i);
|
||||
double_period.parse_next(i)?;
|
||||
ignore_whitespace(i);
|
||||
let end_element = Box::new(expression.parse_next(i)?);
|
||||
ignore_whitespace(i);
|
||||
let end = close_bracket(i)?.end;
|
||||
Ok(ArrayExpression {
|
||||
Ok(ArrayRangeExpression {
|
||||
start,
|
||||
end,
|
||||
elements,
|
||||
non_code_meta: Default::default(),
|
||||
start_element,
|
||||
end_element,
|
||||
end_inclusive: true,
|
||||
digest: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse n..m into a vec of numbers [n, n+1, ..., m-1]
|
||||
fn integer_range(i: TokenSlice) -> PResult<Vec<Expr>> {
|
||||
let (token0, floor) = integer.parse_next(i)?;
|
||||
double_period.parse_next(i)?;
|
||||
let (_token1, ceiling) = integer.parse_next(i)?;
|
||||
Ok((floor..=ceiling)
|
||||
.map(|num| {
|
||||
let num = num as i64;
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: token0.start,
|
||||
end: token0.end,
|
||||
value: num.into(),
|
||||
raw: num.to_string(),
|
||||
digest: None,
|
||||
}))
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
fn object_property(i: TokenSlice) -> PResult<ObjectProperty> {
|
||||
let key = identifier.context(expected("the property's key (the name or identifier of the property), e.g. in 'height: 4', 'height' is the property key")).parse_next(i)?;
|
||||
colon
|
||||
@ -1195,7 +1185,7 @@ fn expr_allowed_in_pipe_expr(i: TokenSlice) -> PResult<Expr> {
|
||||
literal.map(Box::new).map(Expr::Literal),
|
||||
fn_call.map(Box::new).map(Expr::CallExpression),
|
||||
identifier.map(Box::new).map(Expr::Identifier),
|
||||
array.map(Box::new).map(Expr::ArrayExpression),
|
||||
array,
|
||||
object.map(Box::new).map(Expr::ObjectExpression),
|
||||
pipe_sub.map(Box::new).map(Expr::PipeSubstitution),
|
||||
function_expression.map(Box::new).map(Expr::FunctionExpression),
|
||||
@ -1511,25 +1501,6 @@ fn expression_stmt(i: TokenSlice) -> PResult<ExpressionStatement> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Parse a KCL integer, and the token that held it.
|
||||
fn integer(i: TokenSlice) -> PResult<(Token, u64)> {
|
||||
let num = one_of(TokenType::Number)
|
||||
.context(expected("a number token e.g. 3"))
|
||||
.try_map(|token: Token| {
|
||||
let source_ranges = token.as_source_ranges();
|
||||
let value = token.value.clone();
|
||||
token.value.parse().map(|num| (token, num)).map_err(|e| {
|
||||
KclError::Syntax(KclErrorDetails {
|
||||
source_ranges,
|
||||
message: format!("invalid integer {value}: {e}"),
|
||||
})
|
||||
})
|
||||
})
|
||||
.context(expected("an integer e.g. 3 (but not 3.1)"))
|
||||
.parse_next(i)?;
|
||||
Ok(num)
|
||||
}
|
||||
|
||||
/// Parse the given brace symbol.
|
||||
fn some_brace(symbol: &'static str, i: TokenSlice) -> PResult<Token> {
|
||||
one_of((TokenType::Brace, symbol))
|
||||
@ -3060,123 +3031,6 @@ e
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parse_expand_array() {
|
||||
let code = "const myArray = [0..10]";
|
||||
let parser = crate::parser::Parser::new(crate::token::lexer(code).unwrap());
|
||||
let result = parser.ast().unwrap();
|
||||
let expected_result = Program {
|
||||
start: 0,
|
||||
end: 23,
|
||||
body: vec![BodyItem::VariableDeclaration(VariableDeclaration {
|
||||
start: 0,
|
||||
end: 23,
|
||||
declarations: vec![VariableDeclarator {
|
||||
start: 6,
|
||||
end: 23,
|
||||
id: Identifier {
|
||||
start: 6,
|
||||
end: 13,
|
||||
name: "myArray".to_string(),
|
||||
digest: None,
|
||||
},
|
||||
init: Expr::ArrayExpression(Box::new(ArrayExpression {
|
||||
start: 16,
|
||||
end: 23,
|
||||
non_code_meta: Default::default(),
|
||||
elements: vec![
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 0u32.into(),
|
||||
raw: "0".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 1u32.into(),
|
||||
raw: "1".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 2u32.into(),
|
||||
raw: "2".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 3u32.into(),
|
||||
raw: "3".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 4u32.into(),
|
||||
raw: "4".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 5u32.into(),
|
||||
raw: "5".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 6u32.into(),
|
||||
raw: "6".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 7u32.into(),
|
||||
raw: "7".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 8u32.into(),
|
||||
raw: "8".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 9u32.into(),
|
||||
raw: "9".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
Expr::Literal(Box::new(Literal {
|
||||
start: 17,
|
||||
end: 18,
|
||||
value: 10u32.into(),
|
||||
raw: "10".to_string(),
|
||||
digest: None,
|
||||
})),
|
||||
],
|
||||
digest: None,
|
||||
})),
|
||||
digest: None,
|
||||
}],
|
||||
kind: VariableKind::Const,
|
||||
digest: None,
|
||||
})],
|
||||
non_code_meta: NonCodeMeta::default(),
|
||||
digest: None,
|
||||
};
|
||||
|
||||
assert_eq!(result, expected_result);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_error_keyword_in_variable() {
|
||||
let some_program_string = r#"const let = "thing""#;
|
||||
|
@ -24,111 +24,29 @@ expression: actual
|
||||
"digest": null
|
||||
},
|
||||
"init": {
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayExpression",
|
||||
"type": "ArrayRangeExpression",
|
||||
"type": "ArrayRangeExpression",
|
||||
"start": 16,
|
||||
"end": 23,
|
||||
"elements": [
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 0,
|
||||
"raw": "0",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 1,
|
||||
"raw": "1",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 2,
|
||||
"raw": "2",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 3,
|
||||
"raw": "3",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 4,
|
||||
"raw": "4",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 5,
|
||||
"raw": "5",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 6,
|
||||
"raw": "6",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 7,
|
||||
"raw": "7",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 8,
|
||||
"raw": "8",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 9,
|
||||
"raw": "9",
|
||||
"digest": null
|
||||
},
|
||||
{
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 10,
|
||||
"raw": "10",
|
||||
"digest": null
|
||||
}
|
||||
],
|
||||
"startElement": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 17,
|
||||
"end": 18,
|
||||
"value": 0,
|
||||
"raw": "0",
|
||||
"digest": null
|
||||
},
|
||||
"endElement": {
|
||||
"type": "Literal",
|
||||
"type": "Literal",
|
||||
"start": 20,
|
||||
"end": 22,
|
||||
"value": 10,
|
||||
"raw": "10",
|
||||
"digest": null
|
||||
},
|
||||
"endInclusive": true,
|
||||
"digest": null
|
||||
},
|
||||
"digest": null
|
||||
|
@ -2,10 +2,10 @@ use std::fmt::Write;
|
||||
|
||||
use crate::{
|
||||
ast::types::{
|
||||
ArrayExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression, Expr, FormatOptions,
|
||||
FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue, MemberExpression, MemberObject,
|
||||
NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator, UnaryExpression, VariableDeclaration,
|
||||
VariableKind,
|
||||
ArrayExpression, ArrayRangeExpression, BinaryExpression, BinaryOperator, BinaryPart, BodyItem, CallExpression,
|
||||
Expr, FormatOptions, FunctionExpression, IfExpression, Literal, LiteralIdentifier, LiteralValue,
|
||||
MemberExpression, MemberObject, NonCodeValue, ObjectExpression, PipeExpression, Program, TagDeclarator,
|
||||
UnaryExpression, VariableDeclaration, VariableKind,
|
||||
},
|
||||
parser::PIPE_OPERATOR,
|
||||
};
|
||||
@ -113,6 +113,7 @@ impl Expr {
|
||||
match &self {
|
||||
Expr::BinaryExpression(bin_exp) => bin_exp.recast(options),
|
||||
Expr::ArrayExpression(array_exp) => array_exp.recast(options, indentation_level, is_in_pipe),
|
||||
Expr::ArrayRangeExpression(range_exp) => range_exp.recast(options, indentation_level, is_in_pipe),
|
||||
Expr::ObjectExpression(ref obj_exp) => obj_exp.recast(options, indentation_level, is_in_pipe),
|
||||
Expr::MemberExpression(mem_exp) => mem_exp.recast(),
|
||||
Expr::Literal(literal) => literal.recast(),
|
||||
@ -280,6 +281,44 @@ impl ArrayExpression {
|
||||
}
|
||||
}
|
||||
|
||||
/// An expression is syntactically trivial: i.e., a literal, identifier, or similar.
|
||||
fn expr_is_trivial(expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Literal(_) | Expr::Identifier(_) | Expr::TagDeclarator(_) | Expr::PipeSubstitution(_) | Expr::None(_) => {
|
||||
true
|
||||
}
|
||||
Expr::BinaryExpression(_)
|
||||
| Expr::FunctionExpression(_)
|
||||
| Expr::CallExpression(_)
|
||||
| Expr::PipeExpression(_)
|
||||
| Expr::ArrayExpression(_)
|
||||
| Expr::ArrayRangeExpression(_)
|
||||
| Expr::ObjectExpression(_)
|
||||
| Expr::MemberExpression(_)
|
||||
| Expr::UnaryExpression(_)
|
||||
| Expr::IfExpression(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
impl ArrayRangeExpression {
|
||||
fn recast(&self, options: &FormatOptions, _: usize, _: bool) -> String {
|
||||
let s1 = self.start_element.recast(options, 0, false);
|
||||
let s2 = self.end_element.recast(options, 0, false);
|
||||
|
||||
// Format these items into a one-line array. Put spaces around the `..` if either expression
|
||||
// is non-trivial. This is a bit arbitrary but people seem to like simple ranges to be formatted
|
||||
// tightly, but this is a misleading visual representation of the precedence if the range
|
||||
// components are compound expressions.
|
||||
if expr_is_trivial(&self.start_element) && expr_is_trivial(&self.end_element) {
|
||||
format!("[{s1}..{s2}]")
|
||||
} else {
|
||||
format!("[{s1} .. {s2}]")
|
||||
}
|
||||
|
||||
// Assume a range expression fits on one line.
|
||||
}
|
||||
}
|
||||
|
||||
impl ObjectExpression {
|
||||
fn recast(&self, options: &FormatOptions, indentation_level: usize, is_in_pipe: bool) -> String {
|
||||
if self
|
||||
@ -830,6 +869,20 @@ myNestedVar = [callExp(bing.yo)]
|
||||
assert_eq!(recasted, some_program_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_ranges() {
|
||||
let some_program_string = r#"foo = [0..10]
|
||||
ten = 10
|
||||
bar = [0 + 1 .. ten]
|
||||
"#;
|
||||
let tokens = crate::token::lexer(some_program_string).unwrap();
|
||||
let parser = crate::parser::Parser::new(tokens);
|
||||
let program = parser.ast().unwrap();
|
||||
|
||||
let recasted = program.recast(&Default::default(), 0);
|
||||
assert_eq!(recasted, some_program_string);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_recast_space_in_fn_call() {
|
||||
let some_program_string = r#"fn thing = (x) => {
|
||||
|
@ -24,6 +24,7 @@ pub enum Node<'a> {
|
||||
PipeExpression(&'a types::PipeExpression),
|
||||
PipeSubstitution(&'a types::PipeSubstitution),
|
||||
ArrayExpression(&'a types::ArrayExpression),
|
||||
ArrayRangeExpression(&'a types::ArrayRangeExpression),
|
||||
ObjectExpression(&'a types::ObjectExpression),
|
||||
MemberExpression(&'a types::MemberExpression),
|
||||
UnaryExpression(&'a types::UnaryExpression),
|
||||
@ -54,6 +55,7 @@ impl From<&Node<'_>> for SourceRange {
|
||||
Node::PipeExpression(p) => SourceRange([p.start(), p.end()]),
|
||||
Node::PipeSubstitution(p) => SourceRange([p.start(), p.end()]),
|
||||
Node::ArrayExpression(a) => SourceRange([a.start(), a.end()]),
|
||||
Node::ArrayRangeExpression(a) => SourceRange([a.start(), a.end()]),
|
||||
Node::ObjectExpression(o) => SourceRange([o.start(), o.end()]),
|
||||
Node::MemberExpression(m) => SourceRange([m.start(), m.end()]),
|
||||
Node::UnaryExpression(u) => SourceRange([u.start(), u.end()]),
|
||||
@ -90,6 +92,7 @@ impl_from!(Node, CallExpression);
|
||||
impl_from!(Node, PipeExpression);
|
||||
impl_from!(Node, PipeSubstitution);
|
||||
impl_from!(Node, ArrayExpression);
|
||||
impl_from!(Node, ArrayRangeExpression);
|
||||
impl_from!(Node, ObjectExpression);
|
||||
impl_from!(Node, MemberExpression);
|
||||
impl_from!(Node, UnaryExpression);
|
||||
|
@ -183,6 +183,18 @@ where
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::ArrayRangeExpression(are) => {
|
||||
if !f.walk(are.as_ref().into())? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk_value::<WalkT>(&are.start_element, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
if !walk_value::<WalkT>(&are.end_element, f)? {
|
||||
return Ok(false);
|
||||
}
|
||||
Ok(true)
|
||||
}
|
||||
Expr::ObjectExpression(oe) => walk_object_expression(oe, f),
|
||||
Expr::MemberExpression(me) => walk_member_expression(me, f),
|
||||
Expr::UnaryExpression(ue) => walk_unary_expression(ue, f),
|
||||
|
@ -0,0 +1,17 @@
|
||||
r1 = [0..4]
|
||||
assertEqual(r1[4], 4, 0.00001, "last element is included")
|
||||
|
||||
four = 4
|
||||
zero = 0
|
||||
r2 = [zero..four]
|
||||
assertEqual(r2[4], 4, 0.00001, "last element is included")
|
||||
|
||||
five = int(four + 1)
|
||||
r3 = [zero..five]
|
||||
assertEqual(r3[4], 4, 0.00001, "second-to-last element is included")
|
||||
assertEqual(r3[5], 5, 0.00001, "last element is included")
|
||||
|
||||
r4 = [int(zero + 1) .. int(five - 1)]
|
||||
assertEqual(r4[0], 1, 0.00001, "first element is 1")
|
||||
assertEqual(r4[2], 3, 0.00001, "second-to-last element is 3")
|
||||
assertEqual(r4[3], 4, 0.00001, "last element is 4")
|
@ -0,0 +1,3 @@
|
||||
xs = [-5..5]
|
||||
assertEqual(xs[0], -5, 0.001, "first element is -5")
|
||||
assert(false)
|
@ -66,6 +66,8 @@ async fn run_fail(code: &str) -> KclError {
|
||||
gen_test!(property_of_object);
|
||||
gen_test!(index_of_array);
|
||||
gen_test!(comparisons);
|
||||
gen_test!(array_range_expr);
|
||||
gen_test_fail!(array_range_negative_expr, "syntax: Invalid integer: -5.0");
|
||||
gen_test_fail!(
|
||||
invalid_index_str,
|
||||
"semantic: Only integers >= 0 can be used as the index of an array, but you're using a string"
|
||||
|
Reference in New Issue
Block a user