Steffen陌生人指出 电源外壳-RFC RFC0017-Commanic-Compuoric语言规格 out to me recently.

RFC. is about making it easier to implement a DSL in Powershell with C#. They have an example of a DSL to replace types.ps1xml. It is a nice clear example of a DSL.

这是我在这个系列中的第四个帖子覆盖了DSL。

指数

这个例子

这是来自RFC的局部示例:

# Extend the System.Array type
TypeExtension [System.Array] {
    # Add a new Sum method (from Bruce Payette's "Windows PowerShell in Action", p. 435)
    Method Sum {
        $acc = $null
        foreach ($e in $this)
        {
            $acc += $e
        }
        $acc
    }

    # Add an alias property
    Property Count -Alias Length
}

# Add a DateTime property to the System.DateTime class
TypeExtension [System.DateTime] {
    Property DateTime {
        if ((& {Set-StrictMode -Version 1; $this.DisplayHint}) -ieq "Date")
        {
            "{0}" -f $this.ToLongDateString()
        }
        elseif ((& {Set-StrictMode -Version 1; $this.DisplayHint }) -ieq "Time")
        {
            "{0}" -f $this.ToLongTimeString()
        }
        else
        {
            "{0} {1}" -f $this.ToLongDateString(), $this.ToLongTimeString()
        }
    }
}

RFC.大约只是允许我们创建该DSL。主要目标是为AST添加更好的支持,并开放对其他功能的访问。 DSC可以访问此内部的功能是PowerShell内部。

我这么说,因为在我看着这个例子时,我觉得我们将是我们工作的一个很好的例子。

我需要弄清楚什么

我看到我需要弄清楚如何做的事情。如何将方法,别名属性和计算属性添加到类型。

在我的帖子里 pscustomobjects.,我很快提到了使用 更新 - typedata.。我想我可以用它作为起点。

我要做的第一件事就是散步这个例子,并用手做每个人。在我聪明之前,我需要知道如何在PowerShell中做到这一点。

添加脚本方法

第一个示例是脚本方法。

# Extend the System.Array type
TypeExtension [System.Array] {
    # Add a new Sum method (from Bruce Payette's "Windows PowerShell in Action", p. 435)
    Method Sum {
        $acc = $null
        foreach ($e in $this)
        {
            $acc += $e
        }
        $acc
    }
}

Let’s rework that using 更新 - typedata..

$TypeData = @{
    TypeName = 'System.Array'
    MemberType = 'ScriptMethod'
    MemberName = 'Sum'
    Value = {
        $acc = $null
        foreach ($e in $this)
        {
            $acc += $e
        }
        $acc
    }
}
Update-TypeData @TypeData

现在,如果我们创建该对象,我们会得到一个sum方法。

PS:> [system.array]$object = @(1,2)
PS:> $object.Sum()
3

添加别名属性

列表中的下一个属性是别名

Property Count -Alias Length

是这样的:

$TypeData = @{
    TypeName = 'System.Array'
    MemberType = 'AliasProperty'
    MemberName = 'Lenght'
    Value = 'Count'
}
Update-TypeData @TypeData

添加脚本属性

现在用于脚本属性示例。

Property DateTime {
    if ((& {Set-StrictMode -Version 1; $this.DisplayHint}) -ieq "Date")
    {
        "{0}" -f $this.ToLongDateString()
    }
    elseif ((& {Set-StrictMode -Version 1; $this.DisplayHint }) -ieq "Time")
    {
        "{0}" -f $this.ToLongTimeString()
    }
    else
    {
        "{0} {1}" -f $this.ToLongDateString(), $this.ToLongTimeString()
    }
}

以下是PowerShell中当前的等效命令。

$TypeData = @{
    TypeName = 'System.DateTime'
    MemberType = 'ScriptProperty'
    MemberName = 'DateTime'
    Value = {
        if ((& {Set-StrictMode -Version 1; $this.DisplayHint}) -ieq "Date")
        {
            "{0}" -f $this.ToLongDateString()
        }
        elseif ((& {Set-StrictMode -Version 1; $this.DisplayHint }) -ieq "Time")
        {
            "{0}" -f $this.ToLongTimeString()
        }
        else
        {
            "{0} {1}" -f $this.ToLongDateString(), $this.ToLongTimeString()
        }
    }
}
Update-TypeData @TypeData

快速检查结果:

$date = get-date
$date.DateTime

DSL.游戏计划

因此,在查看这些示例后,这看起来像我想要实现的基本语法。

TypeExtension <Type> {
    Method <Name> <ScriptBlock>
    Property <Name> -Alias <PropertName>
    Property <Name> <ScriptBlock>
}

I don’t see any keywords that will conflict with PowerShell. The TypeExtension will be an advanced function that uses a ScriptBlock to collect the child keywords. Method and Property will be implemented as advanced functions. I will end up executing the TypeExtension ScriptBlock to run the Method and Property functions. And I will make the first positional parameter for Method and Property the MemberName.

这re are two approaches that I can take with the implementation of Method and Property.

选项1

I can make the Method and Property keywords functions that take the parameters and executes 更新 - typedata.. I would need to get the type data into the function and would end up doing it with a script scoped variable.

选项2

I can make the Method and Property keywords functions return hashtables. I could then add the typedata to the TypeName key of each hashtable and just splat it into 更新 - typedata..

执行

我决定使用选项2来实现此实施。它只是对我来说感觉很干净和优雅。我认为这将使长期延伸更容易。这应该很快迅速地聚集在一起。

typeeExtension函数

For the TypeExtension, I want the user to be able to provide a type for the first parameter. I would be fine if it is just a string. The second positional parameter will be a ScriptBlock that gets executed. We expect the results from the ScriptBlock to be one or more Hashtables.

We will walk each Hashtable, add the TypeName key and then spat it to 更新 - typedata.. Now that we defined it so simply, it will be a very easy function to write.

function TypeExtension
{
    <#
        .Description
        Allows you to update type information
    #>
    [cmdletbinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Type,

        [Parameter(Mandatory,Position=1)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]
        $TypeData
    )
    process
    {
        try
        {
            $results = & $TypeData

            If( $type -match '^\[(?<Type>.*)\]$' )
            {
                $type = $matches.Type
            }

            foreach($options in $results)
            {
                if($options -is [hashtable])
                {
                    $options.TypeName = $type.ToString()
                    Update-TypeData @options -Force
                }
                else
                {
                    Write-Error "TypeData has unexpected value [$options]"
                }
            }
        }
        catch
        {
            $PSCmdlet.ThrowTerminatingError($PSItem)
        }
    }
}

我在此添加了基本错误和异常处理,因为这将是最终用户调用的公共功能。

方法功能

This function will allow us to create script methods for a given type. The first parameter will be the name and the second will be the method script. We will use those parameters to create a Hashtable.

function Method
{
    <#
        .Description
        Allows you to add a script method to a type
    #>
    [cmdletbinding()]
    param(
        [Parameter(Mandatory,Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(Mandatory,Position=1)]
        [ValidateNotNullOrEmpty()]
        [scriptblock]
        $ScriptBlock
    )
    process
    {
        @{
            MemberType = 'ScriptMethod'
            MemberName = $Name
            Value = $ScriptBlock
        }
    }
}

We return the Hashtable to TypeExtension for processing.

物业功能

When we consider the script property, then this is almost identical to the previous function. But we need to support an alternate syntax with this one for the alias property. I will solve this one with a ParameterSet to handle the two use-cases.

function Property
{
    <#
        .Description
        Allows you to add an alias or script property to a type
    #>
    [cmdletbinding(DefaultParameterSetName='ScriptProperty')]
    param(
        [Parameter(Mandatory,Position=0)]
        [ValidateNotNullOrEmpty()]
        [string]
        $Name,

        [Parameter(
            Mandatory,
            Position=1,
            ParameterSetName='ScriptProperty'
        )]
        [ValidateNotNullOrEmpty()]
        [scriptblock]
        $ScriptBlock,

        [Parameter(
            Mandatory,
            Position=1,
            ParameterSetName='AliasProperty'
        )]
        [ValidateNotNullOrEmpty()]
        [string]
        $Alias
    )

    process
    {
        $typeData = @{
            MemberName = $Name
        }

        If($PSCmdlet.ParameterSetName -eq 'ScriptProperty')
        {
            $typeData.MemberType = 'ScriptProperty'
            $typeData.Value = $ScriptBlock
        }
        else
        {
            $typeData.MemberType = 'AliasProperty'
            $typeData.Value = $Alias
        }

        $typeData
    }
}

现在,如果我们运行原始DSL示例,我们的实施将只是工作。

把它整合在一起

我认为这是我以前的DSL覆盖范围的良好操作。我希望通过这么快地写这件事,我不会从那个原来的RFC中取出任何东西。它不仅仅是创建DSL,这只是如何实现的例子。

I can’t wait to see some of the work that comes out of that RFC. But until then, we have our own DSL implementations to play with. It should be very easy to extend this approach to support the other 更新 - typedata. options.

在我的下一个帖子上,我将采取不同的方法来实现同一场景。