ConvertFrom-Json for PowerShell v2

必要になったので書きました。[^1]

[^1]: Windows7 と Windows Server 2008 早く消え去って欲しい

[Crockford 先生（？）のパーサ](https://github.com/douglascrockford/JSON-js/blob/master/json_parse.js) を PowerShell で書き直しただけです。reviver と一部のエラーチェックはサボっています。

```powershell
Function ConvertFrom-Json {
    param(
        [Parameter(ValueFromPipeline=$True)]
        [string[]]$InputObject
    )
    begin {
        $buf = New-Object System.Text.StringBuilder
    }
    process {
        foreach($b in $InputObject){
            $buf.Append($b) > $null
        }
    }
    end {
        $ErrorActionPreference = "Stop"
        $script:json = $buf.ToString()
        $script:at = 0
        $script:ch = " "
        $script:escape = New-Object psobject |
            Add-Member -PassThru NoteProperty '"' '"' |
            Add-Member -PassThru NoteProperty "\" "\" |
            Add-Member -PassThru NoteProperty "/" "/" |
            Add-Member -PassThru NoteProperty "b" "`b" |
            Add-Member -PassThru NoteProperty "f" "`f" |
            Add-Member -PassThru NoteProperty "n" "`n" |
            Add-Member -PassThru NoteProperty "r" "`r" |
            Add-Member -PassThru NoteProperty "t" "`t"
        $script:err = {
            param($m)
            throw "Syntax Error: $m, $at, $json"
        }
        $script:next = {
            param($c)
            if ($c -and $c -ne $ch) { & $err "Expected '$c' instead of '$ch'" }
            $script:ch = $json[$at]
            $script:at += 1
        }
        $script:number = {
            $str = ""
            $e = ""
            if ($ch -eq "-") {
                $str = "-"
                & $next "-"
            }
            while($ch -ge "0" -and $ch -le "9") {
                $str += $ch
                & $next
            }
            if ($ch -eq "."){
                $str += "."
                & $next
                while($ch -ge "0" -and $ch -le "9") {
                    $str += $ch
                    & $next
                }
            }
            if ($ch -eq "e") {
                & $next "e" # skip e
                if ($ch -eq "+" -or $ch -eq "-") {
                    $e += $ch
                    & $next
                }
                while($ch -ge "0" -and $ch -le "9") {
                    $e += $ch
                    & $next
                }
            }
            $number = [double]$str
            if ($e) { $number *= [Math]::Pow(10, [int]$e) }
            if ($number) {
                return $number
            } else {
                & $err "Bad Number"
            }
        }
        $script:string = {
            $str = ""
            if ($ch -eq '"') {
                & $next
                while($ch){
                    if($ch -eq '"') {
                        & $next
                        return $str
                    } elseif ($ch -eq "\") {
                        & $next
                        if ($ch -eq "u") {
                            $uffff = 0
                            1..4 | ForEach-Object {
                                & $next
                                $hex = [Convert]::ToInt32($ch, 16)
                                $uffff = $uffff * 16 + $hex
                            }
                            $str += [char]::ConvertFromUtf32($uffff)
                        } elseif ($escape.$ch){
                            $str += $escape.$ch
                        } else {
                            break
                        }
                    } else {
                        $str += $ch
                    }
                    & $next
                }
            }
            & $err "Bad String"
        }
        $script:white = {
            while($ch -and $ch -le " ") {
                & $next
            }
        }
        $script:word = {
            switch($ch){
                "t" {
                    & $next "t"
                    & $next "r"
                    & $next "u"
                    & $next "e"
                    return $true
                }
                "f" {
                    & $next "f"
                    & $next "a"
                    & $next "l"
                    & $next "s"
                    & $next "e"
                    return $false
                }
                "n" {
                    & $next "n"
                    & $next "u"
                    & $next "l"
                    & $next "l"
                    return $null
                }
                default {
                    & $err "Unexpected '$ch'"
                }
            }
        }
        $script:array = {
            $arr = @()
            if ($ch -eq "[") {
                & $next "["
                & $white
                if ($ch -eq "]"){
                    & $next "]"
                    return $arr
                }
                while($ch) {
                    $arr += & $value
                    & $white
                    if ($ch -eq "]") {
                        & $next "]"
                        return $arr
                    }
                    & $next ","
                    & $white
                }
            }
            & $err "Bad Array"
        }
        $script:object = {
            $obj = New-Object PSObject
            if ($ch -eq "{"){
                & $next "{"
                & $white
                if ($ch -eq "}") {
                    & $next "}"
                    return $obj
                }
                while($ch){
                    $key = & $string
                    & $white
                    & $next ":"
                    $obj | Add-Member NoteProperty $key (& $value) # error if duplicate key
                    & $white
                    if ($ch -eq "}") {
                        & $next "}"
                        return $obj
                    }
                    & $next ","
                    & $white
                }
            }
            & $err "Bad Object"
        }
        $script:value = {
            & $white
            switch($ch){
                "{" { return & $object }
                "[" { return & $array }
                '"' { return & $string }
                "-" { return & $number }
                default {
                    if ($ch -ge "0" -and $ch -le "9") {
                        return & $number
                    } else {
                        return & $word
                    }
                }
            }
        }
        $result = & $value
        & $white
        if ($ch) {
            & $err "Syntax Error"
        }
        return $result
    }
}
```