Skip to content
Open
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
24 changes: 23 additions & 1 deletion src/functions/private/ConvertFrom-LuaTable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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 {}
Expand All @@ -33,6 +37,7 @@
$script:luaAsPSCustomObject = $AsPSCustomObject.IsPresent
$script:luaMaxDepth = $MaxDepth
$script:luaCurrentDepth = 0
$script:luaSkipValidation = $SkipValidation.IsPresent

Skip-LuaWhitespace

Expand Down Expand Up @@ -81,6 +86,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) {
Expand All @@ -100,6 +113,15 @@
}
$varName = $script:luaString.Substring($identStart, $script:luaPos - $identStart)

# Lua grammar: variable names cannot be reserved words (§3.1)
if ($varName -in $reservedWords) {
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."
}
}
Comment on lines +116 to +123
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This reserved-word validation uses case-insensitive membership (-in), which will flag valid identifiers like Return/While/End even though Lua keywords are case-sensitive. Use a case-sensitive check (e.g., -cin/-ccontains, or an Ordinal HashSet) so only exact lowercase reserved words trigger errors/warnings.

Copilot uses AI. Check for mistakes.

Skip-LuaWhitespace

# Expect '='
Expand Down
2 changes: 1 addition & 1 deletion src/functions/private/ConvertTo-LuaTable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@
# Enum handling
if ($InputObject -is [enum]) {
if ($EnumsAsStrings) {
$escaped = $InputObject.ToString() -replace '\\', '\\\\' -replace '"', '\"'
$escaped = $InputObject.ToString() -replace '\\', '\\' -replace '"', '\"'
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-replace '\\', '\\' is effectively a no-op for backslashes, so this change stops escaping backslashes in enum string output. That will produce Lua strings where sequences like \n/\t can be interpreted as escapes, altering the value, and it doesn’t match the PR description’s intent (“no longer double-escapes” should still result in \\ in the emitted Lua literal for a single \ in the original string). Use the correct replacement to emit a doubled backslash in the Lua literal (or reuse the same escaping routine used for normal strings so enums and strings behave consistently).

Suggested change
$escaped = $InputObject.ToString() -replace '\\', '\\' -replace '"', '\"'
$escaped = $InputObject.ToString() -replace '\\', '\\\\' -replace '"', '\"'

Copilot uses AI. Check for mistakes.
return "`"$escaped`""
}
$underlyingType = [System.Enum]::GetUnderlyingType($InputObject.GetType())
Expand Down
16 changes: 16 additions & 0 deletions src/functions/private/Read-LuaTable.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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."
Expand Down Expand Up @@ -78,6 +86,14 @@

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 ($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. Use bracket notation: [`"$ident`"] = value."
}
Comment on lines +89 to +95
Copy link

Copilot AI Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reserved-word check uses PowerShell's default case-insensitive comparison (-in), which will incorrectly reject valid Lua identifiers that differ by case (e.g., End is a valid identifier in Lua, only lowercase end is reserved). Make this comparison case-sensitive (e.g., -cin/-ccontains, or compare via an Ordinal HashSet) so only exact reserved words are blocked/warned.

Copilot uses AI. Check for mistakes.
}
# Key = value pair
$script:luaPos++ # skip =
Skip-LuaWhitespace
Expand Down
14 changes: 12 additions & 2 deletions src/functions/public/Lua/ConvertFrom-Lua.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,23 @@

# 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
$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 {
Expand Down
51 changes: 51 additions & 0 deletions tests/Lua.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,57 @@ 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 (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
}
}

Context 'Pipeline input' {
Expand Down
Loading