private/parser/ClassParameterExpansionParser.psm1

using module ./EnumParameterExpansionOpTypes.psm1
using module ./ClassSimpleVariableExpression.psm1
using module ./ClassParameterExpansion.psm1

#requires -Version 5
Set-StrictMode -Version Latest

class ParameterExpansionParser {

    [regex]$PREFIX_PATTERN = [regex]'\G(?:[\\$}]|[^\\$}]+)'

    [string]$str
    [ParameterExpansion]$expansion

    ParameterExpansionParser([string]$str) {
        $this.str = $str
        $this.expansion = [ParameterExpansion]::new()
    }

    [ParameterExpansion]Parse() {
        [void]$this._Parse(0, 0)
        return $this.expansion
    }

    hidden [int]_Parse([int]$parser_start_index, [int] $nested_levels) {
        $m = $this.NextMatch($parser_start_index)
        for (; ; ) {
            if (($null -eq $m) -or (-not $m.Success)) {
                if (0 -lt $nested_levels) {
                    # } closing curly brace not found
                    return -1
                }
                break
            }

            if ($m.Value.StartsWith('}') -and (0 -lt $nested_levels) ) {
                # } closing curly brace
                return ($m.Index + $m.Length)
            }
            if ($m.Value.StartsWith('\')) {
                # escape
                if(($m.Index + 1) -lt $this.str.Length){
                    $this.expansion.AddEscapeString(($this.str[$m.Index..($m.Index + 1)] -join ""))
                    $m = $this.NextMatch($m.Index + 2)
                }else{
                    $this.expansion.AddEscapeString(($this.str[$m.Index..($m.Index)] -join ""))
                    $m = $this.NextMatch($m.Index + 1)
                }
            }
            elseif ($m.Value.StartsWith('$')) {
                # variable
                $next_index = $this.ParseParameterExpansion($m.Index, $nested_levels)
                $m = $this.NextMatch($next_index)
            }
            else {
                $this.expansion.AddStringLiteral($m.Value)
                $m = $m.NextMatch()
            }
        }
        return $this.str.Length
    }

    hidden [System.Text.RegularExpressions.Match]NextMatch([int]$index) {
        if ($index -le $this.str.Length) {
            return $this.PREFIX_PATTERN.Match($this.str, $index)
        }
        return $null
    }

    hidden [int]ParseParameterExpansion([int]$start_index, [int] $nested_levels) {
        if ($this.str.IndexOf('${', $start_index, 2) -eq $start_index) {
            # ${variable} format
            return $this.ParseCurlyBracesVariableExpansion($start_index, $nested_levels)
        }
        $m = ([regex]'\G\$(?<variable_name>[a-zA-Z_][a-zA-Z0-9_]*)').Match($this.str, $start_index)
        if ($m.Success) {
            # $variable format
            $this.expansion.AddSimpleVariable($m.Groups["variable_name"].Value, $m.Value)
            return ($m.Index + $m.Length)
        }
        $this.expansion.AddStringLiteral($this.str[$start_index])
        return $start_index + 1
    }

    hidden [int]ParseCurlyBracesVariableExpansion([int]$start_index, [int] $nested_levels) {
        $m = ([regex]'\G\${(?<variable_name>[a-zA-Z_][a-zA-Z0-9_]*)[:}]').Match($this.str, $start_index)
        if (-not $m.Success) {
            Write-Warning -Message "$($this.GetType()): bad substitution: $($this.str.Substring($start_index))"
            return $this.str.Length
        }

        $variable_name = $m.Groups["variable_name"].Value
        if ($m.Value.EndsWith('}')) {
            # ${variable} format
            $this.expansion.AddCurlyBracesVariable([SimpleVariableExpression]::new($variable_name, $m.Value), $null, [ParameterExpansionOpTypes]::BASIC_FORM, $m.Value)
            return ($m.Index + $m.Length)
        }
        if ($m.Value.EndsWith(':')) {
            # ${variable: format
            if ("-" -eq $this.str[($m.Index + $m.Length)]) {
                # ${variable:- format

                $p = [ParameterExpansionParser]::new($this.str)
                $next_index = $p._Parse($start_index + $m.Length + 1, $nested_levels + 1)
                if ($next_index -lt 0) {
                    Write-Warning -Message "$($this.GetType()): bad substitution: $($this.str.Substring($start_index))"
                    return $this.str.Length
                }
                $word = $p.expansion
                $source = $this.str.Substring($start_index, $next_index - $start_index)
                $param = [SimpleVariableExpression]::new($variable_name, $source);
                $this.expansion.AddCurlyBracesVariable($param, $word, [ParameterExpansionOpTypes]::PARAMETER_IS_UNSET_OR_NULL, $source)
                return $next_index
            }
        }
        Write-Warning -Message "$($this.GetType()): bad substitution: $($this.str.Substring($start_index))"
        return $this.str.Length
    }

}