/// <summary>
        /// <para>Compiles a new <see cref="IntermediateExpression"/> from a source <see cref="ExpressionSyntax"/>.</para>
        /// </summary>
        /// <param name="syntax">The parsed string that describes an expression to compile.</param>
        /// <param name="context">The compilation context.</param>
        /// <returns></returns>
        public static IntermediateExpression Compile(ExpressionSyntax syntax, IMathContext context = null)
        {
            var buffer = CompilerBuffers.New();

            CompileSpan(buffer, syntax, 0, syntax.Tokens.Length);

            return(new IntermediateExpression()
            {
                Operations = buffer.Operations.ToArray(),
                Static = buffer.Src.ToArray(),
                DistSize = buffer.Dist.Count,
                Import = context.ResolveTerms(syntax.Terms),
                actions = new IntermediateOperationActions(context)
            });
        }
        private static int FindIndexFromBuffer(CompilerBuffers buffers, ExpressionToken token)
        {
            for (int i = 0; i < buffers.Src.Count; i++)
            {
                var src = buffers.Src[i];

                if (src.ValueClass != token.Value.ValueClass)
                {
                    continue;
                }

                switch (src.ValueClass)
                {
                case ValueClassifier.None:
                    return(i);

                case ValueClassifier.Boolean:
                    if (src.BoolValue == token.Value.BoolValue)
                    {
                        return(i);
                    }
                    break;

                case ValueClassifier.Float:
                case ValueClassifier.FloatFractional:
                    if (src.FloatValue == token.Value.FloatValue)
                    {
                        return(i);
                    }
                    break;

                case ValueClassifier.Int:
                case ValueClassifier.IntFractional:
                    if (src.IntValue == token.Value.IntValue)
                    {
                        return(i);
                    }
                    break;
                }
            }

            return(-1);
        }
        private static IntermediateParameter DescribeIndex(ExpressionSyntax syntax, CompilerBuffers buffers, int index)
        {
            for (byte i = 0; i < buffers.Dist.Count; i++)
            {
                var span = buffers.Dist[i];
                if (span.Contains(index))
                {
                    return(new IntermediateParameter(IntermediateSource.Output, i));
                }
            }

            var token = syntax.Tokens[index];

            if (token.Operation == SyntaxTokenKind.Value)
            {
                int valueIndex = FindIndexFromBuffer(buffers, token);
                if (valueIndex == -1)
                {
                    valueIndex = buffers.Src.Count;
                    buffers.Src.Add(token.Value);
                }
                return(new IntermediateParameter(IntermediateSource.Static, (byte)valueIndex));
            }

            if (token.Operation == SyntaxTokenKind.Source)
            {
                if (token.Multiplier == -1)
                {
                    return(new IntermediateParameter(IntermediateSource.ImportNegated, token.Source));
                }
                else
                {
                    return(new IntermediateParameter(IntermediateSource.Import, token.Source));
                }
            }

            throw new InvalidOperationException(string.Format("Unrecognised token \"{0}\" in {1}", token, syntax));
        }
        private static int CompileSpan(CompilerBuffers buffer, ExpressionSyntax syntax, int start, int length)
        {
            if (length == 0)
            {
                throw new InvalidOperationException("Trying to calculate with 0 length span");
            }
            if (length == 1)
            {
                var singleParameter = DescribeIndex(syntax, buffer, start);
                var singleSpan      = Spread(buffer.Dist, (byte)start, 1);

                buffer.Parameters.Add(singleParameter);

                var intermediateOperationCode = IntermediateOperationCode.Copy;
                var intermediateOperation     = new IntermediateOperation(singleSpan.Index, intermediateOperationCode, buffer.Parameters.ToArray());

                buffer.Parameters.Clear();
                buffer.Operations.Add(intermediateOperation);
                return(singleSpan.Index);
            }

            int spanEnd          = start + length;
            int depth            = 0;
            int parenthesesStart = -1;

            for (int i = start; i < spanEnd; i++)
            {
                switch (syntax.Tokens[i].Operation)
                {
                case SyntaxTokenKind.OpenParentheses:
                    if (++depth == 1)
                    {
                        parenthesesStart = i + 1;
                    }
                    break;

                case SyntaxTokenKind.CloseParentheses:
                    if (--depth == 0)
                    {
                        int growIndex = CompileSpan(buffer, syntax, parenthesesStart, i - parenthesesStart);

                        Grow(buffer.Dist, growIndex);
                    }
                    break;
                }
            }

            int distIndex = -1;

            int interations = start + length;

            var operatorTokens = new List <TokenReference>(interations);

            for (int i = start; i < interations; i++)
            {
                var token    = syntax.Tokens[i];
                var compiler = tokenCompilers[(int)token.Operation];

                if (compiler.Pattern != OperatorPattern.None)
                {
                    operatorTokens.Add(new TokenReference(i, token, compiler));
                }
            }
            operatorTokens.Sort();

            for (int k = 0; k < operatorTokens.Count; k++)
            {
                var tokenReference = operatorTokens[k];
                int i     = tokenReference.Index;
                var token = tokenReference.Token;

                if (IsIndexCalculated(buffer.Dist, i))
                {
                    continue;
                }

                DistSpan currentSpan;

                switch (tokenReference.Compiler.Pattern)
                {
                case OperatorPattern.Prefix:
                {
                    var nextIndex = DescribeIndex(syntax, buffer, i + 1);

                    buffer.Parameters.Add(nextIndex);

                    currentSpan = Spread(buffer.Dist, (byte)i, 2);
                    break;
                }

                case OperatorPattern.Conjective:
                {
                    var lastIndex = DescribeIndex(syntax, buffer, i - 1);
                    var nextIndex = DescribeIndex(syntax, buffer, i + 1);

                    buffer.Parameters.Add(lastIndex);
                    buffer.Parameters.Add(nextIndex);

                    currentSpan = Spread(buffer.Dist, (byte)(i - 1), 3);

                    break;
                }

                default:
                case OperatorPattern.Suffix:
                {
                    var lastIndex = DescribeIndex(syntax, buffer, i - 1);
                    buffer.Parameters.Add(lastIndex);

                    if (operatorTokens.Count <= k - 1)
                    {
                        var nextToken = operatorTokens[k + 1];

                        if (token.Operation == SyntaxTokenKind.Percentage &&
                            nextToken.Token.Operation == SyntaxTokenKind.Source)
                        {
                            var nextIndex = DescribeIndex(syntax, buffer, i + 1);
                            buffer.Parameters.Add(nextIndex);
                            currentSpan = Spread(buffer.Dist, (byte)(i - 1), 3);
                        }
                        else
                        {
                            currentSpan = Spread(buffer.Dist, (byte)(i - 1), 2);
                        }
                    }
                    else
                    {
                        currentSpan = Spread(buffer.Dist, (byte)(i - 1), 2);
                    }

                    break;
                }
                }

                distIndex = currentSpan.Index;

                var intermediateOperationCode = (IntermediateOperationCode)token.Operation;

                var intermediateOperation = new IntermediateOperation(currentSpan.Index, intermediateOperationCode, buffer.Parameters.ToArray());

                buffer.Parameters.Clear();

                buffer.Operations.Add(intermediateOperation);
            }

            return(distIndex);
        }