Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 11 additions & 2 deletions dsc/tests/dsc_expressions.tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ Describe 'Expressions tests' {
@{ text = "[parameters('test').objectArray[1].value[1].name]"; expected = 'three' }
@{ text = "[parameters('test').index]"; expected = '1' }
@{ text = "[parameters('test').objectArray[parameters('test').index].name]"; expected = 'two' }
@{ text = "[parameters('test')['hello']"; expected = '@{world=there}' }
@{ text = "[parameters('test')['hello']['world']]"; expected = 'there' }
@{ text = "[parameters('test')['array'][1][0]]"; expected = 'two' }
@{ text = "[parameters('test')['objectArray'][0]['name']]"; expected = 'one' }
@{ text = "[parameters('test')['objectArray'][1]['value'][1]['name']]"; expected = 'three' }
@{ text = "[parameters('test')[parameters('propertyName')]]"; expected = '@{world=there}' }
) {
param($text, $expected)
$yaml = @"
Expand All @@ -35,6 +41,9 @@ Describe 'Expressions tests' {
- nestedObject:
name: three
value: 3
propertyName:
type: string
defaultValue: hello
resources:
- name: echo
type: Microsoft.DSC.Debug/Echo
Expand Down Expand Up @@ -185,7 +194,7 @@ resources:
if ($out.results[0].result.actualState.output.$key -is [psobject]) {
$out.results[0].result.actualState.output.$key.psobject.properties.value | Should -Be $expected.$key.values -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
} else {
$out.results[0].result.actualState.output.$key | Should -Be $expected.$key -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
$out.results[0].result.actualState.output.$key | Should -Be $expected.$key -Because ($out | ConvertTo-Json -Depth 10 | Out-String)
}
}
}
Expand Down Expand Up @@ -240,7 +249,7 @@ resources:
$LASTEXITCODE | Should -Be 2
$log = Get-Content -Path $TestDrive/error.log -Raw
$log | Should -BeLike "*ERROR* Arguments must be of the same type*"

}

Context 'Resource name expression evaluation' {
Expand Down
3 changes: 2 additions & 1 deletion grammars/tree-sitter-dscexpression/grammar.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ module.exports = grammar({
memberAccess: $ => seq('.', field('name', $.memberName)),
memberName: $ => /[a-zA-Z0-9_-]+/,

index: $ => seq('[', field('indexValue', choice($.expression, $.number)), ']'),
propertyName: $ => seq('\'', field('string', $.string), '\''),
index: $ => seq('[', field('indexValue', choice($.expression, $.number, $.propertyName)), ']'),
}

});
Original file line number Diff line number Diff line change
Expand Up @@ -206,3 +206,12 @@ String with un-escaped single-quote
(arguments
(string))
(ERROR))))

=====
Object index without quotes
=====
[myObject[foo]]
---

(ERROR
(functionName))
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,24 @@ Array index
(index
(number)))))

=====
Object index
=====
[createObject('a',1)['a']]
---

(statement
(expression
(function
(functionName)
(arguments
(string)
(number)))
(accessor
(index
(propertyName
(string))))))

=====
Multiple array indexes
=====
Expand Down
14 changes: 9 additions & 5 deletions lib/dsc-lib/locales/en-us.toml
Original file line number Diff line number Diff line change
Expand Up @@ -551,14 +551,17 @@ unavailableInUserFunction = "The 'variables()' function is not available in user

[parser.expression]
functionNodeNotFound = "Function node not found"
parsingFunction = "Parsing function '%{name}'"
parsingAccessor = "Parsing accessor '%{name}'"
parsingFunction = "Parsing function: %{name}"
parsingAccessor = "Parsing accessor: %{name}"
accessorParsingError = "Error parsing accessor"
parsingMemberAccessor = "Parsing member accessor '%{name}'"
parsingMemberAccessor = "Parsing member accessor: %{name}"
memberNotFound = "Member name not found"
parsingIndexAccessor = "Parsing index accessor '%{index}'"
parsingIndexAccessor = "Parsing index accessor: %{index}"
indexNotFound = "Index value not found"
invalidAccessorKind = "Invalid accessor kind: '%{kind}'"
indexValue = "Index value: %{value} with kind %{kind}"
propertyNameValue = "Property name value: %{value}"
invalidIndexValueKind = "Invalid index value kind: %{kind}"
invalidAccessorKind = "Invalid accessor kind: %{kind}"
functionResult = "Function results: %{results}"
functionResultSecure = "Function result is secure"
evalAccessors = "Evaluating accessors"
Expand All @@ -569,6 +572,7 @@ indexNotValid = "Index is not a valid number"
indexOutOfBounds = "Index is out of bounds"
indexOnNonArray = "Index access on non-array value"
invalidIndexType = "Invalid index type"
propertyNameNotString = "Property name is not a string"

[parser.functions]
foundErrorNode = "Found error node parsing function"
Expand Down
43 changes: 36 additions & 7 deletions lib/dsc-lib/src/parser/expressions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ pub struct Expression {
accessors: Vec<Accessor>,
}

fn node_to_string(node: &Node, statement_bytes: &[u8]) -> Result<String, DscError> {
let text = node.utf8_text(statement_bytes)?;
Ok(text.to_string())
}

impl Expression {
/// Create a new `Expression` instance.
///
Expand All @@ -41,11 +46,11 @@ impl Expression {
let Some(function) = expression.child_by_field_name("function") else {
return Err(DscError::Parser(t!("parser.expression.functionNodeNotFound").to_string()));
};
debug!("{}", t!("parser.expression.parsingFunction", name = function : {:?}));
debug!("{}", t!("parser.expression.parsingFunction", name = node_to_string(&function, statement_bytes)? : {:?}));
let function = Function::new(statement_bytes, &function)?;
let mut accessors = Vec::<Accessor>::new();
if let Some(accessor) = expression.child_by_field_name("accessor") {
debug!("{}", t!("parser.expression.parsingAccessor", name = accessor : {:?}));
debug!("{}", t!("parser.expression.parsingAccessor", name = node_to_string(&accessor, statement_bytes)? : {:?}));
if accessor.is_error() {
return Err(DscError::Parser(t!("parser.expression.accessorParsingError").to_string()));
}
Expand All @@ -57,30 +62,39 @@ impl Expression {
let accessor_kind = accessor.kind();
let value = match accessor_kind {
"memberAccess" => {
debug!("{}", t!("parser.expression.parsingMemberAccessor", name = accessor : {:?}));
debug!("{}", t!("parser.expression.parsingMemberAccessor", name = node_to_string(&accessor, statement_bytes)? : {:?}));
let Some(member_name) = accessor.child_by_field_name("name") else {
return Err(DscError::Parser(t!("parser.expression.memberNotFound").to_string()));
};
let member = member_name.utf8_text(statement_bytes)?;
Accessor::Member(member.to_string())
},
"index" => {
debug!("{}", t!("parser.expression.parsingIndexAccessor", index = accessor : {:?}));
debug!("{}", t!("parser.expression.parsingIndexAccessor", index = node_to_string(&accessor, statement_bytes)? : {:?}));
let Some(index_value) = accessor.child_by_field_name("indexValue") else {
return Err(DscError::Parser(t!("parser.expression.indexNotFound").to_string()));
};
debug!("{}", t!("parser.expression.indexValue", value = node_to_string(&index_value, statement_bytes)? : {:?}, kind = index_value.kind()));
match index_value.kind() {
"number" => {
let value = index_value.utf8_text(statement_bytes)?;
let value = serde_json::from_str(value)?;
Accessor::Index(value)
let number: i64 = value.parse().map_err(|_| DscError::Parser(t!("parser.expression.indexNotValid").to_string()))?;
Accessor::Index(Value::Number(number.into()))
},
"propertyName" => {
let Some(string_node) = index_value.child_by_field_name("string") else {
return Err(DscError::Parser(t!("parser.expression.propertyNameNotString").to_string()));
};
let value = string_node.utf8_text(statement_bytes)?;
debug!("{}", t!("parser.expression.propertyNameValue", value = value : {:?}));
Accessor::Index(Value::String(value.to_string()))
},
"expression" => {
let expression = Expression::new(statement_bytes, &index_value)?;
Accessor::IndexExpression(expression)
},
_ => {
return Err(DscError::Parser(t!("parser.expression.invalidAccessorKind", kind = accessor_kind).to_string()));
return Err(DscError::Parser(t!("parser.expression.invalidIndexValueKind", kind = index_value.kind()).to_string()));
},
}
},
Expand Down Expand Up @@ -186,6 +200,21 @@ impl Expression {
return Err(DscError::Parser(t!("parser.expression.indexOnNonArray").to_string()));
}
}
else if index.is_string() {
let index = index.as_str().ok_or_else(|| DscError::Parser(t!("parser.expression.indexNotValid").to_string()))?;
if let Some(object) = value.as_object() {
if !object.contains_key(index) {
return Err(DscError::Parser(t!("parser.expression.memberNameNotFound", member = index).to_string()));
}
if is_secure {
value = convert_to_secure(&object[index]);
} else {
value = object[index].clone();
}
} else {
return Err(DscError::Parser(t!("parser.expression.accessOnNonObject").to_string()));
}
}
else if !index.is_null() {
return Err(DscError::Parser(t!("parser.expression.invalidIndexType").to_string()));
}
Expand Down