<repo_name>SeeminglyScience/PSLambda<file_sep>/src/PSLambda/Empty.cs
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides utility methods for empty elements.
    /// </summary>
    internal static class Empty
    {
        /// <summary>
        /// Gets an empty <see cref="IScriptExtent" />.
        /// </summary>
        internal static IScriptExtent Extent => new EmptyScriptExtent();

        /// <summary>
        /// Gets an empty <see cref="IScriptPosition" />.
        /// </summary>
        internal static IScriptPosition Position => new EmptyScriptPosition();

        private class EmptyScriptExtent : IScriptExtent
        {
            public int EndColumnNumber => 0;

            public int EndLineNumber => 0;

            public int EndOffset => 0;

            public IScriptPosition EndScriptPosition => Position;

            public string File => string.Empty;

            public int StartColumnNumber => 0;

            public int StartLineNumber => 0;

            public int StartOffset => 0;

            public IScriptPosition StartScriptPosition => Position;

            public string Text => string.Empty;
        }

        private class EmptyScriptPosition : IScriptPosition
        {
            public int ColumnNumber => 0;

            public string File => string.Empty;

            public string Line => string.Empty;

            public int LineNumber => 0;

            public int Offset => 0;

            public string GetFullScript() => string.Empty;
        }
    }
}
<file_sep>/src/PSLambda/Commands/ICommandHandler.cs
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda.Commands
{
    /// <summary>
    /// Provides handling for a custom command.
    /// </summary>
    internal interface ICommandHandler
    {
        /// <summary>
        /// Gets the name of the command.
        /// </summary>
        string CommandName { get; }

        /// <summary>
        /// Creates a Linq expression for a <see cref="CommandAst" /> representing
        /// the custom command.
        /// </summary>
        /// <param name="commandAst">The AST to convert.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the expression.</param>
        /// <returns>An expression representing the command.</returns>
        Expression ProcessAst(CommandAst commandAst, CompileVisitor visitor);
    }
}
<file_sep>/src/PSLambda/LabelScope.cs
using System;
using System.Linq.Expressions;

namespace PSLambda
{
    /// <summary>
    /// Represents the a scope in which the <c>return</c> keyword will work.
    /// </summary>
    internal class LabelScope
    {
        /// <summary>
        /// The parent scope.
        /// </summary>
        internal readonly LabelScope _parent;

        private LabelTarget _label;

        /// <summary>
        /// Initializes a new instance of the <see cref="LabelScope" /> class.
        /// </summary>
        internal LabelScope()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="LabelScope" /> class.
        /// </summary>
        /// <param name="parent">The parent <see cref="LabelScope" />.</param>
        internal LabelScope(LabelScope parent)
        {
            _parent = parent;
        }

        /// <summary>
        /// Gets or sets a value indicating whether an explicit return statement has been used.
        /// </summary>
        public bool IsReturnRequested { get; set; }

        /// <summary>
        /// Gets or sets the implied or explicit return type.
        /// </summary>
        public Type ReturnType { get; set; }

        /// <summary>
        /// Gets the current <c>return</c> label.
        /// </summary>
        public LabelTarget Label
        {
            get
            {
                if (_label != null)
                {
                    return _label;
                }

                _label = Expression.Label(ReturnType);
                return _label;
            }
        }
    }
}
<file_sep>/src/PSLambda/ExpressionExtensions.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides utility methods for creating <see cref="Expression" /> objects.
    /// </summary>
    internal static class ExpressionExtensions
    {
        private static readonly Expression[] s_emptyExpressions = new Expression[0];

        /// <summary>
        /// Compile all asts in a given <see cref="IList{TAst}" />.
        /// </summary>
        /// <param name="asts">The <see cref="Ast" /> objects to compile.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the compile.</param>
        /// <typeparam name="TAst">The type of <see cref="Ast" /> to expect in the list.</typeparam>
        /// <returns>The compiled <see cref="Expression" /> objects.</returns>
        public static Expression[] CompileAll<TAst>(this IList<TAst> asts, CompileVisitor visitor)
            where TAst : Ast
        {
            if (asts == null || asts.Count == 0)
            {
                return s_emptyExpressions;
            }

            var expressions = new Expression[asts.Count];
            for (var i = 0; i < asts.Count; i++)
            {
                expressions[i] = (Expression)asts[i].Visit(visitor);
            }

            return expressions;
        }

        /// <summary>
        /// Compile a <see cref="Ast" /> into a <see cref="Expression" />.
        /// </summary>
        /// <param name="ast">The <see cref="Ast" /> to compile.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the compile.</param>
        /// <returns>The compiled <see cref="Expression" /> object.</returns>
        public static Expression Compile(this Ast ast, CompileVisitor visitor)
        {
            try
            {
                return (Expression)ast.Visit(visitor);
            }
            catch (ArgumentException e)
            {
                visitor.Errors.ReportParseError(ast.Extent, e);
                return Expression.Empty();
            }
            catch (InvalidOperationException e)
            {
                visitor.Errors.ReportParseError(ast.Extent, e);
                return Expression.Empty();
            }
        }
    }
}
<file_sep>/src/PSLambda/PSLambdaAssemblyInitializer.cs
using System;
using System.Collections.Generic;
using System.Management.Automation;

namespace PSLambda
{
    /// <summary>
    /// Provides initialization services when the module is imported.
    /// </summary>
    public class PSLambdaAssemblyInitializer : IModuleAssemblyInitializer
    {
        private const string TypeAcceleratorTypeName = "System.Management.Automation.TypeAccelerators";

        private const string GetPropertyName = "Get";

        /// <summary>
        /// Attempts to create the type accelerator for <see cref="PSDelegate" />.
        /// </summary>
        public void OnImport()
        {
            var accelType = typeof(PSObject).Assembly.GetType(TypeAcceleratorTypeName);
            if (accelType == null)
            {
                return;
            }

            var getProperty = accelType.GetProperty(GetPropertyName);
            if (getProperty == null)
            {
                return;
            }

            if (!(getProperty.GetValue(null) is Dictionary<string, Type> existing))
            {
                return;
            }

            if (existing.ContainsKey(Strings.PSDelegateTypeAcceleratorName))
            {
                return;
            }

            var addMethod = accelType.GetMethod(
                Strings.AddMethodName,
                new[] { typeof(string), typeof(Type) });
            if (addMethod == null)
            {
                return;
            }

            try
            {
                addMethod.Invoke(
                    null,
                    new object[] { Strings.PSDelegateTypeAcceleratorName, typeof(PSDelegate) });
            }
            catch (Exception)
            {
                return;
            }
        }
    }
}
<file_sep>/src/PSLambda/VariableScope.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace PSLambda
{
    /// <summary>
    /// Represents the variable scope for a single block <see cref="Expression" />.
    /// </summary>
    internal class VariableScope
    {
        private static readonly string[] s_dollarUnderNameCache =
        {
            "PSItem", "PSItem2", "PSItem3", "PSItem4", "PSItem5",
            "PSItem6", "PSItem7", "PSItem8", "PSItem9", "PSItem10",
        };

        private (ParameterExpression Parameter, int Version) _dollarUnder;

        /// <summary>
        /// Initializes a new instance of the <see cref="VariableScope" /> class.
        /// </summary>
        /// <param name="parameters">Parameters to include in the scope.</param>
        public VariableScope(ParameterExpression[] parameters)
        {
            Parameters = parameters.ToDictionary(p => p.Name);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="VariableScope" /> class.
        /// </summary>
        /// <param name="parent">The parent <see cref="VariableScope" /> object.</param>
        public VariableScope(VariableScope parent)
        {
            Parent = parent;
        }

        /// <summary>
        /// Gets the parent <see cref="VariableScope" />.
        /// </summary>
        internal VariableScope Parent { get; }

        /// <summary>
        /// Gets the parameter <see cref="Expression" /> objects for the current scope.
        /// </summary>
        internal Dictionary<string, ParameterExpression> Parameters { get; } = new Dictionary<string, ParameterExpression>();

        /// <summary>
        /// Gets the variable <see cref="Expression" /> objects for the current scope.
        /// </summary>
        internal Dictionary<string, ParameterExpression> Variables { get; } = new Dictionary<string, ParameterExpression>();

        /// <summary>
        /// Creates a new variable expression without looking at previous scopes.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="type">The type of the variable.</param>
        /// <returns>The variable <see cref="Expression" />.</returns>
        internal ParameterExpression NewVariable(string name, Type type)
        {
            ParameterExpression variable = Expression.Variable(type, name);
            Variables.Add(name, variable);
            return variable;
        }

        /// <summary>
        /// Gets an already defined variable <see cref="Expression" /> if it already exists,
        /// otherwise one is created.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="type">The type of the variable.</param>
        /// <returns>The variable <see cref="Expression" />.</returns>
        internal ParameterExpression GetOrCreateVariable(string name, Type type)
        {
            return GetOrCreateVariable(name, type, out _);
        }

        /// <summary>
        /// Gets an already defined variable <see cref="Expression" /> if it already exists,
        /// otherwise one is created.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="type">The type of the variable.</param>
        /// <param name="alreadyDefined">
        /// A value indicating whether the variable has already been defined.
        /// </param>
        /// <returns>The variable <see cref="Expression" />.</returns>
        internal ParameterExpression GetOrCreateVariable(string name, Type type, out bool alreadyDefined)
        {
            if (TryGetVariable(name, out ParameterExpression existingVar))
            {
                alreadyDefined = true;
                return existingVar;
            }

            alreadyDefined = false;
            var newVariable = Expression.Parameter(type ?? typeof(object), name);
            Variables.Add(name, newVariable);
            return newVariable;
        }

        /// <summary>
        /// Gets an already defined variable <see cref="Expression" /> if it already exists,
        /// otherwise one is created.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="typeGetter">
        /// A function that retrieves the variable's type if it has not already been defined.
        /// </param>
        /// <returns>The variable <see cref="Expression" />.</returns>
        internal ParameterExpression GetOrCreateVariable(string name, Func<Type> typeGetter)
        {
            if (TryGetVariable(name, out ParameterExpression existingVar))
            {
                return existingVar;
            }

            var newVariable = Expression.Parameter(typeGetter() ?? typeof(object), name);
            Variables.Add(name, newVariable);
            return newVariable;
        }

        /// <summary>
        /// Attempts to get a variable that has already been defined.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="existingVariable">The already defined variable <see cref="Expression" />.</param>
        /// <returns>
        /// A value indicating whether an existing variable <see cref="Expression" /> was found.
        /// </returns>
        internal bool TryGetVariable(string name, out ParameterExpression existingVariable)
        {
            if (Variables.TryGetValue(name, out existingVariable))
            {
                return true;
            }

            if (Parameters.TryGetValue(name, out existingVariable))
            {
                return true;
            }

            if (Parent != null && Parent.TryGetVariable(name, out existingVariable))
            {
                return true;
            }

            return false;
        }

        /// <summary>
        /// Gets the automatic variable <c>$_</c> or <c>$PSItem</c> from the
        /// current scope, or closest parent scope where it is defined.
        /// </summary>
        /// <returns>
        /// The parameter expression referencing dollar under if defined;
        /// otherwise <see langword="null" />.
        /// </returns>
        internal ParameterExpression GetDollarUnder()
        {
            for (VariableScope current = this; current != null; current = current.Parent)
            {
                if (current._dollarUnder.Parameter != null)
                {
                    return current._dollarUnder.Parameter;
                }
            }

            return null;
        }

        /// <summary>
        /// Sets the automatic variable <c>$_</c> or <c>$PSItem</c> in the current
        /// scope. If the variable exists in a parent scope and the type is the same,
        /// then the parent variable will be used. If the variable exists in a parent
        /// scope but the type is not the same, the variable will be renamed behind
        /// the scenes.
        /// </summary>
        /// <param name="type">
        /// The static type that dollar under should contain.
        /// </param>
        /// <returns>
        /// The parameter expression referencing dollar under.
        /// </returns>
        internal ParameterExpression SetDollarUnder(Type type)
        {
            static string GetDollarUnderName(int version)
            {
                if (version < s_dollarUnderNameCache.Length)
                {
                    return s_dollarUnderNameCache[version];
                }

                return string.Concat("PSItem", version);
            }

            for (VariableScope scope = Parent; scope != null; scope = scope.Parent)
            {
                var dollarUnder = scope._dollarUnder;
                if (dollarUnder.Parameter == null)
                {
                    continue;
                }

                if (_dollarUnder.Parameter.Type == type)
                {
                    _dollarUnder = dollarUnder;
                    return _dollarUnder.Parameter;
                }

                var version = dollarUnder.Version + 1;
                _dollarUnder = (Expression.Parameter(type, GetDollarUnderName(version)), version);
                return _dollarUnder.Parameter;
            }

            _dollarUnder = (Expression.Parameter(type, "PSItem"), 0);
            return _dollarUnder.Parameter;
        }
    }
}
<file_sep>/src/PSLambda/SpecialVariables.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Host;

namespace PSLambda
{
    /// <summary>
    /// Provides information about special variables created by the PowerShell engine.
    /// </summary>
    internal static class SpecialVariables
    {
        /// <summary>
        /// Contains the names and types of variables that should be available from any scope.
        /// </summary>
        internal static readonly Dictionary<string, Type> AllScope = new Dictionary<string, Type>(StringComparer.OrdinalIgnoreCase)
        {
            { "?", typeof(bool) },
            { "ExecutionContext", typeof(EngineIntrinsics) },
            { "Home", typeof(string) },
            { "Host", typeof(PSHost) },
            { "PID", typeof(int) },
            { "PSCulture", typeof(string) },
            { "PSHome", typeof(string) },
            { "PSUICulture", typeof(string) },
            { "PSVersionTable", typeof(System.Collections.Hashtable) },
            { "PSEdition", typeof(string) },
            { "ShellId", typeof(string) },
            { "MaximumHistoryCount", typeof(int) },
        };

        /// <summary>
        /// Provides the names and constant <see cref="Expression" /> objects for variables that
        /// should are language features like <c>true</c>.
        /// </summary>
        internal static readonly Dictionary<string, Expression> Constants = new Dictionary<string, Expression>(StringComparer.OrdinalIgnoreCase)
        {
            { Strings.TrueVariableName, Expression.Constant(true, typeof(bool)) },
            { Strings.FalseVariableName, Expression.Constant(false, typeof(bool)) },
            { Strings.NullVariableName, Expression.Constant(null) },
        };

        /// <summary>
        /// Provides the names of variables that should be ignored when aquiring local
        /// scope variables.
        /// </summary>
        internal static readonly HashSet<string> IgnoreLocal = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
        {
            "?",
            "ExecutionContext",
            "Home",
            "Host",
            "PID",
            "PSCulture",
            "PSHome",
            "PSUICulture",
            "PSVersionTable",
            "PSEdition",
            "ShellId",
            "MaximumHistoryCount",
            "MyInvocation",
            "OFS",
            "OutputEncoding",
            "VerboseHelpErrors",
            "LogEngineHealthEvent",
            "LogEngineLifecycleEvent",
            "LogCommandHealthEvent",
            "LogCommandLifecycleEvent",
            "LogProviderHealthEvent",
            "LogProviderLifecycleEvent",
            "LogSettingsEvent",
            "PSLogUserData",
            "NestedPromptLevel",
            "CurrentlyExecutingCommand",
            "PSBoundParameters",
            "Matches",
            "LASTEXITCODE",
            "PSDebugContext",
            "StackTrace",
            "^",
            "$",
            "?",
            "args",
            "input",
            "error",
            "PSEmailServer",
            "PSDefaultParameterValues",
            "PSScriptRoot",
            "PSCommandPath",
            "PSSenderInfo",
            "foreach",
            "switch",
            "PWD",
            "null",
            "true",
            "false",
            "PSModuleAutoLoadingPreference",
            "IsLinux",
            "IsMacOS",
            "IsWindows",
            "IsCoreCLR",
            "DebugPreference",
            "WarningPreference",
            "ErrorActionPreference",
            "InformationPreference",
            "ProgressPreference",
            "VerbosePreference",
            "WhatIfPreference",
            "ConfirmPreference",
            "ErrorView",
            "PSSessionConfigurationName",
            "PSSessionApplicationName",
            "ExecutionContext",
            "Host",
            "PID",
            "PSCulture",
            "PSHOME",
            "PSUICulture",
            "PSVersionTable",
            "PSEdition",
            "ShellId",
        };
    }
}
<file_sep>/src/PSLambda/ParseWriterExtensions.cs
using System;
using System.Globalization;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides utility methods for reporting specific types of
    /// <see cref="ParseError" /> objects.
    /// </summary>
    internal static class ParseWriterExtensions
    {
        /// <summary>
        /// Generate a <see cref="ParseError" /> citing an unsupported <see cref="Ast" /> type.
        /// </summary>
        /// <param name="writer">The <see cref="IParseErrorWriter" /> that should issue the report.</param>
        /// <param name="ast">The <see cref="Ast" /> that is not supported.</param>
        internal static void ReportNotSupportedAstType(
            this IParseErrorWriter writer,
            Ast ast)
        {
            writer.ReportNotSupported(
                ast.Extent,
                ast.GetType().Name,
                string.Format(
                    CultureInfo.CurrentCulture,
                    ErrorStrings.AstNotSupported,
                    ast.GetType().Name));
        }

        /// <summary>
        /// Generate a <see cref="ParseError" /> citing an unsupported element.
        /// </summary>
        /// <param name="writer">The <see cref="IParseErrorWriter" /> that should issue the report.</param>
        /// <param name="extent">The <see cref="IScriptExtent" /> of the unsupported element.</param>
        /// <param name="id">The ID to be shown in the <see cref="ParseError" />.</param>
        /// <param name="message">The message to be shown in the <see cref="ParseError" />.</param>
        internal static void ReportNotSupported(
            this IParseErrorWriter writer,
            IScriptExtent extent,
            string id,
            string message)
        {
            writer.ReportParseError(
                extent,
                string.Format(
                    CultureInfo.CurrentCulture,
                    "{0}.{1}",
                    nameof(ErrorStrings.ElementNotSupported),
                    id),
                string.Format(
                    CultureInfo.CurrentCulture,
                    ErrorStrings.ElementNotSupported,
                    message));
        }

        /// <summary>
        /// Generate a <see cref="ParseError" /> citing a missing element.
        /// </summary>
        /// <param name="writer">The <see cref="IParseErrorWriter" /> that should issue the report.</param>
        /// <param name="extent">The <see cref="IScriptExtent" /> of the missing element.</param>
        /// <param name="id">The ID to be shown in the <see cref="ParseError" />.</param>
        /// <param name="message">The message to be shown in the <see cref="ParseError" />.</param>
        internal static void ReportMissing(
            this IParseErrorWriter writer,
            IScriptExtent extent,
            string id,
            string message)
        {
            writer.ReportParseError(
                extent,
                string.Format(
                    CultureInfo.CurrentCulture,
                    "{0}.{1}",
                    nameof(ErrorStrings.ElementMissing),
                    id),
                string.Format(
                    CultureInfo.CurrentCulture,
                    ErrorStrings.ElementMissing,
                    message));
        }

        /// <summary>
        /// Generate a <see cref="ParseError" />.
        /// </summary>
        /// <param name="writer">The <see cref="IParseErrorWriter" /> that should issue the report.</param>
        /// <param name="extent">The <see cref="IScriptExtent" /> of element in error.</param>
        /// <param name="id">The ID to be shown in the <see cref="ParseError" />.</param>
        /// <param name="message">The message to be shown in the <see cref="ParseError" />.</param>
        internal static void ReportParseError(
            this IParseErrorWriter writer,
            IScriptExtent extent,
            string id = nameof(ErrorStrings.CompileTimeParseError),
            string message = "")
        {
            if (string.IsNullOrWhiteSpace(message))
            {
                message = ErrorStrings.CompileTimeParseError;
            }

            writer.ReportParseError(extent, id, message);
        }

        /// <summary>
        /// Generate a <see cref="ParseError" />.
        /// </summary>
        /// <param name="writer">The <see cref="IParseErrorWriter" /> that should issue the report.</param>
        /// <param name="extent">The <see cref="IScriptExtent" /> of element in error.</param>
        /// <param name="exception">
        /// The exception housing the message to be shown in the <see cref="ParseError" />.
        /// </param>
        /// <param name="id">The ID to be shown in the <see cref="ParseError" />.</param>
        internal static void ReportParseError(
            this IParseErrorWriter writer,
            IScriptExtent extent,
            Exception exception,
            string id = "")
        {
            if (string.IsNullOrEmpty(id))
            {
                id = exception.GetType().Name;
            }

            writer.ReportParseError(
                extent,
                id,
                exception.Message);
        }

        /// <summary>
        /// Generate a <see cref="ParseError" />.
        /// </summary>
        /// <param name="writer">The <see cref="IParseErrorWriter" /> that should issue the report.</param>
        /// <param name="extent">The <see cref="IScriptExtent" /> of element in error.</param>
        internal static void ReportNonConstantTypeAs(
            this IParseErrorWriter writer,
            IScriptExtent extent)
        {
            writer.ReportNotSupported(
                extent,
                nameof(ErrorStrings.NonConstantTypeAs),
                ErrorStrings.NonConstantTypeAs);
        }
    }
}
<file_sep>/src/PSLambda/DelegateSyntaxVisitor.cs
using System;
using System.Collections.Generic;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides the ability to reshape a <see cref="ScriptBlockAst" /> when it fits
    /// the custom syntax for anonymous method expressions.
    /// </summary>
    internal class DelegateSyntaxVisitor : ICustomAstVisitor
    {
        private readonly IParseErrorWriter _errorWriter;

        private readonly List<Tuple<ITypeName, VariableExpressionAst>> _variables = new List<Tuple<ITypeName, VariableExpressionAst>>();

        private IScriptExtent _paramBlockExtent;

        private bool _failed;

        /// <summary>
        /// Initializes a new instance of the <see cref="DelegateSyntaxVisitor" /> class.
        /// </summary>
        /// <param name="errorWriter">The <see cref="IParseErrorWriter" /> to report errors to.</param>
        internal DelegateSyntaxVisitor(IParseErrorWriter errorWriter)
        {
            _errorWriter = errorWriter;
        }

        #pragma warning disable SA1600
        public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
        {
            return scriptBlockExpressionAst.ScriptBlock.Visit(this);
        }

        public object VisitScriptBlock(ScriptBlockAst scriptBlockAst)
        {
            if (scriptBlockAst.ParamBlock != null)
            {
                return scriptBlockAst;
            }

            if (scriptBlockAst.BeginBlock != null)
            {
                _errorWriter.ReportNotSupported(
                    scriptBlockAst.BeginBlock.Extent,
                    nameof(CompilerStrings.BeginBlock),
                    CompilerStrings.BeginBlock);
                return null;
            }

            if (scriptBlockAst.ProcessBlock != null)
            {
                _errorWriter.ReportNotSupported(
                    scriptBlockAst.ProcessBlock.Extent,
                    nameof(CompilerStrings.ProcessBlock),
                    CompilerStrings.ProcessBlock);
                return null;
            }

            if (scriptBlockAst.EndBlock == null)
            {
                _errorWriter.ReportMissing(
                    scriptBlockAst.Extent,
                    nameof(CompilerStrings.EndBlock),
                    CompilerStrings.EndBlock);
                return null;
            }

            var body = scriptBlockAst.EndBlock.Visit(this);
            if (_failed)
            {
                return scriptBlockAst;
            }

            _errorWriter.ThrowIfAnyErrors();
            var parameters = new ParameterAst[_variables.Count];
            for (var i = 0; i < parameters.Length; i++)
            {
                parameters[i] = new ParameterAst(
                    _variables[i].Item2.Extent,
                    (VariableExpressionAst)_variables[i].Item2.Copy(),
                    new[] { new TypeConstraintAst(_variables[i].Item1.Extent, _variables[i].Item1) },
                    null);
            }

            var paramBlock =
                new ParamBlockAst(
                    _paramBlockExtent,
                    Array.Empty<AttributeAst>(),
                    parameters);

            return new ScriptBlockAst(
                scriptBlockAst.Extent,
                paramBlock,
                null,
                null,
                (NamedBlockAst)((Ast)body).Copy(),
                null);
        }

        public object VisitNamedBlock(NamedBlockAst namedBlockAst)
        {
            if (namedBlockAst.Statements == null ||
                namedBlockAst.Statements.Count == 0)
            {
                _errorWriter.ReportMissing(
                    namedBlockAst.Extent,
                    nameof(CompilerStrings.EndBlock),
                    CompilerStrings.EndBlock);
                return null;
            }

            if (namedBlockAst.Statements.Count > 1)
            {
                _failed = true;
                return null;
            }

            var body = namedBlockAst.Statements[0].Visit(this);
            if (body == null)
            {
                _failed = true;
                return null;
            }

            return body;
        }

        public object VisitPipeline(PipelineAst pipelineAst)
        {
            return pipelineAst.PipelineElements[0].Visit(this);
        }

        public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
        {
            _paramBlockExtent = assignmentStatementAst.Left.Extent;
            DelegateParameterVisitor.AddVariables(
                assignmentStatementAst.Left,
                _variables);

            if (!(assignmentStatementAst.Right is PipelineAst pipeline))
            {
                return null;
            }

            if (!(pipeline.PipelineElements[0] is CommandAst commandAst) ||
                commandAst.GetCommandName() != Strings.DelegateSyntaxCommandName ||
                commandAst.CommandElements.Count != 2)
            {
                return null;
            }

            if (commandAst.CommandElements[1] is ScriptBlockExpressionAst sbAst)
            {
                return sbAst.ScriptBlock.EndBlock;
            }

            var expression = commandAst.CommandElements[1] as ExpressionAst;

            var statements =
                new StatementAst[]
                {
                    new CommandExpressionAst(
                        expression.Extent,
                        (ExpressionAst)expression.Copy(),
                        Array.Empty<RedirectionAst>()),
                };

            var statementBlockAst = new StatementBlockAst(
                commandAst.CommandElements[1].Extent,
                statements,
                Array.Empty<TrapStatementAst>());

            return new NamedBlockAst(
                commandAst.CommandElements[1].Extent,
                TokenKind.End,
                statementBlockAst,
                unnamed: true);
        }
        #pragma warning restore SA1600

        private class DelegateParameterVisitor : AstVisitor
        {
            private static readonly ITypeName s_objectTypeName = new TypeName(
                Empty.Extent,
                typeof(object).FullName);

            private List<Tuple<ITypeName, VariableExpressionAst>> _variables = new List<Tuple<ITypeName, VariableExpressionAst>>();

            private DelegateParameterVisitor()
            {
            }

            public static void AddVariables(
                ExpressionAst expression,
                List<Tuple<ITypeName, VariableExpressionAst>> variables)
            {
                var visitor = new DelegateParameterVisitor
                {
                    _variables = variables,
                };

                expression.Visit(visitor);
            }

            public override AstVisitAction VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
            {
                if (convertExpressionAst.Child is VariableExpressionAst variable)
                {
                    _variables.Add(
                        Tuple.Create(
                            convertExpressionAst.Attribute.TypeName,
                            variable));

                    return AstVisitAction.SkipChildren;
                }

                return AstVisitAction.StopVisit;
            }

            public override AstVisitAction VisitVariableExpression(VariableExpressionAst variableExpressionAst)
            {
                _variables.Add(
                    Tuple.Create(
                        s_objectTypeName,
                        variableExpressionAst));

                return AstVisitAction.SkipChildren;
            }
        }

        #pragma warning disable SA1600, SA1201, SA1516
        public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst) => null;
        public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst) => null;
        public object VisitAttribute(AttributeAst attributeAst) => null;
        public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst) => null;
        public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst) => null;
        public object VisitBlockStatement(BlockStatementAst blockStatementAst) => null;
        public object VisitBreakStatement(BreakStatementAst breakStatementAst) => null;
        public object VisitCatchClause(CatchClauseAst catchClauseAst) => null;
        public object VisitCommand(CommandAst commandAst) => null;
        public object VisitCommandExpression(CommandExpressionAst commandExpressionAst) => null;
        public object VisitCommandParameter(CommandParameterAst commandParameterAst) => null;
        public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst) => null;
        public object VisitContinueStatement(ContinueStatementAst continueStatementAst) => null;
        public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst) => null;
        public object VisitDataStatement(DataStatementAst dataStatementAst) => null;
        public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst) => null;
        public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst) => null;
        public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst) => null;
        public object VisitErrorStatement(ErrorStatementAst errorStatementAst) => null;
        public object VisitExitStatement(ExitStatementAst exitStatementAst) => null;
        public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst) => null;
        public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst) => null;
        public object VisitForEachStatement(ForEachStatementAst forEachStatementAst) => null;
        public object VisitForStatement(ForStatementAst forStatementAst) => null;
        public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst) => null;
        public object VisitHashtable(HashtableAst hashtableAst) => null;
        public object VisitIfStatement(IfStatementAst ifStmtAst) => null;
        public object VisitIndexExpression(IndexExpressionAst indexExpressionAst) => null;
        public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst) => null;
        public object VisitMemberExpression(MemberExpressionAst memberExpressionAst) => null;
        public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst) => null;
        public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst) => null;
        public object VisitParamBlock(ParamBlockAst paramBlockAst) => null;
        public object VisitParameter(ParameterAst parameterAst) => null;
        public object VisitParenExpression(ParenExpressionAst parenExpressionAst) => null;
        public object VisitReturnStatement(ReturnStatementAst returnStatementAst) => null;
        public object VisitStatementBlock(StatementBlockAst statementBlockAst) => null;
        public object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst) => null;
        public object VisitSubExpression(SubExpressionAst subExpressionAst) => null;
        public object VisitSwitchStatement(SwitchStatementAst switchStatementAst) => null;
        public object VisitThrowStatement(ThrowStatementAst throwStatementAst) => null;
        public object VisitTrap(TrapStatementAst trapStatementAst) => null;
        public object VisitTryStatement(TryStatementAst tryStatementAst) => null;
        public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst) => null;
        public object VisitTypeExpression(TypeExpressionAst typeExpressionAst) => null;
        public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst) => null;
        public object VisitUsingExpression(UsingExpressionAst usingExpressionAst) => null;
        public object VisitVariableExpression(VariableExpressionAst variableExpressionAst) => null;
        public object VisitWhileStatement(WhileStatementAst whileStatementAst) => null;
        #pragma warning restore SA1600, SA1201, SA1516
    }
}
<file_sep>/src/PSLambda/DelegateTypeConverter.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides conversion from <see cref="PSDelegate" /> to any <see cref="Delegate" /> type.
    /// </summary>
    public class DelegateTypeConverter : PSTypeConverter
    {
        private static readonly Dictionary<PSDelegate, Dictionary<Type, Delegate>> s_delegateCache = new Dictionary<PSDelegate, Dictionary<Type, Delegate>>();

        private static readonly object s_syncObject = new object();

        /// <summary>
        /// Determines if a object can be converted to a specific type.
        /// </summary>
        /// <param name="sourceValue">The value to convert.</param>
        /// <param name="destinationType">The type to conver to.</param>
        /// <returns>A value indicating whether the object can be converted.</returns>
        public override bool CanConvertFrom(object sourceValue, Type destinationType)
        {
            return sourceValue is PSDelegate && typeof(Delegate).IsAssignableFrom(destinationType);
        }

        /// <summary>
        /// Determines if a object can be converted to a specific type.
        /// </summary>
        /// <param name="sourceValue">The value to convert.</param>
        /// <param name="destinationType">The type to conver to.</param>
        /// <returns>A value indicating whether the object can be converted.</returns>
        public override bool CanConvertTo(object sourceValue, Type destinationType)
        {
            return sourceValue is PSDelegate && typeof(Delegate).IsAssignableFrom(destinationType);
        }

        /// <summary>
        /// Converts a <see cref="PSDelegate" /> object to a <see cref="Delegate" /> type.
        /// </summary>
        /// <param name="sourceValue">The <see cref="PSDelegate" /> to convert.</param>
        /// <param name="destinationType">
        /// The type inheriting from <see cref="Delegate" /> to convert to.
        /// </param>
        /// <param name="formatProvider">The parameter is not used.</param>
        /// <param name="ignoreCase">The parameter is not used.</param>
        /// <returns>The converted <see cref="Delegate" /> object.</returns>
        public override object ConvertFrom(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
        {
            return ConvertToDelegate((PSDelegate)sourceValue, destinationType);
        }

        /// <summary>
        /// Converts a <see cref="PSDelegate" /> object to a <see cref="Delegate" /> type.
        /// </summary>
        /// <param name="sourceValue">The <see cref="PSDelegate" /> to convert.</param>
        /// <param name="destinationType">
        /// The type inheriting from <see cref="Delegate" /> to convert to.
        /// </param>
        /// <param name="formatProvider">The parameter is not used.</param>
        /// <param name="ignoreCase">The parameter is not used.</param>
        /// <returns>The converted <see cref="Delegate" /> object.</returns>
        public override object ConvertTo(object sourceValue, Type destinationType, IFormatProvider formatProvider, bool ignoreCase)
        {
            return ConvertToDelegate((PSDelegate)sourceValue, destinationType);
        }

        private Delegate ConvertToDelegate(PSDelegate psDelegate, Type destinationType)
        {
            lock (s_syncObject)
            {
                if (!s_delegateCache.TryGetValue(psDelegate, out Dictionary<Type, Delegate> cacheEntry))
                {
                    cacheEntry = new Dictionary<Type, Delegate>();
                    s_delegateCache.Add(psDelegate, cacheEntry);
                }

                if (!cacheEntry.TryGetValue(destinationType, out Delegate compiledDelegate))
                {
                    compiledDelegate = CompileVisitor.CompileAst(
                        psDelegate.EngineIntrinsics,
                        (ScriptBlockAst)psDelegate.ScriptBlock.Ast,
                        psDelegate.Locals.Values.ToArray(),
                        destinationType);

                    cacheEntry.Add(destinationType, compiledDelegate);
                }

                return compiledDelegate;
            }
        }
    }
}
<file_sep>/src/PSLambda/MemberBinder.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace PSLambda
{
    /// <summary>
    /// Provides member binding for method invocation expressions.
    /// </summary>
    internal class MemberBinder
    {
        private static readonly PSVariable[] s_emptyVariables = new PSVariable[0];

        private static readonly ParameterAst[] s_emptyParameterAsts = new ParameterAst[0];

        private readonly BindingFlags _instanceFlags;

        private readonly BindingFlags _staticFlags;

        private readonly string[] _namespaces;

        private MethodInfo[] _extensionMethods;

        /// <summary>
        /// Initializes a new instance of the <see cref="MemberBinder" /> class.
        /// </summary>
        /// <param name="accessFlags">
        /// The <see cref="BindingFlags" /> to use while resolving members. Only pass
        /// access modifiers (e.g. <see cref="BindingFlags.Public" /> or
        /// <see cref="BindingFlags.NonPublic" />.</param>
        /// <param name="namespaces">The namespaces to resolve extension methods in.</param>
        public MemberBinder(BindingFlags accessFlags, string[] namespaces)
        {
            _instanceFlags = accessFlags | BindingFlags.IgnoreCase | BindingFlags.Instance;
            _staticFlags = accessFlags | BindingFlags.IgnoreCase | BindingFlags.Static;
            _namespaces = new string[namespaces.Length];
            namespaces.CopyTo(_namespaces, 0);
        }

        /// <summary>
        /// Determines the correct member for an expression and creates a
        /// <see cref="Expression" /> representing it's invocation.
        /// </summary>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the bind.</param>
        /// <param name="instance">The instance <see cref="Expression" /> for the invocation.</param>
        /// <param name="name">The member name to use while resolving the method.</param>
        /// <param name="arguments">The arguments to use while resolving the method.</param>
        /// <param name="genericArguments">The generic arguments to use while resolving the method.</param>
        /// <returns>
        /// A <see cref="BindingResult" /> that either contains the
        /// <see cref="Expression" /> or the <see cref="ArgumentException" /> and
        /// error ID.
        /// </returns>
        internal BindingResult BindMethod(
            CompileVisitor visitor,
            Expression instance,
            string name,
            Ast[] arguments,
            Type[] genericArguments)
        {
            return BindMethod(
                visitor,
                instance.Type,
                name,
                arguments,
                instance,
                genericArguments);
        }

        /// <summary>
        /// Determines the correct member for an expression and creates a
        /// <see cref="Expression" /> representing it's invocation.
        /// </summary>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the bind.</param>
        /// <param name="sourceType">The source <see cref="Type" /> for the invocation.</param>
        /// <param name="name">The member name to use while resolving the method.</param>
        /// <param name="arguments">The arguments to use while resolving the method.</param>
        /// <param name="genericArguments">The generic arguments to use while resolving the method.</param>
        /// <returns>
        /// A <see cref="BindingResult" /> that either contains the
        /// <see cref="Expression" /> or the <see cref="ArgumentException" /> and
        /// error ID.
        /// </returns>
        internal BindingResult BindMethod(
            CompileVisitor visitor,
            Type sourceType,
            string name,
            Ast[] arguments,
            Type[] genericArguments)
        {
            return BindMethod(
                visitor,
                sourceType,
                name,
                arguments,
                null,
                genericArguments);
        }

        private BindingResult BindMethod(
            CompileVisitor visitor,
            Type sourceType,
            string name,
            Ast[] arguments,
            Expression instance,
            Type[] genericArguments)
        {
            var methodArgs = new MethodArgument[arguments.Length];
            for (var i = 0; i < methodArgs.Length; i++)
            {
                methodArgs[i] = (MethodArgument)arguments[i];
            }

            var didFindName = false;
            var isInstance = instance != null;
            var methods = sourceType.GetMethods(isInstance ? _instanceFlags : _staticFlags);
            MethodInfo boundMethod;
            BindingResult bindingResult = default;
            for (var i = 0; i < methods.Length; i++)
            {
                if (!methods[i].Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                if (!didFindName)
                {
                    didFindName = true;
                }

                if (genericArguments.Length > 0 &&
                    (!methods[i].IsGenericMethod ||
                    !AreGenericArgumentsValid(methods[i], genericArguments)))
                {
                        continue;
                }

                if (genericArguments.Length > 0)
                {
                    methods[i] = methods[i].MakeGenericMethod(genericArguments);
                }

                if (ShouldBind(methods[i], methodArgs, visitor, out boundMethod))
                {
                    var expressions = new Expression[methodArgs.Length];
                    for (var j = 0; j < methodArgs.Length; j++)
                    {
                        expressions[j] = methodArgs[j].Expression;
                    }

                    bindingResult.Expression = Expression.Call(instance, boundMethod, expressions);
                    return bindingResult;
                }
            }

            if (!isInstance)
            {
                bindingResult.Reason = new ArgumentException(
                    string.Format(
                        CultureInfo.CurrentCulture,
                        ErrorStrings.NoMemberArgumentMatch,
                        sourceType.FullName,
                        name));
                bindingResult.Id = nameof(ErrorStrings.NoMemberArgumentMatch);
                return bindingResult;
            }

            var extensionArgs = new MethodArgument[methodArgs.Length + 1];
            extensionArgs[0] = (MethodArgument)instance;
            for (var i = 1; i < extensionArgs.Length; i++)
            {
                extensionArgs[i] = methodArgs[i - 1];
            }

            methods = GetExtensionMethods();
            for (var i = 0; i < methods.Length; i++)
            {
                if (!methods[i].Name.Equals(name, StringComparison.InvariantCultureIgnoreCase))
                {
                    continue;
                }

                if (!didFindName)
                {
                    didFindName = true;
                }

                if (genericArguments.Length > 0 &&
                    (!methods[i].IsGenericMethod ||
                    !AreGenericArgumentsValid(methods[i], genericArguments)))
                {
                        continue;
                }

                if (genericArguments.Length > 0)
                {
                    methods[i] = methods[i].MakeGenericMethod(genericArguments);
                }

                if (ShouldBind(methods[i], extensionArgs, visitor, out boundMethod))
                {
                    var expressions = new Expression[extensionArgs.Length];
                    for (var j = 0; j < extensionArgs.Length; j++)
                    {
                        expressions[j] = extensionArgs[j].Expression;
                    }

                    bindingResult.Expression = Expression.Call(boundMethod, expressions);
                    return bindingResult;
                }
            }

            if (!didFindName)
            {
                bindingResult.Reason =
                    new ArgumentException(
                        string.Format(
                            CultureInfo.CurrentCulture,
                            ErrorStrings.NoMemberNameMatch,
                            sourceType.FullName,
                            name));

                bindingResult.Id = nameof(ErrorStrings.NoMemberNameMatch);
                return bindingResult;
            }

            bindingResult.Reason = new ArgumentException(
                string.Format(
                    CultureInfo.CurrentCulture,
                    ErrorStrings.NoMemberArgumentMatch,
                    sourceType.FullName,
                    name));
            bindingResult.Id = nameof(ErrorStrings.NoMemberArgumentMatch);
            return bindingResult;
        }

        private MethodInfo[] GetExtensionMethods()
        {
            if (_extensionMethods != null)
            {
                return _extensionMethods;
            }

            var extensionMethods = new List<MethodInfo>();
            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                foreach (var module in assembly.GetModules())
                {
                    foreach (var type in module.GetTypes())
                    {
                        if (string.IsNullOrEmpty(type.Namespace))
                        {
                            continue;
                        }

                        if (!_namespaces.Any(ns => ns.Equals(type.Namespace, StringComparison.InvariantCultureIgnoreCase)))
                        {
                            continue;
                        }

                        foreach (var method in type.GetMethods(_staticFlags))
                        {
                            if (method.IsDefined(typeof(ExtensionAttribute), inherit: false))
                            {
                                extensionMethods.Add(method);
                            }
                        }
                    }
                }
            }

            _extensionMethods = extensionMethods.ToArray();
            return _extensionMethods;
        }

        private bool AreGenericArgumentsValid(MethodInfo method, Type[] genericArguments)
        {
            var genericParameters = method.GetGenericArguments();
            if (genericParameters.Length != genericArguments.Length)
            {
                return false;
            }

            for (var i = 0; i < genericParameters.Length; i++)
            {
                var constraints = genericParameters[i].GetGenericParameterConstraints();
                for (var j = 0; j < constraints.Length; j++)
                {
                    if (!constraints[j].IsAssignableFrom(genericParameters[i]))
                    {
                        return false;
                    }
                }
            }

            return true;
        }

        private bool ShouldBind(
            MethodInfo method,
            MethodArgument[] arguments,
            CompileVisitor visitor,
            out MethodInfo resolvedMethod)
        {
            var parameters = method.GetParameters();
            if (parameters.Length != arguments.Length)
            {
                resolvedMethod = null;
                return false;
            }

            BindingStatus status;
            status._map = new Dictionary<Type, Type>();
            status._hasGenericParams = method.IsGenericMethod;
            for (var i = 0; i < parameters.Length; i++)
            {
                if (arguments[i].Ast != null &&
                    (arguments[i].Ast is ScriptBlockExpressionAst ||
                    arguments[i].Ast is ScriptBlockAst))
                {
                    if (!(typeof(Delegate).IsAssignableFrom(parameters[i].ParameterType) ||
                        typeof(LambdaExpression).IsAssignableFrom(parameters[i].ParameterType)))
                    {
                        resolvedMethod = null;
                        return false;
                    }

                    if (status.IsDelegateMatch(parameters[i].ParameterType, arguments[i], visitor))
                    {
                        continue;
                    }

                    resolvedMethod = null;
                    return false;
                }

                if (arguments[i].Expression == null)
                {
                    arguments[i].Expression = arguments[i].Ast.Compile(visitor);
                }

                if (!status.IsTypeMatch(parameters[i].ParameterType, arguments[i].Expression.Type))
                {
                    resolvedMethod = null;
                    return false;
                }
            }

            if (!status._hasGenericParams || !method.IsGenericMethodDefinition)
            {
                resolvedMethod = method;
                return true;
            }

            var genericParameters = method.GetGenericArguments();
            var genericArguments = new Type[genericParameters.Length];
            for (var i = 0; i < genericParameters.Length; i++)
            {
                genericArguments[i] = status._map[genericParameters[i]];
            }

            resolvedMethod = method.MakeGenericMethod(genericArguments);
            return true;
        }

        /// <summary>
        /// Represents the result of a method binding attempt.
        /// </summary>
        internal struct BindingResult
        {
            /// <summary>
            /// The generated <see cref="Expression" />. Can be <see langkeyword="null" />
            /// if the binding attempt was unsuccessful.
            /// </summary>
            internal Expression Expression;

            /// <summary>
            /// The <see cref="Exception" /> that describes the binding failure. Can be
            /// <see langkeyword="null" /> if the binding was successful.
            /// </summary>
            internal Exception Reason;

            /// <summary>
            /// The ID that should be attached to the <see cref="ParseException" />. Can
            /// be <see langkeyword="null" /> if the binding attempt was successful.
            /// </summary>
            internal string Id;
        }

        private struct BindingStatus
        {
            internal Dictionary<Type, Type> _map;

            internal bool _hasGenericParams;

            internal bool IsTypeMatch(Type parameterType, Type argumentType)
            {
                if (parameterType.IsByRef)
                {
                    parameterType = parameterType.GetElementType();
                }

                if (parameterType.IsAssignableFrom(argumentType))
                {
                    return true;
                }

                if (!_hasGenericParams)
                {
                    return false;
                }

                if (argumentType.IsArray &&
                    parameterType.IsGenericType &&
                    parameterType.GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    argumentType = typeof(IEnumerable<>).MakeGenericType(
                        argumentType.GetElementType());
                }

                if (parameterType.IsPointer != argumentType.IsPointer)
                {
                    return false;
                }

                if (parameterType.HasElementType && parameterType.HasElementType)
                {
                    parameterType = parameterType.GetElementType();
                    argumentType = argumentType.GetElementType();
                }

                if (parameterType.IsGenericType && parameterType.IsGenericType)
                {
                    var parameterGenerics = parameterType.GetGenericArguments();
                    var argumentGenerics = argumentType.GetGenericArguments();

                    if (parameterGenerics.Length != argumentGenerics.Length)
                    {
                        return false;
                    }

                    for (var i = 0; i < parameterGenerics.Length; i++)
                    {
                        if (!IsTypeMatch(parameterGenerics[i], argumentGenerics[i]))
                        {
                            return false;
                        }
                    }

                    var constructedParameterType = parameterType
                        .GetGenericTypeDefinition()
                        .MakeGenericType(argumentGenerics);

                    return constructedParameterType.IsAssignableFrom(argumentType);
                }

                if (!parameterType.IsGenericParameter)
                {
                    return false;
                }

                if (_map.TryGetValue(parameterType, out Type existingResolvedType))
                {
                    return existingResolvedType.IsAssignableFrom(argumentType);
                }

                foreach (var constraint in parameterType.GetGenericParameterConstraints())
                {
                    if (!constraint.IsAssignableFrom(argumentType))
                    {
                        return false;
                    }
                }

                _map.Add(parameterType, argumentType);
                return true;
            }

            internal bool IsDelegateMatch(Type parameterType, MethodArgument argument, CompileVisitor visitor)
            {
                ScriptBlockAst sbAst;
                if (argument.Ast is ScriptBlockExpressionAst sbExpression)
                {
                    sbAst = (ScriptBlockAst)sbExpression.ScriptBlock.Visit(
                        new DelegateSyntaxVisitor(visitor.Errors));
                }
                else
                {
                    sbAst = (ScriptBlockAst)((ScriptBlockAst)argument.Ast).Visit(
                        new DelegateSyntaxVisitor(visitor.Errors));
                }

                argument.Ast = sbAst;
                var parameterMethod = parameterType.GetMethod(Strings.DelegateInvokeMethodName);
                if (parameterMethod == null && typeof(Expression).IsAssignableFrom(parameterType))
                {
                    parameterMethod = parameterType
                        .GetGenericArguments()[0]
                        .GetMethod(Strings.DelegateInvokeMethodName);
                }

                var astHasExplicitReturn = ExplicitReturnVisitor.TryFindExplicitReturn(
                    sbAst,
                    out PipelineBaseAst returnValue);

                if (astHasExplicitReturn)
                {
                    if (parameterMethod.ReturnType == typeof(void) &&
                        returnValue != null)
                    {
                        return false;
                    }
                }

                var parameterParameters = parameterMethod.GetParameters();
                ParameterAst[] sbParameters;
                if (sbAst.ParamBlock != null)
                {
                    sbParameters = new ParameterAst[sbAst.ParamBlock.Parameters.Count];
                    for (var i = 0; i < sbParameters.Length; i++)
                    {
                        sbParameters[i] = sbAst.ParamBlock.Parameters[i];
                    }
                }
                else
                {
                    sbParameters = s_emptyParameterAsts;
                }

                if (parameterParameters.Length != sbParameters.Length)
                {
                    return false;
                }

                var expectedParameterTypes = new Type[parameterParameters.Length];
                for (var i = 0; i < parameterParameters.Length; i++)
                {
                    if (parameterParameters[i].ParameterType.IsGenericParameter)
                    {
                        if (_map.TryGetValue(parameterParameters[i].ParameterType, out Type resolvedType))
                        {
                            expectedParameterTypes[i] = resolvedType;
                            continue;
                        }

                        // TODO: Check if parameter is strongly typed in the AST and use that to
                        //       resolve the targ.
                        return false;
                    }

                    expectedParameterTypes[i] = parameterParameters[i].ParameterType;
                }

                var expectedReturnType = parameterMethod.ReturnType.IsGenericParameter
                    ? null
                    : parameterMethod.ReturnType;

                if (expectedReturnType == null)
                {
                    _map.TryGetValue(parameterMethod.ReturnType, out expectedReturnType);
                }

                var oldErrorWriter = visitor.Errors;
                try
                {
                    visitor.Errors = ParseErrorWriter.CreateNull();
                    argument.Expression = visitor.CompileAstImpl(
                        sbAst,
                        s_emptyVariables,
                        expectedParameterTypes,
                        expectedReturnType,
                        null);
                }
                catch (Exception)
                {
                    // TODO: Better reporting here if all method resolution fails.
                    return false;
                }
                finally
                {
                    visitor.Errors = oldErrorWriter;
                }

                if (parameterMethod.ReturnType.IsGenericParameter &&
                    !_map.ContainsKey(parameterMethod.ReturnType))
                {
                    _map.Add(parameterMethod.ReturnType, ((LambdaExpression)argument.Expression).ReturnType);
                }

                if (parameterType.IsGenericType)
                {
                    var genericParameters = parameterType.GetGenericArguments();
                    var newGenericParameters = new Type[genericParameters.Length];
                    for (var i = 0; i < genericParameters.Length; i++)
                    {
                        if (genericParameters[i].IsGenericParameter)
                        {
                            _map.TryGetValue(genericParameters[i], out Type resolvedType);
                            newGenericParameters[i] = resolvedType;
                            continue;
                        }

                        newGenericParameters[i] = genericParameters[i];
                    }

                    parameterType = parameterType
                        .GetGenericTypeDefinition()
                        .MakeGenericType(newGenericParameters);
                }

                argument.Expression = Expression.Lambda(
                    parameterType,
                    ((LambdaExpression)argument.Expression).Body,
                    ((LambdaExpression)argument.Expression).Parameters);
                return true;
            }
        }

        private class MethodArgument
        {
            internal Ast Ast;

            internal Expression Expression;

            public static explicit operator Expression(MethodArgument argument)
            {
                return argument.Expression;
            }

            public static explicit operator MethodArgument(Ast ast)
            {
                var argument = new MethodArgument { Ast = ast };
                return argument;
            }

            public static explicit operator MethodArgument(Expression expression)
            {
                var argument = new MethodArgument { Expression = expression };
                return argument;
            }
        }

        private class ExplicitReturnVisitor : AstVisitor
        {
            private bool _wasFound;

            private PipelineBaseAst _returnPipeline;

            public override AstVisitAction VisitInvokeMemberExpression(InvokeMemberExpressionAst methodCallAst)
            {
                return AstVisitAction.SkipChildren;
            }

            public override AstVisitAction VisitReturnStatement(ReturnStatementAst returnStatementAst)
            {
                _wasFound = true;
                _returnPipeline = returnStatementAst.Pipeline;
                return AstVisitAction.StopVisit;
            }

            internal static bool TryFindExplicitReturn(Ast ast, out PipelineBaseAst returnValue)
            {
                var visitor = new ExplicitReturnVisitor();
                ast.Visit(visitor);
                returnValue = visitor._returnPipeline;
                return visitor._wasFound;
            }
        }
    }
}
<file_sep>/src/PSLambda/VariableScopeStack.cs
using System;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Represents the current set of variable scopes for a block <see cref="Expression" /> objects.
    /// </summary>
    internal class VariableScopeStack
    {
        private static readonly ParameterExpression[] s_emptyParameters = new ParameterExpression[0];

        private VariableScope _current;

        /// <summary>
        /// Initializes a new instance of the <see cref="VariableScopeStack" /> class.
        /// </summary>
        internal VariableScopeStack()
        {
            _current = new VariableScope(s_emptyParameters);
        }

        /// <summary>
        /// Creates a new variable scope.
        /// </summary>
        /// <returns>
        /// A <see cref="IDisposable" /> handle that will return to the previous scope when disposed.
        /// </returns>
        internal IDisposable NewScope()
        {
            _current = new VariableScope(_current);
            return new ScopeHandle(() => _current = _current?.Parent);
        }

        /// <summary>
        /// Creates a new variable scope.
        /// </summary>
        /// <param name="parameters">Parameters that should be available from the new scope.</param>
        /// <returns>
        /// A <see cref="IDisposable" /> handle that will return to the previous scope when disposed.
        /// </returns>
        internal IDisposable NewScope(ParameterExpression[] parameters)
        {
            var handle = NewScope();
            var currentScope = _current;
            foreach (var parameter in parameters)
            {
                currentScope.Parameters.Add(parameter.Name, parameter);
            }

            return handle;
        }

        /// <summary>
        /// Creates a new variable expression without looking at previous scopes.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="type">The type of the variable.</param>
        /// <returns>The variable <see cref="Expression" />.</returns>
        internal ParameterExpression NewVariable(string name, Type type)
        {
            return _current.NewVariable(name, type);
        }

        /// <summary>
        /// Gets a variable <see cref="Expression" /> from the current or a parent scope.
        /// </summary>
        /// <param name="variableExpressionAst">
        /// The <see cref="VariableExpressionAst" /> to obtain a variable <see cref="Expression" /> for.
        /// </param>
        /// <param name="alreadyDefined">
        /// A value indicating whether the variable has already been defined.
        /// </param>
        /// <returns>The resolved variable <see cref="Expression" />.</returns>
        internal ParameterExpression GetVariable(
            VariableExpressionAst variableExpressionAst,
            out bool alreadyDefined)
        {
            return _current.GetOrCreateVariable(variableExpressionAst.VariablePath.UserPath, variableExpressionAst.StaticType, out alreadyDefined);
        }

        /// <summary>
        /// Gets a variable <see cref="Expression" /> from the current or a parent scope.
        /// </summary>
        /// <param name="name">The name of the variable.</param>
        /// <param name="type">The type of the variable.</param>
        /// <returns>The resolved variable <see cref="Expression" />.</returns>
        internal ParameterExpression GetVariable(string name, Type type)
        {
            return _current.GetOrCreateVariable(name, type);
        }

        /// <summary>
        /// Gets all variables declared in the current scope.
        /// </summary>
        /// <returns>
        /// The variable <see cref="Expression" /> objects declared in the current scope.
        /// </returns>
        internal ParameterExpression[] GetVariables()
        {
            return _current.Variables.Values.ToArray();
        }

        /// <summary>
        /// Gets all parameters declared in the current scope.
        /// </summary>
        /// <returns>
        /// The parameter <see cref="Expression" /> objects declared in the current scope.
        /// </returns>
        internal ParameterExpression[] GetParameters()
        {
            return _current.Parameters.Values.ToArray();
        }

        /// <summary>
        /// Gets the automatic variable <c>$_</c> or <c>$PSItem</c> from the
        /// current scope, or closest parent scope where it is defined.
        /// </summary>
        /// <param name="dollarUnder">
        /// The parameter expression referencing dollar under if defined;
        /// otherwise <see langword="null" />.
        /// </param>
        /// <returns>
        /// <see langword="true" /> if a defined dollar under variable was
        /// found; otherwise <see langword="false" />.
        /// </returns>
        internal bool TryGetDollarUnder(out ParameterExpression dollarUnder)
        {
            dollarUnder = GetDollarUnder();
            return dollarUnder != null;
        }

        /// <summary>
        /// Gets the automatic variable <c>$_</c> or <c>$PSItem</c> from the
        /// current scope, or closest parent scope where it is defined.
        /// </summary>
        /// <returns>
        /// The parameter expression referencing dollar under if defined;
        /// otherwise <see langword="null" />.
        /// </returns>
        internal ParameterExpression GetDollarUnder() => _current.GetDollarUnder();

        /// <summary>
        /// Sets the automatic variable <c>$_</c> or <c>$PSItem</c> in the current
        /// scope. If the variable exists in a parent scope and the type is the same,
        /// then the parent variable will be used. If the variable exists in a parent
        /// scope but the type is not the same, the variable will be renamed behind
        /// the scenes.
        /// </summary>
        /// <param name="type">
        /// The static type that dollar under should contain.
        /// </param>
        /// <returns>
        /// The parameter expression referencing dollar under.
        /// </returns>
        internal ParameterExpression SetDollarUnder(Type type) => _current.SetDollarUnder(type);
    }
}
<file_sep>/src/PSLambda/CompileVisitor.cs
﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Reflection;
using PSLambda.Commands;

using static System.Linq.Expressions.Expression;
using static PSLambda.ExpressionUtils;

namespace PSLambda
{
    /// <summary>
    /// Provides the ability to compile a PowerShell <see cref="Ast" /> object into a
    /// Linq <see cref="Expression" /> tree.
    /// </summary>
    internal class CompileVisitor : ICustomAstVisitor, ICustomAstVisitor2
    {
        private static readonly HashSet<string> s_defaultNamespaces = new HashSet<string>()
        {
            "System.Linq",
        };

        private static readonly CommandService s_commands = new CommandService();

        private static readonly Dictionary<PSVariable, Expression> s_wrapperCache = new Dictionary<PSVariable, Expression>();

        private static readonly ParameterExpression[] s_emptyParameters = new ParameterExpression[0];

        private static readonly PSVariable[] s_emptyVariables = new PSVariable[0];

        private static readonly ParameterModifier[] s_emptyModifiers = new ParameterModifier[0];

        private readonly ConstantExpression _engine;

        private readonly LabelScopeStack _returns = new LabelScopeStack();

        private readonly LoopScopeStack _loops = new LoopScopeStack();

        private readonly VariableScopeStack _scopeStack = new VariableScopeStack();

        private readonly Dictionary<string, PSVariable> _locals = new Dictionary<string, PSVariable>();

        private MemberBinder _binder;

        private Dictionary<string, PSVariable> _allScopes;

        private CompileVisitor(EngineIntrinsics engine)
        {
            _engine = Constant(engine, typeof(EngineIntrinsics));
            Errors = ParseErrorWriter.CreateDefault();
        }

        /// <summary>
        /// Gets or sets the <see cref="Errors" /> we should report errors to.
        /// </summary>
        internal IParseErrorWriter Errors { get; set; }

        #pragma warning disable SA1600
        public object VisitArrayExpression(ArrayExpressionAst arrayExpressionAst)
        {
            var elements = arrayExpressionAst.SubExpression.Statements.CompileAll(this);
            if (elements.Length > 1)
            {
                if (elements.GroupBy(e => e.Type).Count() == 1)
                {
                    return NewArrayInit(elements[0].Type, elements);
                }

                return NewArrayInit(
                    typeof(object),
                    elements);
            }

            if (elements.Length == 1 && elements[0].Type.IsArray)
            {
                return elements[0];
            }

            if (elements.Length == 0)
            {
                return NewArrayInit(
                    typeof(object),
                    elements);
            }

            return NewArrayInit(
                elements[0].Type,
                elements);
        }

        public object VisitArrayLiteral(ArrayLiteralAst arrayLiteralAst)
        {
            var elements = arrayLiteralAst.Elements.CompileAll(this);
            if (elements.GroupBy(e => e.Type).Count() == 1)
            {
                return NewArrayInit(elements[0].Type, elements);
            }

            return NewArrayInit(typeof(object), elements);
        }

        public object VisitAssignmentStatement(AssignmentStatementAst assignmentStatementAst)
        {
            try
            {
                if (assignmentStatementAst.Right is CommandExpressionAst expression &&
                    expression.Expression is VariableExpressionAst rhsVariable &&
                    rhsVariable.VariablePath.UserPath.Equals(
                        Strings.NullVariableName,
                        StringComparison.OrdinalIgnoreCase))
                {
                    var lhs = assignmentStatementAst.Left.Compile(this);
                    return Assign(lhs, Constant(null, lhs.Type));
                }

                VariableExpressionAst variable = assignmentStatementAst.Left as VariableExpressionAst;
                Type targetType = null;
                if (variable == null && assignmentStatementAst.Left is ConvertExpressionAst convert)
                {
                    variable = convert.Child as VariableExpressionAst;
                    if (!TryResolveType(convert.Attribute, out targetType))
                    {
                        return Empty();
                    }
                }

                if (variable == null)
                {
                    return MakeAssignment(
                        assignmentStatementAst.Left.Compile(this),
                        assignmentStatementAst.Right.Compile(this),
                        assignmentStatementAst.Operator,
                        assignmentStatementAst.ErrorPosition);
                }

                if (_locals.ContainsKey(variable.VariablePath.UserPath) ||
                    SpecialVariables.AllScope.ContainsKey(variable.VariablePath.UserPath) ||
                    SpecialVariables.Constants.ContainsKey(variable.VariablePath.UserPath))
                {
                    return MakeAssignment(
                        assignmentStatementAst.Left.Compile(this),
                        assignmentStatementAst.Right.Compile(this),
                        assignmentStatementAst.Operator,
                        assignmentStatementAst.ErrorPosition);
                }

                if (targetType != null)
                {
                    return MakeAssignment(
                        _scopeStack.GetVariable(variable.VariablePath.UserPath, targetType),
                        assignmentStatementAst.Right.Compile(this),
                        assignmentStatementAst.Operator,
                        assignmentStatementAst.ErrorPosition);
                }

                var rhs = assignmentStatementAst.Right.Compile(this);
                return MakeAssignment(
                    _scopeStack.GetVariable(variable.VariablePath.UserPath, rhs.Type),
                    rhs,
                    assignmentStatementAst.Operator,
                    assignmentStatementAst.ErrorPosition);
            }
            catch (ArgumentException e)
            {
                Errors.ReportParseError(assignmentStatementAst.Extent, e);
                return Empty();
            }
        }

        public object VisitBinaryExpression(BinaryExpressionAst binaryExpressionAst)
        {
            var lhs = binaryExpressionAst.Left.Compile(this);
            var rhs = binaryExpressionAst.Right.Compile(this);

            Type rhsTypeConstant = (rhs as ConstantExpression)?.Value as Type;
            switch (binaryExpressionAst.Operator)
            {
                case TokenKind.DotDot:
                    return PSDotDot(lhs, rhs);
                case TokenKind.Format:
                    return PSFormat(lhs, rhs);
                case TokenKind.Join:
                    return PSJoin(lhs, rhs);
                case TokenKind.Ireplace:
                    return PSReplace(lhs, rhs, false);
                case TokenKind.Creplace:
                    return PSReplace(lhs, rhs, true);
                case TokenKind.Isplit:
                    return PSSplit(lhs, rhs, false);
                case TokenKind.Csplit:
                    return PSSplit(lhs, rhs, true);
                case TokenKind.Iin:
                    return PSIsIn(lhs, rhs, false);
                case TokenKind.Cin:
                    return PSIsIn(lhs, rhs, true);
                case TokenKind.Inotin:
                    return Not(PSIsIn(lhs, rhs, false));
                case TokenKind.Cnotin:
                    return Not(PSIsIn(lhs, rhs, true));
                case TokenKind.Icontains:
                    return PSIsIn(rhs, lhs, false);
                case TokenKind.Ccontains:
                    return PSIsIn(rhs, lhs, true);
                case TokenKind.Inotcontains:
                    return Not(PSIsIn(rhs, lhs, false));
                case TokenKind.Cnotcontains:
                    return Not(PSIsIn(rhs, lhs, true));
                case TokenKind.Ige:
                    return Or(PSGreaterThan(lhs, rhs, false), PSEquals(lhs, rhs, false));
                case TokenKind.Cge:
                    return Or(PSGreaterThan(lhs, rhs, true), PSEquals(lhs, rhs, true));
                case TokenKind.Ile:
                    return Or(PSLessThan(lhs, rhs, false), PSEquals(lhs, rhs, false));
                case TokenKind.Cle:
                    return Or(PSLessThan(lhs, rhs, true), PSEquals(lhs, rhs, true));
                case TokenKind.Igt:
                    return PSGreaterThan(lhs, rhs, false);
                case TokenKind.Cgt:
                    return PSGreaterThan(lhs, rhs, true);
                case TokenKind.Ilt:
                    return PSLessThan(lhs, rhs, false);
                case TokenKind.Clt:
                    return PSLessThan(lhs, rhs, true);
                case TokenKind.Ilike:
                    return PSLike(lhs, rhs, isCaseSensitive: false);
                case TokenKind.Clike:
                    return PSLike(lhs, rhs, isCaseSensitive: true);
                case TokenKind.Inotlike:
                    return Not(PSLike(lhs, rhs, isCaseSensitive: false));
                case TokenKind.Cnotlike:
                    return Not(PSLike(lhs, rhs, isCaseSensitive: true));
                case TokenKind.Imatch:
                    return PSMatch(lhs, rhs, isCaseSensitive: false);
                case TokenKind.Cmatch:
                    return PSMatch(lhs, rhs, isCaseSensitive: true);
                case TokenKind.Inotmatch:
                    return Not(PSMatch(lhs, rhs, isCaseSensitive: false));
                case TokenKind.Cnotmatch:
                    return Not(PSMatch(lhs, rhs, isCaseSensitive: true));
                case TokenKind.Ieq:
                    return PSEquals(lhs, rhs, isCaseSensitive: false);
                case TokenKind.Ceq:
                    return PSEquals(lhs, rhs, isCaseSensitive: true);
                case TokenKind.Ine:
                    return Not(PSEquals(lhs, rhs, isCaseSensitive: false));
                case TokenKind.Cne:
                    return Not(PSEquals(lhs, rhs, isCaseSensitive: true));
                case TokenKind.And:
                    return AndAlso(PSIsTrue(lhs), PSIsTrue(rhs));
                case TokenKind.Or:
                    return OrElse(PSIsTrue(lhs), PSIsTrue(rhs));
                case TokenKind.Band:
                    return PSBitwiseOperation(ExpressionType.And, lhs, rhs);
                case TokenKind.Bor:
                    return PSBitwiseOperation(ExpressionType.Or, lhs, rhs);
                case TokenKind.Is:
                    if (rhsTypeConstant == null)
                    {
                        Errors.ReportNonConstantTypeAs(binaryExpressionAst.Right.Extent);
                        return Empty();
                    }

                    return TypeIs(lhs, rhsTypeConstant);
                case TokenKind.IsNot:
                    if (rhsTypeConstant == null)
                    {
                        Errors.ReportNonConstantTypeAs(binaryExpressionAst.Right.Extent);
                        return Empty();
                    }

                    return Not(TypeIs(lhs, rhsTypeConstant));
                case TokenKind.As:
                    if (rhsTypeConstant == null)
                    {
                        Errors.ReportNonConstantTypeAs(binaryExpressionAst.Right.Extent);
                        return Empty();
                    }

                    return PSTypeAs(lhs, rhsTypeConstant);
                case TokenKind.Plus:
                    return MakeBinary(ExpressionType.Add, lhs, rhs);
                case TokenKind.Minus:
                    return MakeBinary(ExpressionType.Subtract, lhs, rhs);
                case TokenKind.Divide:
                    return MakeBinary(ExpressionType.Divide, lhs, rhs);
                case TokenKind.Multiply:
                    return MakeBinary(ExpressionType.Multiply, lhs, rhs);
                case TokenKind.Rem:
                    return MakeBinary(ExpressionType.Modulo, lhs, rhs);
                default:
                    Errors.ReportNotSupported(
                        binaryExpressionAst.ErrorPosition,
                        binaryExpressionAst.Operator.ToString(),
                        binaryExpressionAst.Operator.ToString());
                    return Empty();
            }
        }

        public object VisitBlockStatement(BlockStatementAst blockStatementAst)
        {
            return blockStatementAst.Body.Visit(this);
        }

        public object VisitBreakStatement(BreakStatementAst breakStatementAst)
        {
            return _loops.Break != null
                ? Break(_loops.Break)
                : Return(_returns.GetOrCreateReturnLabel(typeof(void)));
        }

        public object VisitCatchClause(CatchClauseAst catchClauseAst)
        {
            static Type GetExceptionType(CatchClauseAst ast)
            {
                if (ast.IsCatchAll || ast.CatchTypes.Count > 1)
                {
                    return typeof(Exception);
                }

                return ast.CatchTypes.First().TypeName.GetReflectionType() ?? typeof(Exception);
            }

            Type catchType = GetExceptionType(catchClauseAst);

            using (_scopeStack.NewScope())
            {
                ParameterExpression dollarUnder = _scopeStack.SetDollarUnder(catchType);
                Expression catchBody = catchClauseAst.Body.Compile(this);

                if (catchBody.Type != typeof(void))
                {
                    catchBody = Block(typeof(void), catchBody);
                }

                return Catch(dollarUnder, catchBody);
            }
        }

        public object VisitCommandExpression(CommandExpressionAst commandExpressionAst)
        {
            if (commandExpressionAst.Redirections.Count > 0)
            {
                commandExpressionAst.Redirections.CompileAll(this);
                return Empty();
            }

            return commandExpressionAst.Expression.Visit(this);
        }

        public object VisitConstantExpression(ConstantExpressionAst constantExpressionAst)
        {
            return Constant(
                constantExpressionAst.SafeGetValue(),
                constantExpressionAst.StaticType);
        }

        public object VisitContinueStatement(ContinueStatementAst continueStatementAst)
        {
            return Continue(_loops?.Continue);
        }

        public object VisitConvertExpression(ConvertExpressionAst convertExpressionAst)
        {
            if (!TryResolveType(convertExpressionAst.Attribute, out Type resolvedType))
            {
                return Empty();
            }

            if (convertExpressionAst.Child is VariableExpressionAst variableExpression &&
                convertExpressionAst.Parent is AssignmentStatementAst assignment &&
                assignment.Left == convertExpressionAst)
            {
                return _scopeStack.GetVariable(
                    variableExpression.VariablePath.UserPath,
                    resolvedType);
            }

            return PSConvertTo(
                convertExpressionAst.Child.Compile(this),
                resolvedType);
        }

        public object VisitDoUntilStatement(DoUntilStatementAst doUntilStatementAst)
        {
            return NewBlock(() =>
            {
                using (_loops.NewScope())
                {
                    var body = doUntilStatementAst.Body.Compile(this);
                    return new[]
                    {
                        body,
                        Loop(
                            IfThenElse(
                                Not(PSIsTrue(doUntilStatementAst.Condition.Compile(this))),
                                body,
                                Break(_loops.Break)),
                            _loops.Break,
                            _loops.Continue),
                    };
                }
            });
        }

        public object VisitDoWhileStatement(DoWhileStatementAst doWhileStatementAst)
        {
            return NewBlock(() =>
            {
                using (_loops.NewScope())
                {
                    var body = doWhileStatementAst.Body.Compile(this);
                    return new[]
                    {
                        body,
                        Loop(
                            IfThenElse(
                                PSIsTrue(doWhileStatementAst.Condition.Compile(this)),
                                body,
                                Break(_loops.Break)),
                            _loops.Break,
                            _loops.Continue),
                    };
                }
            });
        }

        public object VisitExitStatement(ExitStatementAst exitStatementAst)
        {
            if (exitStatementAst.Pipeline == null)
            {
                return Throw(New(ReflectionCache.ExitException_Ctor));
            }

            return Throw(
                New(
                    ReflectionCache.ExitException_Ctor_Object,
                    Convert(exitStatementAst.Pipeline.Compile(this), typeof(object))));
        }

        public object VisitForEachStatement(ForEachStatementAst forEachStatementAst)
        {
            using (_loops.NewScope())
            {
                var condition = forEachStatementAst.Condition.Compile(this);
                var canEnumerate = TryGetEnumeratorMethod(
                    condition.Type,
                    out MethodInfo getEnumeratorMethod,
                    out MethodInfo getCurrentMethod);

                if (!canEnumerate)
                {
                    Errors.ReportParseError(
                        forEachStatementAst.Condition.Extent,
                        nameof(ErrorStrings.ForEachInvalidEnumerable),
                        string.Format(
                            CultureInfo.CurrentCulture,
                            ErrorStrings.ForEachInvalidEnumerable,
                            condition.Type));

                    Errors.ThrowIfAnyErrors();
                    return Empty();
                }

                using (_scopeStack.NewScope())
                {
                    var enumeratorRef = _scopeStack.GetVariable(
                        Strings.ForEachVariableName,
                        getEnumeratorMethod.ReturnType);
                    try
                    {
                        return Block(
                            _scopeStack.GetVariables(),
                            Assign(enumeratorRef, Call(condition, getEnumeratorMethod)),
                            Loop(
                                IfThenElse(
                                    test: Call(enumeratorRef, ReflectionCache.IEnumerator_MoveNext),
                                    ifTrue: NewBlock(
                                        () => new[]
                                        {
                                            Assign(
                                                _scopeStack.GetVariable(
                                                    forEachStatementAst.Variable.VariablePath.UserPath,
                                                    getCurrentMethod.ReturnType),
                                                Call(enumeratorRef, getCurrentMethod)),
                                            forEachStatementAst.Body.Compile(this),
                                        }),
                                    ifFalse: Break(_loops.Break)),
                                _loops.Break,
                                _loops.Continue));
                    }
                    catch (ArgumentException e)
                    {
                        Errors.ReportParseError(forEachStatementAst.Extent, e);
                        return Empty();
                    }
                }
            }
        }

        public object VisitForStatement(ForStatementAst forStatementAst)
        {
            return NewBlock(
                () =>
                {
                    using (_loops.NewScope())
                    {
                        return new[]
                        {
                            forStatementAst.Initializer.Compile(this),
                            Loop(
                                IfThenElse(
                                    forStatementAst.Condition.Compile(this),
                                    NewBlock(() => new[]
                                    {
                                        forStatementAst.Body.Compile(this),
                                        forStatementAst.Iterator?.Compile(this) ?? Empty(),
                                    }),
                                    Break(_loops.Break)),
                                _loops.Break,
                                _loops.Continue),
                        };
                    }
                });
        }

        public object VisitHashtable(HashtableAst hashtableAst)
        {
            if (hashtableAst.KeyValuePairs.Count == 0)
            {
                return New(
                    ReflectionCache.Hashtable_Ctor,
                    Constant(0),
                    Property(null, ReflectionCache.StringComparer_CurrentCultureIgnoreCase));
            }

            var elements = new ElementInit[hashtableAst.KeyValuePairs.Count];
            for (var i = 0; i < elements.Length; i++)
            {
                elements[i] = ElementInit(
                    ReflectionCache.Hashtable_Add,
                    Convert(hashtableAst.KeyValuePairs[i].Item1.Compile(this), typeof(object)),
                    Convert(hashtableAst.KeyValuePairs[i].Item2.Compile(this), typeof(object)));
            }

            return ListInit(
                New(
                    ReflectionCache.Hashtable_Ctor,
                    Constant(hashtableAst.KeyValuePairs.Count, typeof(int)),
                    Property(null, ReflectionCache.StringComparer_CurrentCultureIgnoreCase)),
                elements);
        }

        public object VisitIfStatement(IfStatementAst ifStmtAst)
        {
            return HandleRemainingClauses(ifStmtAst);
        }

        public object VisitIndexExpression(IndexExpressionAst indexExpressionAst)
        {
            var source = indexExpressionAst.Target.Compile(this);
            if (source.Type.IsArray)
            {
                return ArrayIndex(
                    source,
                    indexExpressionAst.Index.Compile(this));
            }

            var defaultMember =
                source.Type.GetCustomAttribute<DefaultMemberAttribute>(inherit: true);

            if (defaultMember == null)
            {
                Errors.ReportParseError(
                    indexExpressionAst.Extent,
                    nameof(ErrorStrings.UnknownIndexer),
                    string.Format(
                        CultureInfo.CurrentCulture,
                        ErrorStrings.UnknownIndexer,
                        source.Type.FullName));
                return Empty();
            }

            var index = indexExpressionAst.Index.Compile(this);
            var indexerProperty = source.Type
                .GetProperty(defaultMember.MemberName, new[] { index.Type });

            if (indexerProperty == null)
            {
                Errors.ReportParseError(
                    indexExpressionAst.Extent,
                    nameof(ErrorStrings.UnknownIndexer),
                    string.Format(
                        CultureInfo.CurrentCulture,
                        ErrorStrings.UnknownIndexer,
                        source.Type.FullName));
                return Empty();
            }

            return Property(
                source,
                indexerProperty,
                index);
        }

        public object VisitInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
        {
            return CompileInvokeMemberExpression(invokeMemberExpressionAst);
        }

        public object VisitMemberExpression(MemberExpressionAst memberExpressionAst)
        {
            if (!TryResolveConstant(memberExpressionAst.Member, out string memberName))
            {
                return Empty();
            }

            if (memberExpressionAst.Static)
            {
                if (!TryResolveType(memberExpressionAst.Expression, out Type resolvedType))
                {
                    return Empty();
                }

                var resolvedMember = resolvedType
                    .FindMembers(
                        MemberTypes.Property | MemberTypes.Field,
                        BindingFlags.Public | BindingFlags.Static,
                        Type.FilterNameIgnoreCase,
                        memberName)
                    .OrderBy(m => m.MemberType == MemberTypes.Property)
                    .FirstOrDefault();

                if (resolvedMember == null)
                {
                    Errors.ReportParseError(
                        memberExpressionAst.Extent,
                        nameof(ErrorStrings.MissingMember),
                        string.Format(
                            CultureInfo.CurrentCulture,
                            ErrorStrings.MissingMember,
                            memberName,
                            resolvedType.FullName));
                    return Empty();
                }

                if (resolvedMember is FieldInfo field)
                {
                    return Field(null, field);
                }

                return Property(null, (PropertyInfo)resolvedMember);
            }

            try
            {
                return PropertyOrField(
                    memberExpressionAst.Expression.Compile(this),
                    memberName);
            }
            catch (ArgumentException e)
            {
                Errors.ReportParseError(memberExpressionAst.Extent, e, nameof(ErrorStrings.MissingMember));
                return Empty();
            }
        }

        public object VisitNamedBlock(NamedBlockAst namedBlockAst)
        {
            if (namedBlockAst.Statements.Count < 1)
            {
                return Empty();
            }

            return NewBlock(() => namedBlockAst.Statements.CompileAll(this));
        }

        public object VisitParamBlock(ParamBlockAst paramBlockAst)
        {
            foreach (var parameter in paramBlockAst.Parameters)
            {
                parameter.Visit(this);
            }

            return Empty();
        }

        public object VisitParameter(ParameterAst parameterAst)
        {
            return Parameter(
                GetParameterType(parameterAst),
                parameterAst.Name.VariablePath.UserPath);
        }

        public object VisitParenExpression(ParenExpressionAst parenExpressionAst)
        {
            return parenExpressionAst.Pipeline.Visit(this);
        }

        public object VisitPipeline(PipelineAst pipelineAst)
        {
            if (pipelineAst.PipelineElements.Count > 1)
            {
                Errors.ReportNotSupported(
                    pipelineAst.Extent,
                    nameof(ErrorStrings.Pipeline),
                    ErrorStrings.Pipeline);
                return Empty();
            }

            if (pipelineAst.PipelineElements.Count == 0)
            {
                return Empty();
            }

            return pipelineAst.PipelineElements[0].Visit(this);
        }

        public object VisitReturnStatement(ReturnStatementAst returnStatementAst)
        {
            if (returnStatementAst.Pipeline == null)
            {
                try
                {
                    return Return(_returns.GetOrCreateReturnLabel(typeof(void)));
                }
                catch (ArgumentException e)
                {
                    Errors.ReportParseError(returnStatementAst.Extent, e);
                    return Empty();
                }
            }

            Expression pipeline = returnStatementAst.Pipeline.Compile(this);
            try
            {
                return Return(
                    _returns.GetOrCreateReturnLabel(pipeline.Type),
                    pipeline);
            }
            catch (ArgumentException e)
            {
                Errors.ReportParseError(returnStatementAst.Extent, e);
                return Empty();
            }
        }

        public object VisitScriptBlock(ScriptBlockAst scriptBlockAst)
        {
            if (scriptBlockAst.Parent is ScriptBlockExpressionAst sbExpression &&
                sbExpression.Parent is ConvertExpressionAst convert &&
                TryResolveType(convert.Attribute, out Type resolvedType))
            {
                return CompileAstImpl(scriptBlockAst, s_emptyVariables, resolvedType);
            }

            return CompileAstImpl(scriptBlockAst, s_emptyVariables);
        }

        public object VisitScriptBlockExpression(ScriptBlockExpressionAst scriptBlockExpressionAst)
        {
            return scriptBlockExpressionAst.ScriptBlock.Visit(this);
        }

        public object VisitStatementBlock(StatementBlockAst statementBlockAst)
        {
            if (statementBlockAst.Statements.Count == 0)
            {
                return Empty();
            }

            using (_scopeStack.NewScope())
            {
                var stmts = new Expression[statementBlockAst.Statements.Count];
                for (var i = 0; i < stmts.Length; i++)
                {
                    stmts[i] = statementBlockAst.Statements[i].Compile(this);
                }

                try
                {
                    return Block(_scopeStack.GetVariables(), stmts);
                }
                catch (ArgumentException e)
                {
                    Errors.ReportParseError(statementBlockAst.Extent, e);
                    return Empty();
                }
            }
        }

        public object VisitStringConstantExpression(StringConstantExpressionAst stringConstantExpressionAst)
        {
            return Constant(stringConstantExpressionAst.SafeGetValue(), typeof(string));
        }

        public object VisitSubExpression(SubExpressionAst subExpressionAst)
        {
            return subExpressionAst.SubExpression.Visit(this);
        }

        public object VisitSwitchStatement(SwitchStatementAst switchStatementAst)
        {
            return NewBlock(() =>
            {
                using (_loops.NewScope())
                {
                    if (switchStatementAst.Clauses.Count == 0)
                    {
                        return new[]
                        {
                            switchStatementAst.Default.Compile(this),
                            Label(_loops.Break),
                        };
                    }

                    var clauses = new SwitchCase[switchStatementAst.Clauses.Count];
                    for (var i = 0; i < clauses.Length; i++)
                    {
                        clauses[i] = SwitchCase(
                            NewBlock(
                                () => switchStatementAst.Clauses[i].Item2.Compile(this),
                                typeof(void)),
                            switchStatementAst.Clauses[i].Item1.Compile(this));
                    }

                    Expression defaultBlock = null;
                    if (switchStatementAst.Default != null)
                    {
                        defaultBlock = NewBlock(
                            () => switchStatementAst.Default.Compile(this),
                            typeof(void));
                    }

                    return new Expression[]
                    {
                        Switch(
                            switchStatementAst.Condition.Compile(this),
                            defaultBlock,
                            ReflectionCache.ExpressionUtils_PSEqualsIgnoreCase,
                            clauses),
                        Label(_loops.Break),
                    };
                }
            });
        }

        public object VisitThrowStatement(ThrowStatementAst throwStatementAst)
        {
            if (throwStatementAst.IsRethrow)
            {
                return Rethrow();
            }

            if (throwStatementAst.Pipeline == null)
            {
                return Throw(
                    New(
                        ReflectionCache.RuntimeException_Ctor,
                        Constant(CompilerStrings.DefaultThrowMessage)));
            }

            var pipeline = throwStatementAst.Pipeline.Compile(this);
            if (typeof(Exception).IsAssignableFrom(pipeline.Type))
            {
                return Throw(pipeline);
            }

            return Throw(
                New(
                    ReflectionCache.RuntimeException_Ctor,
                    PSConvertTo<string>(pipeline)));
        }

        public object VisitTryStatement(TryStatementAst tryStatementAst)
        {
            if (tryStatementAst.Finally != null && tryStatementAst.CatchClauses.Any())
            {
                return TryCatchFinally(
                    NewBlock(() => tryStatementAst.Body.Compile(this), typeof(void)),
                    NewBlock(() => tryStatementAst.Finally.Compile(this), typeof(void)),
                    tryStatementAst.CatchClauses
                        .Select(ctch => ctch.Visit(this))
                        .Cast<CatchBlock>()
                        .ToArray());
            }

            if (tryStatementAst.Finally == null)
            {
                return TryCatch(
                    NewBlock(() => tryStatementAst.Body.Compile(this), typeof(void)),
                    tryStatementAst.CatchClauses
                        .Select(ctch => ctch.Visit(this))
                        .Cast<CatchBlock>()
                        .ToArray());
            }

            return TryFinally(
                NewBlock(() => tryStatementAst.Body.Compile(this), typeof(void)),
                NewBlock(() => tryStatementAst.Finally.Compile(this), typeof(void)));
        }

        public object VisitTypeExpression(TypeExpressionAst typeExpressionAst)
        {
            if (TryResolveType(typeExpressionAst, out Type type))
            {
                return Constant(type, typeof(Type));
            }

            return Empty();
        }

        public object VisitUnaryExpression(UnaryExpressionAst unaryExpressionAst)
        {
            var child = unaryExpressionAst.Child.Compile(this);
            switch (unaryExpressionAst.TokenKind)
            {
                case TokenKind.PostfixPlusPlus:
                    return Assign(child, Increment(child));
                case TokenKind.PostfixMinusMinus:
                    return Assign(child, Decrement(child));
                case TokenKind.Not:
                    return Not(PSIsTrue(child));
                default:
                    Errors.ReportNotSupported(
                        unaryExpressionAst.Extent,
                        unaryExpressionAst.TokenKind.ToString(),
                        unaryExpressionAst.TokenKind.ToString());
                    return Empty();
            }
        }

        public object VisitWhileStatement(WhileStatementAst whileStatementAst)
        {
            using (_loops.NewScope())
            {
                return Loop(
                    IfThenElse(
                        PSIsTrue(whileStatementAst.Condition.Compile(this)),
                        whileStatementAst.Body.Compile(this),
                        Break(_loops.Break)),
                    _loops.Break,
                    _loops.Continue);
            }
        }

        public object VisitVariableExpression(VariableExpressionAst variableExpressionAst)
        {
            bool isDollarUnder = VariableUtils.IsDollarUnder(variableExpressionAst);
            if (isDollarUnder && _scopeStack.TryGetDollarUnder(out ParameterExpression dollarUnder))
            {
                return dollarUnder;
            }

            if (SpecialVariables.Constants.TryGetValue(variableExpressionAst.VariablePath.UserPath, out Expression expression))
            {
                return expression;
            }

            if (_locals.TryGetValue(variableExpressionAst.VariablePath.UserPath, out PSVariable local))
            {
                return GetExpressionForLocal(local);
            }

            if (!SpecialVariables.AllScope.ContainsKey(variableExpressionAst.VariablePath.UserPath))
            {
                bool alreadyDefined = false;
                try
                {
                    if (!isDollarUnder)
                    {
                        return _scopeStack.GetVariable(variableExpressionAst, out alreadyDefined);
                    }
                }
                finally
                {
                    if (!alreadyDefined &&
                        !(variableExpressionAst.Parent is AssignmentStatementAst ||
                        (variableExpressionAst.Parent is ConvertExpressionAst &&
                        variableExpressionAst.Parent.Parent is AssignmentStatementAst)))
                    {
                        Errors.ReportParseError(
                            variableExpressionAst.Extent,
                            nameof(ErrorStrings.InvalidVariableReference),
                            string.Format(
                                CultureInfo.CurrentCulture,
                                ErrorStrings.InvalidVariableReference,
                                variableExpressionAst.VariablePath.UserPath));
                    }
                }
            }

            return GetExpressionForAllScope(variableExpressionAst.VariablePath.UserPath);
        }

        public object VisitCommand(CommandAst commandAst)
        {
            if (commandAst.Redirections.Count > 0)
            {
                commandAst.Redirections.CompileAll(this);
                return Empty();
            }

            if (s_commands.TryProcessAst(commandAst, this, out Expression expression))
            {
                return expression;
            }

            Errors.ReportNotSupportedAstType(commandAst);
            return Empty();
        }

        public object VisitExpandableStringExpression(ExpandableStringExpressionAst expandableStringExpressionAst)
        {
            var sb = new System.Text.StringBuilder(expandableStringExpressionAst.Value);
            var formatExpressions = new Expression[expandableStringExpressionAst.NestedExpressions.Count];
            var reversed = expandableStringExpressionAst.NestedExpressions.Reverse().ToArray();
            for (var i = 0; i < reversed.Length; i++)
            {
                var startOffset = reversed[i].Extent.StartOffset - expandableStringExpressionAst.Extent.StartOffset - 1;
                sb.Remove(
                    startOffset,
                    reversed[i].Extent.Text.Length)
                    .Insert(
                        startOffset,
                        string.Format(
                            CultureInfo.InvariantCulture,
                            "{{{0}}}",
                            i));

                formatExpressions[i] = PSConvertTo<string>(reversed[i].Compile(this));
            }

            return PSFormat(
                Constant(sb.ToString()),
                NewArrayInit(typeof(object), formatExpressions));
        }

        public object VisitTypeDefinition(TypeDefinitionAst typeDefinitionAst)
        {
            Errors.ReportNotSupportedAstType(typeDefinitionAst);
            return Empty();
        }

        public object VisitPropertyMember(PropertyMemberAst propertyMemberAst)
        {
            Errors.ReportNotSupportedAstType(propertyMemberAst);
            return Empty();
        }

        public object VisitFunctionMember(FunctionMemberAst functionMemberAst)
        {
            Errors.ReportNotSupportedAstType(functionMemberAst);
            return Empty();
        }

        public object VisitBaseCtorInvokeMemberExpression(BaseCtorInvokeMemberExpressionAst baseCtorInvokeMemberExpressionAst)
        {
            Errors.ReportNotSupportedAstType(baseCtorInvokeMemberExpressionAst);
            return Empty();
        }

        public object VisitUsingStatement(UsingStatementAst usingStatement)
        {
            Errors.ReportNotSupportedAstType(usingStatement);
            return Empty();
        }

        public object VisitConfigurationDefinition(ConfigurationDefinitionAst configurationDefinitionAst)
        {
            Errors.ReportNotSupportedAstType(configurationDefinitionAst);
            return Empty();
        }

        public object VisitDynamicKeywordStatement(DynamicKeywordStatementAst dynamicKeywordAst)
        {
            Errors.ReportNotSupportedAstType(dynamicKeywordAst);
            return Empty();
        }

        public object VisitCommandParameter(CommandParameterAst commandParameterAst)
        {
            Errors.ReportNotSupportedAstType(commandParameterAst);
            return Empty();
        }

        public object VisitDataStatement(DataStatementAst dataStatementAst)
        {
            Errors.ReportNotSupportedAstType(dataStatementAst);
            return Empty();
        }

        public object VisitErrorExpression(ErrorExpressionAst errorExpressionAst)
        {
            Errors.ReportNotSupportedAstType(errorExpressionAst);
            return Empty();
        }

        public object VisitErrorStatement(ErrorStatementAst errorStatementAst)
        {
            Errors.ReportNotSupportedAstType(errorStatementAst);
            return Empty();
        }

        public object VisitFileRedirection(FileRedirectionAst fileRedirectionAst)
        {
            Errors.ReportNotSupportedAstType(fileRedirectionAst);
            return Empty();
        }

        public object VisitFunctionDefinition(FunctionDefinitionAst functionDefinitionAst)
        {
            Errors.ReportNotSupportedAstType(functionDefinitionAst);
            return Empty();
        }

        public object VisitMergingRedirection(MergingRedirectionAst mergingRedirectionAst)
        {
            Errors.ReportNotSupportedAstType(mergingRedirectionAst);
            return Empty();
        }

        public object VisitNamedAttributeArgument(NamedAttributeArgumentAst namedAttributeArgumentAst)
        {
            Errors.ReportNotSupportedAstType(namedAttributeArgumentAst);
            return Empty();
        }

        public object VisitTrap(TrapStatementAst trapStatementAst)
        {
            Errors.ReportNotSupportedAstType(trapStatementAst);
            return Empty();
        }

        public object VisitTypeConstraint(TypeConstraintAst typeConstraintAst)
        {
            Errors.ReportNotSupportedAstType(typeConstraintAst);
            return Empty();
        }

        public object VisitUsingExpression(UsingExpressionAst usingExpressionAst)
        {
            Errors.ReportNotSupportedAstType(usingExpressionAst);
            return Empty();
        }

        public object VisitAttribute(AttributeAst attributeAst)
        {
            Errors.ReportNotSupportedAstType(attributeAst);
            return Empty();
        }

        public object VisitAttributedExpression(AttributedExpressionAst attributedExpressionAst)
        {
            Errors.ReportNotSupportedAstType(attributedExpressionAst);
            return Empty();
        }
        #pragma warning restore SA1600

        /// <summary>
        /// Compiles a <see cref="Ast" /> into a <see cref="Delegate" />.
        /// </summary>
        /// <param name="engine">The <see cref="EngineIntrinsics" /> for the current runspace.</param>
        /// <param name="sbAst">The <see cref="ScriptBlockAst" /> to compile.</param>
        /// <param name="localVariables">
        /// Any <see cref="PSVariable" /> objects that should be accessible from the delegate and
        /// are not AllScope or Constant.
        /// </param>
        /// <param name="delegateType">A type that inherits <see cref="Delegate" />.</param>
        /// <returns>The compiled <see cref="Delegate" />.</returns>
        internal static Delegate CompileAst(
            EngineIntrinsics engine,
            ScriptBlockAst sbAst,
            PSVariable[] localVariables,
            Type delegateType)
        {
            var visitor = new CompileVisitor(engine);
            return visitor.CompileAstImpl(sbAst, localVariables, delegateType).Compile();
        }

        /// <summary>
        /// Compiles a <see cref="Ast" /> into a <see cref="Delegate" />.
        /// </summary>
        /// <param name="engine">The <see cref="EngineIntrinsics" /> for the current runspace.</param>
        /// <param name="sbAst">The <see cref="ScriptBlockAst" /> to compile.</param>
        /// <param name="localVariables">
        /// Any <see cref="PSVariable" /> objects that should be accessible from the delegate and
        /// are not AllScope or Constant.
        /// </param>
        /// <returns>The compiled <see cref="Delegate" />.</returns>
        internal static Delegate CompileAst(
            EngineIntrinsics engine,
            ScriptBlockAst sbAst,
            PSVariable[] localVariables)
        {
            var visitor = new CompileVisitor(engine);
            return visitor.CompileAstImpl(sbAst, localVariables).Compile();
        }

        /// <summary>
        /// Compile an <see cref="InvokeMemberExpressionAst" />.
        /// </summary>
        /// <param name="invokeMemberExpressionAst">The AST to interpret.</param>
        /// <returns>The generated <see cref="Expression" />.</returns>
        internal Expression CompileInvokeMemberExpression(InvokeMemberExpressionAst invokeMemberExpressionAst)
        {
            return CompileInvokeMemberExpression(invokeMemberExpressionAst, Type.EmptyTypes);
        }

        /// <summary>
        /// Compile an <see cref="InvokeMemberExpressionAst" />.
        /// </summary>
        /// <param name="invokeMemberExpressionAst">The AST to interpret.</param>
        /// <param name="genericArguments">
        /// The generic type arguments to use while resolving the method.
        /// </param>
        /// <returns>The generated <see cref="Expression" />.</returns>
        internal Expression CompileInvokeMemberExpression(
            InvokeMemberExpressionAst invokeMemberExpressionAst,
            Type[] genericArguments)
        {
            Ast[] arguments;
            if (genericArguments.Length < 1 &&
                TryGetGenericArgumentDeclaration(invokeMemberExpressionAst, ref genericArguments))
            {
                arguments = new Ast[invokeMemberExpressionAst.Arguments.Count - 1];
                for (var i = 0; i < arguments.Length; i++)
                {
                    arguments[i] = invokeMemberExpressionAst.Arguments[i + 1];
                }
            }
            else
            {
                arguments = invokeMemberExpressionAst?.Arguments?.ToArray() ?? new Ast[0];
            }

            if (!TryResolveConstant(invokeMemberExpressionAst.Member, out string memberName))
            {
                return Empty();
            }

            if (invokeMemberExpressionAst.Static)
            {
                if (!TryResolveType(invokeMemberExpressionAst.Expression, out Type resolvedType))
                {
                    return Empty();
                }

                if (memberName.Equals(Strings.ConstructorMemberName, StringComparison.Ordinal))
                {
                    var expressions = arguments.CompileAll(this);
                    return New(GetBestConstructor(resolvedType, expressions), expressions);
                }

                try
                {
                    var result = GetBinder(invokeMemberExpressionAst)
                        .BindMethod(
                            this,
                            resolvedType,
                            memberName,
                            arguments,
                            genericArguments);

                    if (result.Expression != null)
                    {
                        return result.Expression;
                    }

                    Errors.ReportParseError(
                        invokeMemberExpressionAst.Member.Extent,
                        result.Reason,
                        result.Id);
                    return Empty();
                }
                catch (InvalidOperationException e)
                {
                    Errors.ReportParseError(invokeMemberExpressionAst.Extent, e, nameof(ErrorStrings.MissingMember));
                    return Empty();
                }
            }

            try
            {
                var instance = invokeMemberExpressionAst.Expression.Compile(this);
                var result = GetBinder(invokeMemberExpressionAst)
                    .BindMethod(
                        this,
                        instance,
                        memberName,
                        arguments,
                        genericArguments);

                if (result.Expression != null)
                {
                    return result.Expression;
                }

                Errors.ReportParseError(
                    invokeMemberExpressionAst.Member.Extent,
                    result.Reason,
                    result.Id);
                return Empty();
            }
            catch (InvalidOperationException e)
            {
                Errors.ReportParseError(invokeMemberExpressionAst.Extent, e, nameof(ErrorStrings.MissingMember));
                return Empty();
            }
        }

        /// <summary>
        /// Create a new block under a new variable scope.
        /// </summary>
        /// <param name="expressionFactory">A function that generates the body of the block.</param>
        /// <returns>An <see cref="Expression" /> representing the block.</returns>
        internal Expression NewBlock(Func<Expression> expressionFactory)
        {
            using (_scopeStack.NewScope())
            {
                var expressions = expressionFactory();
                return Block(_scopeStack.GetVariables(), expressions);
            }
        }

        /// <summary>
        /// Create a new block under a new variable scope.
        /// </summary>
        /// <param name="expressionFactory">A function that generates the body of the block.</param>
        /// <returns>An <see cref="Expression" /> representing the block.</returns>
        internal Expression NewBlock(Func<IEnumerable<Expression>> expressionFactory)
        {
            using (_scopeStack.NewScope())
            {
                var expressions = expressionFactory();
                return Block(_scopeStack.GetVariables(), expressions);
            }
        }

        /// <summary>
        /// Create a new block under a new variable scope.
        /// </summary>
        /// <param name="expressionFactory">A function that generates the body of the block.</param>
        /// <param name="blockReturnType">The expected return type for the block.</param>
        /// <returns>An <see cref="Expression" /> representing the block.</returns>
        internal Expression NewBlock(Func<Expression> expressionFactory, Type blockReturnType)
        {
            using (_scopeStack.NewScope())
            {
                Expression[] expressions;
                if (blockReturnType == typeof(void))
                {
                    expressions = new[] { expressionFactory(), Empty() };
                }
                else
                {
                    expressions = new[] { expressionFactory() };
                }

                if (blockReturnType == null)
                {
                    return Block(_scopeStack.GetVariables(), expressions);
                }

                return Block(blockReturnType, _scopeStack.GetVariables(), expressions);
            }
        }

        /// <summary>
        /// Attempt to resolve a type constant from an <see cref="Ast" />. If unable, then generate
        /// a <see cref="ParseError" />.
        /// </summary>
        /// <param name="ast">
        /// The <see cref="Ast" /> that is expected to hold a type literal expression.
        /// </param>
        /// <param name="resolvedType">The <see cref="Type" /> if resolution was successful.</param>
        /// <returns><c>true</c> if resolution was successful, otherwise <c>false</c>.</returns>
        internal bool TryResolveType(Ast ast, out Type resolvedType)
        {
            if (ast == null)
            {
                resolvedType = null;
                Errors.ReportParseError(
                    ast.Extent,
                    nameof(ErrorStrings.MissingType),
                    ErrorStrings.MissingType);
                return false;
            }

            if (ast is TypeExpressionAst typeExpression)
            {
                return TryResolveType(typeExpression.TypeName, out resolvedType);
            }

            if (ast is TypeConstraintAst typeConstraint)
            {
                return TryResolveType(typeConstraint.TypeName, out resolvedType);
            }

            resolvedType = null;
            Errors.ReportParseError(
                ast.Extent,
                nameof(ErrorStrings.MissingType),
                ErrorStrings.MissingType);
            return false;
        }

        /// <summary>
        /// Compiles a <see cref="Ast" /> into a <see cref="LambdaExpression" />.
        /// </summary>
        /// <param name="scriptBlockAst">The <see cref="ScriptBlockAst" /> to compile.</param>
        /// <param name="localVariables">
        /// Any <see cref="PSVariable" /> objects that should be accessible from the delegate and
        /// are not AllScope or Constant.
        /// </param>
        /// <param name="delegateType">
        /// The type inheriting from <see cref="Delegate" /> that the
        /// <see cref="LambdaExpression" /> is expected to be.
        /// </param>
        /// <returns>The interpreted <see cref="LambdaExpression" />.</returns>
        internal LambdaExpression CompileAstImpl(
            ScriptBlockAst scriptBlockAst,
            PSVariable[] localVariables,
            Type delegateType)
        {
            if (delegateType != null)
            {
                var delegateMethod = delegateType.GetMethod("Invoke");
                var parameters = delegateMethod.GetParameters();
                var parameterTypes = new Type[parameters.Length];
                for (var i = 0; i < parameterTypes.Length; i++)
                {
                    parameterTypes[i] = parameters[i].ParameterType;
                }

                return CompileAstImpl(
                    scriptBlockAst,
                    localVariables,
                    parameterTypes,
                    delegateMethod.ReturnType,
                    delegateType);
            }

            return CompileAstImpl(
                scriptBlockAst,
                localVariables,
                null,
                null,
                null);
        }

        /// <summary>
        /// Compiles a <see cref="Ast" /> into a <see cref="LambdaExpression" />.
        /// </summary>
        /// <param name="scriptBlockAst">The <see cref="ScriptBlockAst" /> to compile.</param>
        /// <param name="localVariables">
        /// Any <see cref="PSVariable" /> objects that should be accessible from the delegate and
        /// are not AllScope or Constant.
        /// </param>
        /// <param name="expectedParameterTypes">The parameter types to expect.</param>
        /// <param name="expectedReturnType">The return type to expect.</param>
        /// <param name="delegateType">
        /// The type inheriting from <see cref="Delegate" /> that the
        /// <see cref="LambdaExpression" /> is expected to be.
        /// </param>
        /// <returns>The interpreted <see cref="LambdaExpression" />.</returns>
        internal LambdaExpression CompileAstImpl(
            ScriptBlockAst scriptBlockAst,
            PSVariable[] localVariables,
            Type[] expectedParameterTypes,
            Type expectedReturnType,
            Type delegateType)
        {
            foreach (var local in localVariables)
            {
                _locals.Add(local.Name, local);
            }

            scriptBlockAst = ConvertToDelegateAst(scriptBlockAst);
            var lambda = CreateLambda(
                scriptBlockAst,
                () =>
                {
                    using (_scopeStack.NewScope())
                    {
                        var returnsHandle = expectedReturnType == null
                            ? _returns.NewScope()
                            : _returns.NewScope(expectedReturnType);
                        using (returnsHandle)
                        {
                            var requireExplicitReturn =
                                scriptBlockAst.EndBlock.Statements.Count != 1 ||
                                    !(scriptBlockAst.EndBlock.Statements[0] is CommandBaseAst ||
                                    scriptBlockAst.EndBlock.Statements[0] is PipelineAst);

                            var body = scriptBlockAst.EndBlock.Compile(this);
                            return Block(
                                _scopeStack.GetVariables(),
                                _returns.WithReturn(new[] { body }, requireExplicitReturn));
                        }
                    }
                },
                expectedParameterTypes,
                expectedReturnType,
                delegateType);

            Errors.ThrowIfAnyErrors();
            return lambda;
        }

        private static bool FilterGenericInterfaceDefinition(Type m, object filterCriteria)
        {
            return m.IsGenericType && m.GetGenericTypeDefinition() == (Type)filterCriteria;
        }

        private ScriptBlockAst ConvertToDelegateAst(ScriptBlockAst scriptBlockAst)
        {
            return (ScriptBlockAst)scriptBlockAst.Visit(
                new DelegateSyntaxVisitor(Errors));
        }

        private bool TryGetGenericArgumentDeclaration(InvokeMemberExpressionAst ast, ref Type[] arguments)
        {
            if (ast.Arguments == null || ast.Arguments.Count == 0)
            {
                return false;
            }

            if (!(ast.Arguments[0] is TypeExpressionAst typeExpression))
            {
                return false;
            }

            if (!(typeExpression.TypeName is GenericTypeName genericTypeName))
            {
                return false;
            }

            if (!genericTypeName.TypeName.Name.Equals("g", StringComparison.OrdinalIgnoreCase))
            {
                return false;
            }

            arguments = new Type[genericTypeName.GenericArguments.Count];
            for (var i = 0; i < arguments.Length; i++)
            {
                arguments[i] = genericTypeName.GenericArguments[i].GetReflectionType();
                if (arguments[i] == null)
                {
                    Errors.ReportParseError(
                        genericTypeName.GenericArguments[i].Extent,
                        nameof(ErrorStrings.TypeNotFound),
                        string.Format(
                            CultureInfo.CurrentCulture,
                            ErrorStrings.TypeNotFound,
                            genericTypeName.GenericArguments[i].FullName));
                    Errors.ThrowIfAnyErrors();
                    return false;
                }
            }

            return true;
        }

        private LambdaExpression CompileAstImpl(
            ScriptBlockAst scriptBlockAst,
            PSVariable[] localVariables)
        {
            return CompileAstImpl(
                scriptBlockAst,
                localVariables,
                null);
        }

        private Expression MakeAssignment(
            Expression lhs,
            Expression rhs,
            TokenKind operatorKind,
            IScriptExtent operationExtent)
        {
            try
            {
                switch (operatorKind)
                {
                    case TokenKind.Equals:
                        return Assign(lhs, rhs);
                    case TokenKind.PlusEquals:
                        return MakeBinary(ExpressionType.AddAssign, lhs, rhs);
                    case TokenKind.MinusEquals:
                        return MakeBinary(ExpressionType.SubtractAssign, lhs, rhs);
                    case TokenKind.MultiplyEquals:
                        return MakeBinary(ExpressionType.MultiplyAssign, lhs, rhs);
                    case TokenKind.DivideEquals:
                        return MakeBinary(ExpressionType.DivideAssign, lhs, rhs);
                    case TokenKind.RemainderEquals:
                        return MakeBinary(ExpressionType.ModuloAssign, lhs, rhs);
                }
            }
            catch (InvalidOperationException e)
            {
                Errors.ReportParseError(operationExtent, e);
                return Empty();
            }

            Errors.ReportNotSupported(
                operationExtent,
                Strings.OperatorNotSupportedId,
                operatorKind.ToString());
            return Empty();
        }

        private Expression HandleRemainingClauses(IfStatementAst ifStmtAst, int clauseIndex = 0)
        {
            if (clauseIndex >= ifStmtAst.Clauses.Count)
            {
                if (ifStmtAst.ElseClause != null)
                {
                    return ifStmtAst.ElseClause.Compile(this);
                }

                return Empty();
            }

            return IfThenElse(
                PSIsTrue(ifStmtAst.Clauses[clauseIndex].Item1.Compile(this)),
                ifStmtAst.Clauses[clauseIndex].Item2.Compile(this),
                HandleRemainingClauses(ifStmtAst, clauseIndex + 1));
        }

        private LambdaExpression CreateLambda(
            ScriptBlockAst scriptBlockAst,
            Func<Expression> blockFactory,
            Type[] expectedParameterTypes,
            Type expectedReturnType,
            Type delegateType)
        {
            if (!(delegateType != null || expectedParameterTypes != null || expectedReturnType != null))
            {
                using (_scopeStack.NewScope(GetParameters(scriptBlockAst?.ParamBlock)))
                {
                    return Lambda(
                        blockFactory(),
                        _scopeStack.GetParameters());
                }
            }

            var scope =
                _scopeStack.NewScope(
                    GetParameters(
                        scriptBlockAst?.ParamBlock,
                        expectedParameterTypes));

            using (scope)
            {
                if (delegateType == null)
                {
                    return Lambda(
                        NewBlock(
                            () => blockFactory(),
                            expectedReturnType),
                        _scopeStack.GetParameters());
                }

                return Lambda(
                    delegateType,
                    NewBlock(
                        () => blockFactory(),
                        expectedReturnType),
                    _scopeStack.GetParameters());
            }
        }

        private ParameterExpression GetParameter(ParameterAst parameterAst, Type expectedType)
        {
            return Parameter(
                expectedType ?? GetParameterType(parameterAst),
                parameterAst.Name.VariablePath.UserPath);
        }

        private Type GetParameterType(ParameterAst parameter)
        {
            return parameter.Attributes
                .OfType<TypeConstraintAst>()
                .FirstOrDefault()
                ?.TypeName
                .GetReflectionType()
                ?? typeof(object);
        }

        private ConstructorInfo GetBestConstructor(
            Type type,
            Expression[] arguments)
        {
            return (ConstructorInfo)Type.DefaultBinder.SelectMethod(
                BindingFlags.Default,
                type.GetConstructors(),
                arguments.Select(a => a.Type).ToArray(),
                arguments.Length == 0 ? s_emptyModifiers : new[] { new ParameterModifier(arguments.Length) });
        }

        private MethodInfo GetBestMethod(
            Type type,
            string name,
            Expression[] arguments)
        {
            return (MethodInfo)Type.DefaultBinder.SelectMethod(
                BindingFlags.Default,
                Array.ConvertAll(
                    type.FindMembers(MemberTypes.Method, BindingFlags.Default, Type.FilterName, name),
                    member => (MethodInfo)member),
                arguments.Select(a => a.Type).ToArray(),
                new[] { new ParameterModifier(arguments.Length) });
        }

        private ParameterExpression[] GetParameters(ParamBlockAst ast)
        {
            if (ast?.Parameters == null || ast.Parameters.Count == 0)
            {
                return s_emptyParameters;
            }

            return ast.Parameters.Select(p => (ParameterExpression)p.Visit(this)).ToArray();
        }

        private ParameterExpression[] GetParameters(ParamBlockAst ast, Type[] expectedParameterTypes)
        {
            if (ast == null || ast.Parameters.Count == 0)
            {
                return s_emptyParameters;
            }

            var parameters = new ParameterExpression[ast.Parameters.Count];
            for (var i = 0; i < ast.Parameters.Count; i++)
            {
                parameters[i] = GetParameter(ast.Parameters[i], expectedParameterTypes[i]);
            }

            return parameters;
        }

        private Expression CreateWrappedVariableExpression(PSVariable variable, Type variableType)
        {
            return Property(
                Constant(
                    typeof(PSVariableWrapper<>).MakeGenericType(variableType)
                        .GetConstructor(new[] { typeof(PSVariable) })
                        .Invoke(new[] { variable })),
                Strings.PSVariableWrapperValuePropertyName);
        }

        private Expression GetExpressionForLocal(PSVariable variable)
        {
            if (s_wrapperCache.TryGetValue(variable, out Expression cachedExpression))
            {
                return cachedExpression;
            }

            var expression = CreateWrappedVariableExpression(variable, GetTypeForVariable(variable));
            s_wrapperCache.Add(variable, expression);
            return expression;
        }

        private Expression GetExpressionForAllScope(string name)
        {
            if (_allScopes == null)
            {
                using var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace);
                _allScopes = pwsh
                    .AddCommand("Microsoft.PowerShell.Utility\\Get-Variable")
                    .AddParameter("Scope", "Global")
                    .Invoke<PSVariable>()
                    .Where(v => v.Options.HasFlag(ScopedItemOptions.AllScope))
                    .ToDictionary(v => v.Name, StringComparer.OrdinalIgnoreCase);
            }

            if (s_wrapperCache.TryGetValue(_allScopes[name], out Expression cachedExpression))
            {
                return cachedExpression;
            }

            var expression = CreateWrappedVariableExpression(
                _allScopes[name],
                SpecialVariables.AllScope[name]);
            s_wrapperCache.Add(_allScopes[name], expression);
            return expression;
        }

        private Type GetTypeForVariable(PSVariable variable)
        {
            if (SpecialVariables.AllScope.TryGetValue(variable.Name, out Type variableType))
            {
                return variableType;
            }

            var transAttribute = variable.Attributes.OfType<ArgumentTransformationAttribute>().FirstOrDefault();
            if (transAttribute == null)
            {
                return GetTypeForVariableByValue(variable);
            }

            if (ReflectionCache.ArgumentTypeConverterAttribute == null ||
                ReflectionCache.ArgumentTypeConverterAttribute_TargetType == null)
            {
                return GetTypeForVariableByValue(variable);
            }

            if (!ReflectionCache.ArgumentTypeConverterAttribute.IsAssignableFrom(transAttribute.GetType()))
            {
                return GetTypeForVariableByValue(variable);
            }

            return (Type)ReflectionCache.ArgumentTypeConverterAttribute_TargetType.GetValue(transAttribute);
        }

        private Type GetTypeForVariableByValue(PSVariable variable)
        {
            if (variable.Value == null)
            {
                return typeof(object);
            }

            return variable.Value.GetType();
        }

        private bool TryResolveType(ITypeName typeName, out Type resolvedType)
        {
            resolvedType = typeName.GetReflectionType();
            if (resolvedType != null)
            {
                return true;
            }

            Errors.ReportParseError(
                typeName.Extent,
                nameof(ErrorStrings.TypeNotFound),
                string.Format(
                    CultureInfo.CurrentCulture,
                    ErrorStrings.TypeNotFound,
                    typeName.FullName));
            return false;
        }

        private bool TryResolveConstant<TResult>(Ast ast, out TResult resolvedValue)
        {
            object rawValue;
            try
            {
                rawValue = ast.SafeGetValue();
            }
            catch (InvalidOperationException)
            {
                Errors.ReportParseError(
                    ast.Extent,
                    nameof(ErrorStrings.UnexpectedExpression),
                    ErrorStrings.UnexpectedExpression);
                resolvedValue = default;
                return false;
            }

            resolvedValue = (TResult)rawValue;
            return true;
        }

        private bool TryFindGenericInterface(
            Type implementation,
            Type genericDefinition,
            out Type resolvedInterface)
        {
            resolvedInterface = implementation
                .FindInterfaces(FilterGenericInterfaceDefinition, genericDefinition)
                .FirstOrDefault();

            return resolvedInterface != null;
        }

        private MemberBinder GetBinder(Ast ast)
        {
            if (_binder != null)
            {
                return _binder;
            }

            while (ast.Parent != null)
            {
                ast = ast.Parent;
            }

            var namespaces = new HashSet<string>(s_defaultNamespaces);
            foreach (var ns in ((ScriptBlockAst)ast).UsingStatements)
            {
                if (ns.UsingStatementKind != UsingStatementKind.Namespace)
                {
                    continue;
                }

                namespaces.Add(ns.Name.Value);
            }

            _binder = new MemberBinder(BindingFlags.Public, namespaces.ToArray());
            return _binder;
        }

        private bool TryGetEnumeratorMethod(
            Type type,
            out MethodInfo getEnumeratorMethod,
            out MethodInfo getCurrentMethod)
        {
            var canFallbackToEnumerable = false;
            var canFallbackToIDictionary = false;
            var interfaces = type.GetInterfaces();
            for (var i = 0; i < interfaces.Length; i++)
            {
                if (interfaces[i] == typeof(IEnumerable))
                {
                    canFallbackToEnumerable = true;
                    continue;
                }

                if (interfaces[i] == typeof(IDictionary))
                {
                    canFallbackToIDictionary = true;
                    continue;
                }

                if (interfaces[i].IsConstructedGenericType &&
                    interfaces[i].GetGenericTypeDefinition() == typeof(IEnumerable<>))
                {
                    getEnumeratorMethod = interfaces[i].GetMethod(
                        Strings.GetEnumeratorMethodName,
                        Type.EmptyTypes);

                    getCurrentMethod =
                        getEnumeratorMethod.ReturnType.GetMethod(
                            Strings.EnumeratorGetCurrentMethodName,
                            Type.EmptyTypes);
                    return true;
                }
            }

            if (canFallbackToIDictionary)
            {
                getEnumeratorMethod = ReflectionCache.IDictionary_GetEnumerator;
                getCurrentMethod = ReflectionCache.IDictionaryEnumerator_get_Entry;
                return true;
            }

            if (canFallbackToEnumerable)
            {
                getEnumeratorMethod = ReflectionCache.IEnumerable_GetEnumerator;
                getCurrentMethod = ReflectionCache.IEnumerator_get_Current;
                return true;
            }

            getEnumeratorMethod = null;
            getCurrentMethod = null;
            return false;
        }
    }
}
<file_sep>/src/PSLambda/Commands/CommandService.cs
using System;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda.Commands
{
    /// <summary>
    /// Provides management of custom command handlers.
    /// </summary>
    internal class CommandService
    {
        private readonly Dictionary<string, ICommandHandler> _commandRegistry = new Dictionary<string, ICommandHandler>(StringComparer.OrdinalIgnoreCase);

        /// <summary>
        /// Initializes a new instance of the <see cref="CommandService" /> class.
        /// </summary>
        public CommandService()
        {
            RegisterCommand(new DefaultCommand());
            RegisterCommand(new WithCommand());
            RegisterCommand(new GenericCommand());
            RegisterCommand(new LockCommand());
        }

        /// <summary>
        /// Registers a command handler.
        /// </summary>
        /// <param name="handler">The handler to register.</param>
        public void RegisterCommand(ICommandHandler handler)
        {
            if (_commandRegistry.ContainsKey(handler.CommandName))
            {
                return;
            }

            _commandRegistry.Add(handler.CommandName, handler);
        }

        /// <summary>
        /// Attempt to process a <see cref="CommandAst" /> as a custom command.
        /// </summary>
        /// <param name="commandAst">The <see cref="CommandAst" /> to process.</param>
        /// <param name="visitor">
        /// The <see cref="CompileVisitor" /> requesting the expression.
        /// </param>
        /// <param name="expression">
        /// The <see cref="Expression" /> result if a command handler was found.
        /// </param>
        /// <returns><c>true</c> if a commmand handler was matched, otherwise <c>false</c>.</returns>
        public bool TryProcessAst(CommandAst commandAst, CompileVisitor visitor, out Expression expression)
        {
            if (_commandRegistry.TryGetValue(commandAst.GetCommandName(), out ICommandHandler handler))
            {
                expression = handler.ProcessAst(commandAst, visitor);
                return true;
            }

            expression = null;
            return false;
        }
    }
}
<file_sep>/src/PSLambda/ExpressionUtils.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Reflection;
using System.Text.RegularExpressions;
using static System.Linq.Expressions.Expression;

namespace PSLambda
{
    /// <summary>
    /// Provides utility methods for creating <see cref="Expression" /> objects for
    /// operations meant to mimic the PowerShell engine.
    /// </summary>
    internal static class ExpressionUtils
    {
        /// <summary>
        /// Creates an <see cref="Expression" /> representing a less than comparison operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSLessThan(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Equal(PSCompare(lhs, rhs, isCaseSensitive), Constant(-1));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a greater than comparison operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSGreaterThan(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Equal(PSCompare(lhs, rhs, isCaseSensitive), Constant(1));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a equals comparison operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSEquals(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Equal(PSCompare(lhs, rhs, isCaseSensitive), Constant(0));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a comparison operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSCompare(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Call(
                ReflectionCache.LanguagePrimitives_Compare,
                Convert(lhs, typeof(object)),
                Convert(rhs, typeof(object)),
                Constant(!isCaseSensitive, typeof(bool)));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a match comparision operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSMatch(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Call(
                ReflectionCache.Regex_IsMatch,
                PSConvertTo<string>(lhs),
                PSConvertTo<string>(rhs),
                Constant(
                    isCaseSensitive
                        ? RegexOptions.None
                        : RegexOptions.IgnoreCase,
                    typeof(RegexOptions)));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a like comparision operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSLike(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Call(
                New(
                    ReflectionCache.WildcardPattern_Ctor,
                    PSConvertTo<string>(rhs),
                    Constant(
                        isCaseSensitive ? WildcardOptions.None : WildcardOptions.IgnoreCase,
                        typeof(WildcardOptions))),
                ReflectionCache.WildcardPattern_IsMatch,
                PSConvertTo<string>(lhs));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a conversion operation using the
        /// PowerShell engine.
        /// </summary>
        /// <param name="target">The <see cref="Expression" /> to convert.</param>
        /// <param name="targetType">The <see cref="Type" /> to convert to.</param>
        /// <returns>An <see cref="Expression" /> representing the conversion.</returns>
        public static Expression PSConvertTo(Expression target, Type targetType)
        {
            return Convert(
                Call(
                    ReflectionCache.LanguagePrimitives_ConvertTo,
                    Convert(target, typeof(object)),
                    Constant(targetType, typeof(Type))),
                targetType);
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a conversion operation using the
        /// PowerShell engine.
        /// </summary>
        /// <param name="target">The <see cref="Expression" /> to convert.</param>
        /// <typeparam name="T">The <see cref="Type" /> to convert to.</typeparam>
        /// <returns>An <see cref="Expression" /> representing the conversion.</returns>
        public static Expression PSConvertTo<T>(Expression target)
        {
            return Call(
                ReflectionCache.LanguagePrimitives_ConvertToGeneric.MakeGenericMethod(new[] { typeof(T) }),
                Convert(target, typeof(object)));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a conversion operation using the
        /// PowerShell engine.
        /// </summary>
        /// <param name="source">
        /// The <see cref="Expression" /> representing multiple expressions that need to be converted.
        /// </param>
        /// <typeparam name="T">The <see cref="Type" /> to convert to.</typeparam>
        /// <returns>An <see cref="Expression" /> representing the conversion.</returns>
        public static Expression PSConvertAllTo<T>(Expression source)
        {
            var enumeratorVar = Variable(typeof(IEnumerator));
            var collectionVar = Variable(typeof(List<T>));
            var breakLabel = Label();
            return Block(
                new[] { enumeratorVar, collectionVar },
                Assign(collectionVar, New(typeof(List<T>))),
                Assign(
                    enumeratorVar,
                    Call(
                        ReflectionCache.LanguagePrimitives_GetEnumerator,
                        source)),
                Loop(
                    IfThenElse(
                        Call(enumeratorVar, ReflectionCache.IEnumerator_MoveNext),
                        Call(
                            collectionVar,
                            Strings.AddMethodName,
                            Type.EmptyTypes,
                            PSConvertTo<T>(Call(enumeratorVar, ReflectionCache.IEnumerator_get_Current))),
                        Break(breakLabel)),
                    breakLabel),
                Call(collectionVar, Strings.ToArrayMethodName, Type.EmptyTypes));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of an
        /// <see cref="Expression" /> as being <c>true</c> using the rules of the PowerShell engine.
        /// </summary>
        /// <param name="eval">The <see cref="Expression" /> to evaluate.</param>
        /// <returns>An <see cref="Expression" /> representing the evaluation.</returns>
        public static Expression PSIsTrue(Expression eval)
        {
            return Call(
                ReflectionCache.LanguagePrimitives_IsTrue,
                Convert(eval, typeof(object)));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the conversion of a type using the
        /// "as" operator using the conversion rules of the PowerShell engine.
        /// </summary>
        /// <param name="eval">The <see cref="Expression" /> to convert.</param>
        /// <param name="expectedType">The <see cref="Type" /> to convert to.</param>
        /// <returns>An <see cref="Expression" /> representing the conversion.</returns>
        public static Expression PSTypeAs(Expression eval, Type expectedType)
        {
            var resultVar = Variable(expectedType);
            return Block(
                expectedType,
                new[] { resultVar },
                Call(
                    ReflectionCache.LangaugePrimitives_TryConvertToGeneric.MakeGenericMethod(expectedType),
                    Convert(eval, typeof(object)),
                    resultVar),
                resultVar);
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of the "join" operator
        /// from the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSJoin(Expression lhs, Expression rhs)
        {
            return Call(
                ReflectionCache.String_Join,
                PSConvertTo<string>(rhs),
                PSConvertAllTo<string>(lhs));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of the "replace" operator
        /// from the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSReplace(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Call(
                ReflectionCache.Regex_Replace,
                PSConvertTo<string>(lhs),
                PSConvertTo<string>(ArrayIndex(rhs, Constant(0))),
                PSConvertTo<string>(ArrayIndex(rhs, Constant(1))),
                Constant(isCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of the "split" operator
        /// from the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSSplit(Expression lhs, Expression rhs, bool isCaseSensitive)
        {
            return Call(
                ReflectionCache.Regex_Split,
                PSConvertTo<string>(lhs),
                PSConvertTo<string>(rhs),
                Constant(isCaseSensitive ? RegexOptions.None : RegexOptions.IgnoreCase));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing a in comparison operator in
        /// the PowerShell engine.
        /// </summary>
        /// <param name="item">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="items">The <see cref="Expression" /> on the right hand side.</param>
        /// <param name="isCaseSensitive">
        /// A value indicating whether the operation should be case sensitive.
        /// </param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSIsIn(Expression item, Expression items, bool isCaseSensitive)
        {
            var returnLabel = Label(typeof(bool));
            var enumeratorVar = Variable(typeof(IEnumerator));
            return Block(
                new[] { enumeratorVar },
                Assign(
                    enumeratorVar,
                    Call(
                        ReflectionCache.LanguagePrimitives_GetEnumerator,
                        items)),
                Loop(
                    IfThenElse(
                        Call(enumeratorVar, ReflectionCache.IEnumerator_MoveNext),
                        IfThen(
                            PSEquals(
                                item,
                                Call(enumeratorVar, ReflectionCache.IEnumerator_get_Current),
                                isCaseSensitive),
                            Return(returnLabel, SpecialVariables.Constants[Strings.TrueVariableName])),
                        Return(returnLabel, SpecialVariables.Constants[Strings.FalseVariableName]))),
                Label(returnLabel, SpecialVariables.Constants[Strings.FalseVariableName]));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of the "format" operator
        /// from the PowerShell engine.
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSFormat(Expression lhs, Expression rhs)
        {
            if (rhs.NodeType == ExpressionType.NewArrayInit)
            {
                return Call(
                    ReflectionCache.String_Format,
                    Property(null, ReflectionCache.CultureInfo_CurrentCulture),
                    PSConvertTo<string>(lhs),
                    PSConvertAllTo<string>(rhs));
            }

            return Call(
                ReflectionCache.String_Format,
                Property(null, ReflectionCache.CultureInfo_CurrentCulture),
                PSConvertTo<string>(lhs),
                NewArrayInit(typeof(object), PSConvertTo<string>(rhs)));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of the "DotDot" operator
        /// from the PowerShell engine (e.g. 0..100).
        /// </summary>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSDotDot(Expression lhs, Expression rhs)
        {
            return Call(
                ReflectionCache.ExpressionUtils_GetRange,
                PSConvertTo<int>(lhs),
                PSConvertTo<int>(rhs));
        }

        /// <summary>
        /// Creates an <see cref="Expression" /> representing the evaluation of a bitwise comparision
        /// operator from the PowerShell engine.
        /// </summary>
        /// <param name="expressionType">The expression operator.</param>
        /// <param name="lhs">The <see cref="Expression" /> on the left hand side.</param>
        /// <param name="rhs">The <see cref="Expression" /> on the right hand side.</param>
        /// <returns>An <see cref="Expression" /> representing the operation.</returns>
        public static Expression PSBitwiseOperation(
            ExpressionType expressionType,
            Expression lhs,
            Expression rhs)
        {
            var resultType = lhs.Type;
            if (typeof(Enum).IsAssignableFrom(lhs.Type))
            {
                lhs = Convert(lhs, Enum.GetUnderlyingType(lhs.Type));
            }

            if (typeof(Enum).IsAssignableFrom(rhs.Type))
            {
                rhs = Convert(rhs, Enum.GetUnderlyingType(rhs.Type));
            }

            var resultExpression = MakeBinary(expressionType, lhs, rhs);
            if (resultType == resultExpression.Type)
            {
                return resultExpression;
            }

            return PSConvertTo(resultExpression, resultType);
        }

        private static bool PSEqualsIgnoreCase(object first, object second)
        {
            return LanguagePrimitives.Compare(first, second, ignoreCase: true) == 0;
        }

        private static int[] GetRange(int start, int end)
        {
            bool shouldReverse = false;
            if (start > end)
            {
                var tempStart = start;
                start = end;
                end = tempStart;
                shouldReverse = true;
            }

            var result = new int[end - start + 1];
            for (var i = 0; i < result.Length; i++)
            {
                result[i] = i + start;
            }

            if (!shouldReverse)
            {
                return result;
            }

            for (var i = 0; i < result.Length / 2; i++)
            {
                var temp = result[i];
                result[i] = result[result.Length - 1 - i];
                result[result.Length - 1 - i] = temp;
            }

            return result;
        }
    }
}
<file_sep>/src/PSLambda/PSVariableWrapper.cs
using System.Management.Automation;

namespace PSLambda
{
    /// <summary>
    /// Provides a strongly typed wrapper for <see cref="PSVariable" /> objects.
    /// </summary>
    /// <typeparam name="TValue">The type of object contained by the <see cref="PSVariable" />.</typeparam>
    internal class PSVariableWrapper<TValue>
    {
        private readonly object _syncObject = new object();

        private readonly PSVariable _wrappedVariable;

        /// <summary>
        /// Initializes a new instance of the <see cref="PSVariableWrapper{TValue}" /> class.
        /// </summary>
        /// <param name="variable">The <see cref="PSVariable" /> to wrap.</param>
        public PSVariableWrapper(PSVariable variable)
        {
            _wrappedVariable = variable;
        }

        /// <summary>
        /// Gets or sets the value of the <see cref="PSVariable" /> object.
        /// </summary>
        public TValue Value
        {
            get
            {
                lock (_syncObject)
                {
                    return LanguagePrimitives.ConvertTo<TValue>(_wrappedVariable.Value);
                }
            }

            set
            {
                lock (_syncObject)
                {
                    _wrappedVariable.Value = value;
                }
            }
        }
    }
}
<file_sep>/src/PSLambda/ReflectionCache.cs
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Reflection;
using System.Text.RegularExpressions;

namespace PSLambda
{
    /// <summary>
    /// Provides cached objects for generating method and property invocation
    /// <see cref="System.Linq.Expressions.Expression" /> objects.
    /// </summary>
    internal static class ReflectionCache
    {
        /// <summary>
        /// Resolves to <see cref="LanguagePrimitives.GetEnumerator(object)" />.
        /// </summary>
        public static readonly MethodInfo LanguagePrimitives_GetEnumerator =
            typeof(LanguagePrimitives).GetMethod("GetEnumerator", new[] { typeof(object) });

        /// <summary>
        /// Resolves to <see cref="LanguagePrimitives.IsTrue(object)" />.
        /// </summary>
        public static readonly MethodInfo LanguagePrimitives_IsTrue =
            typeof(LanguagePrimitives).GetMethod("IsTrue", new[] { typeof(object) });

        /// <summary>
        /// Resolves to <see cref="LanguagePrimitives.Compare(object, object, bool)" />.
        /// </summary>
        public static readonly MethodInfo LanguagePrimitives_Compare =
            typeof(LanguagePrimitives).GetMethod("Compare", new[] { typeof(object), typeof(object), typeof(bool) });

        /// <summary>
        /// Resolves to <see cref="LanguagePrimitives.ConvertTo{T}(object)" />.
        /// </summary>
        public static readonly MethodInfo LanguagePrimitives_ConvertToGeneric =
            typeof(LanguagePrimitives).GetMethod("ConvertTo", new[] { typeof(object) });

        /// <summary>
        /// Resolves to <see cref="LanguagePrimitives.ConvertTo(object, Type)" />.
        /// </summary>
        public static readonly MethodInfo LanguagePrimitives_ConvertTo =
            typeof(LanguagePrimitives).GetMethod("ConvertTo", new[] { typeof(object), typeof(Type) });

        /// <summary>
        /// Resolves to <see cref="LanguagePrimitives.TryConvertTo{T}(object, out T)" />.
        /// </summary>
        public static readonly MethodInfo LangaugePrimitives_TryConvertToGeneric =
            (MethodInfo)typeof(LanguagePrimitives).FindMembers(
                MemberTypes.Method,
                BindingFlags.Static | BindingFlags.Public,
                (m, filterCriteria) =>
                {
                    var method = m as MethodInfo;
                    if (!method.IsGenericMethod)
                    {
                        return false;
                    }

                    var parameters = method.GetParameters();
                    return parameters.Length == 2
                        && parameters[0].ParameterType == typeof(object)
                        && parameters[1].ParameterType.IsByRef;
                },
                null)
                .FirstOrDefault();

        /// <summary>
        /// Resolves to <see cref="WildcardPattern.WildcardPattern(string, WildcardOptions)" />.
        /// </summary>
        public static readonly ConstructorInfo WildcardPattern_Ctor =
            typeof(WildcardPattern).GetConstructor(new[] { typeof(string), typeof(WildcardOptions) });

        /// <summary>
        /// Resolves to <see cref="Regex.IsMatch(string, string, RegexOptions)" />.
        /// </summary>
        public static readonly MethodInfo Regex_IsMatch =
            typeof(Regex).GetMethod("IsMatch", new[] { typeof(string), typeof(string), typeof(RegexOptions) });

        /// <summary>
        /// Resolves to <see cref="Regex.Replace(string, string, string, RegexOptions)" />.
        /// </summary>
        public static readonly MethodInfo Regex_Replace =
            typeof(Regex).GetMethod("Replace", new[] { typeof(string), typeof(string), typeof(string), typeof(RegexOptions) });

        /// <summary>
        /// Resolves to <see cref="Regex.Split(string, string, RegexOptions)" />.
        /// </summary>
        public static readonly MethodInfo Regex_Split =
            typeof(Regex).GetMethod("Split", new[] { typeof(string), typeof(string), typeof(RegexOptions) });

        /// <summary>
        /// Resolves to <see cref="WildcardPattern.IsMatch(string)" />.
        /// </summary>
        public static readonly MethodInfo WildcardPattern_IsMatch =
            typeof(WildcardPattern).GetMethod("IsMatch", new[] { typeof(string) });

        /// <summary>
        /// Resolves to <see cref="IEnumerator.MoveNext" />.
        /// </summary>
        public static readonly MethodInfo IEnumerator_MoveNext =
            typeof(IEnumerator).GetMethod("MoveNext");

        /// <summary>
        /// Resolves to <see cref="string.Join(string, string[])" />.
        /// </summary>
        public static readonly MethodInfo String_Join =
            typeof(string).GetMethod("Join", new[] { typeof(string), typeof(string[]) });

        /// <summary>
        /// Resolves to <see cref="string.Format(IFormatProvider, string, object[])" />.
        /// </summary>
        public static readonly MethodInfo String_Format =
            typeof(string).GetMethod("Format", new[] { typeof(IFormatProvider), typeof(string), typeof(object[]) });

        /// <summary>
        /// Resolves to <see cref="System.Globalization.CultureInfo.CurrentCulture" />.
        /// </summary>
        public static readonly PropertyInfo CultureInfo_CurrentCulture =
            typeof(System.Globalization.CultureInfo).GetProperty("CurrentCulture");

        /// <summary>
        /// Resolves to the non-public type System.Management.Automation.ArgumentTypeConverterAttribute.
        /// </summary>
        public static readonly Type ArgumentTypeConverterAttribute =
            typeof(PSObject).Assembly.GetType("System.Management.Automation.ArgumentTypeConverterAttribute");

        /// <summary>
        /// Resolves to the non-public property System.Management.Automation.ArgumentTypeConverterAttribute.TargetType.
        /// </summary>
        public static readonly PropertyInfo ArgumentTypeConverterAttribute_TargetType =
            ArgumentTypeConverterAttribute?.GetProperty("TargetType", BindingFlags.Instance | BindingFlags.NonPublic);

        /// <summary>
        /// Resolves to <see cref="RuntimeException.RuntimeException(string)" />.
        /// </summary>
        public static readonly ConstructorInfo RuntimeException_Ctor =
            typeof(RuntimeException).GetConstructor(new[] { typeof(string) });

        /// <summary>
        /// Resolves to <see cref="IDisposable.Dispose" />.
        /// </summary>
        public static readonly MethodInfo IDisposable_Dispose =
            typeof(IDisposable).GetMethod("Dispose");

        /// <summary>
        /// Resolves to the indexer for <see cref="System.Collections.IList" />.
        /// </summary>
        public static readonly PropertyInfo IList_Item =
            typeof(IList).GetProperty("Item");

        /// <summary>
        /// Resolves to the indexer for <see cref="System.Collections.IDictionary" />.
        /// </summary>
        public static readonly PropertyInfo IDictionary_Item =
            typeof(IDictionary).GetProperty("Item");

        /// <summary>
        /// Resolves to the non-public parameterless constructor for System.Management.Automation.ExitException.
        /// </summary>
        public static readonly ConstructorInfo ExitException_Ctor =
            typeof(ExitException).GetConstructor(
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new Type[0],
                new ParameterModifier[0]);

        /// <summary>
        /// Resolves to the non-public constructor System.Management.Automation.ExitException(Object).
        /// </summary>
        public static readonly ConstructorInfo ExitException_Ctor_Object =
            typeof(ExitException).GetConstructor(
                BindingFlags.Instance | BindingFlags.NonPublic,
                null,
                new[] { typeof(object) },
                new[] { new ParameterModifier(1) });

        /// <summary>
        /// Resolves to <see cref="Hashtable.Add(object, object)" />.
        /// </summary>
        public static readonly MethodInfo Hashtable_Add =
            typeof(Hashtable).GetMethod("Add", new[] { typeof(object), typeof(object) });

        /// <summary>
        /// Resolves to <see cref="Hashtable.Hashtable(int, IEqualityComparer)" />.
        /// </summary>
        public static readonly ConstructorInfo Hashtable_Ctor =
            typeof(Hashtable).GetConstructor(new[] { typeof(int), typeof(IEqualityComparer) });

        /// <summary>
        /// Resolves to <see cref="IEnumerator.get_Current" />.
        /// </summary>
        public static readonly MethodInfo IEnumerator_get_Current =
            typeof(IEnumerator).GetMethod(Strings.EnumeratorGetCurrentMethodName, Type.EmptyTypes);

        /// <summary>
        /// Resolves to <see cref="IEnumerable.GetEnumerator" />.
        /// </summary>
        public static readonly MethodInfo IEnumerable_GetEnumerator =
            typeof(IEnumerable).GetMethod(Strings.GetEnumeratorMethodName, Type.EmptyTypes);

        /// <summary>
        /// Resolves to <see cref="StringComparer.CurrentCultureIgnoreCase" />.
        /// </summary>
        public static readonly PropertyInfo StringComparer_CurrentCultureIgnoreCase =
            typeof(StringComparer).GetProperty("CurrentCultureIgnoreCase");

        /// <summary>
        /// Resolves to <see cref="ExpressionUtils.PSEqualsIgnoreCase(object, object)" />.
        /// </summary>
        public static readonly MethodInfo ExpressionUtils_PSEqualsIgnoreCase =
            typeof(ExpressionUtils).GetMethod("PSEqualsIgnoreCase", BindingFlags.Static | BindingFlags.NonPublic);

        /// <summary>
        /// Resolves to <see cref="ExpressionUtils.GetRange(int, int)" />.
        /// </summary>
        public static readonly MethodInfo ExpressionUtils_GetRange =
            typeof(ExpressionUtils).GetMethod("GetRange", BindingFlags.Static | BindingFlags.NonPublic);

        /// <summary>
        /// Resolves to <see cref="System.Threading.Monitor.Enter(object, ref bool)" />.
        /// </summary>
        public static readonly MethodInfo Monitor_Enter =
            typeof(System.Threading.Monitor).GetMethod(
                "Enter",
                new[] { typeof(object), typeof(bool).MakeByRefType() });

        /// <summary>
        /// Resolves to <see cref="System.Threading.Monitor.Exit(object)" />.
        /// </summary>
        public static readonly MethodInfo Monitor_Exit =
            typeof(System.Threading.Monitor).GetMethod("Exit", new[] { typeof(object) });

        /// <summary>
        /// Resolves to <see cref="IEnumerable{T}.GetEnumerator" />.
        /// </summary>
        public static readonly MethodInfo IEnumerable_T_GetEnumerator =
            typeof(IEnumerable<>).GetMethod(Strings.GetEnumeratorMethodName, Type.EmptyTypes);

        /// <summary>
        /// Resolves to <see cref="IEnumerator{T}.get_Current" />.
        /// </summary>
        public static readonly MethodInfo IEnumerator_T_get_Current =
            typeof(IEnumerator<>).GetMethod(Strings.EnumeratorGetCurrentMethodName, Type.EmptyTypes);

        /// <summary>
        /// Resolves to <see cref="IDictionary.GetEnumerator" />.
        /// </summary>
        public static readonly MethodInfo IDictionary_GetEnumerator =
            typeof(IDictionary).GetMethod(Strings.GetEnumeratorMethodName, Type.EmptyTypes);

        /// <summary>
        /// Resolves to <see cref="IDictionaryEnumerator.get_Entry" />.
        /// </summary>
        public static readonly MethodInfo IDictionaryEnumerator_get_Entry =
            typeof(IDictionaryEnumerator).GetMethod("get_Entry", Type.EmptyTypes);
    }
}
<file_sep>/src/PSLambda/Commands/ObjectAndBodyCommandHandler.cs
using System.Globalization;
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda.Commands
{
    /// <summary>
    /// Provides a base for commands that follow the format of
    /// `commandName ($objectTarget) { body }`.
    /// </summary>
    internal abstract class ObjectAndBodyCommandHandler : ICommandHandler
    {
        /// <summary>
        /// Gets the name of the command.
        /// </summary>
        public abstract string CommandName { get; }

        /// <summary>
        /// Creates a Linq expression for a <see cref="CommandAst" /> representing
        /// a custom command.
        /// </summary>
        /// <param name="commandAst">The AST to convert.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the expression.</param>
        /// <returns>An expression representing the command.</returns>
        public Expression ProcessAst(CommandAst commandAst, CompileVisitor visitor)
        {
            if (commandAst.CommandElements.Count != 3)
            {
                visitor.Errors.ReportParseError(
                    commandAst.Extent,
                    nameof(ErrorStrings.MissingKeywordElements),
                    string.Format(
                        CultureInfo.CurrentCulture,
                        ErrorStrings.MissingKeywordElements,
                        CommandName));
                return Expression.Empty();
            }

            if (!(commandAst.CommandElements[2] is ScriptBlockExpressionAst bodyAst))
            {
                visitor.Errors.ReportParseError(
                    commandAst.Extent,
                    nameof(ErrorStrings.MissingKeywordBody),
                    string.Format(
                        CultureInfo.CurrentCulture,
                        ErrorStrings.MissingKeywordBody,
                        CommandName));
                return Expression.Empty();
            }

            return ProcessObjectAndBody(
                commandAst,
                commandAst.CommandElements[1],
                bodyAst,
                visitor);
        }

        /// <summary>
        /// Creates a Linq expression for a <see cref="CommandAst" /> representing
        /// a custom command.
        /// </summary>
        /// <param name="commandAst">The AST to convert.</param>
        /// <param name="targetAst">The AST containing the target of the keyword.</param>
        /// <param name="bodyAst">The AST containing the body of the keyword.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the expression.</param>
        /// <returns>An expression representing the command.</returns>
        protected abstract Expression ProcessObjectAndBody(
            CommandAst commandAst,
            CommandElementAst targetAst,
            ScriptBlockExpressionAst bodyAst,
            CompileVisitor visitor);
    }
}
<file_sep>/README.md
# PSLambda

PSLambda is a runtime compiler for PowerShell ScriptBlock objects. This project is in a very early
state and is not currently recommended for use in production environments.

This project adheres to the Contributor Covenant [code of conduct](https://github.com/SeeminglyScience/PSLambda/tree/master/docs/CODE_OF_CONDUCT.md).
By participating, you are expected to uphold this code. Please report unacceptable behavior to <EMAIL>.

## Build status

|AppVeyor (Windows)|CircleCI (Linux)|CodeCov|
|---|---|---|
|[![Build status](https://ci.appveyor.com/api/projects/status/8n8alv7moy661rr6?svg=true)](https://ci.appveyor.com/project/SeeminglyScience/pslambda) |[![CircleCI](https://circleci.com/gh/SeeminglyScience/PSLambda.svg?style=svg)](https://circleci.com/gh/SeeminglyScience/PSLambda)|[![codecov](https://codecov.io/gh/SeeminglyScience/PSLambda/branch/master/graph/badge.svg)](https://codecov.io/gh/SeeminglyScience/PSLambda)|

## Features

- C# like syntax (due to being compiled from interpreted Linq expression trees) with some PowerShell
  convenience features built in. (See the "Differences from PowerShell" section)

- Run in threads without a `DefaultRunspace` as it does not actually execute any PowerShell code.

- Access and change variables local to the scope the delegate was created in (similar to closures in C#)

- Runs faster than PowerShell in most situations.

- Parse errors similar to the PowerShell parser for compiler errors, including extent of the error.

## Motivation

PowerShell is an excellent engine for exploration. When I'm exploring a new API, even if I intend to write
the actual project in C# I will do the majority of my exploration from PowerShell. Most of the time there
are no issues with doing that. Sometimes though, in projects that make heavy use of delegates you can run
into issues.

Yes the PowerShell engine can convert from `ScriptBlock` to any `Delegate` type, but it's just
a wrapper.  It still requires that `ScriptBlock` to be ran in a `Runspace` at some point. Sometimes
that isn't possible, or just isn't ideal. Mainly when it comes to API's that are mainly `async`/`await`
based.

I also just really like the idea of a more strict syntax in PowerShell without losing **too** much flavor,
so it was a fun project to get up and running.

## What would I use this for

For most folks the answer is probably nothing. This is pretty niche, if you haven't specifically wished
something like this existed in the past, it probably isn't applicable to you.

The reason cited in "Motivation" (interactive API exploration and prototyping) is the only real application
I am specifically planning for. If anyone has any intention of using this more broadly, I'd appreciate
it if you could open an issue or shoot me a DM so I can keep your scenario in mind.

## Installation

### Gallery

```powershell
Install-Module PSLambda -Scope CurrentUser
```

### Source

```powershell
git clone 'https://github.com/SeeminglyScience/PSLambda.git'
Set-Location .\PSLambda
Invoke-Build -Task Install -Configuration Release
```

## How

A custom `ICustomAstVisitor2` class visits each node in the abstract syntax tree of the ScriptBlock and
interprets it as a `System.Linq.Expressions.Expression`. Most of the PowerShell language features are
recreated from scratch as a Linq expression tree, using PowerShell API's where it makes sense to keep
some flavor. This is actually more or less what the PowerShell engine does as well, but it's obviously
still heavily reliant on the `Runspace`/`SessionState` system.

The `psdelegate` type acts as a stand in for the compiled delegate until it is interpreted. Using the
PowerShell type conversion system, if it is passed as an argument to a method that requires a specific
delegate type it is interpreted and compiled at method invocation.

## Usage

There's two main ways to use the module.

1. The `New-PSDelegate` command - this command will take a `ScriptBlock` and optionally a target `Delegate` type. With this method the delegate will be compiled immediately and will need to be recompiled if the delegate type needs to change.

1. The `psdelegate` type accelerator - you can cast a `ScriptBlock` as this type and it will retain the context until converted. This object can then be converted to a specific `Delegate` type later, either by explicittly casting the object as that type or implicitly as a method argument. Local variables are retained from when the `psdelegate` object is created. This method requires that the module be imported into the session as the type will not exist until it is.

### Demonstration of variable scoping and multi-threading

```powershell
using namespace System.Threading
using namespace System.Threading.Tasks
using namespace System.Collections.Concurrent

# PowerShell variables
$queue = [BlockingCollection[string]]::new()
$shouldCancel = [CancellationTokenSource]::new()

# Everything inside this is compiled.
$delegate = [psdelegate]{
    # The ThreadStart block doesn't require "psdelegate" because this is still within the
    # expression tree, including the resulting nested delegate.  Scope is kept.
    $thread = [Thread]::new([ThreadStart]{
        try {
            # Take from the blocking collection defined in PowerShell, and throw an
            # OperationCancelledException if the cancellation token source defined in
            # PowerShell has been cancelled.
            while ($message = $queue.Take($shouldCancel.Token)) {
                # All "AllScope" variables are available including $Host
                $Host.UI.WriteLine($message)

                # Implicit ScriptBlock to delegate conversion when it can determine the
                # method argument type correctly
                $task = [Task]::Run{
                    [Thread]::Sleep(1000)
                    # More Delegate nesting, still retaining variable scope.
                    $Host.UI.WriteLine("Delayed: $message")
                }

                $task.GetAwaiter().GetResult()
            }
        # Make sure to catch anything that could throw inside the ThreadStart delegate, uncaught
        # exceptions will crash PowerShell. Uncaught exceptions are fine outside of the ThreadStart
        # delegate and will throw like any other method.
        } catch [OperationCancelledException] {
            $Host.UI.WriteLine('Thread closed!')
        }
    })

    $thread.Start()
    return $thread
}

$thread = $delegate.Invoke()
$queue.Add('This is a test message!')
$queue.Add('Another test!')
$queue.Add('So many tests')

# To close the thread use:
# $shouldCancel.Cancel()
```

### Create a psdelegate to pass to a method

```powershell
$a = 0
$delegate = [psdelegate]{ $a += 1 }
$actions = $delegate, $delegate, $delegate, $delegate
[System.Threading.Tasks.Parallel]::Invoke($actions)
$a
# 4
```

Creates a delegate that increments the local `PSVariable` "a", and then invokes it in a different thread
four times. Doing the same with `ScriptBlock` objects instead would result in an error stating the
the thread does not have a default runspace.

*Note*: Access to local variables from the `Delegate` is synced across threads for *some* thread safety,
but that doesn't effect anything accessing the variable directly so they are still *not* thread safe.

### Access all scope variables

```powershell
$delegate = New-PSDelegate { $ExecutionContext.SessionState.InvokeProvider.Item.Get("\") }
$delegate.Invoke()
#     Directory:
# Mode          LastWriteTime   Length Name
# ----          -------------   ------ ----
# d--hs-  3/32/2010   9:61 PM          C:\
```

### Use alternate C# esque delegate syntax

```powershell
$timer = [System.Timers.Timer]::new(1000)
$delegate = [psdelegate]{ ($sender, $e) => { $Host.UI.WriteLine($e.SignalTime.ToString()) }}
$timer.Enabled = $true
$timer.add_Elapsed($delegate)
Start-Sleep 10
$timer.remove_Elapsed($delegate)
```

Create a timer that fires an event every 1000 milliseconds, then add a compiled delegated as an event
handler. A couple things to note here.

1. Parameter type inference is done automatically during conversion. This is important because the
  compiled delegate is *not* dynamically typed like PowerShell is.
1. `psdelegate` objects that have been converted to a specific Delegate type are cached, so the instance
  is the same in both `add_Elapsed` and `remove_Elapsed`.

## Differences from PowerShell

While the `ScriptBlock`'s used in the examples look like (and for the most part are) valid PowerShell
syntax, very little from PowerShell is actually used. The abstract syntax tree (AST) is read and
interpreted into a `System.Linq.Expressions.Expression` tree. The rules are a lot closer to C# than
to normal PowerShell. There are some very PowerShell like things thrown in to make it feel a bit more
like PowerShell though.

### Supported PowerShell features

- All operators, including `like`, `match`, `split`, and all the case sensitive/insensitive comparision
  operators. These work mostly the exact same as in PowerShell due to using `LanguagePrimitives` under
  the hood.

- PowerShell conversion rules. Explicit conversion (e.g. `[int]$myVar`) is done using `LanguagePrimitives`.
  This allows for all of the PowerShell type conversions to be accessible from the compiled delegate.
  However, type conversion is *not* automatic like it often is in PowerShell. Comparision operators are
  the exception, as they also use `LanguagePrimitives`.

- Access to PSVariables.  Any variable that is either `AllScope` (like `$Host` and `$ExecutionContext`)
  is from the most local scope is available as a variable in the delegate. This works similar to
  closures in C#, allowing the current value to be read as well as changed.  Changes to the value will
  only be seen in the scope the delegate was created in (unless the variable is AllScope).

### Unsupported PowerShell features

- The extended type system and dynamic typing in genernal.  This means that things like methods, properties
  and even the specific method overload called by an expression is all determined at compile time. If
  a method declares a return type of `object`, you'll need to cast it to something else before you can
  do much with it.

- Commands. Yeah that's a big one I know. There's no way to run a command without a runspace, and if I
  require a runspace to run a command then there isn't a point in any of this. If you absolutely need
  to run a command, you can use the `$ExecutionContext` or `[powershell]` API's, but they are likely to
  be unreliable from a thread other than the pipeline thread.

- Variables need to assigned before they can be used.  If the type is not included in the assignment
  the type will be inferred from the assignment (similar to C#'s `var` keyword, but implied).

- A good amount more. I'll update this list as much as possible as I use this more. If you run into
  something that could use explaining here, opening an issue on this repo would be very helpful.

## Custom language keywords

No support for commands left some room to add some custom keywords that help with some problems
that aren't all that applicable to PowerShell, but may be here.

### With

```powershell
using namespace System.Management.Automation

$delegate = [psdelegate]{
    with ($ps = [powershell]::Create([RunspaceMode]::NewRunspace)) {
        $result = $ps.AddScript('Get-Date').Invoke()
        $result[0].Properties.Add(
            [psnoteproperty]::new(
                'Runspace',
                $ps.Runspace))

        return $result[0]
    }
}

$pso = $delegate.Invoke()
$pso
# Sunday, April 22, 2018 6:51:04 PM
$pso.Runspace
# Id Name        ComputerName  Type     State   Availability
# -- ----        ------------  ----     -----   ------------
# 13 Runspace13  localhost     Local    Closed  None
```

The `with` keyword works like the `using` keyword in C#. The object in the initialization expression (the
parenthesis) will be disposed when the statement ends, even if the event of an unhandled exception.

### Default

```powershell

$delegate = [psdelegate]{ default([ConsoleColor]) }
$delegate.Invoke()
# Black

$delegate = [psdelegate]{ default([object]) }
$delegate.Invoke()
# (returns null)
```

Returns the default value for a type. This will return `null` if the type is nullable, otherwise it
will return the default value for the that type.

### Generic

```powershell
$delegate = [psdelegate]{ generic ([array]::Empty(), [int]) }
$delegate.Invoke().GetType()
# IsPublic IsSerial Name       BaseType
# -------- -------- ----       --------
# True     True     Int32[]    System.Array

$delegate = [psdelegate]{ generic ([tuple]::Create(10, 'string'), [int], [string]) }
$delegate.Invoke()
# Item1 Item2  Length
# ----- -----  ------
#    10 string      2
```

I haven't built in any automatic generic argument inference yet (PowerShell has a little bit), so
right now this keyword is required to use any generic method. The syntax for the generic type arguments
is similar to the `params` keyword.  The first "parameter" should be the the member expression, and
any additional parameters should be types. The resulting `Expression` is just the member expression
with the resolved method.

## Contributions Welcome!

We would love to incorporate community contributions into this project.  If you would like to
contribute code, documentation, tests, or bug reports, please read our [Contribution Guide](https://github.com/SeeminglyScience/PSLambda/tree/master/docs/CONTRIBUTING.md) to learn more.

<file_sep>/src/PSLambda/VariableUtils.cs
using System;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides utility methods for variable expression operations.
    /// </summary>
    internal static class VariableUtils
    {
        /// <summary>
        /// Determines if the specified variable is referencing the automatic
        /// variable <c>$_</c> or <c>$PSItem</c>.
        /// </summary>
        /// <param name="variableExpressionAst">The variable expression to test.</param>
        /// <returns>
        /// <see langword="true" /> if the specified variable references
        /// dollar under, otherwise <see langword="false" />.
        /// </returns>
        public static bool IsDollarUnder(VariableExpressionAst variableExpressionAst)
            => IsDollarUnder(variableExpressionAst.VariablePath.UserPath);

        /// <summary>
        /// Determines if the specified variable is referencing the automatic
        /// variable <c>$_</c> or <c>$PSItem</c>.
        /// </summary>
        /// <param name="variableName">The variable name to test.</param>
        /// <returns>
        /// <see langword="true" /> if the specified variable references
        /// dollar under, otherwise <see langword="false" />.
        /// </returns>
        public static bool IsDollarUnder(string variableName)
        {
            return variableName.Equals("_", StringComparison.Ordinal)
                || variableName.Equals("PSItem", StringComparison.Ordinal);
        }
    }
}
<file_sep>/src/PSLambda/Commands/GenericCommand.cs
using System;
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda.Commands
{
    /// <summary>
    /// Provides handling for the "generic" custom command.
    /// </summary>
    internal class GenericCommand : ICommandHandler
    {
        /// <summary>
        /// Gets the name of the command.
        /// </summary>
        public string CommandName { get; } = "generic";

        /// <summary>
        /// Creates a Linq expression for a <see cref="CommandAst" /> representing
        /// the "generic" command.
        /// </summary>
        /// <param name="commandAst">The AST to convert.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the expression.</param>
        /// <returns>An expression representing the command.</returns>
        public Expression ProcessAst(CommandAst commandAst, CompileVisitor visitor)
        {
            if (commandAst.CommandElements.Count != 2)
            {
                return ReportInvalidSyntax(commandAst.Extent, visitor);
            }

            if (!(commandAst.CommandElements[1] is ParenExpressionAst paren))
            {
                return ReportInvalidSyntax(commandAst.Extent, visitor);
            }

            if (!(paren.Pipeline is PipelineAst pipeline) || pipeline.PipelineElements.Count != 1)
            {
                return ReportInvalidSyntax(commandAst.Extent, visitor);
            }

            if (!(pipeline.PipelineElements[0] is CommandExpressionAst commandExpression))
            {
                return ReportInvalidSyntax(commandAst.Extent, visitor);
            }

            var arrayLiteral = commandExpression.Expression as ArrayLiteralAst;
            if (arrayLiteral.Elements.Count < 2)
            {
                return ReportInvalidSyntax(commandAst.Extent, visitor);
            }

            if (!(arrayLiteral.Elements[0] is InvokeMemberExpressionAst memberExpression))
            {
                return ReportInvalidSyntax(commandAst.Extent, visitor);
            }

            var genericArguments = new Type[arrayLiteral.Elements.Count - 1];
            for (var i = 1; i < arrayLiteral.Elements.Count; i++)
            {
                if (visitor.TryResolveType(arrayLiteral.Elements[i], out Type resolvedType))
                {
                    genericArguments[i - 1] = resolvedType;
                    continue;
                }

                // If a type didn't resolve then a parse error was generated, so exit.
                return Expression.Empty();
            }

            return visitor.CompileInvokeMemberExpression(memberExpression, genericArguments);
        }

        private Expression ReportInvalidSyntax(IScriptExtent extent, CompileVisitor visitor)
        {
            visitor.Errors.ReportParseError(
                extent,
                nameof(ErrorStrings.InvalidGenericSyntax),
                ErrorStrings.InvalidGenericSyntax);
            return Expression.Empty();
        }
    }
}
<file_sep>/src/PSLambda/IParseErrorWriter.cs
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides uniform writing and management of <see cref="ParseError" /> objects.
    /// </summary>
    internal interface IParseErrorWriter
    {
        /// <summary>
        /// Reports a <see cref="ParseError" />. Handling of the <see cref="ParseError" />
        /// may defer between implementations.
        /// </summary>
        /// <param name="extent">
        /// The <see cref="IScriptExtent" /> of the error being reported.
        /// </param>
        /// <param name="id">The id of the error.</param>
        /// <param name="message">
        /// The message to display in the <see cref="System.Management.Automation.ParseException" />
        /// when parsing is completed.
        /// </param>
        void ReportParseError(
            IScriptExtent extent,
            string id,
            string message);

        /// <summary>
        /// Throws if the error limit has been hit. Error limit may vary
        /// between implementations.
        /// </summary>
        void ThrowIfErrorLimitHit();

        /// <summary>
        /// Throws if any error has been reported.
        /// </summary>
        void ThrowIfAnyErrors();
    }
}
<file_sep>/src/PSLambda/LabelScopeStack.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;

namespace PSLambda
{
    /// <summary>
    /// Represents the current set of scopes in which the <c>return</c> keyword will work.
    /// </summary>
    internal class LabelScopeStack
    {
        private LabelScope _current;

        /// <summary>
        /// Gets the implied or explicit return type.
        /// </summary>
        public Type ReturnType => _current?.ReturnType;

        /// <summary>
        /// Creates a new scope in which the <c>return</c> keyword will work.
        /// </summary>
        /// <returns>
        /// A <see cref="IDisposable" /> handle that will return to the previous scope when disposed.
        /// </returns>
        public IDisposable NewScope()
        {
            _current = _current == null ? new LabelScope() : new LabelScope(_current);
            return new ScopeHandle(() => _current = _current?._parent);
        }

        /// <summary>
        /// Creates a new scope in which the <c>return</c> keyword will work while specifying an
        /// expected return type.
        /// </summary>
        /// <param name="returnType">The expected return type.</param>
        /// <returns>
        /// A <see cref="IDisposable" /> handle that will return to the previous scope when disposed.
        /// </returns>
        public IDisposable NewScope(Type returnType)
        {
            _current = _current == null ? new LabelScope() : new LabelScope(_current);
            _current.ReturnType = returnType;
            return new ScopeHandle(() => _current = _current?._parent);
        }

        /// <summary>
        /// Gets the specified statement adding the <c>return</c> label if applicable.
        /// </summary>
        /// <param name="expressions">The expressions to precent the label.</param>
        /// <param name="requireExplicitReturn">
        /// A value indicating whether an explicit return statement should be required.
        /// </param>
        /// <returns>
        /// If a <c>return</c> label is required the supplied expressions will be returned followed
        /// by the required label, otherwise they will be returned unchanged.
        /// </returns>
        public Expression[] WithReturn(IEnumerable<Expression> expressions, bool requireExplicitReturn = true)
        {
            if (_current.IsReturnRequested)
            {
                return expressions.Concat(
                    new[]
                    {
                        Expression.Label(
                            _current.Label,
                            Expression.Default(_current.ReturnType)),
                    }).ToArray();
            }

            if (!requireExplicitReturn)
            {
                return expressions.ToArray();
            }

            _current.IsReturnRequested = true;
            _current.ReturnType = typeof(void);
            return expressions.Concat(new[] { Expression.Label(_current.Label) }).ToArray();
        }

        /// <summary>
        /// Gets an existing <c>return</c> label if one has already been defined, otherwise
        /// one is created.
        /// </summary>
        /// <param name="type">The expected return type.</param>
        /// <returns>The <see cref="LabelTarget" /> requested.</returns>
        public LabelTarget GetOrCreateReturnLabel(Type type)
        {
            if (_current != null && _current.IsReturnRequested)
            {
                return _current.Label;
            }

            _current.IsReturnRequested = true;
            _current.ReturnType = type;
            return _current.Label;
        }
    }
}
<file_sep>/src/PSLambda/LoopScopeStack.cs
using System;
using System.Linq.Expressions;

namespace PSLambda
{
    /// <summary>
    /// Represents a set of scopes in which the <c>break</c> or <c>continue</c> keywords
    /// may be used.
    /// </summary>
    internal class LoopScopeStack
    {
        private LoopScope _current;

        /// <summary>
        /// Gets the current label for the <c>break</c> keyword.
        /// </summary>
        internal LabelTarget Break => _current?.Break;

        /// <summary>
        /// Gets the current label for the <c>continue</c> keyword.
        /// </summary>
        internal LabelTarget Continue => _current?.Continue;

        /// <summary>
        /// Creates a new scope in which the <c>break</c> or <c>continue</c> keywords
        /// may be used.
        /// </summary>
        /// <returns>
        /// A <see cref="IDisposable" /> handle that will return to the previous scope when disposed.
        /// </returns>
        internal IDisposable NewScope()
        {
            _current = new LoopScope()
            {
                Parent = _current,
                Break = Expression.Label(),
                Continue = Expression.Label(),
            };

            return new ScopeHandle(() => _current = _current?.Parent);
        }
    }
}
<file_sep>/src/PSLambda/Strings.cs
namespace PSLambda
{
    /// <summary>
    /// Provides string constant values for various strings used throughout the project.
    /// </summary>
    internal class Strings
    {
        /// <summary>
        /// Constant containing a string similar to "GetEnumerator".
        /// </summary>
        public const string GetEnumeratorMethodName = "GetEnumerator";

        /// <summary>
        /// Constant containing a string similar to "get_Current".
        /// </summary>
        public const string EnumeratorGetCurrentMethodName = "get_Current";

        /// <summary>
        /// Constant containing a string similar to "psdelegate".
        /// </summary>
        public const string PSDelegateTypeAcceleratorName = "psdelegate";

        /// <summary>
        /// Constant containing a string similar to "ExecutionContext".
        /// </summary>
        public const string ExecutionContextVariableName = "ExecutionContext";

        /// <summary>
        /// Constant containing a string similar to "foreach".
        /// </summary>
        public const string ForEachVariableName = "foreach";

        /// <summary>
        /// Constant containing a string similar to "null".
        /// </summary>
        public const string NullVariableName = "null";

        /// <summary>
        /// Constant containing a string similar to "true".
        /// </summary>
        public const string TrueVariableName = "true";

        /// <summary>
        /// Constant containing a string similar to "false".
        /// </summary>
        public const string FalseVariableName = "false";

        /// <summary>
        /// Constant containing a string similar to "new".
        /// </summary>
        public const string ConstructorMemberName = "new";

        /// <summary>
        /// Constant containing a string similar to "OperatorNotSupported".
        /// </summary>
        public const string OperatorNotSupportedId = "OperatorNotSupported";

        /// <summary>
        /// Constant containing a string similar to ">".
        /// </summary>
        public const string DelegateSyntaxCommandName = ">";

        /// <summary>
        /// Constant containing a string similar to "Invoke".
        /// </summary>
        public const string DelegateInvokeMethodName = "Invoke";

        /// <summary>
        /// Constant containing a string similar to "Value".
        /// </summary>
        public const string PSVariableWrapperValuePropertyName = "Value";

        /// <summary>
        /// Constant containing a string similar to "Item".
        /// </summary>
        public const string DefaultIndexerPropertyName = "Item";

        /// <summary>
        /// Constant containing a string similar to "ElementAtOrDefault".
        /// </summary>
        public const string ElementAtOrDefaultMethodName = "ElementAtOrDefault";

        /// <summary>
        /// Constant containing a string similar to "Add".
        /// </summary>
        public const string AddMethodName = "Add";

        /// <summary>
        /// Constant containing a string similar to "ToArray".
        /// </summary>
        public const string ToArrayMethodName = "ToArray";
    }
}
<file_sep>/src/PSLambda/NewPSDelegateCommand.cs
using System;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;
using System.Management.Automation.Runspaces;

namespace PSLambda
{
    /// <summary>
    /// Represents the New-PSDelegate command.
    /// </summary>
    [Cmdlet(VerbsCommon.New, "PSDelegate")]
    [OutputType(typeof(Delegate))]
    public class NewPSDelegateCommand : PSCmdlet
    {
        /// <summary>
        /// Gets or sets the value for the parameter "DelegateType".
        /// </summary>
        [Parameter]
        [ValidateNotNull]
        public Type DelegateType { get; set; }

        /// <summary>
        /// Gets or sets the value for the parameter "Expression".
        /// </summary>
        [Parameter(Position = 0, Mandatory = true)]
        [ValidateNotNull]
        public ScriptBlock Expression { get; set; }

        /// <summary>
        /// The EndProcessing method.
        /// </summary>
        protected override void EndProcessing()
        {
            var variables = SessionState.InvokeCommand.InvokeScript(
                "Get-Variable -Scope 0",
                false,
                PipelineResultTypes.Output,
                null,
                null)
                .Select(pso => pso.BaseObject)
                .Cast<PSVariable>()
                .Where(v => !SpecialVariables.IgnoreLocal.Contains(v.Name));

            try
            {
                if (DelegateType == null)
                {
                    WriteObject(
                        CompileVisitor.CompileAst(
                            (EngineIntrinsics)SessionState.PSVariable.GetValue(Strings.ExecutionContextVariableName),
                            (ScriptBlockAst)Expression.Ast,
                            variables.ToArray()),
                        enumerateCollection: false);
                    return;
                }

                WriteObject(
                    CompileVisitor.CompileAst(
                        (EngineIntrinsics)SessionState.PSVariable.GetValue(Strings.ExecutionContextVariableName),
                        (ScriptBlockAst)Expression.Ast,
                        variables.ToArray(),
                        DelegateType),
                    enumerateCollection: false);
            }
            catch (ParseException e)
            {
                ThrowTerminatingError(new ErrorRecord(e.ErrorRecord, e));
            }
        }
    }
}
<file_sep>/src/PSLambda/Commands/DefaultCommand.cs
using System;
using System.Linq.Expressions;
using System.Management.Automation;
using System.Management.Automation.Language;

namespace PSLambda.Commands
{
    /// <summary>
    /// Provides handling for the "default" custom command.
    /// </summary>
    internal class DefaultCommand : ICommandHandler
    {
        /// <summary>
        /// Gets the name of the command.
        /// </summary>
        public string CommandName { get; } = "default";

        /// <summary>
        /// Creates a Linq expression for a <see cref="CommandAst" /> representing
        /// the "default" command.
        /// </summary>
        /// <param name="commandAst">The AST to convert.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the expression.</param>
        /// <returns>An expression representing the command.</returns>
        public Expression ProcessAst(CommandAst commandAst, CompileVisitor visitor)
        {
            if (commandAst.CommandElements == null || commandAst.CommandElements.Count != 2)
            {
                visitor.TryResolveType(null, out _);
                return Expression.Empty();
            }

            // Is using syntax "default([TypeName])"
            if (commandAst.CommandElements[1] is ParenExpressionAst paren &&
                paren.Pipeline is PipelineAst pipeline &&
                pipeline.PipelineElements.Count == 1 &&
                pipeline.PipelineElements[0] is CommandExpressionAst commandExpression &&
                commandExpression.Expression is TypeExpressionAst &&
                visitor.TryResolveType(commandExpression.Expression, out Type resolvedType))
            {
                return Expression.Default(resolvedType);
            }

            // Is using syntax "default TypeName"
            if (commandAst.CommandElements[1] is StringConstantExpressionAst stringConstant &&
                LanguagePrimitives.TryConvertTo(stringConstant.Value, out resolvedType))
            {
                return Expression.Default(resolvedType);
            }

            // Unknown syntax, but this method will produce parse errors for us.
            if (visitor.TryResolveType(commandAst.CommandElements[1], out resolvedType))
            {
                return Expression.Default(resolvedType);
            }

            return Expression.Empty();
        }
    }
}
<file_sep>/src/PSLambda/PSDelegate.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Management.Automation;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Represents an <see cref="Delegate" /> that is not yet compiled but can be
    /// converted implicitly by the PowerShell engine.
    /// </summary>
    public sealed class PSDelegate
    {
        private Delegate _defaultDelegate;

        /// <summary>
        /// Initializes a new instance of the <see cref="PSDelegate" /> class.
        /// </summary>
        /// <param name="scriptBlock">The <see cref="ScriptBlock" /> to compile.</param>
        public PSDelegate(ScriptBlock scriptBlock)
        {
            using (var pwsh = PowerShell.Create(RunspaceMode.CurrentRunspace))
            {
                Locals =
                    pwsh.AddCommand("Microsoft.PowerShell.Utility\\Get-Variable")
                        .AddParameter("Scope", 0)
                        .Invoke<PSVariable>()
                        .Where(v => !SpecialVariables.IgnoreLocal.Contains(v.Name))
                        .ToDictionary(v => v.Name);

                pwsh.Commands.Clear();

                EngineIntrinsics =
                    pwsh.AddCommand("Microsoft.PowerShell.Utility\\Get-Variable")
                        .AddParameter("Name", "ExecutionContext")
                        .AddParameter("ValueOnly", true)
                        .Invoke<EngineIntrinsics>()
                        .First();
            }

            ScriptBlock = scriptBlock;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="PSDelegate" /> class.
        /// </summary>
        /// <param name="scriptBlock">The <see cref="ScriptBlock" /> to compile.</param>
        /// <param name="engine">
        /// The <see cref="EngineIntrinsics" /> instance for the current runspace.
        /// </param>
        /// <param name="locals">
        /// Any <see cref="PSVariable" /> objects that are local to the
        /// current scope with the exception of AllScope variables.
        /// </param>
        internal PSDelegate(ScriptBlock scriptBlock, EngineIntrinsics engine, Dictionary<string, PSVariable> locals)
        {
            Locals = locals;
            EngineIntrinsics = engine;
            ScriptBlock = scriptBlock;
        }

        /// <summary>
        /// Gets the ScriptBlock represented by the delegate.
        /// </summary>
        public ScriptBlock ScriptBlock { get; }

        /// <summary>
        /// Gets the <see cref="EngineIntrinsics" /> from the origin
        /// <see cref="System.Management.Automation.Runspaces.Runspace" />.
        /// </summary>
        internal EngineIntrinsics EngineIntrinsics { get; }

        /// <summary>
        /// Gets the <see cref="PSVariable" /> objects from origin SessionStateScope.
        /// </summary>
        internal Dictionary<string, PSVariable> Locals { get; }

        /// <summary>
        /// Gets the default <see cref="Delegate" /> that is used for the
        /// <see cref="PSDelegate.Invoke(object[])" /> method.
        /// </summary>
        internal Delegate DefaultDelegate
        {
            get
            {
                if (_defaultDelegate != null)
                {
                    return _defaultDelegate;
                }

                return _defaultDelegate = CreateDefaultDelegate();
            }
        }

        /// <summary>
        /// Invokes the compiled <see cref="Delegate" /> represented by this object. If the
        /// <see cref="Delegate" /> has not yet been compiled, it will be compiled prior to
        /// invocation.
        /// </summary>
        /// <param name="arguments">Arguments to pass to the <see cref="Delegate" />.</param>
        /// <returns>The result returned by the <see cref="Delegate" />.</returns>
        public object Invoke(params object[] arguments)
        {
            return DefaultDelegate.DynamicInvoke(arguments);
        }

        private Delegate CreateDefaultDelegate()
        {
            return CompileVisitor.CompileAst(
                EngineIntrinsics,
                (ScriptBlockAst)ScriptBlock.Ast,
                Locals.Values.ToArray());
        }
    }
}
<file_sep>/src/PSLambda/ParseErrorWriter.cs
using System.Collections.Generic;
using System.Management.Automation;
using System.Management.Automation.Language;

namespace PSLambda
{
    /// <summary>
    /// Provides uniform writing and management of <see cref="ParseError" /> objects.
    /// </summary>
    internal abstract class ParseErrorWriter : IParseErrorWriter
    {
        /// <summary>
        /// Initializes a new instance of the <see cref="ParseErrorWriter" /> class.
        /// </summary>
        protected ParseErrorWriter()
        {
        }

        /// <summary>
        /// Creates an instance of the default <see cref="ParseErrorWriter" />. This
        /// error writer will report full error messages and will throw after a maximum
        /// of three errors.
        /// </summary>
        /// <returns>The defualt <see cref="ParseErrorWriter" />.</returns>
        public static ParseErrorWriter CreateDefault() => new DefaultParseErrorWriter();

        /// <summary>
        /// Creates an instance of the null <see cref="ParseErrorWriter" />. This
        /// error writer will not report any error messages and will throw immediately
        /// after the first error has been reported.
        /// </summary>
        /// <returns>The null <see cref="ParseErrorWriter" />.</returns>
        public static ParseErrorWriter CreateNull() => new NullParseErrorWriter();

        /// <summary>
        /// Reports a <see cref="ParseError" />. Handling of the <see cref="ParseError" />
        /// may defer between implementations.
        /// </summary>
        /// <param name="extent">
        /// The <see cref="IScriptExtent" /> of the error being reported.
        /// </param>
        /// <param name="id">The id of the error.</param>
        /// <param name="message">
        /// The message to display in the <see cref="System.Management.Automation.ParseException" />
        /// when parsing is completed.
        /// </param>
        public virtual void ReportParseError(IScriptExtent extent, string id, string message)
        {
        }

        /// <summary>
        /// Throws if the error limit has been hit. Error limit may vary
        /// between implementations.
        /// </summary>
        public virtual void ThrowIfErrorLimitHit()
        {
        }

        /// <summary>
        /// Throws if any error has been reported.
        /// </summary>
        public virtual void ThrowIfAnyErrors()
        {
        }

        private class NullParseErrorWriter : ParseErrorWriter
        {
            private int _errorCount;

            public override void ReportParseError(IScriptExtent extent, string id, string message)
            {
                _errorCount++;
                throw new ParseException();
            }

            public override void ThrowIfAnyErrors()
            {
                if (_errorCount > 0)
                {
                    throw new ParseException();
                }
            }

            public override void ThrowIfErrorLimitHit()
            {
                if (_errorCount > 0)
                {
                    throw new ParseException();
                }
            }
        }

        private class DefaultParseErrorWriter : ParseErrorWriter
        {
            private const int ErrorLimit = 3;

            private readonly List<ParseError> _errors = new List<ParseError>();

            public override void ReportParseError(IScriptExtent extent, string id, string message)
            {
                _errors.Add(
                    new ParseError(
                        extent,
                        id,
                        message));

                ThrowIfErrorLimitHit();
            }

            public override void ThrowIfErrorLimitHit()
            {
                if (_errors.Count < ErrorLimit)
                {
                    return;
                }

                throw new ParseException(_errors.ToArray());
            }

            public override void ThrowIfAnyErrors()
            {
                if (_errors.Count < 1)
                {
                    return;
                }

                throw new ParseException(_errors.ToArray());
            }
        }
    }
}
<file_sep>/src/PSLambda/Commands/WithCommand.cs
using System;
using System.Linq.Expressions;
using System.Management.Automation.Language;

namespace PSLambda.Commands
{
    /// <summary>
    /// Provides handling for the "with" custom command.
    /// </summary>
    internal class WithCommand : ObjectAndBodyCommandHandler
    {
        /// <summary>
        /// Gets the name of the command.
        /// </summary>
        public override string CommandName { get; } = "with";

        /// <summary>
        /// Creates a Linq expression for a <see cref="CommandAst" /> representing
        /// the "with" command.
        /// </summary>
        /// <param name="commandAst">The AST to convert.</param>
        /// <param name="targetAst">The AST containing the target of the keyword.</param>
        /// <param name="bodyAst">The AST containing the body of the keyword.</param>
        /// <param name="visitor">The <see cref="CompileVisitor" /> requesting the expression.</param>
        /// <returns>An expression representing the command.</returns>
        protected override Expression ProcessObjectAndBody(
            CommandAst commandAst,
            CommandElementAst targetAst,
            ScriptBlockExpressionAst bodyAst,
            CompileVisitor visitor)
        {
            var disposeVar = Expression.Variable(typeof(IDisposable));
            return visitor.NewBlock(() =>
                Expression.Block(
                    typeof(void),
                    new[] { disposeVar },
                    Expression.Assign(
                        disposeVar,
                        Expression.Convert(
                            targetAst.Compile(visitor),
                            typeof(IDisposable))),
                    Expression.TryFinally(
                        bodyAst.ScriptBlock.EndBlock.Compile(visitor),
                        Expression.Call(disposeVar, ReflectionCache.IDisposable_Dispose))));
        }
    }
}
<file_sep>/src/PSLambda/ScopeHandle.cs
using System;

namespace PSLambda
{
    /// <summary>
    /// Represents a handle for the current scope.
    /// </summary>
    internal class ScopeHandle : IDisposable
    {
        private readonly Action _disposer;

        private bool _isDisposed;

        /// <summary>
        /// Initializes a new instance of the <see cref="ScopeHandle" /> class.
        /// </summary>
        /// <param name="disposer">The action that disposes of the scope.</param>
        public ScopeHandle(Action disposer)
        {
            _disposer = disposer;
        }

        /// <summary>
        /// Disposes of the current scope.
        /// </summary>
        public void Dispose()
        {
            if (_isDisposed)
            {
                return;
            }

            _disposer?.Invoke();
            _isDisposed = true;
        }
    }
}
<file_sep>/src/PSLambda/LoopScope.cs
using System.Linq.Expressions;

namespace PSLambda
{
    /// <summary>
    /// Represents a scope in which the <c>break</c> or <c>continue</c> keywords
    /// may be used.
    /// </summary>
    internal class LoopScope
    {
        /// <summary>
        /// Gets or sets the parent scope.
        /// </summary>
        public LoopScope Parent { get; set; }

        /// <summary>
        /// Gets or sets the label for the <c>break</c> keyword.
        /// </summary>
        public LabelTarget Break { get; set; }

        /// <summary>
        /// Gets or sets the label for the <c>continue</c> keyword.
        /// </summary>
        public LabelTarget Continue { get; set; }
    }
}
