From 62661b97226a8a5b5e8447cff017876c3797deae Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Thu, 16 Apr 2026 19:03:24 +0200 Subject: [PATCH 1/5] 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/5] 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/5] 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 { From d036c0f1cc50960d972d7f86bc0c5e935392dfcd Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Tue, 28 Apr 2026 22:54:47 +0200 Subject: [PATCH 4/5] Fix PSScriptAnalyzer linter warnings: align assignment operators in ConvertFrom-Lua hashtable; resolve indentation inconsistency in Read-LuaTable by inlining throw message --- src/functions/private/Read-LuaTable.ps1 | 4 +--- src/functions/public/Lua/ConvertFrom-Lua.ps1 | 6 +++--- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/functions/private/Read-LuaTable.ps1 b/src/functions/private/Read-LuaTable.ps1 index ba0f3d3..6620093 100644 --- a/src/functions/private/Read-LuaTable.ps1 +++ b/src/functions/private/Read-LuaTable.ps1 @@ -91,9 +91,7 @@ if ($script:luaSkipValidation) { Write-Warning "Reserved word '$ident' used as a bare identifier key at position $identStart." } else { - $msg = "Reserved word '$ident' cannot be used as a bare identifier key" + - " in a Lua table. Use bracket notation: [`"$ident`"] = value." - throw $msg + throw "Reserved word '$ident' cannot be used as a bare identifier key. Use bracket notation: [`"$ident`"] = value." } } # Key = value pair diff --git a/src/functions/public/Lua/ConvertFrom-Lua.ps1 b/src/functions/public/Lua/ConvertFrom-Lua.ps1 index 5e5d7d1..1702322 100644 --- a/src/functions/public/Lua/ConvertFrom-Lua.ps1 +++ b/src/functions/public/Lua/ConvertFrom-Lua.ps1 @@ -88,10 +88,10 @@ process { $convertParams = @{ - InputString = $InputObject + InputString = $InputObject AsPSCustomObject = -not $AsHashtable - MaxDepth = $Depth - SkipValidation = $SkipValidation.IsPresent + MaxDepth = $Depth + SkipValidation = $SkipValidation.IsPresent } $result = ConvertFrom-LuaTable @convertParams if ($NoEnumerate -and $result -is [System.Array]) { From 9368876d713bdd0975633e84af354b87f4b39a5f Mon Sep 17 00:00:00 2001 From: Marius Storhaug Date: Wed, 29 Apr 2026 11:03:42 +0200 Subject: [PATCH 5/5] =?UTF-8?q?Fix=20case-sensitive=20reserved=20word=20ch?= =?UTF-8?q?ecks:=20use=20-cin=20instead=20of=20-in=20per=20Lua=20=C2=A73.1?= =?UTF-8?q?;=20add=20tests=20for=20capitalized=20identifiers=20(End,=20Whi?= =?UTF-8?q?le)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/functions/private/ConvertFrom-LuaTable.ps1 | 2 +- src/functions/private/Read-LuaTable.ps1 | 2 +- tests/Lua.Tests.ps1 | 11 +++++++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/functions/private/ConvertFrom-LuaTable.ps1 b/src/functions/private/ConvertFrom-LuaTable.ps1 index 1a95daa..ad34c66 100644 --- a/src/functions/private/ConvertFrom-LuaTable.ps1 +++ b/src/functions/private/ConvertFrom-LuaTable.ps1 @@ -114,7 +114,7 @@ $varName = $script:luaString.Substring($identStart, $script:luaPos - $identStart) # Lua grammar: variable names cannot be reserved words (§3.1) - if ($varName -in $reservedWords) { + if ($varName -cin $reservedWords) { if ($script:luaSkipValidation) { Write-Warning "Reserved word '$varName' used as a variable name at position $identStart." } else { diff --git a/src/functions/private/Read-LuaTable.ps1 b/src/functions/private/Read-LuaTable.ps1 index 6620093..f94b6e4 100644 --- a/src/functions/private/Read-LuaTable.ps1 +++ b/src/functions/private/Read-LuaTable.ps1 @@ -87,7 +87,7 @@ 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) { + if ($ident -cin $reservedWords) { if ($script:luaSkipValidation) { Write-Warning "Reserved word '$ident' used as a bare identifier key at position $identStart." } else { diff --git a/tests/Lua.Tests.ps1 b/tests/Lua.Tests.ps1 index 0e2fd7c..063feb4 100644 --- a/tests/Lua.Tests.ps1 +++ b/tests/Lua.Tests.ps1 @@ -522,6 +522,17 @@ B = { val = 2 } $result['while'] | Should -Be 2 } + It 'Allows capitalized reserved words as bare table keys (Lua keywords are case-sensitive)' { + $result = ConvertFrom-Lua -InputObject '{ End = 1, While = "loop" }' -AsHashtable + $result['End'] | Should -Be 1 + $result['While'] | Should -Be 'loop' + } + + It 'Allows capitalized reserved words as assignment variable names (Lua keywords are case-sensitive)' { + $result = ConvertFrom-Lua -InputObject 'End = 1' -AsHashtable + $result['End'] | Should -Be 1 + } + It 'Throws on reserved word as assignment variable name' { { ConvertFrom-Lua -InputObject 'end = 1' } | Should -Throw '*Reserved word*' }