//Compiles built-in as function in place. It is compiled in such a manner each time //it is referenced. But normally its body is just one or two op codes, so this is not a problem. private void CompileBuiltin(ElaBuiltinKind kind, ElaExpression exp, LabelMap map, string name) { StartSection(); //Here we determine the number of parameters based on the function kind. var pars = BuiltinParams(kind); cw.StartFrame(pars); var funSkipLabel = cw.DefineLabel(); cw.Emit(Op.Br, funSkipLabel); var address = cw.Offset; pdb.StartFunction(name, address, pars); AddLinePragma(exp); //Gets the actual typeId for built-in CompileBuiltinInline(kind, exp, map, Hints.None); cw.Emit(Op.Ret); frame.Layouts.Add(new MemoryLayout(currentCounter, cw.FinishFrame(), address)); EndSection(); pdb.EndFunction(frame.Layouts.Count - 1, cw.Offset); cw.MarkLabel(funSkipLabel); cw.Emit(Op.PushI4, pars); cw.Emit(Op.Newfun, frame.Layouts.Count - 1); }
//This step compiles instances - this should be done after types (as soon as instances //reference types) and after the first step as well (as soon as instances reference type classes //and can reference any other local and non-local names). It is important however to compile //instances before any user typeId gets executed because they effectively mutate function tables. private void ProcessInstances(ElaProgram prog, LabelMap map) { if (prog.Instances != null) { CompileInstances(prog.Instances, map); } }
//An entry method for includes compilation private void CompileModuleIncludes(ElaProgram prog, LabelMap map) { var inc = prog.Includes; for (var i = 0; i < inc.Count; i++) CompileModuleInclude(inc[i], map); }
//Performs validation of overlapping for complex case of pattern matchine //(such as when pattern matching is done in a function definition). private void ValidateOverlapComplex(LabelMap map, IEnumerable <ElaEquation> seq) { var lst = new List <ElaJuxtaposition>(); foreach (var ejx in seq) { var jx = (ElaJuxtaposition)ejx.Left; foreach (var ojx in lst) { var can = false; for (var i = 0; i < ojx.Parameters.Count; i++) { if (i < jx.Parameters.Count && jx.Parameters[i].CanFollow(ojx.Parameters[i])) { can = true; break; } } if (!can) { AddWarning(map, ElaCompilerWarning.MatchEntryNotReachable, jx, FormatNode(jx), FormatNode(ojx)); } } lst.Add(jx); } }
//Used to compile a 'try' expression. private void CompileTryExpression(ElaTry n, LabelMap map, Hints hints) { var catchLab = cw.DefineLabel(); var exitLab = cw.DefineLabel(); //Generate a start of a 'try' section AddLinePragma(n); cw.Emit(Op.Start, catchLab); CompileExpression(n.Expression, map, Hints.None, n); //Leaving 'try' section cw.Emit(Op.Leave); cw.Emit(Op.Br, exitLab); cw.MarkLabel(catchLab); cw.Emit(Op.Leave); //Throw hint is to tell match compiler to generate a different typeId if //all pattern fail - to rethrow an original error instead of generating a //new MatchFailed error. CompileSimpleMatch(n.Entries.Equations, map, hints | Hints.Throw, null); cw.MarkLabel(exitLab); cw.Emit(Op.Nop); if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, n); } }
//Now we can compile global user defined functions and lazy sections. This is //user typeId however it is not executed when bindings are done therefore we wouldn't need to enforce //laziness here. TopLevel done through pattern matching are rejected on this stage. private FastList <ElaEquation> ProcessFunctions(FastList <ElaEquation> exps, LabelMap map) { var len = exps.Count; var list = new FastList <ElaEquation>(len); for (var i = 0; i < len; i++) { var b = exps[i]; if (b.Right != null) { //We need to ensure that this is a global binding that it is not defined by pattern matching if (b.IsFunction() || (b.Right.Type == ElaNodeType.LazyLiteral && (b.Left.Type == ElaNodeType.NameReference || b.Left.Type == ElaNodeType.LazyLiteral))) { CompileDeclaration(b, map, Hints.None); } else { list.Add(b); } } else { list.Add(b); } } return(list); }
//Compile conditional if-then-else operator private void CompileConditionalOperator(ElaCondition s, LabelMap map, Hints hints) { AddLinePragma(s); CompileExpression(s.Condition, map, Hints.Scope, s); var falseLab = cw.DefineLabel(); cw.Emit(Op.Brfalse, falseLab); //Both the True and False parts may be the tail expressions //Also this whole operator can be used as a statement. Or can be compiled //in a situation when some of the referenced names are not initialized (Lazy) var left = (hints & Hints.Left) == Hints.Left ? Hints.Left : Hints.None; var tail = (hints & Hints.Tail) == Hints.Tail ? Hints.Tail : Hints.None; if (s.True != null) CompileExpression(s.True, map, left | tail | Hints.Scope, s); if (s.False != null) { var skipLabel = cw.DefineLabel(); cw.Emit(Op.Br, skipLabel); cw.MarkLabel(falseLab); CompileExpression(s.False, map, left | tail | Hints.Scope, s); cw.MarkLabel(skipLabel); cw.Emit(Op.Nop); } else { AddError(ElaCompilerError.ElseMissing, s.True); AddHint(ElaCompilerHint.AddElse, s.True); } }
//Inlines a constructor call, method is called from TryOptimizeConstructor private void CompileConstructorCall(string prefix, string name, ElaJuxtaposition juxta, LabelMap map, ScopeVar sv, ScopeVar sv2) { var len = juxta.Parameters.Count; //For optimization purposes we use a simplified creation algorythm for constructors //with 1 and 2 parameters if (len == 1) { CompileExpression(juxta.Parameters[0], map, Hints.None, juxta); TypeCheckIf(prefix, name, 0); } else if (len == 2) { CompileExpression(juxta.Parameters[0], map, Hints.None, juxta); TypeCheckIf(prefix, name, 0); CompileExpression(juxta.Parameters[1], map, Hints.None, juxta); TypeCheckIf(prefix, name, 1); } else CompileConstructorParameters(prefix, name, juxta, map); PushVar(sv); PushVar(sv2); if (len == 1) cw.Emit(Op.Newtype1); else if (len == 2) cw.Emit(Op.Newtype2); else cw.Emit(Op.Newtype); }
//Compiles Ela 'is' expression. private void CompileTypeCheck(ElaTypeCheck n, LabelMap map, Hints hints) { var failLab = cw.DefineLabel(); var endLab = cw.DefineLabel(); var sysVar = AddVariable(); CompileExpression(n.Expression, map, Hints.None, n); PopVar(sysVar); //Here we are checking all classes specified in a pattern. We have to loop //through all classes and generate a check instruction (Traitch) for each. for (var i = 0; i < n.Traits.Count; i++) { var t = n.Traits[i]; PushVar(sysVar); CheckTypeOrClass(t.Prefix, t.Name, failLab, n); } cw.Emit(Op.PushI1_1); cw.Emit(Op.Br, endLab); cw.MarkLabel(failLab); cw.Emit(Op.PushI1_0); cw.MarkLabel(endLab); cw.Emit(Op.Nop); if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(n); }
private void CompileLazyList(ElaGenerator s, LabelMap map, Hints hints) { var fun = CompileRecursiveFor(s, map, hints, -1, -1); CompileExpression(s.Target, map, Hints.None, s); PushVar(fun); cw.Emit(Op.Call); }
//Compiles Ela 'is' expression. private void CompileTypeCheck(ElaTypeCheck n, LabelMap map, Hints hints) { var failLab = cw.DefineLabel(); var endLab = cw.DefineLabel(); var sysVar = AddVariable(); CompileExpression(n.Expression, map, Hints.None, n); PopVar(sysVar); //Here we are checking all classes specified in a pattern. We have to loop //through all classes and generate a check instruction (Traitch) for each. for (var i = 0; i < n.Traits.Count; i++) { var t = n.Traits[i]; PushVar(sysVar); CheckTypeOrClass(t.Prefix, t.Name, failLab, n); } cw.Emit(Op.PushI1_1); cw.Emit(Op.Br, endLab); cw.MarkLabel(failLab); cw.Emit(Op.PushI1_0); cw.MarkLabel(endLab); cw.Emit(Op.Nop); if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, n); } }
//Compile conditional if-then-else operator private void CompileConditionalOperator(ElaCondition s, LabelMap map, Hints hints) { AddLinePragma(s); CompileExpression(s.Condition, map, Hints.Scope, s); var falseLab = cw.DefineLabel(); cw.Emit(Op.Brfalse, falseLab); //Both the True and False parts may be the tail expressions //Also this whole operator can be used as a statement. Or can be compiled //in a situation when some of the referenced names are not initialized (Lazy) var left = (hints & Hints.Left) == Hints.Left ? Hints.Left : Hints.None; var tail = (hints & Hints.Tail) == Hints.Tail ? Hints.Tail : Hints.None; if (s.True != null) { CompileExpression(s.True, map, left | tail | Hints.Scope, s); } if (s.False != null) { var skipLabel = cw.DefineLabel(); cw.Emit(Op.Br, skipLabel); cw.MarkLabel(falseLab); CompileExpression(s.False, map, left | tail | Hints.Scope, s); cw.MarkLabel(skipLabel); cw.Emit(Op.Nop); } else { AddError(ElaCompilerError.ElseMissing, s.True); AddHint(ElaCompilerHint.AddElse, s.True); } }
//An entry method for instance compilation private void CompileInstances(ElaClassInstance s, LabelMap map) { var ins = s; //First we need to compile default instances so that //other instances in this class will be able to use them while (ins != null) { if (ins.TypeName == null) { CompileInstanceBody(ins, map); } ins = ins.And; } ins = s; //Now we compile specific instances while (ins != null) { if (ins.TypeName != null) { CompileInstanceBody(ins, map); } ins = ins.And; } }
//Compiles an 'open' module directive. private void CompileModuleInclude(ElaModuleInclude s, LabelMap map) { var modFrame = IncludeModule( s.Alias, //alias s.Name, //name s.DllName, //dllname s.Path.ToArray(), //path s.Line, //line s.Column, //col s.RequireQualified, //qualified s, //ElaExpression true, //add variable false); //NoPrelude //Process explicit import list for a module if module is valid and has an import list if (s.HasImportList && modFrame != null) { var il = s.ImportList; for (var i = 0; i < il.Count; i++) { var im = il[i]; var a = AddVariable(im.LocalName, im, im.Private ? ElaVariableFlags.Private : ElaVariableFlags.None, -1); var sv = default(ScopeVar); //Query a name from an import list directly in a module if (!modFrame.GlobalScope.Locals.TryGetValue(im.Name, out sv)) AddError(ElaCompilerError.UndefinedNameInModule, im, s.Alias, im.Name); AddLinePragma(im); cw.Emit(Op.Pushext, (frame.HandleMap.Count - 1) | sv.Address << 8); PopVar(a); } } }
//An entry method for type compilation. Ensures that types ('type') are //always compiled before type extensions ('data'). private void CompileTypes(ElaNewtype v, LabelMap map) { CompileHeaders(v); CompileTypeHeaderOnly(v, map); CompileDataHeaderOnly(v, map); CompileTypeBodyOnly(v, map); CompileDataBodyOnly(v, map); }
//Compiles a pattern as lazy by a making a right hand a thunk and then delegating //a job to 'CompileLazyPattern' routine. private void CompileLazyBindingPattern(ElaEquation eq, LabelMap map) { CompileLazyExpression(eq.Right, map, Hints.None); var sys = AddVariable(); PopVar(sys); CompileLazyPattern(sys, eq.Left, false); }
//An entry method for includes compilation private void CompileModuleIncludes(ElaProgram prog, LabelMap map) { var inc = prog.Includes; for (var i = 0; i < inc.Count; i++) { CompileModuleInclude(inc[i], map); } }
//Compiles xs literal private ExprData CompileTuple(ElaTupleLiteral v, LabelMap map, Hints hints) { CompileTupleParameters(v, v.Parameters, map); if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(v); return new ExprData(DataKind.VarType, (Int32)ElaTypeCode.Tuple); }
private void CompileGenerator(ElaGenerator s, LabelMap map, Hints hints) { StartScope(false, s.Line, s.Column); var iter = cw.DefineLabel(); var breakExit = cw.DefineLabel(); var newMap = new LabelMap(map); var addr = -1; if (s.Pattern.Type == ElaNodeType.NameReference) addr = AddVariable(s.Pattern.GetName(), s.Pattern, ElaVariableFlags.None, -1); else addr = AddVariable(); var serv = AddVariable(); CompileExpression(s.Target, map, Hints.None, s); cw.Emit(Op.Dup); PopVar(serv); cw.Emit(Op.Isnil); cw.Emit(Op.Brtrue, breakExit); cw.MarkLabel(iter); PushVar(serv); cw.Emit(Op.Isnil); cw.Emit(Op.Brtrue, breakExit); PushVar(serv); cw.Emit(Op.Head); PopVar(addr); PushVar(serv); cw.Emit(Op.Tail); PopVar(0 | ((addr >> 8) + 1) << 8); if (s.Pattern.Type != ElaNodeType.NameReference) CompilePattern(addr, s.Pattern, iter, false /*allowBang*/, false /*forceStrict*/); if (s.Guard != null) { CompileExpression(s.Guard, map, Hints.None, s); cw.Emit(Op.Brfalse, iter); } if (s.Body != null) { CompileExpression(s.Body, newMap, Hints.Scope, s); if (s.Body.Type != ElaNodeType.Generator) cw.Emit(Op.Cons); } cw.Emit(Op.Br, iter); cw.MarkLabel(breakExit); EndScope(); cw.Emit(Op.Nop); }
//Main compilation method that runs compilation in seven steps by rewriting binding order. private void CompileProgram(ElaProgram prog, LabelMap map) { ProcessIncludesClassesTypes(prog, map); var list = RunForwardDeclaration(prog.TopLevel.Equations, map); list = ProcessFunctions(list, map); ProcessInstances(prog, map); list = ProcessBindings(list, map); ProcessExpressions(list, map); }
//Used to compile an anonymous function (lambda). This function returns a number of parameters //in compiled lambda. private int CompileLambda(ElaEquation bid) { var fc = new ElaJuxtaposition(); //Lambda is parsed as a an application of a lambda operator, e.g. //expr -> expr, therefore it needs to be transformed in order to be //able to use existing match compilation logic. if (bid.Left.Type == ElaNodeType.Juxtaposition && !bid.Left.Parens) //Parens flag is added if an expression is in parens and //therefore should be qualified as a pattern. { var f = (ElaJuxtaposition)bid.Left; fc.Parameters.Add(f.Target); fc.Parameters.AddRange(f.Parameters); } else { fc.Parameters.Add(bid.Left); } bid.Left = fc; var parLen = fc.Parameters.Count; StartFun(null, parLen); var funSkipLabel = cw.DefineLabel(); var map = new LabelMap(); cw.Emit(Op.Br, funSkipLabel); //We start a real (VM based) lexical scope for a function. StartScope(true, bid.Right.Line, bid.Right.Column); StartSection(); var address = cw.Offset; CompileFunctionMatch(parLen, bid, map, Hints.Scope | Hints.Tail); var funHandle = frame.Layouts.Count; var ss = EndFun(funHandle); frame.Layouts.Add(new MemoryLayout(currentCounter, ss, address)); EndScope(); EndSection(); cw.Emit(Op.Ret); cw.MarkLabel(funSkipLabel); AddLinePragma(bid); //Function is constructed cw.Emit(Op.PushI4, parLen); cw.Emit(Op.Newfun, funHandle); return(parLen); }
//Compiles xs literal private ExprData CompileTuple(ElaTupleLiteral v, LabelMap map, Hints hints) { CompileTupleParameters(v, v.Parameters, map); if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, v); } return(new ExprData(DataKind.VarType, (Int32)ElaTypeCode.Tuple)); }
//Used to compile a 'match' expression. private void CompileMatchExpression(ElaMatch n, LabelMap map, Hints hints) { CompileExpression(n.Expression, map, Hints.None, n); AddLinePragma(n); CompileSimpleMatch(n.Entries.Equations, map, hints, n.Expression); if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, n); } }
//Compiles constructor parameters. This method is called from CompileContructorCall, it is used //when inlining constructor and when constructor has >2 arguments. private void CompileConstructorParameters(string prefix, string name, ElaJuxtaposition juxta, LabelMap map) { var pars = juxta.Parameters; cw.Emit(Op.Newtup, pars.Count); for (var i = 0; i < pars.Count; i++) { CompileExpression(pars[i], map, Hints.None, juxta); TypeCheckIf(prefix, name, i); cw.Emit(Op.Tupcons, i); } }
//Compiles any given expression as lazy, can be used to automatically generate thunks //as well as to compile an explicit lazy literal. private ExprData CompileLazyExpression(ElaExpression exp, LabelMap map, Hints hints) { Label funSkipLabel; int address; LabelMap newMap; CompileFunctionProlog(null, 1, exp.Line, exp.Column, out funSkipLabel, out address, out newMap); var ed = CompileExpression(exp, newMap, Hints.Scope | Hints.FunBody, null); CompileFunctionEpilog(null, 1, address, funSkipLabel); cw.Emit(Op.Newlazy); return ed; }
private void CompileTypeBody(ElaNewtype v, LabelMap map) { if (v.HasBody) { for (var i = 0; i < v.Constructors.Count; i++) { var c = v.Constructors[i]; var cf = v.ConstructorFlags[i]; cf = cf == ElaVariableFlags.None ? v.Flags : cf | v.Flags; CompileConstructor(v.Name, v.ScopeVar, c, cf, v.TypeModuleId); } } }
//This method checks if an instance member binding is a function reference with a //correct number of arguments. private bool TryResolveInstanceBinding(int args, ElaExpression exp, LabelMap map) { if (exp == null) { return(false); } //This is not a function at all, but a polymorphic constant. We, however, cannot //execute it right away - as a result we need to defer its execution by creating //a thunk. Code should not be executing when instances are run. if (args == 0) { CompileLazyExpression(exp, map, Hints.None); return(true); } //A simple case - a direct name reference, need to check its type arguments if (exp.Type == ElaNodeType.NameReference) { var sv = GetVariable(exp.GetName(), CurrentScope, GetFlags.NoError, 0, 0); if ((sv.VariableFlags & ElaVariableFlags.Function) == ElaVariableFlags.Function && sv.Data == args) { AddLinePragma(exp); PushVar(sv); return(true); } } else if (exp.Type == ElaNodeType.FieldReference) { //A more complex case - this can be a qualified name (with a module alias) var fr = (ElaFieldReference)exp; if (fr.TargetObject.Type != ElaNodeType.NameReference) { return(false); } CodeFrame _; var sv = FindByPrefix(fr.TargetObject.GetName(), fr.FieldName, out _); if ((sv.VariableFlags & ElaVariableFlags.Function) == ElaVariableFlags.Function && sv.Data == args) { AddLinePragma(exp); PushVar(sv); return(true); } } return(false); }
//This is an old typeId that was used to optimize small ranges (to generate them in-place). //It is currently not used, but in future it may be rejuvenated. private bool TryOptimizeRange(ElaRange range, LabelMap map, Hints hints) { if (range.First.Type != ElaNodeType.Primitive || (range.Second != null && range.Second.Type != ElaNodeType.Primitive) || range.Last.Type != ElaNodeType.Primitive) return false; var fst = (ElaPrimitive)range.First; var snd = range.Second != null ? (ElaPrimitive)range.Second : null; var lst = (ElaPrimitive)range.Last; if (fst.Value.LiteralType != ElaTypeCode.Integer || (snd != null && snd.Value.LiteralType != ElaTypeCode.Integer) || lst.Value.LiteralType != ElaTypeCode.Integer) return false; var fstVal = fst.Value.AsInteger(); var sndVal = snd != null ? snd.Value.AsInteger() : fstVal + 1; var lstVal = lst.Value.AsInteger(); var step = sndVal - fstVal; if (Math.Abs((fstVal - lstVal) / step) > 20) return false; cw.Emit(Op.Newlist); if (snd != null) { cw.Emit(Op.PushI4, fstVal); fstVal = sndVal; cw.Emit(Op.Cons); } for (;;) { cw.Emit(Op.PushI4, fstVal); cw.Emit(Op.Cons); fstVal += step; if (step > 0) { if (fstVal > lstVal) break; } else if (fstVal < lstVal) break; } cw.Emit(Op.Genfin); return true; }
//Type class declarations, modules includes and type declarations can be compiled in the first place. private void ProcessIncludesClassesTypes(ElaProgram prog, LabelMap map) { CompileModuleIncludes(prog, map); if (prog.Classes != null) { CompileClass(prog.Classes, map); } if (prog.Types != null) { CompileTypes(prog.Types, map); } }
//An entry method for range generation. Implementation of ranges are not provided by a compiler, //instead a particular data type implement ranges by providing an instance of Enum class. Functions //succ, enumFrom and enumFromTo are explicitly invoked by this method. private void CompileRange(ElaExpression parent, ElaRange range, LabelMap map, Hints hints) { AddLinePragma(range); var snd = AddVariable(); var fst = AddVariable(); //Compile the first element which should always be present. CompileExpression(range.First, map, Hints.None, range); PopVar(fst); //If the second element is missing we will calculate it using 'succ'. if (range.Second == null) { var sv = GetFunction("succ", range); PushVar(fst); PushVar(sv); cw.Emit(Op.Call); PopVar(snd); } else { CompileExpression(range.Second, map, Hints.None, range); PopVar(snd); } //If a last element is missing we need to generate an infinite range //using 'enumFrom' function. if (range.Last == null) { var sv = GetFunction("enumFrom", range); PushVar(snd); PushVar(fst); PushVar(sv); cw.Emit(Op.Call); cw.Emit(Op.Call); } else { //An ordinary strict range. var sv = GetFunction("enumFromTo", range); PushVar(snd); PushVar(fst); CompileExpression(range.Last, map, Hints.None, range); PushVar(sv); cw.Emit(Op.Call); cw.Emit(Op.Call); cw.Emit(Op.Call); } }
//This method only compiles types declared through 'type' keyword //and not type extensions ('data' declarations). private void CompileTypeBodyOnly(ElaNewtype nt, LabelMap map) { var v = nt; while (v != null) { if (!v.Extends && !v.Header) { CompileTypeBody(v, map); } v = v.And; } }
//Validates a built-in class definition and compile a built-in member private void CompileBuiltinMember(ElaBuiltinKind kind, ElaTypeClass s, int memIndex, LabelMap map) { var flags = ElaVariableFlags.Builtin | ElaVariableFlags.ClassFun; if (memIndex > s.Members.Count - 1) { AddError(ElaCompilerError.InvalidBuiltinClassDefinition, s, s.BuiltinName); return; } var m = s.Members[memIndex]; CompileBuiltin(kind, m, map, m.Name); AddLinePragma(m); PopVar(AddVariable(m.Name, m, flags, (Int32)kind)); }
//Compiles a primitive literal value private ExprData CompilePrimitive(ElaPrimitive p, LabelMap map, Hints hints) { AddLinePragma(p); PushPrimitive(p.Value); if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(p); if (p.Parens && p.Value.IsNegative()) { AddWarning(ElaCompilerWarning.SectionAmbiguity, p, p.Value.ToString()); AddHint(ElaCompilerHint.AddSpaceSection, p, p.Value.ToString().TrimStart('-')); } return new ExprData(DataKind.VarType, (Int32)p.Value.LiteralType); }
//Warnings can be ignored or generated as errors private void AddWarning(LabelMap map, ElaCompilerWarning warning, int line, int col, params object[] args) { if (map.NoWarnings) { return; } else if (options.WarningsAsErrors) { AddError((ElaCompilerError)warning, line, col, args); } else if (!options.NoWarnings) { Errors.Add(new ElaMessage(Strings.GetWarning(warning, args), MessageType.Warning, (Int32)warning, line, col)); } }
//Compiles a provided set of equations. This method is to be used for local //scopes only. private void CompileEquationSet(ElaEquationSet set, LabelMap map) { var list = RunForwardDeclaration(set.Equations, map); list = ProcessFunctions(list, map); list = ProcessBindings(list, map); //Expressions are not allowed in this context if (list.Count > 0) { for (var i = 0; i < list.Count; i++) { AddError(ElaCompilerError.InvalidExpression, list[i], FormatNode(list[i])); } } }
//Performs validation of overlapping for simple case of pattern matching //(such as 'match' expression with a single pattern per entry). private void ValidateOverlapSimple(LabelMap map, IEnumerable <ElaEquation> seq, ElaExpression mexp) { var lst = new List <ElaExpression>(); foreach (var e in seq) { foreach (var o in lst) { if (!e.Left.CanFollow(o)) { AddWarning(map, ElaCompilerWarning.MatchEntryNotReachable, e.Left, FormatNode(e.Left), FormatNode(o)); } } lst.Add(e.Left); } }
//Perform an eta expansion for a given expression private void EtaExpand(string name, ElaExpression exp, LabelMap map, int args) { //Here we generate a function which has a provided number of //arguments if (name != null) { StartFun(name, args); } StartSection(); StartScope(true, exp.Line, exp.Column); cw.StartFrame(args); var funSkipLabel = cw.DefineLabel(); cw.Emit(Op.Br, funSkipLabel); var address = cw.Offset; if (exp.Type != ElaNodeType.Equation) { CompileExpression(exp, map, Hints.None, null); } else { CompileFunction((ElaEquation)exp, map); } //Functions are curried so generate a call for each argument for (var i = 0; i < args; i++) { cw.Emit(Op.Call); } cw.Emit(Op.Ret); frame.Layouts.Add(new MemoryLayout(currentCounter, cw.FinishFrame(), address)); EndSection(); EndScope(); if (name != null) { EndFun(frame.Layouts.Count - 1); } cw.MarkLabel(funSkipLabel); cw.Emit(Op.PushI4, args); 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; }
//Compiles record literal private ExprData CompileRecord(ElaRecordLiteral p, LabelMap map, Hints hints) { AddLinePragma(p); cw.Emit(Op.Newrec, p.Fields.Count); for (var i = 0; i < p.Fields.Count; i++) { var f = p.Fields[i]; CompileExpression(f.FieldValue, map, Hints.None, f); cw.Emit(Op.Pushstr, AddString(f.FieldName)); cw.Emit(Op.Reccons, i); } if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(p); return new ExprData(DataKind.VarType, (Int32)ElaTypeCode.Record); }
//Compiles list literal private ExprData CompileList(ElaListLiteral p, LabelMap map, Hints hints) { var len = p.Values.Count; AddLinePragma(p); cw.Emit(Op.Newlist); //If len is 0 than we have an empty (nil) list which is created by Newlist. for (var i = 0; i < len; i++) { CompileExpression(p.Values[p.Values.Count - i - 1], map, Hints.None, p); cw.Emit(Op.Cons); } if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(p); return new ExprData(DataKind.VarType, (Int32)ElaTypeCode.List); }
private ExprData CompileLazy(ElaLazyLiteral exp, LabelMap map, Hints hints) { var ed = default(ExprData); //Try to optimize lazy section for a case //when a function application is marked as lazy if (!TryOptimizeLazy(exp, map, hints)) { //Regular lazy section compilation //Create a closure around section ed = CompileLazyExpression(exp.Expression, map, hints); } if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(exp); return ed; }
//Compiles a primitive literal value private ExprData CompilePrimitive(ElaPrimitive p, LabelMap map, Hints hints) { AddLinePragma(p); PushPrimitive(p, p.Value); if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, p); } if (p.Parens && p.Value.IsNegative()) { AddWarning(map, ElaCompilerWarning.SectionAmbiguity, p, p.Value.ToString()); AddHint(ElaCompilerHint.AddSpaceSection, p, p.Value.ToString().TrimStart('-')); } return(new ExprData(DataKind.VarType, (Int32)p.Value.LiteralType)); }
//Compiles record literal private ExprData CompileRecord(ElaRecordLiteral p, LabelMap map, Hints hints) { AddLinePragma(p); cw.Emit(Op.Newrec, p.Fields.Count); for (var i = 0; i < p.Fields.Count; i++) { var f = p.Fields[i]; CompileExpression(f.FieldValue, map, Hints.None, f); cw.Emit(Op.Pushstr, AddString(f.FieldName)); cw.Emit(Op.Reccons, i); } if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, p); } return(new ExprData(DataKind.VarType, (Int32)ElaTypeCode.Record)); }
//Compiles any given expression as lazy, can be used to automatically generate thunks //as well as to compile an explicit lazy literal. private ExprData CompileLazyExpression(ElaExpression exp, LabelMap map, Hints hints) { Label funSkipLabel; int address; LabelMap newMap; CompileFunctionProlog(null, 1, exp.Line, exp.Column, out funSkipLabel, out address, out newMap); var ed = CompileExpression(exp, newMap, Hints.Scope | Hints.FunBody, null); CompileFunctionEpilog(null, 1, address, funSkipLabel); if (ed.Type == DataKind.BuiltinError) { cw.Emit(Op.Api, 18); } cw.Emit(Op.Newlazy); return(default(ExprData)); }
private ExprData CompileLazy(ElaLazyLiteral exp, LabelMap map, Hints hints) { var ed = default(ExprData); //Try to optimize lazy section for a case //when a function application is marked as lazy if (!TryOptimizeLazy(exp, map, hints)) { //Regular lazy section compilation //Create a closure around section ed = CompileLazyExpression(exp.Expression, map, hints); } if ((hints & Hints.Left) == Hints.Left) { AddValueNotUsed(map, exp); } return(ed); }
//Compiles xs parameters, can be used in all cases where xs creation is needed. private void CompileTupleParameters(ElaExpression v, List<ElaExpression> pars, LabelMap map) { //Optimize xs creates for a case of pair (a single op typeId is used). if (pars.Count == 2) { CompileExpression(pars[0], map, Hints.None, v); CompileExpression(pars[1], map, Hints.None, v); AddLinePragma(v); cw.Emit(Op.Newtup_2); } else { AddLinePragma(v); cw.Emit(Op.Newtup, pars.Count); for (var i = 0; i < pars.Count; i++) { CompileExpression(pars[i], map, Hints.None, v); cw.Emit(Op.Tupcons, i); } } }
//Compiles logical OR operator in a lazy manner. private void CompileLogicalOr(ElaExpression parent, ElaExpression left, ElaExpression right, LabelMap map, Hints hints) { //Logical OR is executed in a lazy manner var exitLab = default(Label); var termLab = default(Label); var ut = hints; if ((ut & Hints.Left) == Hints.Left) ut ^= Hints.Left; if ((ut & Hints.Tail) == Hints.Tail) ut ^= Hints.Tail; CompileExpression(left, map, ut, parent); termLab = cw.DefineLabel(); exitLab = cw.DefineLabel(); cw.Emit(Op.Brtrue, termLab); CompileExpression(right, map, ut, parent); cw.Emit(Op.Br, exitLab); cw.MarkLabel(termLab); cw.Emit(Op.PushI1_1); cw.MarkLabel(exitLab); cw.Emit(Op.Nop); }
//This method contains main compilation logic for 'match' expressions and for 'try' expressions. //This method only supports a single pattern per entry. //A 'mexp' (matched expression) parameter can be null and is used for additional validation only. private void CompileSimpleMatch(IEnumerable<ElaEquation> bindings, LabelMap map, Hints hints, ElaExpression mexp) { ValidateOverlapSimple(bindings, mexp); var failLab = cw.DefineLabel(); var endLab = cw.DefineLabel(); //We need to remembers the first set of system addresses because we will have //to push them manually for the entries other than first. var firstSys = -1; var first = true; //Loops through all bindings //For the first iteration we assume that all values are already on stack. //For the next iteration we manually push all values. foreach (var b in bindings) { //Each match entry starts a lexical scope StartScope(false, b.Line, b.Column); //For second+ entries we put a label where we would jump if a previous //pattern fails if (!first) { cw.MarkLabel(failLab); failLab = cw.DefineLabel(); } var p = b.Left; var sysVar = -1; //We apply an optimization if a we have a name reference (only for the first iteration). if (p.Type == ElaNodeType.NameReference && first) sysVar = AddVariable(p.GetName(), p, ElaVariableFlags.Parameter, -1); else sysVar = AddVariable(); //Create an unnamed system variable //This is not the first entry, so we have to push values first if (!first) PushVar(firstSys); PopVar(sysVar); //We have to remember addresses calculated in a first iteration //to be able to push them once again in a second iteration. if (first) firstSys = sysVar; CompilePattern(sysVar, p, failLab, false /*allowBang*/, false /*forceStrict*/); first = false; //Compile entry expression if (b.Right == null) AddError(ElaCompilerError.InvalidMatchEntry, b.Left, FormatNode(b)); else CompileExpression(b.Right, map, Hints.None, b); //Close current scope EndScope(); //If everything is OK skip through 'fail' clause cw.Emit(Op.Br, endLab); } //We fall here if all patterns have failed cw.MarkLabel(failLab); //If this hints is set than we generate a match for a 'try' //(exception handling) block. Instead of MatchFailed we need //to rethrow an original exception. Block 'try' always have //just a single expression to match, therefore firstSys[0] is //pretty safe here. if ((hints & Hints.Throw) == Hints.Throw) { PushVar(firstSys); cw.Emit(Op.Rethrow); } else cw.Emit(Op.Failwith, (Int32)ElaRuntimeError.MatchFailed); //Happy path for match cw.MarkLabel(endLab); cw.Emit(Op.Nop); }
//Compiles a sequencing operator private void CompileSeq(ElaExpression parent, ElaExpression left, ElaExpression right, LabelMap map, Hints hints) { var ut = hints; if ((ut & Hints.Left) == Hints.Left) ut ^= Hints.Left; //Sequence operators forces left expression, pops it and yields a value //of a right expression. Evaliation is done in a strict order. CompileExpression(left, map, Hints.None, parent); cw.Emit(Op.Force); cw.Emit(Op.Pop); CompileExpression(right, map, ut, parent); }
//Used to compile an anonymous function (lambda). This function returns a number of parameters //in compiled lambda. private int CompileLambda(ElaEquation bid) { var fc = new ElaJuxtaposition(); //Lambda is parsed as a an application of a lambda operator, e.g. //expr -> expr, therefore it needs to be transformed in order to be //able to use existing match compilation logic. if (bid.Left.Type == ElaNodeType.Juxtaposition && !bid.Left.Parens) //Parens flag is added if an expression is in parens and //therefore should be qualified as a pattern. { var f = (ElaJuxtaposition)bid.Left; fc.Parameters.Add(f.Target); fc.Parameters.AddRange(f.Parameters); } else fc.Parameters.Add(bid.Left); bid.Left = fc; var parLen = fc.Parameters.Count; StartFun(null, parLen); var funSkipLabel = cw.DefineLabel(); var map = new LabelMap(); cw.Emit(Op.Br, funSkipLabel); //We start a real (VM based) lexical scope for a function. StartScope(true, bid.Right.Line, bid.Right.Column); StartSection(); var address = cw.Offset; CompileFunctionMatch(parLen, bid, map, Hints.Scope | Hints.Tail); var funHandle = frame.Layouts.Count; var ss = EndFun(funHandle); frame.Layouts.Add(new MemoryLayout(currentCounter, ss, address)); EndScope(); EndSection(); cw.Emit(Op.Ret); cw.MarkLabel(funSkipLabel); AddLinePragma(bid); //Function is constructed cw.Emit(Op.PushI4, parLen); cw.Emit(Op.Newfun, funHandle); return parLen; }
//Main method used to compile functions. Can compile regular named functions, //named functions in-place (FunFlag.Inline) and type constructors (FunFlag.Newtype). //Return 'true' if a function is clean (no no-inits references). private bool CompileFunction(ElaEquation dec) { var fc = (ElaJuxtaposition)dec.Left; var pars = fc.Parameters.Count; //Target can be null in a case of an anonymous function (lambda) var name = fc.Target != null ? fc.Target.GetName() : String.Empty; StartFun(name, pars); var funSkipLabel = Label.Empty; var map = new LabelMap(); var startLabel = cw.DefineLabel(); //Functions are always compiled in place, e.g. when met. Therefore a 'goto' //instruction is emitted to skip through function definition. funSkipLabel = cw.DefineLabel(); cw.Emit(Op.Br, funSkipLabel); //FunStart label is needed for tail recursive calls when we emit a 'goto' //instead of an actual function call. map.FunStart = startLabel; //Preserve some information about a function we're currently in. map.FunctionName = name; map.FunctionParameters = pars; map.FunctionScope = CurrentScope; cw.MarkLabel(startLabel); //We start a real (VM based) lexical scope for a function. StartScope(true, dec.Right.Line, dec.Right.Column); //We add a special 'context' variable; it is not initialized AddVariable("context", dec, ElaVariableFlags.Context, -1); //StartSection create a real lexical scope. StartSection(); var hints = Hints.Scope|Hints.Tail; AddLinePragma(dec); var address = cw.Offset; cleans.Push(true); CompileFunctionMatch(pars, dec, map, hints); var ret = cleans.Pop(); //This logic creates a function (by finally emitting Newfun). var funHandle = frame.Layouts.Count; var ss = EndFun(funHandle); frame.Layouts.Add(new MemoryLayout(currentCounter, ss, address)); EndScope(); EndSection(); cw.Emit(Op.Ret); cw.MarkLabel(funSkipLabel); AddLinePragma(dec); //Function is constructed cw.Emit(Op.PushI4, pars); cw.Emit(Op.Newfun, funHandle); return ret; }
//Perform an eta expansion for a given expression private void EtaExpand(string name, ElaExpression exp, LabelMap map, int args) { //Here we generate a function which has a provided number of //arguments if (name != null) StartFun(name, args); StartSection(); StartScope(true, exp.Line, exp.Column); cw.StartFrame(args); var funSkipLabel = cw.DefineLabel(); cw.Emit(Op.Br, funSkipLabel); var address = cw.Offset; if (exp.Type != ElaNodeType.Equation) CompileExpression(exp, map, Hints.None, null); else CompileFunction((ElaEquation)exp); //Functions are curried so generate a call for each argument for (var i = 0; i < args; i++) cw.Emit(Op.Call); cw.Emit(Op.Ret); frame.Layouts.Add(new MemoryLayout(currentCounter, cw.FinishFrame(), address)); EndSection(); EndScope(); if (name != null) EndFun(frame.Layouts.Count - 1); cw.MarkLabel(funSkipLabel); cw.Emit(Op.PushI4, args); cw.Emit(Op.Newfun, frame.Layouts.Count - 1); }
//Pattern match compilation method which is used by functions defined by pattern matching. //Argument patNum contains number of patterns that should be in each. private void CompileFunctionMatch(int patNum, IEnumerable<ElaEquation> bindings, LabelMap map, Hints hints) { ValidateOverlapComplex(bindings); var failLab = cw.DefineLabel(); var endLab = cw.DefineLabel(); //We need to remembers the first set of system addresses because we will have //to push them manually for the entries other than first. var firstSys = new int[patNum]; var first = true; //Loops through all bindings //For the first iteration we assume that all values are already on stack. //For the next iteration we manually push all values. foreach (var b in bindings) { //Each match entry starts a lexical scope StartScope(false, b.Line, b.Column); //This match compilation is used for functions only, //therefore the left-hand should always be a juxtaposition. var fc = (ElaJuxtaposition)b.Left; var len = fc.Parameters.Count; var pars = fc.Parameters; //Entries have different number of patterns, so generate an error and continue //compilation in order to prevent generation of 'redundant' error messages. if (len < patNum) AddError(ElaCompilerError.PatternsTooFew, b.Left, FormatNode(b.Left), patNum, len); else if (len > patNum) AddError(ElaCompilerError.PatternsTooMany, b.Left, FormatNode(b.Left), patNum, len); //For second+ entries we put a label where we would jump if a previous //pattern fails if (!first) { cw.MarkLabel(failLab); failLab = cw.DefineLabel(); } for (var i = 0; i < len; i++) { var p = pars[i]; var sysVar = -1; //We apply an optimization if a we have a name reference (only for the first iteration). if (p.Type == ElaNodeType.NameReference && first) sysVar = AddVariable(p.GetName(), p, ElaVariableFlags.Parameter, -1); else sysVar = AddVariable(); //Create an unnamed system variable //This is not the first entry, so we have to push values first if (!first && firstSys.Length > i) PushVar(firstSys[i]); PopVar(sysVar); //We have to remember addresses calculated in a first iteration //to be able to push them once again in a second iteration. if (first && firstSys.Length > i) firstSys[i] = sysVar; } //Now compile patterns for (var i = 0; i < len; i++) if (firstSys.Length > i && pars.Count > i) CompilePattern(firstSys[i], pars[i], failLab, true /*allowBang*/, false /*forceStrict*/); first = false; //Compile entry expression if (b.Right == null) AddError(ElaCompilerError.InvalidMatchEntry, b.Left, FormatNode(b)); else CompileExpression(b.Right, map, hints, b); //Close current scope EndScope(); //If everything is OK skip through 'fail' clause cw.Emit(Op.Br, endLab); } //We fall here if all patterns have failed cw.MarkLabel(failLab); cw.Emit(Op.Failwith, (Int32)ElaRuntimeError.MatchFailed); //Happy path for match cw.MarkLabel(endLab); cw.Emit(Op.Nop); }
//Used to compile a 'match' expression. private void CompileMatchExpression(ElaMatch n, LabelMap map, Hints hints) { CompileExpression(n.Expression, map, Hints.None, n); AddLinePragma(n); CompileSimpleMatch(n.Entries.Equations, map, hints, n.Expression); if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(n); }
//Used to compile a 'try' expression. private void CompileTryExpression(ElaTry n, LabelMap map, Hints hints) { var catchLab = cw.DefineLabel(); var exitLab = cw.DefineLabel(); //Generate a start of a 'try' section AddLinePragma(n); cw.Emit(Op.Start, catchLab); CompileExpression(n.Expression, map, Hints.None, n); //Leaving 'try' section cw.Emit(Op.Leave); cw.Emit(Op.Br, exitLab); cw.MarkLabel(catchLab); cw.Emit(Op.Leave); //Throw hint is to tell match compiler to generate a different typeId if //all pattern fail - to rethrow an original error instead of generating a //new MatchFailed error. CompileSimpleMatch(n.Entries.Equations, map, hints | Hints.Throw, null); cw.MarkLabel(exitLab); cw.Emit(Op.Nop); if ((hints & Hints.Left) == Hints.Left) AddValueNotUsed(n); }
private int CompileRecursiveFor(ElaGenerator s, LabelMap map, Hints hints, int parent, int parentTail) { var funAddr = AddVariable(); StartSection(); StartScope(true, s.Line, s.Column); cw.StartFrame(1); var funSkipLabel = cw.DefineLabel(); cw.Emit(Op.Br, funSkipLabel); var address = cw.Offset; var exitLab = cw.DefineLabel(); var endLab = cw.DefineLabel(); var iterLab = cw.DefineLabel(); var head = AddVariable(); var tail = AddVariable(); var sys = AddVariable(); cw.Emit(Op.Dup); PopVar(sys); cw.Emit(Op.Isnil); cw.Emit(Op.Brtrue, endLab); PushVar(sys); cw.Emit(Op.Head); PopVar(head); PushVar(sys); cw.Emit(Op.Tail); PopVar(tail); if (s.Pattern.Type == ElaNodeType.NameReference) { var addr = AddVariable(s.Pattern.GetName(), s.Pattern, ElaVariableFlags.None, -1); PushVar(head); PopVar(addr); } else CompilePattern(head, s.Pattern, iterLab, false /*allowBang*/, false /*forceStrict*/); if (s.Guard != null) { CompileExpression(s.Guard, map, Hints.None, s); cw.Emit(Op.Brfalse, iterLab); } if (s.Body.Type == ElaNodeType.Generator) { var f = (ElaGenerator)s.Body; var child = CompileRecursiveFor(f, map, hints, funAddr, tail); CompileExpression(f.Target, map, Hints.None, f); PushVar(child); cw.Emit(Op.Call); cw.Emit(Op.Br, exitLab);// } else { PushVar(tail); PushVar(1 | (funAddr >> 8) << 8); cw.Emit(Op.LazyCall); CompileExpression(s.Body, map, Hints.None, s); cw.Emit(Op.Cons); cw.Emit(Op.Br, exitLab); } cw.MarkLabel(iterLab); PushVar(tail); PushVar(1 | (funAddr >> 8) << 8); cw.Emit(Op.Call); cw.Emit(Op.Br, exitLab);// cw.MarkLabel(endLab); if (parent == -1) cw.Emit(Op.Newlist); else { PushVar(1 | (parentTail >> 8) << 8); PushVar(2 | (parent >> 8) << 8); cw.Emit(Op.Call); } cw.MarkLabel(exitLab); cw.Emit(Op.Ret); frame.Layouts.Add(new MemoryLayout(currentCounter, cw.FinishFrame(), address)); EndSection(); EndScope(); cw.MarkLabel(funSkipLabel); cw.Emit(Op.PushI4, 1); cw.Emit(Op.Newfun, frame.Layouts.Count - 1); PopVar(funAddr); return funAddr; }
//This methods tries to optimize lazy section. It would only work when a lazy //section if a function application that result in saturation (no partial applications) //allowed. In such a case this method eliminates "double" function call (which would be //the result of a regular compilation logic). If this method fails than regular compilation //logic is used. private bool TryOptimizeLazy(ElaLazyLiteral lazy, LabelMap map, Hints hints) { var body = default(ElaExpression); //Only function application is accepted if ((body = lazy.Expression).Type != ElaNodeType.Juxtaposition) return false; var funCall = (ElaJuxtaposition)body; //If a target is not a variable we can't check what is actually called if (funCall.Target.Type != ElaNodeType.NameReference) return false; var varRef = (ElaNameReference)funCall.Target; var scopeVar = GetVariable(varRef.Name, varRef.Line, varRef.Column); var len = funCall.Parameters.Count; //Only one parameter is allowed if (len > 1) return false; //If a target is not function we can't optimize it if ((scopeVar.VariableFlags & ElaVariableFlags.Function) != ElaVariableFlags.Function) return false; //Only saturation case is optimized if (scopeVar.Data != funCall.Parameters.Count) return false; //We can only optimize a thunk if a last parameter (that will be executed in a strict manner) //is either a primitive value or an already initialized variable. for (var i = 0; i < len; i++) { var p = funCall.Parameters[i]; //Need to check if variable is already initialized. if (p.Type == ElaNodeType.NameReference) { var ssv = GetVariable(p.GetName(), CurrentScope, GetFlags.NoError, 0, 0); if ((ssv.Flags & ElaVariableFlags.NoInit) == ElaVariableFlags.NoInit) return false; } else if (p.Type != ElaNodeType.Primitive) return false; } for (var i = 0; i < len; i++) CompileExpression(funCall.Parameters[len - i - 1], map, Hints.None, funCall); var sl = len - 1; AddLinePragma(varRef); PushVar(scopeVar); //We partially apply function and create a new function for (var i = 0; i < sl; i++) cw.Emit(Op.Call); AddLinePragma(lazy); //LazyCall uses a function provided to create a thunk //and remembers last function arguments as ElaFunction.LastParameter cw.Emit(Op.LazyCall, len); return true; }