Skip to content

Commit ec56f8e

Browse files
Add ConvertTo-Lua and ConvertFrom-Lua with tests and test data
1 parent 8ef5a87 commit ec56f8e

8 files changed

Lines changed: 1223 additions & 129 deletions

File tree

src/functions/private/ConvertFrom-LuaTable.ps1

Lines changed: 201 additions & 40 deletions
Large diffs are not rendered by default.

src/functions/private/ConvertTo-LuaTable.ps1

Lines changed: 60 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,10 @@
55
66
.DESCRIPTION
77
Recursively converts a PowerShell object (hashtable, array, PSCustomObject, or primitive)
8-
into a Lua table string. This is the internal serialization engine used by ConvertTo-Lua.
8+
into a Lua table constructor string. This is the internal serialization engine used by ConvertTo-Lua.
9+
10+
Uses fixed 4-space indentation per the Lua community convention.
11+
Properties with $null values are omitted (Lua nil-means-absent semantics).
912
#>
1013
[OutputType([string])]
1114
[CmdletBinding()]
@@ -15,22 +18,26 @@
1518
[AllowNull()]
1619
[object] $InputObject,
1720

18-
# The current indentation depth for formatting.
21+
# The current recursion depth.
1922
[Parameter()]
20-
[int] $Depth = 0,
23+
[int] $CurrentDepth = 0,
2124

22-
# Number of spaces per indentation level.
25+
# Maximum allowed recursion depth.
2326
[Parameter()]
24-
[int] $IndentSize = 4,
27+
[int] $MaxDepth = 2,
2528

2629
# Whether to compress the output (no newlines or indentation).
2730
[Parameter()]
28-
[switch] $Compress
31+
[switch] $Compress,
32+
33+
# Serialize enum values as their string name instead of numeric value.
34+
[Parameter()]
35+
[switch] $EnumsAsStrings
2936
)
3037

3138
begin {
32-
$indent = if ($Compress) { '' } else { ' ' * ($IndentSize * $Depth) }
33-
$childIndent = if ($Compress) { '' } else { ' ' * ($IndentSize * ($Depth + 1)) }
39+
$indent = if ($Compress) { '' } else { ' ' * (4 * $CurrentDepth) }
40+
$childIndent = if ($Compress) { '' } else { ' ' * (4 * ($CurrentDepth + 1)) }
3441
$newline = if ($Compress) { '' } else { "`n" }
3542
$separator = if ($Compress) { ',' } else { ",`n" }
3643
}
@@ -48,28 +55,47 @@
4855
}
4956
}
5057

58+
# Enum handling
59+
if ($InputObject -is [enum]) {
60+
if ($EnumsAsStrings) {
61+
$escaped = $InputObject.ToString() -replace '\\', '\\\\' -replace '"', '\"'
62+
return "`"$escaped`""
63+
}
64+
return ([int]$InputObject).ToString([System.Globalization.CultureInfo]::InvariantCulture)
65+
}
66+
5167
if ($InputObject -is [int] -or $InputObject -is [long] -or
52-
$InputObject -is [float] -or $InputObject -is [double] -or
53-
$InputObject -is [decimal] -or $InputObject -is [int16] -or
54-
$InputObject -is [int64] -or $InputObject -is [uint16] -or
55-
$InputObject -is [uint32] -or $InputObject -is [uint64] -or
56-
$InputObject -is [byte] -or $InputObject -is [sbyte] -or
57-
$InputObject -is [single]) {
68+
$InputObject -is [int16] -or $InputObject -is [int64] -or
69+
$InputObject -is [uint16] -or $InputObject -is [uint32] -or
70+
$InputObject -is [uint64] -or $InputObject -is [byte] -or
71+
$InputObject -is [sbyte]) {
72+
return $InputObject.ToString([System.Globalization.CultureInfo]::InvariantCulture)
73+
}
74+
75+
if ($InputObject -is [float] -or $InputObject -is [double] -or
76+
$InputObject -is [decimal] -or $InputObject -is [single]) {
5877
return $InputObject.ToString([System.Globalization.CultureInfo]::InvariantCulture)
5978
}
6079

6180
if ($InputObject -is [string]) {
62-
$escaped = $InputObject -replace '\\', '\\' -replace '"', '\"' -replace "`n", '\n' -replace "`r", '\r' -replace "`t", '\t'
81+
$escaped = $InputObject -replace '\\', '\\' -replace '"', '\"' -replace "`0", '\0' -replace "`a", '\a' -replace "`b", '\b' -replace "`f", '\f' -replace "`n", '\n' -replace "`r", '\r' -replace "`t", '\t' -replace "`v", '\v'
6382
return "`"$escaped`""
6483
}
6584

85+
# Depth check for complex types
86+
if ($CurrentDepth -ge $MaxDepth) {
87+
Write-Warning "Depth limit ($MaxDepth) exceeded at depth $CurrentDepth. Serializing remaining object as string."
88+
$str = $InputObject.ToString() -replace '\\', '\\\\' -replace '"', '\"'
89+
return "`"$str`""
90+
}
91+
6692
if ($InputObject -is [System.Collections.IList]) {
6793
if ($InputObject.Count -eq 0) {
6894
return '{}'
6995
}
7096
$items = [System.Collections.Generic.List[string]]::new()
7197
foreach ($item in $InputObject) {
72-
$value = ConvertTo-LuaTable -InputObject $item -Depth ($Depth + 1) -IndentSize $IndentSize -Compress:$Compress
98+
$value = ConvertTo-LuaTable -InputObject $item -CurrentDepth ($CurrentDepth + 1) -MaxDepth $MaxDepth -Compress:$Compress -EnumsAsStrings:$EnumsAsStrings
7399
$items.Add("$childIndent$value")
74100
}
75101
return "{$newline$($items -join $separator)$newline$indent}"
@@ -82,27 +108,42 @@
82108
}
83109
$entries = [System.Collections.Generic.List[string]]::new()
84110
foreach ($key in $InputObject.Keys) {
85-
$value = ConvertTo-LuaTable -InputObject $InputObject[$key] -Depth ($Depth + 1) -IndentSize $IndentSize -Compress:$Compress
111+
$val = $InputObject[$key]
112+
# Omit $null values per Lua nil-means-absent semantics
113+
if ($null -eq $val) {
114+
continue
115+
}
116+
$value = ConvertTo-LuaTable -InputObject $val -CurrentDepth ($CurrentDepth + 1) -MaxDepth $MaxDepth -Compress:$Compress -EnumsAsStrings:$EnumsAsStrings
86117
$luaKey = Format-LuaKey -Key ([string]$key)
87118
$space = if ($Compress) { '' } else { ' ' }
88119
$entries.Add("$childIndent$luaKey$space=${space}$value")
89120
}
121+
if ($entries.Count -eq 0) {
122+
return '{}'
123+
}
90124
return "{$newline$($entries -join $separator)$newline$indent}"
91125
}
92126

93-
# Handle PSCustomObject (from ConvertFrom-Json etc.)
127+
# Handle PSCustomObject
94128
if ($InputObject -is [psobject]) {
95129
$properties = $InputObject.PSObject.Properties | Where-Object { $_.MemberType -eq 'NoteProperty' }
96130
if (-not $properties) {
97131
return '{}'
98132
}
99133
$entries = [System.Collections.Generic.List[string]]::new()
100134
foreach ($prop in $properties) {
101-
$value = ConvertTo-LuaTable -InputObject $prop.Value -Depth ($Depth + 1) -IndentSize $IndentSize -Compress:$Compress
135+
# Omit $null values per Lua nil-means-absent semantics
136+
if ($null -eq $prop.Value) {
137+
continue
138+
}
139+
$value = ConvertTo-LuaTable -InputObject $prop.Value -CurrentDepth ($CurrentDepth + 1) -MaxDepth $MaxDepth -Compress:$Compress -EnumsAsStrings:$EnumsAsStrings
102140
$luaKey = Format-LuaKey -Key $prop.Name
103141
$space = if ($Compress) { '' } else { ' ' }
104142
$entries.Add("$childIndent$luaKey$space=${space}$value")
105143
}
144+
if ($entries.Count -eq 0) {
145+
return '{}'
146+
}
106147
return "{$newline$($entries -join $separator)$newline$indent}"
107148
}
108149

src/functions/private/Format-LuaKey.ps1

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
Formats a string as a valid Lua table key.
55
66
.DESCRIPTION
7-
Returns the key as a bare identifier if it matches Lua identifier rules,
8-
otherwise wraps it in bracket-quote notation: ["key"].
7+
Returns the key as a bare identifier if it matches Lua identifier rules
8+
and is not a reserved word, otherwise wraps it in bracket-quote notation: ["key"].
99
#>
1010
[OutputType([string])]
1111
[CmdletBinding()]
@@ -15,10 +15,18 @@
1515
[string] $Key
1616
)
1717

18-
begin {}
18+
begin {
19+
# Lua 5.4 reserved words per §3.1
20+
$reservedWords = @(
21+
'and', 'break', 'do', 'else', 'elseif', 'end',
22+
'false', 'for', 'function', 'goto', 'if', 'in',
23+
'local', 'nil', 'not', 'or', 'repeat', 'return',
24+
'then', 'true', 'until', 'while'
25+
)
26+
}
1927

2028
process {
21-
if ($Key -match '^[a-zA-Z_][a-zA-Z0-9_]*$') {
29+
if ($Key -match '^[a-zA-Z_][a-zA-Z0-9_]*$' -and $Key -notin $reservedWords) {
2230
return $Key
2331
}
2432
$escaped = $Key -replace '\\', '\\\\' -replace '"', '\"'

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

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,15 @@
11
function ConvertFrom-Lua {
22
<#
33
.SYNOPSIS
4-
Converts a Lua table string to a PowerShell object.
4+
Converts a Lua table constructor string to a PowerShell object.
55
66
.DESCRIPTION
7-
Takes a Lua table string and parses it into PowerShell objects. Lua tables with
8-
string keys become ordered hashtables (or PSCustomObjects with -AsObject), Lua
9-
sequences become arrays, and Lua primitives are converted to their PowerShell
10-
equivalents.
7+
Takes a Lua table constructor string and parses it into PowerShell objects.
8+
By default, Lua tables with string keys become PSCustomObjects and Lua
9+
sequences become arrays. Use -AsHashtable to get ordered hashtables instead.
1110
1211
Supports the following Lua to PowerShell type mappings:
13-
- Lua table (key = value) -> [ordered] hashtable or [PSCustomObject]
12+
- Lua table (key = value) -> [PSCustomObject] or [ordered] hashtable
1413
- Lua sequence (array) -> [object[]]
1514
- Lua double-quoted string -> [string]
1615
- Lua single-quoted string -> [string]
@@ -26,10 +25,9 @@
2625
```powershell
2726
'{ name = "Alice", age = 30 }' | ConvertFrom-Lua
2827
29-
Name Value
30-
---- -----
31-
name Alice
32-
age 30
28+
name age
29+
---- ---
30+
Alice 30
3331
```
3432
3533
.EXAMPLE
@@ -43,39 +41,52 @@
4341
4442
.EXAMPLE
4543
```powershell
46-
'{ server = "localhost", port = 8080, enabled = true }' | ConvertFrom-Lua -AsObject
44+
'{ name = "Alice" }' | ConvertFrom-Lua -AsHashtable
4745
48-
server port enabled
49-
------ ---- -------
50-
localhost 8080 True
46+
Name Value
47+
---- -----
48+
name Alice
5149
```
5250
5351
.NOTES
54-
[Lua Table Documentation](https://www.lua.org/pil/2.5.html)
52+
[Lua 5.4 Reference Manual - Table Constructors](https://www.lua.org/manual/5.4/manual.html#3.4.9)
5553
5654
.LINK
5755
https://psmodule.io/Lua/Functions/ConvertFrom-Lua/
5856
5957
.LINK
60-
https://www.lua.org/pil/2.5.html
58+
https://www.lua.org/manual/5.4/manual.html#3.4.9
6159
#>
6260
[OutputType([object])]
6361
[CmdletBinding()]
6462
param(
65-
# The Lua table string to convert to a PowerShell object.
66-
[Parameter(Mandatory, ValueFromPipeline)]
63+
# The Lua table constructor string to convert to a PowerShell object.
64+
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
6765
[ValidateNotNullOrEmpty()]
6866
[string] $InputObject,
6967

70-
# Output PSCustomObjects instead of hashtables for Lua tables with string keys.
68+
# Output ordered hashtables instead of PSCustomObjects for Lua tables with string keys.
69+
[Parameter()]
70+
[switch] $AsHashtable,
71+
72+
# Max nesting depth allowed in input. Throws a terminating error when exceeded.
73+
[Parameter()]
74+
[int] $Depth = 1024,
75+
76+
# Output arrays as a single object instead of enumerating elements through the pipeline.
7177
[Parameter()]
72-
[switch] $AsObject
78+
[switch] $NoEnumerate
7379
)
7480

7581
begin {}
7682

7783
process {
78-
ConvertFrom-LuaTable -InputString $InputObject -AsPSCustomObject:$AsObject
84+
$result = ConvertFrom-LuaTable -InputString $InputObject -AsPSCustomObject:(-not $AsHashtable) -MaxDepth $Depth
85+
if ($NoEnumerate) {
86+
Write-Output -NoEnumerate $result
87+
} else {
88+
$result
89+
}
7990
}
8091

8192
end {}

src/functions/public/Lua/ConvertTo-Lua.ps1

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,22 @@
11
function ConvertTo-Lua {
22
<#
33
.SYNOPSIS
4-
Converts a PowerShell object to a Lua table string.
4+
Converts a PowerShell object to a Lua table constructor string.
55
66
.DESCRIPTION
77
Takes a PowerShell object (hashtable, PSCustomObject, array, or primitive value) and
8-
converts it to a Lua table string representation. Nested structures are recursively
9-
converted with proper indentation.
8+
converts it to a Lua table constructor string representation. Nested structures are
9+
recursively converted with 4-space indentation.
1010
1111
Supports the following type mappings:
1212
- [hashtable] / [ordered] -> Lua table with key = value pairs
1313
- [PSCustomObject] -> Lua table with key = value pairs
1414
- [array] -> Lua table (sequence)
1515
- [string] -> Lua double-quoted string with escape sequences
16-
- [int] / [long] / [double] / [decimal] -> Lua number
16+
- [int] / [long] -> Lua integer
17+
- [float] / [double] -> Lua float
1718
- [bool] -> Lua boolean (true/false)
18-
- $null -> nil
19+
- $null -> omitted (nil means absent in Lua)
1920
2021
.EXAMPLE
2122
```powershell
@@ -36,46 +37,56 @@
3637
3738
.EXAMPLE
3839
```powershell
39-
[PSCustomObject]@{ server = "localhost"; port = 8080; enabled = $true } | ConvertTo-Lua
40+
"hello" | ConvertTo-Lua -AsArray
4041
4142
{
42-
server = "localhost",
43-
port = 8080,
44-
enabled = true
43+
"hello"
4544
}
4645
```
4746
4847
.NOTES
49-
[Lua Table Documentation](https://www.lua.org/pil/2.5.html)
48+
[Lua 5.4 Reference Manual - Table Constructors](https://www.lua.org/manual/5.4/manual.html#3.4.9)
5049
5150
.LINK
5251
https://psmodule.io/Lua/Functions/ConvertTo-Lua/
5352
5453
.LINK
55-
https://www.lua.org/pil/2.5.html
54+
https://www.lua.org/manual/5.4/manual.html#3.4.9
5655
#>
5756
[OutputType([string])]
5857
[CmdletBinding()]
5958
param(
60-
# The object to convert to a Lua table string.
61-
[Parameter(Mandatory, ValueFromPipeline)]
59+
# The object to convert to a Lua table constructor string.
60+
[Parameter(Mandatory, Position = 0, ValueFromPipeline)]
6261
[AllowNull()]
6362
[object] $InputObject,
6463

65-
# Number of spaces per indentation level.
64+
# Max recursion depth for nested object serialization. Emits a warning when exceeded.
6665
[Parameter()]
67-
[ValidateRange(1, 16)]
68-
[int] $Depth = 4,
66+
[ValidateRange(0, 100)]
67+
[int] $Depth = 2,
6968

70-
# Whether to compress the output by removing whitespace and newlines.
69+
# Omit whitespace and indentation.
7170
[Parameter()]
72-
[switch] $Compress
71+
[switch] $Compress,
72+
73+
# Serialize PowerShell enum values as their string name instead of numeric value.
74+
[Parameter()]
75+
[switch] $EnumsAsStrings,
76+
77+
# Always wrap output in a Lua sequence table, even for a single value.
78+
[Parameter()]
79+
[switch] $AsArray
7380
)
7481

7582
begin {}
7683

7784
process {
78-
ConvertTo-LuaTable -InputObject $InputObject -Depth 0 -IndentSize $Depth -Compress:$Compress
85+
$objectToConvert = $InputObject
86+
if ($AsArray -and $InputObject -isnot [System.Collections.IList]) {
87+
$objectToConvert = @(, $InputObject)
88+
}
89+
ConvertTo-LuaTable -InputObject $objectToConvert -CurrentDepth 0 -MaxDepth $Depth -Compress:$Compress -EnumsAsStrings:$EnumsAsStrings
7990
}
8091

8192
end {}

0 commit comments

Comments
 (0)