Last active
July 16, 2025 14:48
-
-
Save ElianFabian/813f924808a0ebf8d10f197fbf0de717 to your computer and use it in GitHub Desktop.
Basic PowerShell to GlovePIE transpiler. For a better debugging experience with AST: https://github.com/lzybkr/ShowPSAst. For more information about GlovePIE: https://github.com/GlovePIEPreservation/GlovePIE/wiki.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<# | |
NOTES: | |
----- Something that we could do to make this more powerful would be to first transpile to a low level PowerShell code and then transpile to GlovePIE. | |
- Numeric types in GlovePIE are int32 and float, so we should use them instead of | |
other alternatives. | |
- When we wait inside an if, let's say it's the first line, it will not execute until | |
the rest of the code is executed or there is a wait. | |
- If an if has not finished due to a pending wait then the if will be ignored in that execution | |
- There's an 'int' function that seems to truncate float numbers (this does not convert strings to int). | |
- There's a 'float' that converts an int to float and convert strings to floats. | |
- GlovePIE supports hex notation for numbers (eg.g 0x10) but not binary (e.g. 0b10). | |
#> | |
# This will be useful for testing | |
# function Get-DeterministicGuid { | |
# param ( | |
# [Parameter(Mandatory)] | |
# [int] $Index, | |
# [string] $Seed = "MySeed" | |
# ) | |
# $hashInput = "$Seed-$Index" | |
# $hashBytes = [System.Text.Encoding]::UTF8.GetBytes($hashInput) | |
# $md5 = [System.Security.Cryptography.MD5]::Create() | |
# $guidBytes = $md5.ComputeHash($hashBytes) | |
# return [guid]::new($guidBytes) | |
# } | |
$script:InlinedVariableMap = @{} | |
# name, time, indentationLevel | |
$script:WaitTimes = @() | |
# This is to optimize the GlovePIE output, to avoid adding | |
# unnecessary wait calls. | |
$script:LastWaitIndex = -1 | |
$script:IndentationSize = 4 | |
function GetIndentation([uint] $IndentationLevel) { | |
return ' ' * ($IndentationLevel * $script:IndentationSize) | |
} | |
function NewUuid { | |
return (New-Guid).Guid.Replace('-', '') | |
} | |
# Rudimentary way to implement line number transpilation constant | |
# The caveat of this implementation is that we don't know the line number | |
# at transpilation time, this is not a big deal actually, but we could not use it | |
# for inline foreach statements | |
$lineNumberVariableUuid = "lineNumber_$(NewUuid)" | |
$GlovePie = @{ | |
FloatMaxValue = '(3.402823 * 10^38)' | |
FloatMinValue = '(-3.402823 * 10^38)' | |
FloatEpsilon = '(1.401298 * 10^-45)' | |
FloatNaN = '(0/0)' | |
FloatPositiveInfinity = '(1/0)' | |
FloatNegativeInfinity = '(-1/0)' | |
FloatE = '2.718282' | |
FloatPi = '3.141593' | |
FloatTau = '6.283185' | |
IntMaxValue = '2147483647' | |
IntMinValue = '-2147483648' | |
} | |
$AliasToFullName = @{ | |
"byte" = "System.Byte" | |
"sbyte" = "System.SByte" | |
"short" = "System.Int16" | |
"ushort" = "System.UInt16" | |
"int" = "System.Int32" | |
"int32" = "System.Int32" | |
"int64" = "System.Int64" | |
"uint" = "System.UInt32" | |
"long" = "System.Int64" | |
"ulong" = "System.UInt64" | |
"float" = "System.Single" | |
"double" = "System.Double" | |
"decimal" = "System.Decimal" | |
"object" = "System.Object" | |
"bool" = "System.Boolean" | |
"char" = "System.Char" | |
"string" = "System.String" | |
"void" = "System.Void" | |
} | |
# Another option would be: Invoke-Expression "[$Type].FullName" | |
function ResolveTypeName([string] $Type) { | |
if ([type]::GetType($Type)) { | |
return $Type | |
} | |
return $AliasToFullName[$Type] | |
} | |
function GetTypeCode([string] $Type) { | |
$resolvedType = ResolveTypeName $Type | |
switch ($resolvedType) { | |
'System.String' { 1 } | |
'System.Int32' { 2 } | |
'System.Float' { 3 } | |
'System.Numerics.Vector3' { 4 } | |
default { 0 } | |
} | |
} | |
# TODO: think more about the null implementation in GlovePIE | |
function GetVariable([string] $Variable) { | |
if ($script:InlinedVariableMap.Count -gt 0 -and $script:InlinedVariableMap.ContainsKey($Variable)) { | |
return $script:InlinedVariableMap.$Variable | |
} | |
switch ($Variable) { | |
# true is the same as 1, except the toString() | |
'true' { 'true' } | |
# false is the same as 0, except the toString() | |
'false' { 'false' } | |
# null is the same as 0 in GlovePIE | |
'null' { 'null' } | |
'GlovePieLineNumber' { $lineNumberVariableUuid } | |
default { | |
"var.$Variable" | |
} | |
} | |
} | |
function GetOperator ([string] $Operator) { | |
switch ($Operator) { | |
'Equals' { '=' } | |
'Plus' { '+' } | |
'PlusPlus' { '@plus-plus' } | |
'PostfixPlusPlus' { '@postfix-plus-plus' } | |
'PlusEquals' { '@plus-equals' } | |
'Minus' { '-' } | |
'MinusMinus' { '@minus-minus' } | |
'PostfixMinusMinus' { '@postfix-minus-minus' } | |
'MinusEquals' { '@minus-equals' } | |
'Multiply' { '*' } | |
'MultiplyEquals' { '@multiply-equals' } | |
'Divide' { '/' } | |
'DivideEquals' { '@divide-equals' } | |
'Rem' { '%' } | |
'Cgt' { '>' } | |
'Cge' { '>=' } | |
'Ilt' { '<' } | |
'Ile' { '<=' } | |
'Ceq' { '==' } | |
'Cne' { '!=' } | |
'Igt' { '>' } | |
'Ige' { '>=' } | |
'Clt' { '<' } | |
'Cle' { '<=' } | |
# NOTES: Actually this is not always a good equivalent, but I think | |
# its useful to be able to use the approximate versions of equality. | |
'Ieq' { '~=' } | |
'Ine' { '~!=' } | |
'Iin' { '@in' } | |
'Cin' { '@in' } | |
'Inotin' { '@not-in' } | |
'Cnotin' { '@not-in' } | |
'Icontains' { '@contains' } | |
'Ccontains' { '@contains' } | |
'Inotcontains' { '@not-contains' } | |
'Cnotcontains' { '@not-contains' } | |
'DotDot' { '@dot-dot' } | |
'Is' { '@is' } | |
'Isnot' { '@is-not' } | |
'And' { 'and' } | |
'Or' { 'or' } | |
'Xor' { 'xor' } | |
'Not' { 'not' } | |
'Band' { '&' } | |
'Bor' { '|' } | |
'Shr' { 'shr' } | |
'Shl' { 'shl' } | |
'Bnot' { '@bnot' } | |
'Bxor' { '@bxor' } | |
default { | |
"|$($Operator)|" | |
} | |
} | |
} | |
function GetCommand ( | |
[System.Management.Automation.Language.CommandElementAst[]] $Command, | |
[uint32] $IndentationLevel | |
) { | |
$first = $Command[0] | |
switch ($first) { | |
{ $_[0].Value.StartsWith('.\') -or $_[0].Value.StartsWith('./') } { | |
"Execute '$_'" | |
} | |
'Write-Host' { | |
$rawArguments = $Ast.CommandElements | Select-Object -Skip 1 | |
$arguments = foreach ($element in $rawArguments) { | |
$result = Invoke-AstTranspilation $element -IndentationLevel $IndentationLevel | |
if ($result -eq $GlovePie.Null) { | |
"" | |
} | |
else { | |
$result | |
} | |
} | |
"DebugPrint $(($arguments | ForEach-Object { $_ }) -join ' ')" | |
} | |
'Start-Sleep' { | |
$rawArguments = $Ast.CommandElements | Select-Object -Skip 1 | |
# $arguments = foreach ($element in $rawArguments) { | |
# $result = Invoke-AstTranspilation $element -IndentationLevel $IndentationLevel | |
# if ($result -eq $GlovePie.Null) { | |
# "''" | |
# } | |
# else { | |
# $result | |
# } | |
# } | |
$waitTime = Invoke-AstTranspilation $Command[1] -IndentationLevel $IndentationLevel | |
$wait = @{ | |
name = "wait_$(NewUuid)" | |
time = $waitTime | |
indentationLevel = $IndentationLevel | |
} | |
$script:WaitTimes += $wait | |
@( | |
"var.$($wait.name) = $($wait.time)" | |
"$(GetIndentation $IndentationLevel)wait var.$($wait.name)" | |
"$(GetIndentation $IndentationLevel)var.$($wait.name) = 0" | |
) -join "`n" | |
} | |
'pie' { | |
$value = (Invoke-AstTranspilation -Ast $Command[1] -IndentationLevel $IndentationLevel).EndBlock | |
[scriptblock]::Create($value).Invoke($IndentationLevel) | |
} | |
'pieln' { | |
$value = (Invoke-AstTranspilation -Ast $Command[1] -IndentationLevel $IndentationLevel).EndBlock | |
[scriptblock]::Create($value).Invoke($IndentationLevel) | |
} | |
'executeOnce' { | |
$variableName = "skip_$(NewUuid)" | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
$script:LastWaitIndex = $script:WaitTimes.Count - 1 | |
$stringBuilder.Append("$(GetIndentation $IndentationLevel)") > $null | |
$stringBuilder.AppendLine("if not var.$variableName then") > $null | |
$stringBuilder.AppendLine("$(GetIndentation ($IndentationLevel + 1))var.$variableName = true") > $null | |
$stringBuilder.Append((Invoke-AstTranspilation -Ast $Command[1].ScriptBlock -IndentationLevel ($IndentationLevel + 1))) > $null | |
$stringBuilder.Append("$(GetIndentation $IndentationLevel)") > $null | |
$stringBuilder.AppendLine('endif') > $null | |
for ($i = 0; $i -lt $script:WaitTimes.Count; $i++) { | |
$wait = $script:WaitTimes[$i] | |
if ($i -le $script:LastWaitIndex -and ($wait.indentationLevel -le ($IndentationLevel + 1))) { | |
if ($IndentationLevel -ne 0) { | |
continue | |
} | |
} | |
$stringBuilder.Append((GetIndentation $IndentationLevel)) > $null | |
$stringBuilder.AppendLine("wait var.$($wait.name)") > $null | |
} | |
$stringBuilder.ToString() | |
} | |
Default { | |
"?????()" | |
} | |
} | |
} | |
# TODO: see if there's is shorter way to work with types (I'm lazy to do it now) | |
function GetTypeCondition([string] $Type, [string] $Value, [switch] $Not) { | |
if ($Not) { | |
$notStr = "not " | |
} | |
$sanitizedType = ResolveTypeName $Type | |
switch ($sanitizedType) { | |
'System.String' { | |
"$notStr((sign($Value) == 0 and (($Value + 1) - $Value) != 1 and len($Value * 0) > 0) || (len($Value) == 0 and $Value != 0))" | |
} | |
'System.Int32' { | |
"$notStr((($Value + 1) - $Value) == 1 and not (sign($Value) == 0 and ($Value) < 0) and ($Value * 0 + 1)[3] == 0 and len(floor($Value)) == len($Value))" | |
} | |
'System.Single' { | |
"$notStr((($Value + 1) - $Value) == 1 and not (sign($Value) == 0 and ($Value) < 0) and ($Value * 0 + 1)[3] == 0 and len(floor($Value)) != len($Value))" | |
} | |
'System.Numerics.Vector3' { | |
"$notStr(len($Value * 0) == 0 and ($Value * 0 + 1)[3] > 0)" | |
} | |
default { | |
if ($Not) { | |
'true' | |
} | |
else { 'false' } | |
} | |
} | |
} | |
function ConvertArgToRadiansStr($Arg) { | |
# return "((($Arg)/360) * $($GlovePie.FloatTau))" | |
return "(($Arg) * $($GlovePie.FloatTau)/360)" | |
} | |
function ConvertArgToDegreesStr($Arg) { | |
# return "((($Arg)/$($GlovePie.FloatTau)) * 360)" | |
return "(($Arg) * 360/$($GlovePie.FloatTau))" | |
} | |
function GetInvokeMemeber( | |
[object] $Expression, | |
[string] $Member, | |
[bool] $IsStatic, | |
[object[]] $ArgumentList | |
) { | |
if ($ArgumentList) { | |
$arg0 = $ArgumentList[0] | |
$arg1 = $ArgumentList[1] | |
$arg2 = $ArgumentList[2] | |
} | |
$sanitizedMember = $member.Trim("'").Trim('"') | |
# TODO: we could check that that argument count is valid | |
$type = ResolveTypeName $Expression.FullName | |
if (-not $IsStatic) { | |
$result = switch ($sanitizedMember) { | |
'Equals' { | |
# The left doesn't support parenthesis in GlovePIE | |
"$Expression == ($arg0)" | |
} | |
'ToString' { | |
"($Expression + '')" | |
} | |
'GetType' { | |
"1 * $(GetTypeCondition -Type 'System.String' -Value $Expression) + 2 * $(GetTypeCondition -Type 'System.Int32' -Value $Expression) + 3 * $(GetTypeCondition -Type 'System.Single' -Value $Expression) + 4 * $(GetTypeCondition -Type 'System.Numerics.Vector3' -Value $Expression)" | |
} | |
'Length' { | |
"abs($Expression)" | |
} | |
'LengthSquared' { | |
# "(($Expression)[1])^2 + (($Expression)[2])^2 + (($Expression)[3])^2)" | |
"(abs($Expression) * abs($Expression))" | |
} | |
default { | |
# Write-Error "$Expression.$Member not supported" | |
# "($Expression.$Member() /* not supported */)" | |
switch ($type) { | |
'System.Numerics.Vector3' { | |
switch ($sanitizedMember) { | |
'Length' { | |
"sqrt((($arg1)[1] - ($arg0)[1])^2 + (($arg1)[2] - ($arg0)[2])^2 + (($arg1)[3] - ($arg0)[3])^2)" | |
} | |
'LengthSquared' { | |
"(($arg1)[1] - ($arg0)[1])^2 + (($arg1)[2] - ($arg0)[2])^2 + (($arg1)[3] - ($arg0)[3])^2)" | |
} | |
} | |
} | |
} | |
} | |
} | |
return $result | |
} | |
$result = switch ($type) { | |
'System.Numerics.Vector3' { | |
switch ($sanitizedMember) { | |
# TODO: Add the other functions | |
'Abs' { | |
"[abs(($arg0)[1]), abs(($arg0)[2]), abs(($arg0)[3])]" | |
} | |
'Add' { | |
"(($arg0) + ($arg1))" | |
} | |
'Cross' { | |
"(($arg0) cross ($arg1))" | |
} | |
'Dot' { | |
"(($arg0) dot ($arg1))" | |
} | |
'Equals' { | |
"(($arg0) == ($arg1))" | |
} | |
'Distance' { | |
"sqrt((($arg1)[1] - ($arg0)[1])^2 + (($arg1)[2] - ($arg0)[2])^2 + (($arg1)[3] - ($arg0)[3])^2)" | |
} | |
'DistanceSquared' { | |
"(($arg1)[1] - ($arg0)[1])^2 + (($arg1)[2] - ($arg0)[2])^2 + (($arg1)[3] - ($arg0)[3])^2)" | |
} | |
'Divide' { | |
if ($arg1 -is [float] -or $arg1 -is [int]) { | |
"(($arg0) / ($arg1))" | |
} | |
else { | |
"[(($arg1)[1] / ($arg0)[1]), (($arg1)[2] / ($arg0)[2]), (($arg1)[3] / ($arg0)[3])]" | |
} | |
} | |
'Multiply' { | |
"[(($arg1)[1] * ($arg0)[1]), (($arg1)[2] * ($arg0)[2]), (($arg1)[3] * ($arg0)[3])]" | |
} | |
'Negate' { | |
"not($arg0)" | |
} | |
'new' { | |
"[($arg0), ($arg1), ($arg2)]" | |
} | |
'Normalize' { | |
"(($arg0) / abs($arg0))" | |
} | |
'Subtract' { | |
"(($arg0) - ($arg1))" | |
} | |
default { | |
Write-Error "[$Type] not supported" | |
"([$Type]::$Member() /* not supported */)" | |
} | |
} | |
} | |
'System.String' { | |
switch ($sanitizedMember) { | |
'IsNullOrEmpty' { | |
"len($arg0) == 0" | |
} | |
'Equals' { | |
"$arg0 == ($arg1)" | |
} | |
default { | |
Write-Error "[$Type] not supported" | |
"([$Type]::$Member() /* not supported */)" | |
} | |
} | |
} | |
'System.Int32' { | |
switch ($sanitizedMember) { | |
'Abs' { | |
"abs($arg0)" | |
} | |
'Clamp' { | |
"min(max(($arg0), ($arg1)), ($arg2))" | |
} | |
'CopySign' { | |
"abs($arg0) * sign($arg1)" | |
} | |
'CreateChecked' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'CreateSaturating' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'CreateTruncating' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'DivRem' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'Equals' { | |
"($arg0) == ($arg1)" | |
} | |
'IsEvenInteger' { | |
"not (odd($arg0))" | |
} | |
'IsNegative' { | |
"($arg0 < 0)" | |
} | |
'IsOddInteger' { | |
"odd($arg0)" | |
} | |
'IsPositive' { | |
"($arg0 > 0)" | |
} | |
'IsPow2' { | |
"(($arg0) != 0 and (($arg0) & (($arg0) - 1)) == 0)" | |
} | |
'LeadingZeroCount' { | |
"(31 - floor(log2(($arg0) | 1)))" | |
} | |
'Parse' { | |
"int(float($arg0))" | |
} | |
default { | |
Write-Error "[$Type] not supported" | |
"([$Type]::$Member() /* not supported */)" | |
} | |
} | |
} | |
'System.Single' { | |
# NOTE: SinPi and similar functions won't return accurate results when transpiled to GlovePIE | |
# due to lack of precision. Keep this in mind for cases when the result is actually infinite. | |
# Maybe we could workaround this by checking the given value, if it is something like | |
# 0.5, 1, 1.5, 2, ... We could just return the exact value. | |
switch ($sanitizedMember) { | |
'Abs' { | |
"abs($arg0)" | |
} | |
'Acos' { | |
ConvertArgToRadiansStr "acos($arg0) rad" | |
} | |
'Acosh' { | |
ConvertArgToRadiansStr "acosh($arg0) rad" | |
} | |
'AcosPi' { | |
"$(ConvertArgToRadiansStr "acos($arg0) rad")/$($GlovePie.FloatPi)" | |
} | |
'Asin' { | |
ConvertArgToRadiansStr "asin($arg0) rad" | |
} | |
'Asinh' { | |
ConvertArgToRadiansStr "asinh($arg0) rad" | |
} | |
'AsinPi' { | |
"$(ConvertArgToRadiansStr "asin($arg0) rad")/$($GlovePie.FloatPi)" | |
} | |
'Atan' { | |
ConvertArgToRadiansStr "atan($arg0) rad" | |
} | |
'Atan2' { | |
ConvertArgToRadiansStr "atan2(($arg0), ($arg1)) rad" | |
} | |
'Atan2Pi' { | |
"$(ConvertArgToRadiansStr "atan2(($arg0), ($arg1)) rad")/$($GlovePie.FloatPi)" | |
} | |
'Atanh' { | |
ConvertArgToRadiansStr "atanh($arg0) rad" | |
} | |
'AtanPi' { | |
"$(ConvertArgToRadiansStr "atan($arg0) rad")/$($GlovePie.FloatPi)" | |
} | |
'BitDecrement' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'BitIncrement' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'Cbrt' { | |
"($arg0)^(1/3)" | |
} | |
'Ceiling' { | |
"ceil($arg0)" | |
} | |
'Clamp' { | |
"min(max(($arg0), ($arg1)), ($arg2))" | |
} | |
'CopySign' { | |
"abs($arg0) * sign($arg1)" | |
} | |
'Cos' { | |
"cos($($arg0) rad)" | |
} | |
'Cosh' { | |
"cosh($($arg0) rad)" | |
} | |
'CosPi' { | |
"(cos($($arg0) * $($GlovePie.FloatPi) rad))" | |
} | |
'CreateChecked' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'CreateSaturating' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'CreateTruncating' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'DegreesToRadians' { | |
ConvertArgToRadiansStr $arg0 | |
} | |
'Equals' { | |
"($arg0) == ($arg1)" | |
} | |
'Exp' { | |
"exp($arg0)" | |
} | |
'Exp10' { | |
"(10^($arg0))" | |
} | |
'Exp10M1' { | |
"(10^($arg0) - 1)" | |
} | |
'Exp2' { | |
"(2^($arg0))" | |
} | |
'Exp2M1' { | |
"(2^($arg0) - 1)" | |
} | |
'ExpM1' { | |
"(exp($arg0) - 1)" | |
} | |
'Floor' { | |
"floor($arg0)" | |
} | |
'FusedMultiplyAdd' { | |
# This is not an accurate implementation of FusedMultiplyAdd | |
"((($arg0) * ($arg1)) + ($arg2)) /* ~FusedMultiplyAdd */" | |
} | |
'Hypot' { | |
"sqrt(($arg0) * ($arg0) + ($arg1) * ($arg1))" | |
} | |
'Ieee754Remainder' { | |
"(($arg0) - ($arg1) * round(($arg0) / ($arg1)))" | |
} | |
'ILogB' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'IsEvenInteger' { | |
"not (odd($arg0))" | |
} | |
'IsFinite' { | |
"(not isInfinite($arg0) and not(isNan($arg0)))" | |
} | |
'IsInfinity' { | |
"isInfinite($arg0)" | |
} | |
'IsInteger' { | |
"(truncate($arg0) == ($arg0))" | |
} | |
'IsNaN' { | |
"isNaN($arg0)" | |
} | |
'IsNegative' { | |
"($arg0 < 0)" | |
} | |
'IsNegativeInfinity' { | |
"($arg0 == $($GlovePie.FloatNegativeInfinity))" | |
} | |
'IsOddInteger' { | |
"odd($arg0)" | |
} | |
'IsPositive' { | |
"($arg0 > 0)" | |
} | |
'IsPositiveInfinity' { | |
"($arg0 == $($GlovePie.FloatPositiveInfinity))" | |
} | |
'IsPow2' { | |
"(($arg0) != 0 and (($arg0) & (($arg0) - 1)) == 0)" | |
} | |
'IsRealNumber' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'IsSubnormal' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'Lerp' { | |
$a = $arg0 | |
$b = $arg1 | |
$t = $arg2 | |
"(($a) + (($b) - ($a))*($t))" | |
} | |
'Log' { | |
if ($ArgumentList.Count -eq 2) { | |
"logN($arg0, $arg1)" | |
} | |
elseif ($ArgumentList.Count -eq 1) { | |
"ln($arg0)" | |
} | |
else { | |
Write-Error "log does not supporte parameter count of $($ArgumentList.Count)" | |
"ln(error)" | |
} | |
} | |
'Log10' { | |
"log10($arg0)" | |
} | |
'Log10P1' { | |
"log10(($arg0) + 1)" | |
} | |
'Log2' { | |
"log2($arg0)" | |
} | |
'Log2P1' { | |
"log2(($arg0) + 1)" | |
} | |
'LogP1' { | |
"lnXP1($arg0)" | |
} | |
#region NOTE: [float]::Max*() and [float]::Min*() aren't exactly equal to max() and min() | |
'Max' { | |
"max(($arg0), ($arg1) /* Max */)" | |
} | |
'MaxMagnitude' { | |
"max(($arg0), ($arg1) /* MaxMagnitude */)" | |
} | |
'MaxMagnitudeNumber' { | |
"max(($arg0), ($arg1) /* MaxMagnitudeNumber */)" | |
} | |
'MaxNumber' { | |
"max(($arg0), ($arg1) /* MaxNumber */)" | |
} | |
'Min' { | |
"min(($arg0), ($arg1))" | |
} | |
'MinMagnitude' { | |
"min(($arg0), ($arg1) /* MinMagnitude */)" | |
} | |
'MinMagnitudeNumber' { | |
"min(($arg0), ($arg1) /* MinMagnitudeNumber */)" | |
} | |
'MinNumber' { | |
"min(($arg0), ($arg1) /* MinNumber */)" | |
} | |
#endregion | |
'Parse' { | |
"float($arg0)" | |
} | |
'Pow' { | |
"($arg0)^($arg1)" | |
} | |
'RadiansToDegrees' { | |
ConvertArgToDegreesStr $arg0 | |
} | |
'ReciprocalEstimate' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'ReciprocalSqrtEstimate' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'ReferenceEquals' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'RootN' { | |
"(($arg0)^($arg1))" | |
} | |
'Round' { | |
"round($arg0)" | |
} | |
'ScaleB' { | |
"(($arg0) * 2^($arg1))" | |
} | |
'Sign' { | |
"sign($arg0)" | |
} | |
'Sin' { | |
"sin($arg0 rad)" | |
} | |
'SinCos' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'SinCosPi' { | |
Write-Error "[$Expression]::$Member not supported" | |
"($Member() /* not supported */)" | |
} | |
'Sinh' { | |
"sinh($arg0 rad)" | |
} | |
'SinPi' { | |
"sin($arg0 rad * $($GlovePie.FloatPi))" | |
} | |
'Sqrt' { | |
"sqrt($arg0)" | |
} | |
'Tan' { | |
"tan($arg0 rad)" | |
} | |
'Tanh' { | |
"tanh($arg0 rad)" | |
} | |
'TanPi' { | |
"tan($arg0 rad * $($GlovePie.FloatPi))" | |
} | |
'Truncate' { | |
"trunc($arg0)" | |
} | |
'TryParse' { | |
Write-Error "[float]::SinCosPi not supported" | |
"([$Type]::$Member() /* not supported */)" | |
} | |
default { | |
Write-Error "[$Type] not supported" | |
"([$Type]::$Member() /* not supported */)" | |
} | |
} | |
} | |
default { | |
Write-Error "Type [$Expression] not supported" | |
"($Expression::$Member() /* not supported */)" | |
} | |
} | |
return $result | |
} | |
# Low level implementation of 'elseif', actually we don't need this because | |
# GlovePIE supports elseif natively, but we'll keep this for future reference. | |
# function Invoke-IfStatementTranspilation-Old { | |
# [OutputType([string])] | |
# param ( | |
# [object[]] $IfSentenceTuple, | |
# [AllowNull()] | |
# [object] $ElseClause, | |
# [string] $IndentationLevel | |
# ) | |
# if ($IfSentenceTuple.Count -eq 0) { | |
# return | |
# } | |
# $sentenceList = [System.Collections.Generic.Queue[object]]::new($IfSentenceTuple) | |
# $sentenceTuple = $sentenceList.Dequeue() | |
# $pipelineAst = $sentenceTuple.Item1 | |
# $statementBlock = $sentenceTuple.Item2 | |
# $stringBuilder = [System.Text.StringBuilder]::new() | |
# $stringBuilder.Append($IndentationLevel) > $null | |
# $stringBuilder.Append('if (') > $null | |
# $conditionStr = Invoke-AstTranspilation $pipelineAst -IndentationLevel "$IndentationLevel" | |
# $stringBuilder.Append($conditionStr) > $null | |
# $stringBuilder.AppendLine(') {') > $null | |
# $blockStr = Invoke-AstTranspilation $statementBlock -IndentationLevel "$IndentationLevel " | |
# $stringBuilder.AppendLine($blockStr) > $null | |
# $stringBuilder.Append($IndentationLevel) > $null | |
# $stringBuilder.Append('} ') > $null | |
# if ($sentenceList.Count -eq 0 -and $ElseClause) { | |
# $stringBuilder.AppendLine('else {') > $null | |
# $elseClauseStr = Invoke-AstTranspilation $ElseClause -IndentationLevel "$IndentationLevel " | |
# $stringBuilder.AppendLine("$elseClauseStr") > $null | |
# $stringBuilder.Append("$IndentationLevel") > $null | |
# $stringBuilder.Append('}') > $null | |
# return $stringBuilder.ToString() | |
# } | |
# if ($sentenceList.Count -gt 0) { | |
# $stringBuilder.AppendLine('else {') > $null | |
# $elseClauseStr = Invoke-IfStatementTranspilation -IfSentenceTuple ($sentenceList.ToArray()) -ElseClause $ElseClause -IndentationLevel "$IndentationLevel " | |
# $stringBuilder.AppendLine($elseClauseStr) > $null | |
# $stringBuilder.Append($IndentationLevel) > $null | |
# $stringBuilder.Append('}') > $null | |
# } | |
# return $stringBuilder.ToString() | |
# } | |
# NOTES: I prefer using curly braces, but at least on GlovePIE 0.43 it | |
# shows error for specific cases where endif does not. | |
function Invoke-IfStatementTranspilation { | |
param ( | |
[object[]] $IfSentenceTuple, | |
[AllowNull()] | |
[object] $ElseClause, | |
[uint32] $IndentationLevel | |
) | |
if ($IfSentenceTuple.Count -eq 0) { | |
return | |
} | |
$sentenceList = [System.Collections.Generic.Queue[object]]::new($IfSentenceTuple) | |
$sentenceTuple = $sentenceList.Dequeue() | |
$pipelineAst = $sentenceTuple.Item1 | |
$statementBlock = $sentenceTuple.Item2 | |
$script:LastWaitIndex = $script:WaitTimes.Count - 1 | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
$stringBuilder.Append((GetIndentation $IndentationLevel)) > $null | |
$stringBuilder.Append('if ') > $null | |
$conditionStr = Invoke-AstTranspilation $pipelineAst #-IndentationLevel $IndentationLevel | |
$stringBuilder.Append($conditionStr) > $null | |
$stringBuilder.AppendLine(' then') > $null | |
$blockStr = Invoke-AstTranspilation $statementBlock -IndentationLevel ($IndentationLevel + 1) | |
$stringBuilder.Append($blockStr) > $null | |
$stringBuilder.Append((GetIndentation $IndentationLevel)) > $null | |
while ($sentenceList.Count -gt 0) { | |
$sentenceTuple = $sentenceList.Dequeue() | |
$pipelineAst = $sentenceTuple.Item1 | |
$statementBlock = $sentenceTuple.Item2 | |
# $script:LastWaitIndex = $script:WaitTimes.Count - 1 | |
$conditionStr = Invoke-AstTranspilation $pipelineAst #-IndentationLevel $IndentationLevel | |
$stringBuilder.AppendLine("else if $conditionStr then") > $null | |
$blockStr = Invoke-AstTranspilation $statementBlock -IndentationLevel ($IndentationLevel + 1) | |
$stringBuilder.Append($blockStr) > $null | |
$stringBuilder.Append((GetIndentation $IndentationLevel)) > $null | |
} | |
if ($ElseClause) { | |
# $script:LastWaitIndex = $script:WaitTimes.Count - 1 | |
$stringBuilder.AppendLine('else') > $null | |
$elseClauseStr = Invoke-AstTranspilation $ElseClause -IndentationLevel ($IndentationLevel + 1) | |
$stringBuilder.Append("$elseClauseStr") > $null | |
$stringBuilder.Append((GetIndentation $IndentationLevel)) > $null | |
} | |
$stringBuilder.Append('endif') > $null | |
$stringBuilder.AppendLine() > $null | |
for ($i = 0; $i -lt $script:WaitTimes.Count; $i++) { | |
$wait = $script:WaitTimes[$i] | |
if ($i -le $script:LastWaitIndex -and ($wait.indentationLevel -le ($IndentationLevel + 1))) { | |
# NOTE: Adding this makes it works perfectly (At least for now, but I have to understand this better) | |
if ($IndentationLevel -ne 0) { | |
continue | |
} | |
} | |
$stringBuilder.Append((GetIndentation $IndentationLevel)) > $null | |
$stringBuilder.AppendLine("wait var.$($wait.name)") > $null | |
} | |
return $stringBuilder.ToString() | |
} | |
function Invoke-AstTranspilation { | |
[OutputType([string])] | |
param ( | |
[System.Management.Automation.Language.ast] $Ast, | |
# TODO: Instead of indentation we could use Depth as int, that way we'll get: | |
# - Easy to check the level of nesting | |
# - To be able to change the indentation size with a variable | |
[uint32] $IndentationLevel = 0 | |
) | |
switch ($Ast.GetType().Name) { | |
'ScriptBlockExpressionAst' { | |
return $Ast.ScriptBlock | |
} | |
'ScriptBlockAst' { | |
return Invoke-AstTranspilation $Ast.EndBlock -IndentationLevel $IndentationLevel | |
} | |
'StatementBlockAst' { | |
if ($Ast.Statements.Count -eq 0) { | |
return | |
} | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
foreach ($statement in $Ast.Statements) { | |
$stringBuilder.AppendLine((Invoke-AstTranspilation $statement -IndentationLevel $IndentationLevel)) > $null | |
} | |
return $stringBuilder.ToString() | |
} | |
'NamedBlockAst' { | |
if ($Ast.Statements.Count -eq 0) { | |
return | |
} | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
foreach ($statement in $Ast.Statements) { | |
$stringBuilder.AppendLine((Invoke-AstTranspilation $statement -IndentationLevel $IndentationLevel)) > $null | |
} | |
return $stringBuilder.ToString() | |
} | |
'AssignmentStatementAst' { | |
$left = Invoke-AstTranspilation -Ast $Ast.Left -IndentationLevel $IndentationLevel | |
$operator = GetOperator $Ast.Operator | |
if ($Ast.Right.GetType().FullName -eq 'System.Management.Automation.Language.PipelineAst') { | |
$right = (Invoke-AstTranspilation $Ast.Right.PipelineElements[0] -IndentationLevel $IndentationLevel).Trim() | |
} | |
else { | |
$right = Invoke-AstTranspilation $Ast.Right.Expression -IndentationLevel $IndentationLevel | |
} | |
$result = switch ($operator) { | |
'@plus-equals' { "$left = $left + $right" } | |
'@minus-equals' { "$left = $left - $right" } | |
'@multiply-equals' { "$left = $left * $right" } | |
'@divide-equals' { "$left = $left / $right" } | |
default { | |
"$left = $right" | |
} | |
} | |
return (GetIndentation $IndentationLevel) + $result | |
} | |
'ConvertExpressionAst' { | |
# I don't know what to do with the type, # so we'll just return the expression | |
# But if in future we need to do something with the type, I guess we should | |
# check for ConvertExpressionAst in AssignmentStatementAst | |
return @( | |
"/* $($Ast.Type) */" | |
Invoke-AstTranspilation -Ast $Ast.Child -IndentationLevel $IndentationLevel | |
) -join ' ' | |
} | |
'IfStatementAst' { | |
return Invoke-IfStatementTranspilation -IfSentenceTuple $Ast.Clauses -ElseClause $Ast.ElseClause -IndentationLevel $IndentationLevel | |
} | |
'SwitchStatementAst' { | |
# Flags (SwitchFlags) -CaseSensitive with no flags this must be converted to if, with flag with elseif | |
# Condition PipelineBaseAst | |
# Clauses (StatementBlockAst[]) | |
# Deafult (StatementBlockAst) | |
} | |
'PipelineAst' { | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
foreach ($element in $Ast.PipelineElements) { | |
$stringBuilder.Append((Invoke-AstTranspilation $element -IndentationLevel $IndentationLevel)) > $null | |
} | |
return (GetIndentation $IndentationLevel) + $stringBuilder.ToString() | |
} | |
'CommandExpressionAst' { | |
return Invoke-AstTranspilation $Ast.Expression -IndentationLevel $IndentationLevel | |
} | |
'UnaryExpressionAst' { | |
$operator = GetOperator $Ast.TokenKind | |
$operand = Invoke-AstTranspilation -Ast $Ast.Child -IndentationLevel $IndentationLevel | |
$result = switch ($operator) { | |
'@plus-plus' { "$operand = $operand + 1" } | |
'@postfix-plus-plus' { "$operand = $operand + 1" } | |
'@minus-minus' { "$operand = $operand - 1" } | |
'@postfix-minus-minus' { "$operand = $operand - 1" } | |
'@bnot' { "-($operand + 1)" } | |
'+' { $operand } | |
default { | |
"($operator $operand)" | |
} | |
} | |
return (GetIndentation $IndentationLevel) + $result | |
} | |
'BinaryExpressionAst' { | |
$left = Invoke-AstTranspilation $Ast.Left -IndentationLevel $IndentationLevel | |
$right = Invoke-AstTranspilation $Ast.Right -IndentationLevel $IndentationLevel | |
$operator = GetOperator $Ast.Operator | |
if ($operator -eq '==' -or $operator -eq '~=') { | |
if ($left -is [System.Management.Automation.Language.ITypeName] -and $right -is [System.Management.Automation.Language.ITypeName]) { | |
$leftType = ResolveTypeName $left.FullName | |
$rightType = ResolveTypeName $right.FullName | |
$leftCode = GetTypeCode $leftType | |
$rightCode = GetTypeCode $rightType | |
return "$leftCode == $rightCode" | |
} | |
elseif ($left -is [System.Management.Automation.Language.ITypeName]) { | |
$type = ResolveTypeName $left.FullName | |
$code = GetTypeCode $type | |
return "$code == $right" | |
} | |
elseif ($right -is [System.Management.Automation.Language.ITypeName]) { | |
$type = ResolveTypeName $right.FullName | |
$code = GetTypeCode $type | |
return "$left == $code" | |
} | |
} | |
$result = switch ($operator) { | |
'@bxor' { | |
"($left | $right) & -(($left & $right) + 1)" | |
} | |
'@in' { | |
if ($right -is [array]) { | |
# "InSet(($left),$($right -join ','))" | |
"($left) is in ($($right -join ','))" | |
} | |
elseif ($right -is [hashtable]) { | |
"($($right.Start)) <= ($left) and ($left) <= ($($right.End))" | |
} | |
else { | |
"in // Could not transpile: $right" | |
} | |
} | |
'@not-in' { | |
if ($right -is [array]) { | |
# "(not InSet(($left),$($right -join ',')))" | |
"not (($left) is in ($($right -join ',')))" | |
} | |
elseif ($right -is [hashtable]) { | |
"($left) < ($($right.Start)) or ($($right.End)) < ($left)" | |
} | |
else { | |
"in // Could not transpile: $right" | |
} | |
} | |
'@contains' { | |
# "InSet(($right),$($left -join ','))" | |
"($left) is in ($($right -join ','))" | |
} | |
'@not-contains' { | |
# "(not InSet(($right),$($left -join ',')))" | |
"not (($left) is in ($($right -join ',')))" | |
} | |
'@dot-dot' { | |
@{ | |
Start = $left | |
End = $right | |
} | |
} | |
'@is' { | |
GetTypeCondition -Type $right.Name -Value $left | |
} | |
'@is-not' { | |
GetTypeCondition -Type $right.Name -Value $left -Not | |
} | |
default { | |
"($left) $operator ($right)" | |
} | |
} | |
return $result | |
} | |
'VariableExpressionAst' { | |
$name = $Ast.VariablePath.UserPath | |
return GetVariable $name | |
} | |
'ConstantExpressionAst' { | |
# The double toString() of '125.0' is '125', which it's a problem | |
# since we want to explictly indicate that is a float. | |
if ($Ast.Value -is [double]) { | |
$doubleStr = $Ast.Value.ToString([System.Globalization.CultureInfo]::InvariantCulture) | |
if (-not $doubleStr.Contains('.')) { | |
return "$($Ast.Value).0" | |
} | |
} | |
return $Ast.Value | |
} | |
'StringConstantExpressionAst' { | |
switch ($Ast.StringConstantType) { | |
'BareWord' { | |
return $Ast.Value | |
} | |
'DoubleQuoted' { | |
return '"' + $Ast.Value + '"' | |
} | |
'SingleQuoted' { | |
return "'" + $Ast.Value + "'" | |
} | |
'DoubleQuotedHereString' { | |
# TODO: analyze this case | |
return $Ast.Value | |
} | |
'SingleQuotedHereString' { | |
# TODO: analyze this case | |
return $Ast.Value | |
} | |
} | |
} | |
'ExpandableStringExpressionAst' { | |
$start = $Ast.Extent.StartLineNumber - 1 | |
$quote = switch ($Ast.StringConstantType) { | |
'BareWord' { '' } | |
'DoubleQuoted' { '"' } | |
'SingleQuoted' { "'" } | |
'DoubleQuotedHereString' { | |
# TODO: analyze this case | |
'' | |
} | |
'SingleQuotedHereString' { | |
# TODO: analyze this case | |
'' | |
} | |
} | |
$stringBuilder = [System.Text.StringBuilder]::new($Ast.Value) | |
for ($i = $Ast.NestedExpressions.Count - 1; $i -gt -1; $i--) { | |
$expression = $Ast.NestedExpressions[$i] | |
switch ($expression.GetType().Name) { | |
'SubExpressionAst' { | |
# In some cases like 'Write-Host "not 15: $(-bnot 15)"' there is extra space on the left, I don't know why | |
$expressionStr = (Invoke-AstTranspilation -Ast $expression.SubExpression -IndentationLevel $IndentationLevel).Trim() | |
$rawExpression = $expression.ToString() | |
$start = $expression.SubExpression.Extent.StartColumnNumber - $Ast.Extent.StartColumnNumber - 3 | |
$length = $rawExpression.Length | |
$stringBuilder.Replace("$rawExpression", "$quote + ($($expressionStr)) + $quote", $start, $length) > $null | |
} | |
'VariableExpressionAst' { | |
$variable = Invoke-AstTranspilation -Ast $expression -IndentationLevel $IndentationLevel | |
$start = $expression.Extent.StartColumnNumber - $Ast.Extent.StartColumnNumber - 1 | |
$length = $expression.VariablePath.UserPath.Length + 1 | |
$stringBuilder.Replace("`$$($expression.VariablePath.UserPath)", "$quote + $variable + $quote", $start, $length) > $null | |
} | |
} | |
} | |
$stringBuilder.Insert(0, $quote) > $null | |
$stringBuilder.Append($quote) > $null | |
return $stringBuilder.ToString() | |
} | |
'ArrayExpressionAst' { | |
return $Ast.SubExpression.Statements[0].PipelineElements[0].Expression.Elements | ForEach-Object { | |
Invoke-AstTranspilation -Ast $_ -IndentationLevel $IndentationLevel | |
} | |
} | |
# 'ArrayLiteralAst' { | |
# return $Ast.Elements | ForEach-Object { | |
# Invoke-AstTranspilation -Ast $_ -IndentationLevel $IndentationLevel | |
# } | |
# } | |
'SubExpressionAst' { | |
return Invoke-AstTranspilation -Ast $Ast.SubExpression -IndentationLevel $IndentationLevel | |
} | |
'ParenExpressionAst' { | |
return '(' + (Invoke-AstTranspilation -Ast $Ast.Pipeline) + ')' | |
} | |
'MemberExpressionAst' { | |
$expression = Invoke-AstTranspilation -Ast $Ast.Expression -IndentationLevel $IndentationLevel | |
$member = Invoke-AstTranspilation -Ast $Ast.Member -IndentationLevel $IndentationLevel | |
$sanitizedMember = $member.Trim("'").Trim('"') | |
$type = ResolveTypeName $expression.FullName | |
$result = if ($Ast.Static) { | |
switch ($type) { | |
'System.Numerics.Vector3' { | |
switch ($sanitizedMember) { | |
'One' { | |
'[1, 1, 1]' | |
} | |
'Zero' { | |
"[0, 0, 0]" | |
} | |
'UnitX' { | |
"[1, 0, 0]" | |
} | |
'UnitY' { | |
"[0, 1, 0]" | |
} | |
'UnitZ' { | |
"[0, 0, 1]" | |
} | |
} | |
} | |
'System.String' { | |
switch ($sanitizedMember) { | |
'Empty' { | |
"''" | |
} | |
} | |
} | |
'System.Int32' { | |
switch ($sanitizedMember) { | |
'MaxValue' { $GlovePie.IntMaxValue } | |
'MinValue' { $GlovePie.IntMinValue } | |
default { | |
Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
'null' | |
} | |
} | |
} | |
'System.Single' { | |
switch ($sanitizedMember) { | |
'MaxValue' { $GlovePie.FloatMaxValue } | |
'MinValue' { $GlovePie.FloatMinValue } | |
'Epsilon' { $GlovePie.FloatEpsilon } | |
'NaN' { $GlovePie.FloatNaN } | |
'PositiveInfinity' { $GlovePie.FloatPositiveInfinity } | |
'NegativeInfinity' { $GlovePie.FloatNegativeInfinity } | |
'E' { $GlovePie.FloatE } | |
'Pi' { $GlovePie.FloatPi } | |
'Tau' { $GlovePie.FloatTau } | |
default { | |
Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
'null' | |
} | |
} | |
} | |
} | |
} | |
else { | |
switch ($type) { | |
'System.String' { | |
switch ($member) { | |
'Length' { | |
"len($expression)" | |
} | |
default { | |
Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
'null' | |
} | |
} | |
} | |
'System.Numerics.Vector3' { | |
switch ($member) { | |
'X' { | |
"($expression)[1]" | |
} | |
'Y' { | |
"($expression)[2]" | |
} | |
'Z' { | |
"($expression)[3]" | |
} | |
default { | |
Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
'null' | |
} | |
} | |
} | |
default { | |
# NOTE: since it's not possible to know the type of a variable (at least yet) we allow to call | |
# any property from any kind of object. | |
# TODO: X,Y, and Z are also setters, so we should implement that as well | |
switch ($member) { | |
'Length' { | |
"len($expression)" | |
} | |
'X' { | |
"($expression)[1]" | |
} | |
'Y' { | |
"($expression)[2]" | |
} | |
'Z' { | |
"($expression)[3]" | |
} | |
default { | |
Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
'null' | |
} | |
} | |
} | |
} | |
# switch ($expression.GetType().Name) { | |
# 'string' { | |
# switch ($member) { | |
# 'Length' { | |
# "len($expression)" | |
# } | |
# default { | |
# Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
# 'null' | |
# } | |
# } | |
# } | |
# default { | |
# Write-Error "Unsupported member '$sanitizedMember' for type '$type'" | |
# 'null' | |
# } | |
# } | |
} | |
return $result | |
} | |
'TypeExpressionAst' { | |
return $Ast.TypeName | |
} | |
'InvokeMemberExpressionAst' { | |
$expression = Invoke-AstTranspilation -Ast $Ast.Expression -IndentationLevel $IndentationLevel | |
$member = Invoke-AstTranspilation -Ast $Ast.Member -IndentationLevel $IndentationLevel | |
$sanitizedMember = $member.Trim("'").Trim('"') | |
$arguments = $Ast.Arguments | Where-Object { $_ } | ForEach-Object { | |
Invoke-AstTranspilation -Ast $_ -IndentationLevel $IndentationLevel | |
} | |
return GetInvokeMemeber -Expression $expression -IsStatic $Ast.Static -Member $sanitizedMember -ArgumentList $arguments | |
} | |
'CommandAst' { | |
$result = switch ($Ast.InvocationOperator) { | |
'Unknown' { GetCommand $Ast.CommandElements -IndentationLevel $IndentationLevel } | |
'Ampersand' { | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
foreach ($element in $Ast.CommandElements) { | |
$expressionStr = Invoke-AstTranspilation -Ast $element -IndentationLevel $IndentationLevel | |
if (-not $expressionStr.StartsWith("'") -and -not $expressionStr.StartsWith('"')) { | |
$expressionStr = "'$expressionStr'" | |
} | |
$stringBuilder.Append($expressionStr) > $null | |
} | |
"Execute $($stringBuilder.ToString())" | |
} | |
default { | |
Write-Warning "Unsupported operator: '$($Ast.InvocationOperator)'" | |
} | |
} | |
# NOTE: We don't want to add the indentation to better control it from the 'pie' scriptblock argument | |
if ($Ast.CommandElements -and $Ast.CommandElements[0].Value -in @('pie', 'executeOnce')) { | |
return $result | |
} | |
return $result | |
} | |
'CommandParameterAst' { | |
# TODO: Implement | |
# ParameterName | |
# Argument | |
} | |
'FunctionDefinitionAst' { | |
# TODO: think of future implementation | |
Write-Warning "Cannot process function '$($Ast.Name)'" | |
} | |
'ForEachStatementAst' { | |
$stringBuilder = [System.Text.StringBuilder]::new() | |
$variableName = $Ast.Variable.VariablePath.UserPath | |
$enumerable = Invoke-AstTranspilation -Ast $Ast.Condition.PipelineElements[0].Expression -IndentationLevel $IndentationLevel | |
if ($enumerable -is [array]) { | |
$enumerator = $enumerable | |
} | |
elseif ($enumerable -is [hashtable]) { | |
if ($enumerable.Start.ToString().StartsWith("'")) { | |
$quote = "'" | |
} | |
elseif ($enumerable.Start.ToString().StartsWith('"')) { | |
$quote = '"' | |
} | |
$start = $enumerable.Start.ToString().Trim("'").Trim('"') | |
$end = $enumerable.End.ToString().Trim("'").Trim('"') | |
$enumerator = $start..$end | |
} | |
else { | |
Write-Error "Unsupported enumerable type in foreach-statement: $_" | |
return '' | |
} | |
foreach ($element in $enumerator) { | |
$sanitizedElement = if ($element.GetType().FullName -eq 'System.Char') { | |
"'$element'" | |
} | |
else { | |
$element | |
} | |
$script:InlinedVariableMap.$variableName = $sanitizedElement | |
$body = Invoke-AstTranspilation -Ast $Ast.Body -IndentationLevel $IndentationLevel | |
$stringBuilder.Append($body) > $null | |
} | |
$script:InlinedVariableMap.Remove($variableName) | |
return $stringBuilder.ToString() | |
} | |
'IndexExpressionAst' { | |
$target = Invoke-AstTranspilation -Ast $Ast.Target -IndentationLevel $IndentationLevel | |
$index = Invoke-AstTranspilation -Ast $Ast.Index -IndentationLevel $IndentationLevel | |
$sanitizedIndex = switch ($index.GetType().FullName) { | |
'System.Int32' { | |
$index + 1 | |
} | |
'System.Collections.Hashtable' { | |
"$($index.Start + 1)..$($index.End + 1)" | |
} | |
default { | |
Write-Error "Index is not supported for type '$_'" | |
} | |
} | |
return "$target[$sanitizedIndex]" | |
} | |
'ExitStatementAst' { | |
# 'ExitProgram' does not exist immediately, we need to wait a little. | |
# Since we've implemented sync waits we can do this very easily. | |
if ($IndentationLevel -eq 0) { | |
return @( | |
(GetIndentation $IndentationLevel) + "ExitProgram" | |
(GetIndentation $IndentationLevel) + "wait 1" | |
) -join "`n" | |
} | |
$wait = @{ | |
name = "wait_$(NewUuid)" | |
time = 1 | |
indentationLevel = $IndentationLevel | |
} | |
$script:WaitTimes += $wait | |
return @( | |
(GetIndentation $IndentationLevel) + "var.$($wait.name) = $($wait.time)" | |
(GetIndentation $IndentationLevel) + 'ExitProgram' | |
(GetIndentation $IndentationLevel) + "wait var.$($wait.name)" | |
(GetIndentation $IndentationLevel) + "var.$($wait.name) = 0" | |
) -join "`n" | |
} | |
'ThrowStatementAst' { | |
if ($Ast.Pipeline) { | |
$message = Invoke-AstTranspilation -Ast $Ast.Pipeline | |
} | |
else { | |
$message = "''" | |
} | |
if ($IndentationLevel -eq 0) { | |
return @( | |
(GetIndentation $IndentationLevel) + "DebugPrint '❌ ERROR at line $lineNumberVariableUuid`: ' + $message" | |
(GetIndentation $IndentationLevel) + "ExitProgram" | |
(GetIndentation $IndentationLevel) + "wait 1" | |
) -join "`n" | |
} | |
$wait = @{ | |
name = "wait_$(NewUuid)" | |
time = 1 | |
indentationLevel = $IndentationLevel | |
} | |
$script:WaitTimes += $wait | |
return @( | |
(GetIndentation $IndentationLevel) + "DebugPrint '❌ ERROR at line $lineNumberVariableUuid`: ' + $message" | |
(GetIndentation $IndentationLevel) + "var.$($wait.name) = $($wait.time)" | |
(GetIndentation $IndentationLevel) + 'ExitProgram' | |
(GetIndentation $IndentationLevel) + "wait var.$($wait.name)" | |
(GetIndentation $IndentationLevel) + "var.$($wait.name) = 0" | |
) -join "`n" | |
} | |
default { | |
Write-Warning "Unknown type: $($_.GetType().Name) from $_" | |
} | |
} | |
} | |
$scriptPath = "$PSScriptRoot/Test.ps1" | |
$rawContent = Get-Content -LiteralPath "$scriptPath" -Raw | |
$ast = [scriptblock]::Create($rawContent).Ast | |
$result = Invoke-AstTranspilation $ast | |
if (-not $result) { | |
$result = "// Could not transpile`n" | |
} | |
else { | |
$lineNumber = 0 | |
$result = ($result -split "`n" | ForEach-Object { | |
$lineNumber++ | |
$_.Replace($lineNumberVariableUuid, $lineNumber) | |
} | |
) -join "`n" | |
} | |
New-Item -Path "$PSScriptRoot/Test.ps1.PIE" -Value $result -Force | |
# Stop-Process -Name GlovePIE -Force -ErrorAction SilentlyContinue | |
# & "C:\Users\empresa\Documents\Elian\Proyectos\GlovePIE-master\GlovePIE-0.43\GlovePIE.exe" "$PSScriptRoot/Test.ps1.PIE" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$watcher = New-Object System.IO.FileSystemWatcher | |
$watcher.Path = "." | |
$watcher.Filter = "Test.ps1" | |
$watcher.NotifyFilter = [System.IO.NotifyFilters]'LastWrite' | |
$watcher.EnableRaisingEvents = $true | |
$jobUuid = "0d7eb6dc-c017-480e-a75d-05f75ed8c179" | |
Unregister-Event -SourceIdentifier $jobUuid -ErrorAction SilentlyContinue | |
Remove-Job -Name $jobUuid -Force -ErrorAction SilentlyContinue | |
Register-ObjectEvent -InputObject $watcher -EventName Changed -SourceIdentifier $jobUuid -Action { | |
& .\Ast.ps1 | |
} | Out-Null | |
Write-Host "Test.ps1.PIE will be updated everytime the Test.ps1 file is saved" -ForegroundColor Cyan |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
pieln { param ($indentionLevel) | |
@( | |
'/*' | |
' This is a multiline' | |
' comment from Powershell' | |
'*/' | |
) -join "`n" | |
} | |
executeOnce { | |
foreach ($x in 1..15) { | |
Write-Host | |
} | |
pieln { | |
'// This is a comment' | |
} | |
foreach ($n in 1..10) { | |
Write-Host "n: $n, lineNumber: $GlovePieLineNumber" | |
} | |
$waitTime = pie { | |
# It seems that in RandomRange(a, b), b is exclusive | |
'RandomRange(1, 5)' | |
} | |
Write-Host "waitTime: $waitTime" | |
if ($waitTime -ceq 4) { | |
throw "waitTime can't be 4" | |
} | |
$random = pie { | |
'RandomRange(0, 100)' | |
} | |
Write-Host "random: $random" | |
if ($random % 2 -ceq 0) { | |
Start-Sleep 0 | |
foreach ($n in 1000..1010) { | |
Write-Host "n: $n" | |
} | |
} | |
foreach ($n in 11..20) { | |
Write-Host "n: $n" | |
} | |
Start-Sleep $waitTime | |
foreach ($n in 21..30) { | |
Write-Host "n: $n" | |
} | |
exit | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
This is a multiline | |
comment from Powershell | |
*/ | |
if not var.skip_99ed01b6be6f45d19cf5a92c7fbc6900 then | |
var.skip_99ed01b6be6f45d19cf5a92c7fbc6900 = true | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
DebugPrint | |
// This is a comment | |
DebugPrint "n: " + 1 + ", lineNumber: " + 24 + "" | |
DebugPrint "n: " + 2 + ", lineNumber: " + 25 + "" | |
DebugPrint "n: " + 3 + ", lineNumber: " + 26 + "" | |
DebugPrint "n: " + 4 + ", lineNumber: " + 27 + "" | |
DebugPrint "n: " + 5 + ", lineNumber: " + 28 + "" | |
DebugPrint "n: " + 6 + ", lineNumber: " + 29 + "" | |
DebugPrint "n: " + 7 + ", lineNumber: " + 30 + "" | |
DebugPrint "n: " + 8 + ", lineNumber: " + 31 + "" | |
DebugPrint "n: " + 9 + ", lineNumber: " + 32 + "" | |
DebugPrint "n: " + 10 + ", lineNumber: " + 33 + "" | |
var.waitTime = RandomRange(1, 5) | |
DebugPrint "waitTime: " + var.waitTime + "" | |
if (var.waitTime) == (4) then | |
DebugPrint '❌ ERROR at line 38: ' + "waitTime can't be 4" | |
var.wait_44d2b5bb92b946d6b2c10041f86c470b = 1 | |
ExitProgram | |
wait var.wait_44d2b5bb92b946d6b2c10041f86c470b | |
var.wait_44d2b5bb92b946d6b2c10041f86c470b = 0 | |
endif | |
wait var.wait_44d2b5bb92b946d6b2c10041f86c470b | |
var.random = RandomRange(0, 100) | |
DebugPrint "random: " + var.random + "" | |
if ((var.random) % (2)) == (0) then | |
var.wait_56d8b015a06a4311a3432167074592e6 = 0 | |
wait var.wait_56d8b015a06a4311a3432167074592e6 | |
var.wait_56d8b015a06a4311a3432167074592e6 = 0 | |
DebugPrint "n: " + 1000 + "" | |
DebugPrint "n: " + 1001 + "" | |
DebugPrint "n: " + 1002 + "" | |
DebugPrint "n: " + 1003 + "" | |
DebugPrint "n: " + 1004 + "" | |
DebugPrint "n: " + 1005 + "" | |
DebugPrint "n: " + 1006 + "" | |
DebugPrint "n: " + 1007 + "" | |
DebugPrint "n: " + 1008 + "" | |
DebugPrint "n: " + 1009 + "" | |
DebugPrint "n: " + 1010 + "" | |
endif | |
wait var.wait_56d8b015a06a4311a3432167074592e6 | |
DebugPrint "n: " + 11 + "" | |
DebugPrint "n: " + 12 + "" | |
DebugPrint "n: " + 13 + "" | |
DebugPrint "n: " + 14 + "" | |
DebugPrint "n: " + 15 + "" | |
DebugPrint "n: " + 16 + "" | |
DebugPrint "n: " + 17 + "" | |
DebugPrint "n: " + 18 + "" | |
DebugPrint "n: " + 19 + "" | |
DebugPrint "n: " + 20 + "" | |
var.wait_d2cc449fc85f49d48b736b0aef264314 = var.waitTime | |
wait var.wait_d2cc449fc85f49d48b736b0aef264314 | |
var.wait_d2cc449fc85f49d48b736b0aef264314 = 0 | |
DebugPrint "n: " + 21 + "" | |
DebugPrint "n: " + 22 + "" | |
DebugPrint "n: " + 23 + "" | |
DebugPrint "n: " + 24 + "" | |
DebugPrint "n: " + 25 + "" | |
DebugPrint "n: " + 26 + "" | |
DebugPrint "n: " + 27 + "" | |
DebugPrint "n: " + 28 + "" | |
DebugPrint "n: " + 29 + "" | |
DebugPrint "n: " + 30 + "" | |
var.wait_fe031c8204cb4cfeb45c6bcc0d011f87 = 1 | |
ExitProgram | |
wait var.wait_fe031c8204cb4cfeb45c6bcc0d011f87 | |
var.wait_fe031c8204cb4cfeb45c6bcc0d011f87 = 0 | |
endif | |
wait var.wait_44d2b5bb92b946d6b2c10041f86c470b | |
wait var.wait_56d8b015a06a4311a3432167074592e6 | |
wait var.wait_d2cc449fc85f49d48b736b0aef264314 | |
wait var.wait_fe031c8204cb4cfeb45c6bcc0d011f87 | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment