From 0f02a4e11d8a97a4d0f886de791e32e496d41fdf Mon Sep 17 00:00:00 2001 From: Steve Lee Date: Mon, 29 Sep 2025 16:13:49 -0700 Subject: [PATCH] Fix expression grammar to work with boolean functions --- dsc/tests/dsc_functions.tests.ps1 | 25 +++++++++++++++++-- tree-sitter-dscexpression/grammar.js | 12 ++++++--- .../test/corpus/invalid_expressions.txt | 10 ++------ .../test/corpus/valid_expressions.txt | 19 +++++++++++++- 4 files changed, 52 insertions(+), 14 deletions(-) diff --git a/dsc/tests/dsc_functions.tests.ps1 b/dsc/tests/dsc_functions.tests.ps1 index f69a2e4d0..71a59465d 100644 --- a/dsc/tests/dsc_functions.tests.ps1 +++ b/dsc/tests/dsc_functions.tests.ps1 @@ -120,7 +120,7 @@ Describe 'tests for function expressions' { @{ expression = "[intersection(parameters('firstObject'), parameters('firstArray'))]"; isError = $true } @{ expression = "[intersection(parameters('firstArray'), parameters('secondArray'), parameters('fifthArray'))]"; expected = @('cd') } @{ expression = "[intersection(parameters('firstObject'), parameters('secondObject'), parameters('sixthObject'))]"; expected = [pscustomobject]@{ two = 'b' } } - @{ expression = "[intersection(parameters('nestedObject1'), parameters('nestedObject2'))]"; expected = [pscustomobject]@{ + @{ expression = "[intersection(parameters('nestedObject1'), parameters('nestedObject2'))]"; expected = [pscustomobject]@{ shared = [pscustomobject]@{ value = 42; flag = $true } level = 1 } } @@ -711,9 +711,30 @@ Describe 'tests for function expressions' { properties: output: `"$expression`" "@ - $out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log + $null = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log $LASTEXITCODE | Should -Not -Be 0 $errorContent = Get-Content $TestDrive/error.log -Raw $errorContent | Should -Match ([regex]::Escape($expectedError)) } + + It 'mixed booleans with functions works' -TestCases @( + @{ expression = "[and(true(), false, not(false))]"; expected = $false } + @{ expression = "[or(false, false(), not(false()))]"; expected = $true } + @{ expression = "[and(true(), true, not(false))]"; expected = $true } + @{ expression = "[or(false, false(), not(true()))]"; expected = $false } + ) { + param($expression, $expected) + + $config_yaml = @" + `$schema: https://aka.ms/dsc/schemas/v3/bundled/config/document.json + resources: + - name: Echo + type: Microsoft.DSC.Debug/Echo + properties: + output: "$expression" +"@ + $out = dsc -l trace config get -i $config_yaml 2>$TestDrive/error.log | ConvertFrom-Json + $LASTEXITCODE | Should -Be 0 -Because (Get-Content $TestDrive/error.log -Raw) + $out.results[0].result.actualState.output | Should -BeExactly $expected + } } diff --git a/tree-sitter-dscexpression/grammar.js b/tree-sitter-dscexpression/grammar.js index 4cf9c2d35..d0f61e8f6 100644 --- a/tree-sitter-dscexpression/grammar.js +++ b/tree-sitter-dscexpression/grammar.js @@ -1,6 +1,8 @@ const PREC = { ESCAPEDSTRING: 2, EXPRESSIONSTRING: 1, + FUNCTION: 1, + BOOLEAN: 0, STRINGLITERAL: -11, } @@ -20,8 +22,11 @@ module.exports = grammar({ expression: $ => seq(field('function', $.function), optional(field('accessor',$.accessor))), stringLiteral: $ => token(prec(PREC.STRINGLITERAL, /[^\[](.|\n)*?/)), - function: $ => seq(field('name', $.functionName), '(', field('args', optional($.arguments)), ')'), - functionName: $ => /[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z0-9]+)?/, + function: $ => prec(PREC.FUNCTION, seq(field('name', $.functionName), '(', field('args', optional($.arguments)), ')')), + functionName: $ => choice( + /[a-zA-Z][a-zA-Z0-9]*(\.[a-zA-Z0-9]+)?/, + $._booleanLiteral + ), arguments: $ => seq($._argument, repeat(seq(',', $._argument))), _argument: $ => choice($.expression, $._quotedString, $.number, $.boolean), @@ -29,7 +34,8 @@ module.exports = grammar({ // ARM strings are not allowed to contain single-quote characters unless escaped string: $ => /([^']|''|\n)*/, number: $ => /-?\d+/, - boolean: $ => choice('true', 'false'), + boolean: $ => prec(PREC.BOOLEAN, $._booleanLiteral), + _booleanLiteral: $ => choice('true', 'false'), accessor: $ => repeat1(choice($.memberAccess, $.index)), diff --git a/tree-sitter-dscexpression/test/corpus/invalid_expressions.txt b/tree-sitter-dscexpression/test/corpus/invalid_expressions.txt index 851c05c88..7defac4de 100644 --- a/tree-sitter-dscexpression/test/corpus/invalid_expressions.txt +++ b/tree-sitter-dscexpression/test/corpus/invalid_expressions.txt @@ -18,7 +18,6 @@ String parameter not in quotes (function (functionName) (ERROR - (functionName) (functionName))))) ===== @@ -79,8 +78,6 @@ String starting with bracket --- (ERROR - (ERROR - (functionName)) (functionName)) ===== @@ -179,8 +176,7 @@ Expression with member accessor outside (functionName) (arguments (number)))) - (ERROR - (functionName))) + (ERROR)) ===== Expression with index accessor outside @@ -209,6 +205,4 @@ String with un-escaped single-quote (functionName) (arguments (string)) - (ERROR - (functionName) - (functionName))))) + (ERROR)))) diff --git a/tree-sitter-dscexpression/test/corpus/valid_expressions.txt b/tree-sitter-dscexpression/test/corpus/valid_expressions.txt index ac7b6cebd..cb005a73d 100644 --- a/tree-sitter-dscexpression/test/corpus/valid_expressions.txt +++ b/tree-sitter-dscexpression/test/corpus/valid_expressions.txt @@ -34,7 +34,7 @@ Simple expression ===== Multiple arguments ===== -[myFunction('argString', 1, -1, true)] +[myFunction('argString', 1, -1, true, false)] --- (statement @@ -45,6 +45,7 @@ Multiple arguments (string) (number) (number) + (boolean) (boolean))))) ===== @@ -335,3 +336,19 @@ User Function (arguments (number) (number))))) + +===== +User Function with nested function +===== +[Environment.getEnvironmentConfig(true(), false)] +--- + +(statement + (expression + (function + (functionName) + (arguments + (expression + (function + (functionName))) + (boolean)))))