Example #1
0
        //Compile a given expression as a pattern. If match fails proceed to failLab.
        private void CompilePattern(int sysVar, ElaExpression exp, Label failLab, bool allowBang, bool forceStrict)
        {
            AddLinePragma(exp);

            switch (exp.Type)
            {
                case ElaNodeType.LazyLiteral:
                    {
                        var n = (ElaLazyLiteral)exp;

                        //Normally this flag is set when everything is already compiled as lazy.
                        if (forceStrict)
                            CompilePattern(sysVar, n.Expression, failLab, allowBang, forceStrict);
                        else
                            CompileLazyPattern(sysVar, exp, allowBang);
                    }
                    break;
                case ElaNodeType.FieldReference:
                    {
                        //We treat this expression as a constructor with a module alias
                        var n = (ElaFieldReference)exp;
                        var fn = n.FieldName;
                        var alias = n.TargetObject.GetName();
                        PushVar(sysVar);

                        if (n.TargetObject.Type != ElaNodeType.NameReference)
                            AddError(ElaCompilerError.InvalidPattern, n, FormatNode(n));
                        else
                            EmitSpecName(alias, "$$$$" + fn, n, ElaCompilerError.UndefinedName);

                        cw.Emit(Op.Skiptag);
                        cw.Emit(Op.Br, failLab);
                    }
                    break;
                case ElaNodeType.NameReference:
                    {
                        //Irrefutable pattern, always binds expression to a name, unless it is
                        //a constructor pattern
                        var n = (ElaNameReference)exp;

                        //Bang pattern are only allowed in constructors and functions
                        if (n.Bang && !allowBang)
                        {
                            AddError(ElaCompilerError.BangPatternNotValid, exp, FormatNode(exp));
                            AddHint(ElaCompilerHint.BangsOnlyFunctions, exp);
                        }

                        if (n.Uppercase) //This is a constructor
                        {
                            if (sysVar != -1)
                                PushVar(sysVar);

                            EmitSpecName(null, "$$$$" + n.Name, n, ElaCompilerError.UndefinedName);

                            //This op codes skips one offset if an expression
                            //on the top of the stack has a specified tag.
                            cw.Emit(Op.Skiptag);
                            cw.Emit(Op.Br, failLab);
                        }
                        else
                        {
                            var newV = false;
                            var addr = AddMatchVariable(n.Name, n, out newV);

                            //This is a valid situation, it means that the value is
                            //already on the top of the stack.
                            if (sysVar > -1 && newV)
                                PushVar(sysVar);

                            if (n.Bang)
                                cw.Emit(Op.Force);

                            //The binding is already done, so just idle.
                            if (newV)
                                PopVar(addr);
                        }
                    }
                    break;
                case ElaNodeType.UnitLiteral:
                    {
                        //Unit pattern is redundant, it is essentially the same as checking
                        //the type of an expression which is what we do here.
                        PushVar(sysVar);
                        cw.Emit(Op.Force);
                        cw.Emit(Op.PushI4, (Int32)ElaTypeCode.Unit);
                        cw.Emit(Op.Ctype);

                        //Types are not equal, proceed to fail.
                        cw.Emit(Op.Brfalse, failLab);
                    }
                    break;
                case ElaNodeType.Primitive:
                    {
                        var n = (ElaPrimitive)exp;

                        //Compare a given value with a primitive
                        PushVar(sysVar);
                        PushPrimitive(n.Value);
                        cw.Emit(Op.Cneq);

                        //Values not equal, proceed to fail.
                        cw.Emit(Op.Brtrue, failLab);
                    }
                    break;
                case ElaNodeType.As:
                    {
                        var n = (ElaAs)exp;
                        CompilePattern(sysVar, n.Expression, failLab, allowBang, false /*forceStrict*/);
                        var newV = false;
                        var addr = AddMatchVariable(n.Name, n, out newV);
                        PushVar(sysVar);
                        PopVar(addr);
                    }
                    break;
                case ElaNodeType.Placeholder:
                    //This is a valid situation, it means that the value is
                    //already on the top of the stack. Otherwise - nothing have to be done.
                    if (sysVar == -1)
                        cw.Emit(Op.Pop);
                    break;
                case ElaNodeType.RecordLiteral:
                    {
                        var n = (ElaRecordLiteral)exp;
                        CompileRecordPattern(sysVar, n, failLab, allowBang);
                    }
                    break;
                case ElaNodeType.TupleLiteral:
                    {
                        var n = (ElaTupleLiteral)exp;
                        CompileTuplePattern(sysVar, n, failLab, allowBang);
                    }
                    break;
                case ElaNodeType.Juxtaposition:
                    {
                        //An infix pattern, currently the only case is head/tail pattern.
                        var n = (ElaJuxtaposition)exp;
                        CompileComplexPattern(sysVar, n, failLab, allowBang);
                    }
                    break;
                case ElaNodeType.ListLiteral:
                    {
                        var n = (ElaListLiteral)exp;

                        //We a have a nil pattern '[]'
                        if (!n.HasValues())
                        {
                            PushVar(sysVar);
                            cw.Emit(Op.Isnil);
                            cw.Emit(Op.Brfalse, failLab);
                        }
                        else
                        {
                            //We don't want to write the same compilation logic twice,
                            //so here we transform a list literal into a chain of function calls, e.g.
                            //[1,2,3] goes to 1::2::3::[] with a mandatory nil at the end.
                            var len = n.Values.Count;
                            ElaExpression last = ElaListLiteral.Empty;
                            var fc = default(ElaJuxtaposition);

                            //Loops through all elements in literal backwards
                            for (var i = 0; i < len; i++)
                            {
                                var nn = n.Values[len - i - 1];
                                fc = new ElaJuxtaposition();
                                fc.SetLinePragma(nn.Line, nn.Column);
                                fc.Parameters.Add(nn);
                                fc.Parameters.Add(last);
                                last = fc;
                            }

                            //Now we can compile it as head/tail pattern
                            CompilePattern(sysVar, fc, failLab, allowBang, false /*forceStrict*/);
                        }
                    }
                    break;
                default:
                    AddError(ElaCompilerError.InvalidPattern, exp, FormatNode(exp));
                    break;
            }
        }
Example #2
0
        private void ProcessDoBlock(ElaExpression cexp1, ElaExpression cexp2, ref ElaExpression rootExp)
        {
            var eqt = default(ElaJuxtaposition);
            var lam = default(ElaLambda);
            var letb = default(ElaLetBinding);

            if (cexp2 == null)
            {
                cexp2 = cexp1;
                cexp1 = new ElaPlaceholder();
            }

            if (rootExp.Type == ElaNodeType.Juxtaposition)
                eqt = (ElaJuxtaposition)rootExp;
            else if (rootExp.Type == ElaNodeType.Lambda)
            {
                lam = (ElaLambda)rootExp;
                eqt = lam.Right as ElaJuxtaposition;
            }
            else if (rootExp.Type == ElaNodeType.LetBinding)
            {
                letb = (ElaLetBinding)rootExp;
                eqt = letb.Expression as ElaJuxtaposition;
            }
            else if (rootExp.Type != ElaNodeType.None)
            {
                eqt = new ElaJuxtaposition { Spec = true };
                eqt.SetLinePragma(cexp1.Line, cexp1.Column);
                eqt.Parameters.Add(null);
                eqt.Parameters.Add(rootExp);
            }

            if (eqt != null && !eqt.Spec)
            {
                var eqt2 = new ElaJuxtaposition();
                eqt2.SetLinePragma(eqt.Line, eqt.Column);
                eqt2.Target = new ElaNameReference(t) { Name = ">>-" };
                eqt2.Parameters.Add(null);
                eqt2.Parameters.Add(eqt);
                eqt = eqt2;
            }

            if (eqt != null && eqt.Parameters.Count == 2)
            {
                if (eqt.Parameters[0] == null)
                {
                    eqt.Target = new ElaNameReference(t) { Name = ">>=" };

                    var lambda = new ElaLambda();
                    lambda.SetLinePragma(cexp2.Line, cexp2.Column);
                    lambda.Left = new ElaPlaceholder();

                    var lambda2 = new ElaLambda();
                    lambda2.SetLinePragma(cexp2.Line, cexp2.Column);
                    lambda2.Left = cexp1;

                    var eqt1 = new ElaJuxtaposition { Spec = true };
                    eqt1.SetLinePragma(cexp2.Line, cexp2.Column);
                    eqt1.Target = new ElaNameReference(t) { Name = ">>=" };
                    eqt1.Parameters.Add(cexp2);
                    eqt1.Parameters.Add(lambda2);
                    lambda.Right = eqt1;

                    eqt.Parameters[0] = eqt.Parameters[1];
                    eqt.Parameters[1] = lambda;
                    rootExp = lambda2;
                }
                else
                {
                    throw new Exception("Unable to process do-notation.");
                }
            }
            else
            {
                if (eqt == null)
                {
                    eqt = new ElaJuxtaposition { Spec = true };
                    eqt.SetLinePragma(cexp1.Line, cexp1.Column);

                    if (lam != null)
                        lam.Right = eqt;
                    else if (letb != null)
                        letb.Expression = eqt;
                }

                eqt.Target = new ElaNameReference(t) { Name = ">>=" };
                eqt.Parameters.Add(cexp2);

                var lambda = new ElaLambda();
                lambda.SetLinePragma(cexp2.Line, cexp2.Column);
                lambda.Left = cexp1;

                eqt.Parameters.Add(lambda);
                rootExp = lambda;
            }
        }
Example #3
0
        private ElaExpression ValidateDoBlock(ElaExpression exp)
        {
            if (exp.Type == ElaNodeType.Juxtaposition && ((ElaJuxtaposition)exp).Parameters[0] == null)
            {
                var ext = ((ElaJuxtaposition)exp).Parameters[1];
                var ctx = default(ElaContext);

                if (ext.Type == ElaNodeType.Context)
                {
                    ctx = (ElaContext)ext;
                    ext = ctx.Expression;
                }

                var eqt = new ElaJuxtaposition { Spec = true };
                eqt.SetLinePragma(exp.Line, exp.Column);
                eqt.Target = new ElaNameReference(t) { Name = ">>=" };
                eqt.Parameters.Add(ext);

                var jux = new ElaJuxtaposition();
                jux.SetLinePragma(exp.Line, exp.Column);
                jux.Target = new ElaNameReference { Name = "point" };
                jux.Parameters.Add(new ElaUnitLiteral());
                eqt.Parameters.Add(new ElaLambda { Left = new ElaPlaceholder(), Right = jux });

                if (ctx != null)
                {
                    ctx.Expression = eqt;
                    exp = ctx;
                }
                else
                    exp = eqt;
            }

            var root = exp;

            while (true)
            {
                if (exp.Type == ElaNodeType.Juxtaposition)
                {
                    var juxta = (ElaJuxtaposition)exp;

                    if (juxta.Parameters.Count == 2)
                    {
                        juxta.Parameters[0] = Reduce(juxta.Parameters[0], juxta);
                        juxta.Parameters[1] = Reduce(juxta.Parameters[1], juxta);
                        exp = juxta.Parameters[1];
                    }
                    else
                        break;
                }
                else if (exp.Type == ElaNodeType.LetBinding)
                {
                    var lb = (ElaLetBinding)exp;
                    lb.Expression = Reduce(lb.Expression, lb);
                    exp = lb.Expression;
                }
                else if (exp.Type == ElaNodeType.Lambda)
                {
                    var lb = (ElaLambda)exp;
                    lb.Right = Reduce(lb.Right, lb);

                    if (lb.Left.Type != ElaNodeType.NameReference &&
                        lb.Left.Type != ElaNodeType.Placeholder)
                    {
                        var em = new ElaMatch();
                        em.SetLinePragma(lb.Left.Line, lb.Left.Column);
                        em.Expression = new ElaNameReference { Name = "$x01" };
                        em.Entries = new ElaEquationSet();

                        var eq1 = new ElaEquation();
                        eq1.SetLinePragma(lb.Left.Line, lb.Left.Column);
                        eq1.Left = lb.Left;
                        eq1.Right = lb.Right;
                        em.Entries.Equations.Add(eq1);

                        var eq2 = new ElaEquation();
                        eq2.SetLinePragma(lb.Left.Line, lb.Left.Column);
                        eq2.Left = new ElaNameReference { Name = "$x02" };
                        var errExp = new ElaJuxtaposition();
                        errExp.SetLinePragma(lb.Left.Line, lb.Left.Column);
                        errExp.Target = new ElaNameReference { Name = "failure" };
                        errExp.Parameters.Add(new ElaNameReference { Name = "$x02" });
                        eq2.Right = errExp;
                        em.Entries.Equations.Add(eq2);

                        lb.Left = new ElaNameReference { Name = "$x01" };
                        lb.Right = em;
                        exp = lb;
                    }
                    else
                        exp = lb.Right;
                }
                else
                    break;
            }

            var ret = new ElaLazyLiteral { Expression = root };
            ret.SetLinePragma(root.Line, root.Column);
            return ret;
        }
Example #4
0
        //Compiling a regular function call.
        private ExprData CompileFunctionCall(ElaJuxtaposition v, LabelMap map, Hints hints)
        {
            var ed = ExprData.Empty;
            var bf = default(ElaNameReference);
            var sv = default(ScopeVar);

            if (!map.HasContext && TryOptimizeConstructor(v, map))
                return ed;

            if (!map.HasContext && v.Target.Type == ElaNodeType.NameReference)
            {
                bf = (ElaNameReference)v.Target;
                sv = GetVariable(bf.Name, bf.Line, bf.Column);

                //If the target is one of the built-in application function we need to transform this
                //to a regular function call, e.g. 'x |> f' is translated into 'f x' by manually creating
                //an appropriates AST node. This is done to simplify compilation - so that all optimization
                //of a regular function call would be applied to pipes as well.
                if ((sv.Flags & ElaVariableFlags.Builtin) == ElaVariableFlags.Builtin)
                {
                    var k = (ElaBuiltinKind)sv.Data;

                    if (v.Parameters.Count == 2)
                    {
                        if (k == ElaBuiltinKind.BackwardPipe)
                        {
                            var fc = new ElaJuxtaposition { Target = v.Parameters[0] };
                            fc.SetLinePragma(v.Line, v.Column);
                            fc.Parameters.Add(v.Parameters[1]);
                            return CompileFunctionCall(fc, map, hints);
                        }
                        else if (k == ElaBuiltinKind.ForwardPipe)
                        {
                            var fc = new ElaJuxtaposition { Target = v.Parameters[1] };
                            fc.SetLinePragma(v.Line, v.Column);
                            fc.Parameters.Add(v.Parameters[0]);
                            return CompileFunctionCall(fc, map, hints);
                        }
                        else if (k == ElaBuiltinKind.LogicalOr)
                        {
                            CompileLogicalOr(v, v.Parameters[0], v.Parameters[1], map, hints);
                            return ed;
                        }
                        else if (k == ElaBuiltinKind.LogicalAnd)
                        {
                            CompileLogicalAnd(v, v.Parameters[0], v.Parameters[1], map, hints);
                            return ed;
                        }
                        else if (k == ElaBuiltinKind.Seq)
                        {
                            CompileSeq(v, v.Parameters[0], v.Parameters[1], map, hints);
                            return ed;
                        }
                    }
                }
            }

            //We can't apply tail call optimization for the context bound call
            var tail = (hints & Hints.Tail) == Hints.Tail && !map.HasContext;
            var len = v.Parameters.Count;

            //Compile arguments to which a function is applied
            for (var i = 0; i < len; i++)
                CompileExpression(v.Parameters[len - i - 1], map, Hints.None, v);

            //If this a tail call and we effectively call the same function we are currently in,
            //than do not emit an actual function call, just do a goto. (Tail recursion optimization).
            if (tail && map.FunctionName != null && map.FunctionName == v.GetName() &&
                map.FunctionParameters == len && map.FunctionScope == GetScope(map.FunctionName)
                && (sv.Flags & ElaVariableFlags.ClassFun) != ElaVariableFlags.ClassFun)
            {
                AddLinePragma(v);
                cw.Emit(Op.Br, map.FunStart);
                return ed;
            }

            if (bf != null)
            {
                if (v.Parameters[0].Type == ElaNodeType.Primitive &&
                    bf.Line == v.Parameters[0].Line && bf.Column + bf.Name.Length == v.Parameters[0].Column &&
                    ((ElaPrimitive)v.Parameters[0]).Value.IsNegative())
                {
                    var par = ((ElaPrimitive)v.Parameters[0]).Value.ToString();
                    AddWarning(ElaCompilerWarning.NegationAmbiguity, v, bf.Name, par);
                    AddHint(ElaCompilerHint.AddSpaceApplication, v, bf.Name, par, par.TrimStart('-'));
                }

                //The target is one of built-in functions and therefore can be inlined for optimization.
                if ((sv.Flags & ElaVariableFlags.Builtin) == ElaVariableFlags.Builtin)
                {
                    var kind = (ElaBuiltinKind)sv.Data;
                    var pars = BuiltinParams(kind);

                    //We inline built-ins only when all arguments are provided
                    //If this is not the case a built-in is compiled into a function in-place
                    //and than called.
                    if (len != pars)
                    {
                        AddLinePragma(bf);
                        CompileBuiltin(kind, v.Target, map, bf.Name);

                        if (v.FlipParameters)
                            cw.Emit(Op.Flip);

                        for (var i = 0; i < len; i++)
                            cw.Emit(Op.Call);
                    }
                    else
                        CompileBuiltinInline(kind, v.Target, map, hints);

                    return ed;
                }
                else
                {
                    //Regular situation, just push a target name
                    AddLinePragma(v.Target);
                    PushVar(sv);

                    if ((sv.VariableFlags & ElaVariableFlags.Function) == ElaVariableFlags.Function)
                        ed = new ExprData(DataKind.FunParams, sv.Data);
                    else if ((sv.VariableFlags & ElaVariableFlags.ObjectLiteral) == ElaVariableFlags.ObjectLiteral)
                        ed = new ExprData(DataKind.VarType, (Int32)ElaVariableFlags.ObjectLiteral);
                }
            }
            else
                ed = CompileExpression(v.Target, map, Hints.None, v);

            //Why it comes from AST? Because parser do not save the difference between pre-, post- and infix applications.
            //However Ela does support left and right sections for operators - and for such cases an additional flag is used
            //to signal about a section.
            if (v.FlipParameters)
                cw.Emit(Op.Flip);

            //It means that we are trying to call "not a function". Ela is a dynamic language, still it's worth to generate
            //a warning in such a case.
            if (ed.Type == DataKind.VarType)
                AddWarning(ElaCompilerWarning.FunctionInvalidType, v.Target, FormatNode(v.Target));

            AddLinePragma(v);

            for (var i = 0; i < len; i++)
            {
                var last = i == v.Parameters.Count - 1;

                //Use a tail call if this function call is a tail expression and optimizations are enabled.
                if (last && tail && opt)
                    cw.Emit(Op.Callt);
                else
                    cw.Emit(Op.Call);
            }

            return ed;
        }