void ClearLocalsAndBlocks() { Locals.Clear(); AllLocalBindingRecords.Clear(); TempLocalNames.Clear(); SpareLocals.Clear(); OuterLocals.Clear(); }
public ILocalBuilder PushInnerLocal([NotNull] IRoutineBuilder rb, [NotNull] ZilAtom atom, LocalBindingType reason, ISourceLine src) { if (Locals.TryGetValue(atom, out var prev)) { // save the old binding if (OuterLocals.TryGetValue(atom, out var stk) == false) { stk = new Stack <LocalBindingRecord>(); OuterLocals.Add(atom, stk); } stk.Push(prev); } ILocalBuilder result; if (SpareLocals.Count > 0) { // reuse a spare variable result = SpareLocals.Pop(); } else { // allocate a new variable with a unique name var tempName = MakeUniqueVariableName(atom); try { result = rb.DefineLocal(tempName.Text); } catch (InvalidOperationException) { throw new CompilerError(CompilerMessages.Expression_Needs_Temporary_Variables_Not_Allowed_Here); } TempLocalNames.Add(tempName); } var lbr = new LocalBindingRecord(reason, src, atom.Text, result); Locals[atom] = lbr; AllLocalBindingRecords.Add(lbr); return(result); }
void BuildRoutine([NotNull] ZilRoutine routine, [NotNull] IRoutineBuilder rb, bool entryPoint, bool traceRoutines) { // give the user a chance to rewrite the routine routine = MaybeRewriteRoutine(Context, routine); // set up arguments and locals ClearLocalsAndBlocks(); if (Context.TraceRoutines) { rb.EmitPrint("[" + routine.Name, false); } DefineLocalsFromArgSpec(); if (Context.TraceRoutines) { rb.EmitPrint("]\n", false); } // define a block for the routine Blocks.Clear(); Blocks.Push(new Block { Name = routine.ActivationAtom, AgainLabel = rb.RoutineStart, ReturnLabel = null, Flags = BlockFlags.None }); // generate code for routine body int i = 1; foreach (var stmt in routine.Body) { // only want the result of the last statement // and we never want results in the entry routine, since it can't return CompileStmt(rb, stmt, !entryPoint && i == routine.BodyLength); i++; } // the entry point has to quit instead of returning if (entryPoint) { rb.EmitQuit(); } // clean up WarnAboutUnusedLocals(); ClearLocalsAndBlocks(); // helpers void DefineLocalsFromArgSpec() { foreach (var arg in routine.ArgSpec) { var originalArgName = arg.Atom; var uniqueArgName = MakeUniqueVariableName(originalArgName); if (uniqueArgName != originalArgName) { /* When a parameter has to be renamed because of a conflict, use TempLocalNames * to reserve the new name so we don't collide with it later. For example: * * <GLOBAL FOO <>> * <ROUTINE BLAH (FOO) * <PROG ((FOO)) ...>> * * We rename the local variable to FOO?1 to avoid shadowing the global. * Now the temporary variable bound by the PROG has to be FOO?2. * ZIL code only sees the name FOO: the local is shadowed inside the PROG, * and the global can always be accessed with SETG and GVAL. */ TempLocalNames.Add(uniqueArgName); } var lb = MakeLocalBuilder(arg, uniqueArgName.Text); if (traceRoutines && arg.Type == ArgItem.ArgType.Required) { // TODO: print OPT parameters when tracing routine execution too rb.EmitPrint(" " + originalArgName + "=", false); rb.EmitPrint(PrintOp.Number, lb); } var lbr = new LocalBindingRecord(arg.Type.ToLocalBindingType(), routine.SourceLine, originalArgName.Text, lb); Locals.Add(originalArgName, lbr); AllLocalBindingRecords.Add(lbr); SetOrEmitDefaultValue(lb, arg); } } ILocalBuilder MakeLocalBuilder(ArgItem arg, string uniqueArgName) { ILocalBuilder lb; switch (arg.Type) { case ArgItem.ArgType.Required: try { lb = rb.DefineRequiredParameter(uniqueArgName); } catch (InvalidOperationException) { throw new CompilerError( CompilerMessages.Expression_Needs_Temporary_Variables_Not_Allowed_Here); } break; case ArgItem.ArgType.Optional: lb = rb.DefineOptionalParameter(uniqueArgName); break; case ArgItem.ArgType.Auxiliary: lb = rb.DefineLocal(uniqueArgName); break; default: throw UnhandledCaseException.FromEnum(arg.Type); } return(lb); } void SetOrEmitDefaultValue(ILocalBuilder lb, ArgItem arg) { if (arg.DefaultValue == null) { return; } Debug.Assert(arg.Type == ArgItem.ArgType.Optional || arg.Type == ArgItem.ArgType.Auxiliary); // setting any default value counts as a write MarkVariableAsWritten(lb); lb.DefaultValue = CompileConstant(arg.DefaultValue); if (lb.DefaultValue != null) { return; } ILabel nextLabel = null; // ReSharper disable once SwitchStatementMissingSomeCases switch (arg.Type) { case ArgItem.ArgType.Optional when !rb.HasArgCount: // not a constant throw new CompilerError(routine.SourceLine, CompilerMessages.Optional_Args_With_Nonconstant_Defaults_Not_Supported_For_This_Target); case ArgItem.ArgType.Optional: nextLabel = rb.DefineLabel(); rb.Branch(Condition.ArgProvided, lb, null, nextLabel, true); goto default; default: var val = CompileAsOperand(rb, arg.DefaultValue, routine.SourceLine, lb); if (val != lb) { rb.EmitStore(lb, val); } break; } if (nextLabel != null) { rb.MarkLabel(nextLabel); } } void WarnAboutUnusedLocals() { foreach (var lbr in AllLocalBindingRecords) { if (lbr.IsEverRead || lbr.IsEverWritten) { continue; } if (lbr.Type == LocalBindingType.CompilerTemporary) { continue; } //XXX not sure about this if (lbr.Type == LocalBindingType.RoutineRequired) { continue; } var warning = new CompilerError( lbr.Definition, CompilerMessages.Local_Variable_0_Is_Never_Used, lbr.BoundName); Context.HandleError(warning); } } }