Exemple #1
0
        private void PreProcessRunStatement(ParseNode node)
        {
            NodeStartHousekeeping(node);
            if (options.LoadProgramsInSameAddressSpace)
            {
                bool hasOn = node.Nodes.Any(cn => cn.Token.Type == TokenType.ON);
                if (!hasOn)
                {
                    string subprogramName = node.Nodes[1].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());
                        OpcodeBranchIfTrue branchOpcode = new OpcodeBranchIfTrue();
                        AddOpcode(branchOpcode);
                        // if it wasn't then load it now
                        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
                        branchOpcode.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.
                    }
                }
            }
        }
Exemple #2
0
        private void PreProcessWhenStatement(ParseNode node)
        {
            NodeStartHousekeeping(node);
            nextBraceIsFunction = true; // triggers aren't really functions but act like it a lot.

            int expressionHash = ConcatenateNodes(node).GetHashCode();
            string triggerIdentifier = "when-" + expressionHash.ToString();
            Trigger triggerObject = context.Triggers.GetTrigger(triggerIdentifier);

            currentCodeSection = triggerObject.Code;
            VisitNode(node.Nodes[1]);
            OpcodeBranchIfTrue branchToBody = new OpcodeBranchIfTrue();
            branchToBody.Distance = 3;
            AddOpcode(branchToBody);
            AddOpcode(new OpcodePush(true));       // wasn't triggered yet, so preserve.
            AddOpcode(new OpcodeReturn((short)0)); // premature return because it wasn't triggered

            // make flag that remembers whether to remove trigger:
            // defaults to true = removal should happen.
            string triggerKeepName = "$keep-" + triggerIdentifier;
            PushTriggerKeepName(triggerKeepName);
            AddOpcode(new OpcodePush(triggerKeepName));
            AddOpcode(new OpcodePush(false));
            AddOpcode(new OpcodeStoreGlobal());

            VisitNode(node.Nodes[3]);

            // PRESERVE will determine whether or not the trigger returns true (true means
            // re-enable the trigger upon exit.)
            PopTriggerKeepName();
            AddOpcode(new OpcodePush(triggerKeepName));
            AddOpcode(new OpcodeReturn((short)0));
            nextBraceIsFunction = false;
        }
Exemple #3
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);
        }
Exemple #4
0
        // This is actually used for BOTH LOCK expressions and DEFINE FUNCTIONs, as they
        // both end up creating, effectively, a user function.
        private void PreProcessUserFunctionStatement(ParseNode node)
        {
            NodeStartHousekeeping(node);

            // The name of the lock or function to be executed:
            string userFuncIdentifier;
            // The syntax node for the body of the lock or function: the stuff it actually executes.
            ParseNode bodyNode;

            bool isLock = IsLockStatement(node);
            bool isDefFunc = IsDefineFunctionStatement(node);
            bool needImplicitArgBottom = false;
            StorageModifier storageType = GetStorageModifierFor(node);

            ParseNode lastSubNode = node.Nodes[node.Nodes.Count-1];
            if (isLock)
            {
                userFuncIdentifier = lastSubNode.Nodes[1].Token.Text; // The IDENT of: LOCK IDENT TO EXPR.
                bodyNode = lastSubNode.Nodes[3]; // The EXPR of: LOCK IDENT TO EXPR.
                needImplicitArgBottom = true;
            }
            else if (isDefFunc)
            {
                userFuncIdentifier = lastSubNode.Nodes[1].Token.Text; // The IDENT of: DEFINE FUNCTION IDENT INSTRUCTION_BLOCK.
                bodyNode = lastSubNode.Nodes[2]; // The INSTRUCTION_BLOCK of: DEFINE FUNCTION IDENT INSTRUCTION_BLOCK.
            }
            else
                return; // Should only be the case when scanning elements for anonymous functions

            UserFunction userFuncObject = context.UserFunctions.GetUserFunction(
                userFuncIdentifier,
                (storageType == StorageModifier.GLOBAL ? (Int16)0 : GetContainingScopeId(node)),
                node );
            int expressionHash = ConcatenateNodes(bodyNode).GetHashCode();

            needImplicitReturn = true; // Locks always need an implicit return.  Functions might not if all paths have an explicit one.

            // Both locks and functions also get an identifier storing their
            // destination instruction pointer, but the means of doing so
            // is slightly different.  Locks always need a dummy do-nothing
            // function to exist at first, which then can get replaced later
            // when the statement containing the lock definition is encountered.
            // Whereas, function bodies don't get overwritten like that.  They
            // exist exactly once, and can be "forward" called from higher up in
            // the same scope so they get assigned when the scope is first opened.
            //
            if (isLock && !userFuncObject.IsInitialized())
            {
                currentCodeSection = userFuncObject.InitializationCode;

                if (userFuncObject.IsSystemLock())
                {
                    AddOpcode(new OpcodePush(userFuncObject.ScopelessPointerIdentifier));
                    AddOpcode(new OpcodeExists());
                    var branch = new OpcodeBranchIfTrue();
                    branch.Distance = 4;
                    AddOpcode(branch);
                    AddOpcode(new OpcodePush(userFuncObject.ScopelessPointerIdentifier));
                    AddOpcode(new OpcodePushRelocateLater(null), userFuncObject.DefaultLabel);
                    AddOpcode(new OpcodeStore());
                }
                else
                {
                    // initialization code - unfortunately the lock implementation presumed global namespace
                    // and insisted on inserting an initialization block in front of the entire program to set up
                    // the GLOBAL lock value.  This assumption was thorny to remove, so for now, we'll make the init
                    // code consist of a dummy NOP until a better solution can be found.  Note this does put a NOP
                    // into the code PER LOCK.  Which is silly.  It's because lockObject.IsInitialized() doesn't
                    // know how to tell the difference between initialization code that's deliberately empty versus
                    // initialization code being empty because the lock has never been set up properly yet.
                    AddOpcode(new OpcodeNOP());
                }

                // build default dummy function to be used when this is a LOCK:
                currentCodeSection = userFuncObject.GetUserFunctionOpcodes(0);
                AddOpcode(new OpcodeArgBottom()).Label = userFuncObject.DefaultLabel;;
                AddOpcode(new OpcodePush("$" + userFuncObject.ScopelessIdentifier));
                AddOpcode(new OpcodeReturn(0));
            }

            // lock expression's or function body's code
            currentCodeSection = userFuncObject.GetUserFunctionOpcodes(expressionHash);
            bool secondInstanceSameLock = currentCodeSection.Count > 0;
            if (! secondInstanceSameLock)
            {
                forcedNextLabel = userFuncObject.GetUserFunctionLabel(expressionHash);

                if (isLock) // locks need to behave as if they had braces even though they don't - so they get lexical scope ids for closure reasons:
                    BeginScope(bodyNode);
                if (isDefFunc)
                    nextBraceIsFunction = true;
                if (needImplicitArgBottom)
                    AddOpcode(new OpcodeArgBottom());

                VisitNode(bodyNode);

                Int16 implicitReturnScopeDepth = 0;

                if (isDefFunc)
                    nextBraceIsFunction = false;
                if (isLock) // locks need to behave as if they had braces even though they don't - so they get lexical scope ids for closure reasons:
                {
                    EndScope(bodyNode, false);
                    implicitReturnScopeDepth = 1;
                }

                if (needImplicitReturn)
                {
                    if (isDefFunc)
                        AddOpcode(new OpcodePush(0)); // Functions must push a dummy return val when making implicit returns. Locks already leave an expr atop the stack.
                    AddOpcode(new OpcodeReturn(implicitReturnScopeDepth));
                }
                userFuncObject.ScopeNode = GetContainingBlockNode(node); // This limits the scope of the function to the instruction_block the DEFINE was in.
                userFuncObject.IsFunction = !(isLock);
            }
        }