Skip to content

Commit d532ebf

Browse files
🩹 [Patch]: Reserved words in Lua input now detected with option to skip validation (#6)
* Add reserved word checks for Lua variable names and table keys in conversion functions * Add SkipValidation parameter to allow warnings for reserved words in Lua parsing * Refactor ConvertFrom-Lua to use a hashtable for parameters and improve error message for reserved words in Read-LuaTable * Fix PSScriptAnalyzer linter warnings: align assignment operators in ConvertFrom-Lua hashtable; resolve indentation inconsistency in Read-LuaTable by inlining throw message * Fix case-sensitive reserved word checks: use -cin instead of -in per Lua §3.1; add tests for capitalized identifiers (End, While)
1 parent e070ac5 commit d532ebf

5 files changed

Lines changed: 114 additions & 4 deletions

File tree

‎src/functions/private/ConvertFrom-LuaTable.ps1‎

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,11 @@
2222

2323
# Maximum allowed nesting depth.
2424
[Parameter()]
25-
[int] $MaxDepth = 1024
25+
[int] $MaxDepth = 1024,
26+
27+
# Skip strict Lua grammar validation. Warnings are emitted instead of terminating errors.
28+
[Parameter()]
29+
[switch] $SkipValidation
2630
)
2731

2832
begin {}
@@ -33,6 +37,7 @@
3337
$script:luaAsPSCustomObject = $AsPSCustomObject.IsPresent
3438
$script:luaMaxDepth = $MaxDepth
3539
$script:luaCurrentDepth = 0
40+
$script:luaSkipValidation = $SkipValidation.IsPresent
3641

3742
Skip-LuaWhitespace
3843

@@ -81,6 +86,14 @@
8186
}
8287

8388
if ($assignmentDetected) {
89+
# Lua 5.4 reserved words per §3.1
90+
$reservedWords = @(
91+
'and', 'break', 'do', 'else', 'elseif', 'end',
92+
'false', 'for', 'function', 'goto', 'if', 'in',
93+
'local', 'nil', 'not', 'or', 'repeat', 'return',
94+
'then', 'true', 'until', 'while'
95+
)
96+
8497
# Parse one or more assignment statements into an ordered dictionary
8598
$assignments = [ordered]@{}
8699
while ($script:luaPos -lt $script:luaString.Length) {
@@ -100,6 +113,15 @@
100113
}
101114
$varName = $script:luaString.Substring($identStart, $script:luaPos - $identStart)
102115

116+
# Lua grammar: variable names cannot be reserved words (§3.1)
117+
if ($varName -cin $reservedWords) {
118+
if ($script:luaSkipValidation) {
119+
Write-Warning "Reserved word '$varName' used as a variable name at position $identStart."
120+
} else {
121+
throw "Reserved word '$varName' cannot be used as a variable name at position $identStart."
122+
}
123+
}
124+
103125
Skip-LuaWhitespace
104126

105127
# Expect '='

‎src/functions/private/ConvertTo-LuaTable.ps1‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@
5858
# Enum handling
5959
if ($InputObject -is [enum]) {
6060
if ($EnumsAsStrings) {
61-
$escaped = $InputObject.ToString() -replace '\\', '\\\\' -replace '"', '\"'
61+
$escaped = $InputObject.ToString() -replace '\\', '\\' -replace '"', '\"'
6262
return "`"$escaped`""
6363
}
6464
$underlyingType = [System.Enum]::GetUnderlyingType($InputObject.GetType())

‎src/functions/private/Read-LuaTable.ps1‎

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@
1313
begin {}
1414

1515
process {
16+
# Lua 5.4 reserved words per §3.1
17+
$reservedWords = @(
18+
'and', 'break', 'do', 'else', 'elseif', 'end',
19+
'false', 'for', 'function', 'goto', 'if', 'in',
20+
'local', 'nil', 'not', 'or', 'repeat', 'return',
21+
'then', 'true', 'until', 'while'
22+
)
23+
1624
$script:luaCurrentDepth++
1725
if ($script:luaCurrentDepth -gt $script:luaMaxDepth) {
1826
throw "Maximum nesting depth ($($script:luaMaxDepth)) exceeded."
@@ -78,6 +86,14 @@
7886

7987
if ($script:luaPos -lt $script:luaString.Length -and
8088
$script:luaString[$script:luaPos] -eq '=') {
89+
# Lua grammar: Name cannot be a reserved word (§3.1)
90+
if ($ident -cin $reservedWords) {
91+
if ($script:luaSkipValidation) {
92+
Write-Warning "Reserved word '$ident' used as a bare identifier key at position $identStart."
93+
} else {
94+
throw "Reserved word '$ident' cannot be used as a bare identifier key. Use bracket notation: [`"$ident`"] = value."
95+
}
96+
}
8197
# Key = value pair
8298
$script:luaPos++ # skip =
8399
Skip-LuaWhitespace

‎src/functions/public/Lua/ConvertFrom-Lua.ps1‎

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,13 +77,23 @@
7777

7878
# Output arrays as a single object instead of enumerating elements through the pipeline.
7979
[Parameter()]
80-
[switch] $NoEnumerate
80+
[switch] $NoEnumerate,
81+
82+
# Skip strict Lua grammar validation (e.g., reserved words as bare keys). Warnings are emitted instead of errors.
83+
[Parameter()]
84+
[switch] $SkipValidation
8185
)
8286

8387
begin {}
8488

8589
process {
86-
$result = ConvertFrom-LuaTable -InputString $InputObject -AsPSCustomObject:(-not $AsHashtable) -MaxDepth $Depth
90+
$convertParams = @{
91+
InputString = $InputObject
92+
AsPSCustomObject = -not $AsHashtable
93+
MaxDepth = $Depth
94+
SkipValidation = $SkipValidation.IsPresent
95+
}
96+
$result = ConvertFrom-LuaTable @convertParams
8797
if ($NoEnumerate -and $result -is [System.Array]) {
8898
Write-Output -InputObject $result -NoEnumerate
8999
} else {

‎tests/Lua.Tests.ps1‎

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,68 @@ B = { val = 2 }
507507
It 'Throws on assignment with missing value' {
508508
{ ConvertFrom-Lua -InputObject 'A = ' } | Should -Throw '*Unexpected end of input*'
509509
}
510+
511+
It 'Throws on reserved word as bare table key' {
512+
{ ConvertFrom-Lua -InputObject '{ end = 1 }' } | Should -Throw '*Reserved word*'
513+
}
514+
515+
It 'Throws on reserved word as bare table key (while)' {
516+
{ ConvertFrom-Lua -InputObject '{ while = "loop" }' } | Should -Throw '*Reserved word*'
517+
}
518+
519+
It 'Allows reserved words in bracket notation' {
520+
$result = ConvertFrom-Lua -InputObject '{ ["end"] = 1, ["while"] = 2 }' -AsHashtable
521+
$result['end'] | Should -Be 1
522+
$result['while'] | Should -Be 2
523+
}
524+
525+
It 'Allows capitalized reserved words as bare table keys (Lua keywords are case-sensitive)' {
526+
$result = ConvertFrom-Lua -InputObject '{ End = 1, While = "loop" }' -AsHashtable
527+
$result['End'] | Should -Be 1
528+
$result['While'] | Should -Be 'loop'
529+
}
530+
531+
It 'Allows capitalized reserved words as assignment variable names (Lua keywords are case-sensitive)' {
532+
$result = ConvertFrom-Lua -InputObject 'End = 1' -AsHashtable
533+
$result['End'] | Should -Be 1
534+
}
535+
536+
It 'Throws on reserved word as assignment variable name' {
537+
{ ConvertFrom-Lua -InputObject 'end = 1' } | Should -Throw '*Reserved word*'
538+
}
539+
540+
It 'Throws on reserved word as assignment variable name (while as assignment)' {
541+
{ ConvertFrom-Lua -InputObject 'while = 42' } | Should -Throw '*Reserved word*'
542+
}
543+
544+
It 'SkipValidation allows reserved word as bare table key with warning' {
545+
$result = ConvertFrom-Lua -InputObject '{ end = 1 }' -AsHashtable -SkipValidation 3>&1
546+
$warnings = $result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }
547+
$output = $result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] }
548+
$warnings.Message | Should -BeLike '*Reserved word*end*'
549+
$output['end'] | Should -Be 1
550+
}
551+
552+
It 'SkipValidation allows reserved word as assignment variable name with warning' {
553+
$result = ConvertFrom-Lua -InputObject 'end = 1' -AsHashtable -SkipValidation 3>&1
554+
$warnings = $result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] }
555+
$output = $result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] }
556+
$warnings.Message | Should -BeLike '*Reserved word*end*'
557+
$output['end'] | Should -Be 1
558+
}
559+
560+
It 'SkipValidation emits a warning for each reserved word occurrence' {
561+
$result = ConvertFrom-Lua -InputObject '{ end = 1, while = 2, for = 3 }' -AsHashtable -SkipValidation 3>&1
562+
$warnings = @($result | Where-Object { $_ -is [System.Management.Automation.WarningRecord] })
563+
$output = $result | Where-Object { $_ -isnot [System.Management.Automation.WarningRecord] }
564+
$warnings.Count | Should -Be 3
565+
$warnings[0].Message | Should -BeLike '*end*'
566+
$warnings[1].Message | Should -BeLike '*while*'
567+
$warnings[2].Message | Should -BeLike '*for*'
568+
$output['end'] | Should -Be 1
569+
$output['while'] | Should -Be 2
570+
$output['for'] | Should -Be 3
571+
}
510572
}
511573

512574
Context 'Pipeline input' {

0 commit comments

Comments
 (0)