//This method check if a given name (qualified or not, prefix may be null) is a type or class //and emit an appropriate code to check it. //This method assumes that a value to check is already on the top of the stack. private void CheckTypeOrClass(string prefix, string name, Label failLab, ElaExpression exp) { //This item can either check a specific type or a trait. var isType = NameExists(prefix, "$$" + name); //A type with such a name exists var isClass = NameExists(prefix, "$$$" + name); //A class with such a name exists //OK, this is ambiguity, better to report about that. We will consider a symbol //to be a type and compile further. if (isType && isClass) { AddWarning(ElaCompilerWarning.TypeClassAmbiguity, exp, prefix == null ? name : prefix + "." + name, FormatNode(exp)); //This hint suggests to use prefix, it is stupid to generate it, if we have a prefix already. if (prefix == null) AddHint(ElaCompilerHint.TypeClassAmbiguity, exp); } cw.Emit(Op.Force); if (isType) { EmitSpecName(prefix, "$$" + name, exp, ElaCompilerError.UndefinedType); cw.Emit(Op.Ctype); } else { EmitSpecName(prefix, "$$$" + name, exp, ElaCompilerError.UnknownClass); cw.Emit(Op.Traitch); } cw.Emit(Op.Brfalse, failLab); }
internal void Emit(Op op, Label label) { if (!label.IsEmpty()) { fixups.Add(ops.Count); Emit(op, label.GetIndex()); } else Emit(op, 0); }
//A generic case of constructor pattern private void CompileConstructorPattern(int sysVar, ElaJuxtaposition call, Label failLab, bool allowBang) { var n = String.Empty; PushVar(sysVar); //We have a qualified name here, in such case we don't just check //the presence of a constructor but ensure that this constructor originates //from a given module if (call.Target.Type == ElaNodeType.FieldReference) { var fr = (ElaFieldReference)call.Target; n = fr.FieldName; var alias = fr.TargetObject.GetName(); if (fr.TargetObject.Type != ElaNodeType.NameReference) AddError(ElaCompilerError.InvalidPattern, fr, FormatNode(fr)); else EmitSpecName(alias, "$$$$" + n, fr, ElaCompilerError.UndefinedName); } else { //Here we simply check that a constructor symbol is defined n = call.Target.GetName(); EmitSpecName(null, "$$$$" + n, call.Target, 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); //We will skip this if tags are equal for (var i = 0; i < call.Parameters.Count; i++) { PushVar(sysVar); cw.Emit(Op.Untag, i); //Unwrap it //Now we need to create a new system variable to hold //an unwrapped value. var sysVar2 = -1; var p = call.Parameters[i]; //Don't do redundant bindings for simple patterns if (!IsSimplePattern(p)) { sysVar2 = AddVariable(); PopVar(sysVar2); } CompilePattern(sysVar2, p, failLab, allowBang, false /*forceStrict*/); } }
//Currently this method only compiles head/tail pattern which is processed by parser //as function application. However it can be extended to support custom 'infix' patterns in future. private void CompileComplexPattern(int sysVar, ElaJuxtaposition call, Label failLab, bool allowBang) { if (call.Target == null) CompileHeadTail(sysVar, call, failLab, allowBang); else if (call.Target.Type == ElaNodeType.NameReference) { var targetName = call.Target.GetName(); var sv = GetVariable(call.Target.GetName(), CurrentScope, GetFlags.NoError, call.Target.Line, call.Target.Column); //The head symbol corresponds to a constructor, this is a special case of pattern if ((sv.VariableFlags & ElaVariableFlags.Builtin) == ElaVariableFlags.Builtin && (ElaBuiltinKind)sv.Data == ElaBuiltinKind.Cons) CompileHeadTail(sysVar, call, failLab, allowBang); else CompileConstructorPattern(sysVar, call, failLab, allowBang); } else if (call.Target.Type == ElaNodeType.FieldReference) CompileConstructorPattern(sysVar, call, failLab, allowBang); else { //We don't yet support other cases AddError(ElaCompilerError.InvalidPattern, call.Target, FormatNode(call.Target)); return; } }
//Compile a xs pattern in the form: {fieldName=pat,..}. This pattern can fail at //run-time if a given expression doesn't support Len and Pushelem op codes. private void CompileTuplePattern(int sysVar, ElaTupleLiteral tuple, Label failLab, bool allowBang) { var len = tuple.Parameters.Count; //Check the length first PushVar(sysVar); cw.Emit(Op.Len); cw.Emit(Op.PushI4, len); cw.Emit(Op.Cneq); cw.Emit(Op.Brtrue, failLab); //Length not equal, proceed to fail //Loops through all xs patterns for (var i = 0; i < len; i++) { var pat = tuple.Parameters[i]; PushVar(sysVar); //Generate a 'short' op typeId for the first entry if (i == 0) cw.Emit(Op.PushI4_0); else cw.Emit(Op.PushI4, i); cw.Emit(Op.Pushelem); //Here we need to bind a value of an element to a new system //variable in order to match it. var sysVar2 = AddVariable(); PopVar(sysVar2); //Match an element of a xs CompilePattern(sysVar2, pat, failLab, allowBang, false /*forceStrict*/); } }
//Compile a record pattern in the form: {fieldName=pat,..}. Here we don't check the //type of an expression on the top of the stack - in a case if try to match a non-record //using this pattern the whole match would fail on Pushfld operation. private void CompileRecordPattern(int sysVar, ElaRecordLiteral rec, Label failLab, bool allowBang) { //Loops through all record fields for (var i = 0; i < rec.Fields.Count; i++) { var fld = rec.Fields[i]; var addr = AddVariable(); var si = AddString(fld.FieldName); PushVar(sysVar); cw.Emit(Op.Pushstr, si); cw.Emit(Op.Hasfld); cw.Emit(Op.Brfalse, failLab); PushVar(sysVar); cw.Emit(Op.Pushstr, si); cw.Emit(Op.Pushfld); PopVar(addr); //We obtain a value of field, now we need to match it using a pattern in //a field value (it could be a name reference or a non-irrefutable pattern). CompilePattern(addr, fld.FieldValue, failLab, allowBang, false /*forceStrict*/); } }
//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; } }
//Compiles a special case of constructor pattern - head/tail pattern. private void CompileHeadTail(int sysVar, ElaJuxtaposition call, Label failLab, bool allowBang) { var fst = call.Parameters[0]; var snd = call.Parameters[1]; //Now check if a list is nil. If this is the case proceed to fail. PushVar(sysVar); cw.Emit(Op.Isnil); cw.Emit(Op.Brtrue, failLab); //Take a head of a list PushVar(sysVar); cw.Emit(Op.Head); var sysVar2 = -1; //For a case of a simple pattern we don't need to create to additional system //variable - these patterns are aware that they might accept -1 and it means that //the value is already on the top of the stack. if (!IsSimplePattern(fst)) { sysVar2 = AddVariable(); PopVar(sysVar2); } CompilePattern(sysVar2, fst, failLab, allowBang, false /*forceStrict*/); //Take a tail of a list PushVar(sysVar); cw.Emit(Op.Tail); sysVar2 = -1; //Again, don't do redundant bindings for simple patterns if (!IsSimplePattern(snd)) { sysVar2 = AddVariable(); PopVar(sysVar2); } CompilePattern(sysVar2, snd, failLab, allowBang, false /*forceStrict*/); }
internal Label DefineLabel() { var lab = new Label(labels.Count); labels.Add(Label.EmptyLabel); return lab; }
internal void MarkLabel(Label label) { labels[label.GetIndex()] = ops.Count; }
//Generates the last past of a simple function by emitting Ret, initializing function and creating //a new function through Newfun. This method should be called right after compiling an expression //that should be a body of a function. private void CompileFunctionEpilog(string name, int pars, int address, Label funSkipLabel) { cw.Emit(Op.Ret); var ff = 0; if (name != null) ff = EndFun(frame.Layouts.Count); else ff = cw.FinishFrame(); frame.Layouts.Add(new MemoryLayout(currentCounter, ff, address)); EndSection(); EndScope(); cw.MarkLabel(funSkipLabel); cw.Emit(Op.PushI4, pars); cw.Emit(Op.Newfun, frame.Layouts.Count - 1); }
//Generates the first part of the typeId needed to create a simple argument function. After //calling this method one should compile an expression that will become a function body. private void CompileFunctionProlog(string name, int pars, int line, int col, out Label funSkipLabel, out int address, out LabelMap newMap) { if (name != null) StartFun(name, pars); StartSection(); StartScope(true, line, col); cw.StartFrame(pars); funSkipLabel = cw.DefineLabel(); cw.Emit(Op.Br, funSkipLabel); address = cw.Offset; newMap = new LabelMap(); newMap.FunctionScope = CurrentScope; newMap.FunctionParameters = pars; }