Example #1
0
        private void PreProcessRunStatement(ParseNode node)
        {
            NodeStartHousekeeping(node);
            if (options.LoadProgramsInSameAddressSpace)
            {
                int progNameIndex = 1;
                if (node.Nodes[1].Token.Type == TokenType.ONCE)
                {
                    ++progNameIndex;
                }
                bool hasOn = node.Nodes.Any(cn => cn.Token.Type == TokenType.ON);
                if (!hasOn)
                {
                    string subprogramName = node.Nodes[progNameIndex].Token.Text; // It assumes it already knows at compile-time how many unique program filenames exist, 
                    if (!context.Subprograms.Contains(subprogramName)) // which it uses to decide how many of these blocks to make,
                    {                                                   // which is why we can't defer run filenames until runtime like we can with the others.
                        Subprogram subprogramObject = context.Subprograms.GetSubprogram(subprogramName);
                        // Function code
                        currentCodeSection = subprogramObject.FunctionCode;
                        // verify if the program has been loaded
                        Opcode functionStart = AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier));
                        // Becuse of Cpu.SaveAndClearPointers(), the subprogram's pointer identifier won't
                        // exist in this program context until it has been compiled once.  If it does exist,
                        // then skip the compiling step and just run the code that's already there:
                        AddOpcode(new OpcodeExists());
                        // branch to where the compiler loads the code:
                        OpcodeBranchIfFalse branchToLoad = new OpcodeBranchIfFalse();
                        AddOpcode(branchToLoad);
                        // Now the top of the stack should be the argument pushed by
                        // VisitRunStatement that flags if there was a 'once' keyword:
                        // If there was no 'once', then branch to where the already
                        // loaded code gets run, even though it's been run before:
                        OpcodeBranchIfFalse branchToRun = new OpcodeBranchIfFalse();
                        AddOpcode(branchToRun);
                        // If it falls through to here, that means the code was already
                        // loaded, AND the 'once' keyword was present in the instance
                        // where this loading function got called, so just do nothing
                        // and return.
                        AddOpcode(new OpcodePush(0));
                        AddOpcode(new OpcodeReturn(0));
                        // if it wasn't then load it now:
                        // First throw away the 'once' argument since it doesn't matter when
                        // we're going to be compiling regardless:
                        OpcodePop firstOpcodeOfLoadSection = new OpcodePop();
                        AddOpcode(firstOpcodeOfLoadSection);
                        branchToLoad.DestinationLabel = firstOpcodeOfLoadSection.Label;
                        AddOpcode(new OpcodePush(subprogramObject.PointerIdentifier));
                        AddOpcode(new OpcodePush(new KOSArgMarkerType()));
                        AddOpcode(new OpcodePush(subprogramObject.SubprogramName));
                        AddOpcode(new OpcodePush(null)); // The output filename - only used for compile-to-file rather than for running.
                        AddOpcode(new OpcodeCall("load()"));
                        AddOpcode(new OpcodePop()); // all functions now return a value even if it's a dummy we ignore.
                        // store the address of the program in the pointer variable
                        // (the load() function pushes the address onto the stack)
                        AddOpcode(new OpcodeStore());
                        // call the program
                        Opcode callOpcode = AddOpcode(new OpcodeCall(subprogramObject.PointerIdentifier));
                        // set the call opcode as the destination of the previous branch
                        branchToRun.DestinationLabel = callOpcode.Label;
                        // return to the caller address, after adding a dummy return val:

                        // maybe TODO?  Right now the RETURN command is being prevented from being used outside 
                        // a function declaration.  But in principle we could have programs return exit codes
                        // using the same architecture, and in fact that is why this dummy return value is needed,
                        // because OpcodeReturn now expects such a return value to exist and throws an exception when it
                        // does not.
                        // If an EXIT command was implemented, it would maybe allow an exit code that can be read here:
                        AddOpcode(new OpcodePop()); // for now: throw away return code from subprogram.
                        AddOpcode(new OpcodePush(0)); // Replace it with new dummy return code.
                        AddOpcode(new OpcodeReturn(0)); // return that.

                        // set the function start label
                        subprogramObject.FunctionLabel = functionStart.Label;

                        // Initialization code
                        currentCodeSection = subprogramObject.InitializationCode;
                        // removed pointer initialization since it overwrote any existing values when loading ksm files.
                    }
                }
            }
        }
Example #2
0
        protected IEnumerable <Opcode> BuildBoilerplateLoader()
        {
            List <Opcode> boilerplate = new List <Opcode>();

            InternalPath path = new BuiltInPath();

            // First label of the load/runner will be called "@LR00", which is important because that's
            // what we hardcode all the compiler-built VisitRunStatement's to call out to:
            labelCounter = 0;
            labelFormat  = "@LR{0:D2}";
            boilerplate.Add(new OpcodePushScope(-999, 0)
            {
                Label = nextLabel, SourcePath = path
            });

            // High level kerboscript function calls flip the argument orders for us, but
            // low level kRISC does not so the parameters have to be read in stack order:

            // store parameter 2 in a local name:
            boilerplate.Add(new OpcodeStoreLocal("$runonce")
            {
                Label = nextLabel, SourcePath = path
            });

            // store parameter 1 in a local name:
            boilerplate.Add(new OpcodeStoreLocal("$filename")
            {
                Label = nextLabel, SourcePath = path
            });

            // Unconditionally call load() no matter what.  load() will abort and return
            // early if the program was already compiled, and tell us that on the stack:
            boilerplate.Add(new OpcodePush(new kOS.Safe.Execution.KOSArgMarkerType())
            {
                Label = nextLabel, SourcePath = path
            });
            boilerplate.Add(new OpcodePush("$filename")
            {
                Label = nextLabel, SourcePath = path
            });
            boilerplate.Add(new OpcodeEval()
            {
                Label = nextLabel, SourcePath = path
            });
            boilerplate.Add(new OpcodePush(true)
            {
                Label = nextLabel, SourcePath = path
            });                                                                           // the flag that tells load() to abort early if it's already loaded:
            boilerplate.Add(new OpcodePush(null)
            {
                Label = nextLabel, SourcePath = path
            });
            boilerplate.Add(new OpcodeCall("load()")
            {
                Label = nextLabel, SourcePath = path
            });

            // Stack now has the 2 return values of load():
            //    Topmost value is a boolean flag for whether or not the program was already loaded.
            //    Second-from-top value is the entry point into the program.

            // If load() didn't claim the program was already loaded, or if we aren't operating
            // in "once" mode, then jump to the part where we call the loaded program, else
            // fall through to a dummy do-nothing return for the "run once, but it already ran" case:
            Opcode branchFromOne = new OpcodeBranchIfFalse()
            {
                Label = nextLabel, SourcePath = path
            };

            boilerplate.Add(branchFromOne);
            boilerplate.Add(new OpcodePush("$runonce")
            {
                Label = nextLabel, SourcePath = path
            });
            Opcode branchFromTwo = new OpcodeBranchIfFalse()
            {
                Label = nextLabel, SourcePath = path
            };

            boilerplate.Add(branchFromTwo);

            // If we fall through to this opcode (the br.false above doesn't jump), we are
            // in the "program already ran, so skip running it" clause of a RUN ONCE.
            // In that case the stack will still have the jump address returned by load(),
            // and also all the args that were meant to be passed to the program (which is not
            // getting run.)  In that case, we need to pop the stack of everything above the
            // next KOSArgMarker to emulate what a call would have done to the stack had it run.
            Opcode loopStart = new OpcodePop()
            {
                Label = nextLabel, SourcePath = path
            };

            boilerplate.Add(loopStart);
            boilerplate.Add(new OpcodeTestArgBottom()
            {
                Label = nextLabel, SourcePath = path
            });
            Opcode branchThatExitsLoop = new OpcodeBranchIfTrue()
            {
                Label = nextLabel, SourcePath = path
            };

            boilerplate.Add(branchThatExitsLoop);
            boilerplate.Add(new OpcodeBranchJump()
            {
                Label = nextLabel, SourcePath = path, DestinationLabel = loopStart.Label
            });
            Opcode afterLoop = new OpcodePop()
            {
                Label = nextLabel, SourcePath = path
            };                                                                           // Pop the KOSArgMarker that was under the args

            boilerplate.Add(afterLoop);
            branchThatExitsLoop.DestinationLabel = afterLoop.Label;
            boilerplate.Add(new OpcodePush(0)
            {
                Label = nextLabel, SourcePath = path
            });                                                                            // ---+-- The dummy do-nothing return.
            boilerplate.Add(new OpcodeReturn(1)
            {
                Label = nextLabel, SourcePath = path
            });                                                                          // ---'

            // Actually call the Program from its entry Point, which is now the thing left on top
            // of the stack from the second return value of load():
            Opcode branchTo = new OpcodeStoreLocal("$entrypoint")
            {
                Label = nextLabel, SourcePath = path
            };

            boilerplate.Add(branchTo);
            boilerplate.Add(new OpcodeCall("$entrypoint")
            {
                Label = nextLabel, SourcePath = path
            });

            boilerplate.Add(new OpcodePop()
            {
                Label = nextLabel, SourcePath = path
            });
            boilerplate.Add(new OpcodePush(0)
            {
                Label = nextLabel, SourcePath = path
            });
            boilerplate.Add(new OpcodeReturn(1)
            {
                Label = nextLabel, SourcePath = path
            });

            branchFromOne.DestinationLabel = branchTo.Label;
            branchFromTwo.DestinationLabel = branchTo.Label;

            return(boilerplate);
        }