From 62661b97226a8a5b5e8447cff017876c3797deae Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Apr 2026 19:03:24 +0200 Subject: [PATCH 1/3] Add reserved word checks for Lua variable names and table keys in conversion functions --- .../private/ConvertFrom-LuaTable.ps1 | 13 +++++++++++ src/functions/private/ConvertTo-LuaTable.ps1 | 2 +- src/functions/private/Read-LuaTable.ps1 | 12 ++++++++++ tests/Lua.Tests.ps1 | 22 +++++++++++++++++++ 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/functions/private/ConvertFrom-LuaTable.ps1 b/src/functions/private/ConvertFrom-LuaTable.ps1 index b3b3b63..52f891f 100644 --- a/src/functions/private/ConvertFrom-LuaTable.ps1 +++ b/src/functions/private/ConvertFrom-LuaTable.ps1 @@ -81,6 +81,14 @@ } if ($assignmentDetected) { + # Lua 5.4 reserved words per §3.1 + $reservedWords = @( + 'and', 'break', 'do', 'else', 'elseif', 'end', + 'false', 'for', 'function', 'goto', 'if', 'in', + 'local', 'nil', 'not', 'or', 'repeat', 'return', + 'then', 'true', 'until', 'while' + ) + # Parse one or more assignment statements into an ordered dictionary $assignments = [ordered]@{} while ($script:luaPos -lt $script:luaString.Length) { @@ -100,6 +108,11 @@ } $varName = $script:luaString.Substring($identStart, $script:luaPos - $identStart) + # Lua grammar: variable names cannot be reserved words (§3.1) + if ($varName -in $reservedWords) { + throw "Reserved word '$varName' cannot be used as a variable name at position $identStart." + } + Skip-LuaWhitespace # Expect '=' diff --git a/src/functions/private/ConvertTo-LuaTable.ps1 b/src/functions/private/ConvertTo-LuaTable.ps1 index 984d7af..39660c5 100644 --- a/src/functions/private/ConvertTo-LuaTable.ps1 +++ b/src/functions/private/ConvertTo-LuaTable.ps1 @@ -58,7 +58,7 @@ # Enum handling if ($InputObject -is [enum]) { if ($EnumsAsStrings) { - $escaped = $InputObject.ToString() -replace '\\', '\\\\' -replace '"', '\"' + $escaped = $InputObject.ToString() -replace '\\', '\\' -replace '"', '\"' return "`"$escaped`"" } $underlyingType = [System.Enum]::GetUnderlyingType($InputObject.GetType()) diff --git a/src/functions/private/Read-LuaTable.ps1 b/src/functions/private/Read-LuaTable.ps1 index 0ceadc1..4509b76 100644 --- a/src/functions/private/Read-LuaTable.ps1 +++ b/src/functions/private/Read-LuaTable.ps1 @@ -13,6 +13,14 @@ begin {} process { + # Lua 5.4 reserved words per §3.1 + $reservedWords = @( + 'and', 'break', 'do', 'else', 'elseif', 'end', + 'false', 'for', 'function', 'goto', 'if', 'in', + 'local', 'nil', 'not', 'or', 'repeat', 'return', + 'then', 'true', 'until', 'while' + ) + $script:luaCurrentDepth++ if ($script:luaCurrentDepth -gt $script:luaMaxDepth) { throw "Maximum nesting depth ($($script:luaMaxDepth)) exceeded." @@ -78,6 +86,10 @@ if ($script:luaPos -lt $script:luaString.Length -and $script:luaString[$script:luaPos] -eq '=') { + # Lua grammar: Name cannot be a reserved word (§3.1) + if ($ident -in $reservedWords) { + throw "Reserved word '$ident' cannot be used as a bare identifier key in a Lua table. Use bracket notation: [\"$ident\"] = value." + } # Key = value pair $script:luaPos++ # skip = Skip-LuaWhitespace diff --git a/tests/Lua.Tests.ps1 b/tests/Lua.Tests.ps1 index 2ca23d7..64b7952 100644 --- a/tests/Lua.Tests.ps1 +++ b/tests/Lua.Tests.ps1 @@ -507,6 +507,28 @@ B = { val = 2 } It 'Throws on assignment with missing value' { { ConvertFrom-Lua -InputObject 'A = ' } | Should -Throw '*Unexpected end of input*' } + + It 'Throws on reserved word as bare table key' { + { ConvertFrom-Lua -InputObject '{ end = 1 }' } | Should -Throw '*Reserved word*' + } + + It 'Throws on reserved word as bare table key (while)' { + { ConvertFrom-Lua -InputObject '{ while = "loop" }' } | Should -Throw '*Reserved word*' + } + + It 'Allows reserved words in bracket notation' { + $result = ConvertFrom-Lua -InputObject '{ ["end"] = 1, ["while"] = 2 }' -AsHashtable + $result['end'] | Should -Be 1 + $result['while'] | Should -Be 2 + } + + It 'Throws on reserved word as assignment variable name' { + { ConvertFrom-Lua -InputObject 'end = 1' } | Should -Throw '*Reserved word*' + } + + It 'Throws on reserved word as assignment variable name (return as assignment)' { + { ConvertFrom-Lua -InputObject 'return = 42' } | Should -Throw '*Reserved word*' + } } Context 'Pipeline input' { From dca24395a88e5b399665d9e0e133a82db50b47ab Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Apr 2026 22:21:00 +0200 Subject: [PATCH 2/3] Add SkipValidation parameter to allow warnings for reserved words in Lua parsing --- .../private/ConvertFrom-LuaTable.ps1 | 13 ++++++-- src/functions/private/Read-LuaTable.ps1 | 6 +++- src/functions/public/Lua/ConvertFrom-Lua.ps1 | 8 +++-- tests/Lua.Tests.ps1 | 33 +++++++++++++++++-- 4 files changed, 53 insertions(+), 7 deletions(-) diff --git a/src/functions/private/ConvertFrom-LuaTable.ps1 b/src/functions/private/ConvertFrom-LuaTable.ps1 index 52f891f..1a95daa 100644 --- a/src/functions/private/ConvertFrom-LuaTable.ps1 +++ b/src/functions/private/ConvertFrom-LuaTable.ps1 @@ -22,7 +22,11 @@ # Maximum allowed nesting depth. [Parameter()] - [int] $MaxDepth = 1024 + [int] $MaxDepth = 1024, + + # Skip strict Lua grammar validation. Warnings are emitted instead of terminating errors. + [Parameter()] + [switch] $SkipValidation ) begin {} @@ -33,6 +37,7 @@ $script:luaAsPSCustomObject = $AsPSCustomObject.IsPresent $script:luaMaxDepth = $MaxDepth $script:luaCurrentDepth = 0 + $script:luaSkipValidation = $SkipValidation.IsPresent Skip-LuaWhitespace @@ -110,7 +115,11 @@ # Lua grammar: variable names cannot be reserved words (§3.1) if ($varName -in $reservedWords) { - throw "Reserved word '$varName' cannot be used as a variable name at position $identStart." + if ($script:luaSkipValidation) { + Write-Warning "Reserved word '$varName' used as a variable name at position $identStart." + } else { + throw "Reserved word '$varName' cannot be used as a variable name at position $identStart." + } } Skip-LuaWhitespace diff --git a/src/functions/private/Read-LuaTable.ps1 b/src/functions/private/Read-LuaTable.ps1 index 4509b76..5f1313e 100644 --- a/src/functions/private/Read-LuaTable.ps1 +++ b/src/functions/private/Read-LuaTable.ps1 @@ -88,7 +88,11 @@ $script:luaString[$script:luaPos] -eq '=') { # Lua grammar: Name cannot be a reserved word (§3.1) if ($ident -in $reservedWords) { - throw "Reserved word '$ident' cannot be used as a bare identifier key in a Lua table. Use bracket notation: [\"$ident\"] = value." + if ($script:luaSkipValidation) { + Write-Warning "Reserved word '$ident' used as a bare identifier key at position $identStart." + } else { + throw "Reserved word '$ident' cannot be used as a bare identifier key in a Lua table. Use bracket notation: [`"$ident`"] = value." + } } # Key = value pair $script:luaPos++ # skip = diff --git a/src/functions/public/Lua/ConvertFrom-Lua.ps1 b/src/functions/public/Lua/ConvertFrom-Lua.ps1 index dbabba9..f6fd41c 100644 --- a/src/functions/public/Lua/ConvertFrom-Lua.ps1 +++ b/src/functions/public/Lua/ConvertFrom-Lua.ps1 @@ -77,13 +77,17 @@ # Output arrays as a single object instead of enumerating elements through the pipeline. [Parameter()] - [switch] $NoEnumerate + [switch] $NoEnumerate, + + # Skip strict Lua grammar validation (e.g., reserved words as bare keys). Warnings are emitted instead of errors. + [Parameter()] + [switch] $SkipValidation ) begin {} process { - $result = ConvertFrom-LuaTable -InputString $InputObject -AsPSCustomObject:(-not $AsHashtable) -MaxDepth $Depth + $result = ConvertFrom-LuaTable -InputString $InputObject -AsPSCustomObject:(-not $AsHashtable) -MaxDepth $Depth -SkipValidation:$SkipValidation if ($NoEnumerate -and $result -is [System.Array]) { Write-Output -InputObject $result -NoEnumerate } else { diff --git a/tests/Lua.Tests.ps1 b/tests/Lua.Tests.ps1 index 64b7952..0e2fd7c 100644 --- a/tests/Lua.Tests.ps1 +++ b/tests/Lua.Tests.ps1 @@ -526,8 +526,37 @@ B = { val = 2 } { ConvertFrom-Lua -InputObject 'end = 1' } | Should -Throw '*Reserved word*' } - It 'Throws on reserved word as assignment variable name (return as assignment)' { - { ConvertFrom-Lua -InputObject 'return = 42' } | Should -Throw '*Reserved word*' + It 'Throws on reserved word as assignment variable name (while as assignment)' { + { ConvertFrom-Lua -InputObject 'while = 42' } | Should -Throw '*Reserved word*' + } + + It 'SkipValidation allows reserved word as bare table key with warning' { + $result = ConvertFrom-Lua -InputObject '{ end = 1 }' -AsHashtable -SkipValidation 3>&1 + $warnings = $result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] } + $output = $result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] } + $warnings.Message | Should -BeLike '*Reserved word*end*' + $output['end'] | Should -Be 1 + } + + It 'SkipValidation allows reserved word as assignment variable name with warning' { + $result = ConvertFrom-Lua -InputObject 'end = 1' -AsHashtable -SkipValidation 3>&1 + $warnings = $result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] } + $output = $result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] } + $warnings.Message | Should -BeLike '*Reserved word*end*' + $output['end'] | Should -Be 1 + } + + It 'SkipValidation emits a warning for each reserved word occurrence' { + $result = ConvertFrom-Lua -InputObject '{ end = 1, while = 2, for = 3 }' -AsHashtable -SkipValidation 3>&1 + $warnings = @($result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }) + $output = $result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] } + $warnings.Count | Should -Be 3 + $warnings[0].Message | Should -BeLike '*end*' + $warnings[1].Message | Should -BeLike '*while*' + $warnings[2].Message | Should -BeLike '*for*' + $output['end'] | Should -Be 1 + $output['while'] | Should -Be 2 + $output['for'] | Should -Be 3 } } From 10c60f9eb90ba87d2081b994ca4d3b0a1a7c716f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Apr 2026 23:24:56 +0200 Subject: [PATCH 3/3] Refactor ConvertFrom-Lua to use a hashtable for parameters and improve error message for reserved words in Read-LuaTable --- src/functions/private/Read-LuaTable.ps1 | 4 +++- src/functions/public/Lua/ConvertFrom-Lua.ps1 | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/functions/private/Read-LuaTable.ps1 b/src/functions/private/Read-LuaTable.ps1 index 5f1313e..ba0f3d3 100644 --- a/src/functions/private/Read-LuaTable.ps1 +++ b/src/functions/private/Read-LuaTable.ps1 @@ -91,7 +91,9 @@ if ($script:luaSkipValidation) { Write-Warning "Reserved word '$ident' used as a bare identifier key at position $identStart." } else { - throw "Reserved word '$ident' cannot be used as a bare identifier key in a Lua table. Use bracket notation: [`"$ident`"] = value." + $msg = "Reserved word '$ident' cannot be used as a bare identifier key" + + " in a Lua table. Use bracket notation: [`"$ident`"] = value." + throw $msg } } # Key = value pair diff --git a/src/functions/public/Lua/ConvertFrom-Lua.ps1 b/src/functions/public/Lua/ConvertFrom-Lua.ps1 index f6fd41c..5e5d7d1 100644 --- a/src/functions/public/Lua/ConvertFrom-Lua.ps1 +++ b/src/functions/public/Lua/ConvertFrom-Lua.ps1 @@ -87,7 +87,13 @@ begin {} process { - $result = ConvertFrom-LuaTable -InputString $InputObject -AsPSCustomObject:(-not $AsHashtable) -MaxDepth $Depth -SkipValidation:$SkipValidation + $convertParams = @{ + InputString = $InputObject + AsPSCustomObject = -not $AsHashtable + MaxDepth = $Depth + SkipValidation = $SkipValidation.IsPresent + } + $result = ConvertFrom-LuaTable @convertParams if ($NoEnumerate -and $result -is [System.Array]) { Write-Output -InputObject $result -NoEnumerate } else {