//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; } }
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; } }
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; }
//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; }