/// <summary> /// Compile source text into compiled codeparts. /// </summary> /// <param name="filePath">The name that should get reported to the user on /// runtime errors in this compiled code. Even if the text is not from an /// actual file this should still be a pseudo-filename for reporting, for /// example "(commandline)" or "(socket stream)" /// </param> /// <param name="startLineNum">Assuming scriptText is a subset of some bigger buffer, line 1 of scripttext /// corresponds to line (what) of the more global something, for reporting numbers on errors.</param> /// <param name="scriptText">The text to be compiled.</param> /// <param name="contextId">The name of the runtime context (i.e. "interpreter").</param> /// <param name="options">settings for the compile</param> /// <returns>The CodeParts made from the scriptText</returns> public abstract List<CodePart> Compile(string filePath, int startLineNum, string scriptText, string contextId, CompilerOptions options);
public void Boot() { // break all running programs currentContext = null; contexts.Clear(); PushInterpreterContext(); currentStatus = Status.Running; currentTime = 0; maxUpdateTime = 0.0; maxExecutionTime = 0.0; // clear stack (which also orphans all local variables so they can get garbage collected) stack.Clear(); // clear global variables globalVariables.Variables.Clear(); // clear interpreter if (shared.Interpreter != null) shared.Interpreter.Reset(); // load functions if (shared.FunctionManager != null) shared.FunctionManager.Load(); // load bindings if (shared.BindingMgr != null) shared.BindingMgr.Load(); // Booting message if (shared.Screen != null) { shared.Screen.ClearScreen(); string bootMessage = string.Format("kOS Operating System\n" + "KerboScript v{0}\n(manual at {1})\n \n" + "Proceed.\n", SafeHouse.Version, SafeHouse.DocumentationURL); List<string> nags = Debug.GetPendingNags(); if (nags.Count > 0) { bootMessage += "##################################################\n" + "# NAG MESSAGES #\n" + "##################################################\n" + "# Further details about these important messages #\n" + "# can be found in the KSP error log. If you see #\n" + "# this, then read the error log. It's important.#\n" + "--------------------------------------------------\n"; bootMessage = nags.Aggregate(bootMessage, (current, msg) => current + (msg + "\n")); bootMessage += "##################################################\n"; shared.Processor.SetMode(Module.ProcessorModes.OFF); } shared.Screen.Print(bootMessage); } if (!shared.Processor.CheckCanBoot()) return; string filename = shared.Processor.BootFilename; // Check to make sure the boot file name is valid, and then that the boot file exists. if (string.IsNullOrEmpty(filename)) { SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); } else if (filename.Equals("None", StringComparison.InvariantCultureIgnoreCase)) { SafeHouse.Logger.Log("Boot file name is \"None\", skipping boot script"); } else if (shared.VolumeMgr.CurrentVolume.Open(filename) == null) { SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", filename)); } else { var bootContext = "program"; string bootCommand = string.Format("run {0}.", filename); var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager, IsCalledFromRun = false }; shared.ScriptHandler.ClearContext(bootContext); List<CodePart> parts = shared.ScriptHandler.Compile( "sys:boot", 1, bootCommand, bootContext, options); IProgramContext programContext = SwitchToProgramContext(); programContext.Silent = true; programContext.AddParts(parts); } }
public void Boot() { // break all running programs currentContext = null; contexts.Clear(); PushInterpreterContext(); currentStatus = Status.Running; currentTime = 0; timeWaitUntil = 0; maxUpdateTime = 0.0; maxTriggersTime = 0.0; maxExecutionTime = 0.0; // clear stack (which also orphans all local variables so they can get garbage collected) stack.Clear(); // clear global variables globalVariables.Variables.Clear(); // clear interpreter if (shared.Interpreter != null) shared.Interpreter.Reset(); // load functions if (shared.FunctionManager != null) shared.FunctionManager.Load(); // load bindings if (shared.BindingMgr != null) shared.BindingMgr.Load(); // Booting message if (shared.Screen != null) { shared.Screen.ClearScreen(); string bootMessage = string.Format("kOS Operating System\n" + "KerboScript v{0}\n \n" + "Proceed.\n", Core.VersionInfo); List<string> nags = Safe.Utilities.Debug.GetPendingNags(); if (nags.Count > 0) { bootMessage += "##################################################\n" + "# NAG MESSAGES #\n" + "##################################################\n" + "# Further details about these important messages #\n" + "# can be found in the KSP error log. If you see #\n" + "# this, then read the error log. It's important.#\n" + "--------------------------------------------------\n"; bootMessage = nags.Aggregate(bootMessage, (current, msg) => current + (msg + "\n")); bootMessage += "##################################################\n"; } shared.Screen.Print(bootMessage); } if (shared.VolumeMgr == null) { SafeHouse.Logger.Log("No volume mgr"); } else if (!shared.VolumeMgr.CheckCurrentVolumeRange(shared.Vessel)) { SafeHouse.Logger.Log("Boot volume not in range"); } else if (shared.VolumeMgr.CurrentVolume == null) { SafeHouse.Logger.Log("No current volume"); } else if (shared.ScriptHandler == null) { SafeHouse.Logger.Log("No script handler"); } else { string filename = shared.Processor.BootFilename; // Check to make sure the boot file name is valid, and then that the boot file exists. if (String.IsNullOrEmpty(filename)) { SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); } else if (filename.Equals("None", StringComparison.InvariantCultureIgnoreCase)) { SafeHouse.Logger.Log("Boot file name is \"None\", skipping boot script"); } else if (shared.VolumeMgr.CurrentVolume.GetByName(filename) == null) { SafeHouse.Logger.Log(String.Format("Boot file \"{0}\" is missing, skipping boot script", filename)); } else { string filePath = shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume) + "/" + filename; shared.ScriptHandler.ClearContext("program"); var programContext = ((CPU)shared.Cpu).SwitchToProgramContext(); programContext.Silent = true; var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true }; List<CodePart> parts = shared.ScriptHandler.Compile( filePath, 1, String.Format("run {0}.", filename), "program", options); programContext.AddParts(parts); } } }
public void Boot() { // break all running programs currentContext = null; contexts.Clear(); PushInterpreterContext(); currentRunSection = Section.Main; currentTime = 0; maxUpdateTime = 0.0; maxExecutionTime = 0.0; // clear stack (which also orphans all local variables so they can get garbage collected) stack.Clear(); // clear global variables globalVariables.Variables.Clear(); // clear interpreter if (shared.Interpreter != null) shared.Interpreter.Reset(); // load functions if (shared.FunctionManager != null) shared.FunctionManager.Load(); // load bindings if (shared.BindingMgr != null) shared.BindingMgr.Load(); // Booting message if (shared.Screen != null) { shared.Screen.ClearScreen(); string bootMessage = string.Format("kOS Operating System\n" + "KerboScript v{0}\n(manual at {1})\n \n" + "Proceed.\n", SafeHouse.Version, SafeHouse.DocumentationURL); List<string> nags = Debug.GetPendingNags(); if (nags.Count > 0) { bootMessage += "##################################################\n" + "# NAG MESSAGES #\n" + "##################################################\n" + "# Further details about these important messages #\n" + "# can be found in the KSP error log. If you see #\n" + "# this, then read the error log. It's important.#\n" + "--------------------------------------------------\n"; bootMessage = nags.Aggregate(bootMessage, (current, msg) => current + (msg + "\n")); bootMessage += "##################################################\n"; shared.Processor.SetMode(Module.ProcessorModes.OFF); } shared.Screen.Print(bootMessage); } if (!shared.Processor.CheckCanBoot()) return; VolumePath path = shared.Processor.BootFilePath; // Check to make sure the boot file name is valid, and then that the boot file exists. if (path == null) { SafeHouse.Logger.Log("Boot file name is empty, skipping boot script"); } else { // Boot is only called once right after turning the processor on, // the volume cannot yet have been changed from that set based on // Config.StartOnArchive, and Processor.CheckCanBoot() has already // handled the range check for the archive. Volume sourceVolume = shared.VolumeMgr.CurrentVolume; var file = shared.VolumeMgr.CurrentVolume.Open(path); if (file == null) { SafeHouse.Logger.Log(string.Format("Boot file \"{0}\" is missing, skipping boot script", path)); } else { var bootContext = "program"; string bootCommand = string.Format("run \"{0}\".", file.Path); var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager, IsCalledFromRun = false }; shared.ScriptHandler.ClearContext(bootContext); List<CodePart> parts = shared.ScriptHandler.Compile(new BootGlobalPath(bootCommand), 1, bootCommand, bootContext, options); IProgramContext programContext = SwitchToProgramContext(); programContext.Silent = true; programContext.AddParts(parts); } } }
protected void CompileCommand(string commandText) { if (Shared.ScriptHandler == null) return; try { CompilerOptions options = new CompilerOptions { LoadProgramsInSameAddressSpace = false, FuncManager = Shared.FunctionManager, IsCalledFromRun = false }; List<CodePart> commandParts = Shared.ScriptHandler.Compile("interpreter history", commandHistoryIndex, commandText, "interpreter", options); if (commandParts == null) return; var interpreterContext = ((CPU)Shared.Cpu).GetInterpreterContext(); interpreterContext.AddParts(commandParts); } catch (Exception e) { if (Shared.Logger != null) { Shared.Logger.Log(e); } } }
public override void Execute(SharedObjects shared) { // run() is strange. It needs two levels of args - the args to itself, and the args it is meant to // pass on to the program it's invoking. First, these are the args to run itself: object volumeId = PopValueAssert(shared, true); string fileName = PopValueAssert(shared, true).ToString(); AssertArgBottomAndConsume(shared); // Now the args it is going to be passing on to the program: var progArgs = new List<Object>(); int argc = CountRemainingArgs(shared); for (int i = 0; i < argc; ++i) progArgs.Add(PopValueAssert(shared, true)); AssertArgBottomAndConsume(shared); if (shared.VolumeMgr == null) return; if (shared.VolumeMgr.CurrentVolume == null) throw new Exception("Volume not found"); ProgramFile file = shared.VolumeMgr.CurrentVolume.GetByName(fileName, true); if (file == null) throw new Exception(string.Format("File '{0}' not found", fileName)); if (shared.ScriptHandler == null) return; if (volumeId != null) { Volume targetVolume = shared.VolumeMgr.GetVolume(volumeId); if (targetVolume != null) { if (shared.ProcessorMgr != null) { string filePath = string.Format("{0}/{1}", shared.VolumeMgr.GetVolumeRawIdentifier(targetVolume), fileName); var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; List<CodePart> parts = shared.ScriptHandler.Compile(filePath, 1, file.StringContent, "program", options); var builder = new ProgramBuilder(); builder.AddRange(parts); List<Opcode> program = builder.BuildProgram(); shared.ProcessorMgr.RunProgramOn(program, targetVolume); } } else { throw new KOSFileException("Volume not found"); } } else { // clear the "program" compilation context shared.ScriptHandler.ClearContext("program"); string filePath = shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume) + "/" + fileName; var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; var programContext = ((CPU)shared.Cpu).SwitchToProgramContext(); List<CodePart> codeParts; if (file.Category == FileCategory.KSM) { string prefix = programContext.Program.Count.ToString(); codeParts = shared.VolumeMgr.CurrentVolume.LoadObjectFile(filePath, prefix, file.BinaryContent); } else { try { codeParts = shared.ScriptHandler.Compile(filePath, 1, file.StringContent, "program", options); } catch (Exception) { // If it died due to a compile error, then we won't really be able to switch to program context // as was implied by calling Cpu.SwitchToProgramContext() up above. The CPU needs to be // told that it's still in interpreter context, or else it fails to advance the interpreter's // instruction pointer and it will just try the "call run()" instruction again: shared.Cpu.BreakExecution(false); throw; } } programContext.AddParts(codeParts); } // Because run() returns FIRST, and THEN the CPU jumps to the new program's first instruction that it set up, // it needs to put the return stack in a weird order. Its return value needs to be buried UNDER the args to the // program it's calling: UsesAutoReturn = false; shared.Cpu.PushStack(0); // dummy return that all functions have. // Put the args for the program being called back on in the same order they were in before (so read the list backward): shared.Cpu.PushStack(new KOSArgMarkerType()); for (int i = argc - 1; i >= 0; --i) shared.Cpu.PushStack(progArgs[i]); }
public override void Execute(SharedObjects shared) { bool defaultOutput = false; bool justCompiling = false; // is this load() happening to compile, or to run? string fileNameOut = null; object topStack = PopValueAssert(shared, true); // null if there's no output file (output file means compile, not run). if (topStack != null) { justCompiling = true; string outputArg = topStack.ToString(); if (outputArg.Equals("-default-compile-out-")) defaultOutput = true; else fileNameOut = PersistenceUtilities.CookedFilename(outputArg, Volume.KOS_MACHINELANGUAGE_EXTENSION); } string fileName = null; topStack = PopValueAssert(shared, true); if (topStack != null) fileName = topStack.ToString(); AssertArgBottomAndConsume(shared); if (fileName == null) throw new KOSFileException("No filename to load was given."); ProgramFile file = shared.VolumeMgr.CurrentVolume.GetByName(fileName, (!justCompiling)); // if running, look for KSM first. If compiling look for KS first. if (file == null) throw new KOSFileException(string.Format("Can't find file '{0}'.", fileName)); fileName = file.Filename; // just in case GetByName picked an extension that changed it. // filename is now guaranteed to have an extension. To make default output name, replace the extension with KSM: if (defaultOutput) fileNameOut = fileName.Substring(0, fileName.LastIndexOf('.')) + "." + Volume.KOS_MACHINELANGUAGE_EXTENSION; if (fileNameOut != null && fileName == fileNameOut) throw new KOSFileException("Input and output filenames must differ."); if (shared.VolumeMgr == null) return; if (shared.VolumeMgr.CurrentVolume == null) throw new KOSFileException("Volume not found"); if (shared.ScriptHandler != null) { var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; string filePath = shared.VolumeMgr.GetVolumeRawIdentifier(shared.VolumeMgr.CurrentVolume) + "/" + fileName; // add this program to the address space of the parent program, // or to a file to save: if (justCompiling) { List<CodePart> compileParts = shared.ScriptHandler.Compile(filePath, 1, file.StringContent, String.Empty, options); bool success = shared.VolumeMgr.CurrentVolume.SaveObjectFile(fileNameOut, compileParts); if (!success) { throw new KOSFileException("Can't save compiled file: not enough space or access forbidden"); } } else { var programContext = ((CPU)shared.Cpu).SwitchToProgramContext(); List<CodePart> parts; if (file.Category == FileCategory.KSM) { string prefix = programContext.Program.Count.ToString(); parts = shared.VolumeMgr.CurrentVolume.LoadObjectFile(filePath, prefix, file.BinaryContent); } else { parts = shared.ScriptHandler.Compile(filePath, 1, file.StringContent, "program", options); } int programAddress = programContext.AddObjectParts(parts); // push the entry point address of the new program onto the stack shared.Cpu.PushStack(programAddress); } } }
public override void Execute(SharedObjects shared) { // NOTE: The built-in load() function actually ends up returning // two things on the stack: on top is a boolean for whether the program // was already loaded, and under that is an integer for where to jump to // to call it. The load() function is NOT meant to be called directly from // a user script. // (unless it's being called in compile-only mode, in which case it // returns the default dummy zero on the stack like everything else does). bool defaultOutput = false; bool justCompiling = false; // is this load() happening to compile, or to run? GlobalPath outPath = null; object topStack = PopValueAssert(shared, true); // null if there's no output file (output file means compile, not run). if (topStack != null) { justCompiling = true; string outputArg = topStack.ToString(); if (outputArg.Equals("-default-compile-out-")) defaultOutput = true; else outPath = shared.VolumeMgr.GlobalPathFromObject(outputArg); } object skipAlreadyObject = PopValueAssert(shared, false); bool skipIfAlreadyCompiled = (skipAlreadyObject is bool) ? (bool)skipAlreadyObject : false; object pathObject = PopValueAssert(shared, true); AssertArgBottomAndConsume(shared); if (pathObject == null) throw new KOSFileException("No filename to load was given."); GlobalPath path = shared.VolumeMgr.GlobalPathFromObject(pathObject); Volume volume = shared.VolumeMgr.GetVolumeFromPath(path); VolumeFile file = volume.Open(path, !justCompiling) as VolumeFile; // if running, look for KSM first. If compiling look for KS first. if (file == null) throw new KOSFileException(string.Format("Can't find file '{0}'.", path)); path = GlobalPath.FromVolumePath(file.Path, shared.VolumeMgr.GetVolumeId(volume)); if (skipIfAlreadyCompiled && !justCompiling) { var programContext = ((CPU)shared.Cpu).SwitchToProgramContext(); int programAddress = programContext.GetAlreadyCompiledEntryPoint(path.ToString()); if (programAddress >= 0) { // TODO - The check could also have some dependancy on whether the file content changed on // disk since last time, but that would also mean having to have a way to clear out the old // copy of the compiled file from the program context, which right now doesn't exist. (Without // that, doing something like a loop that re-wrote a file and re-ran it 100 times would leave // 100 old dead copies of the compiled opcodes in memory, only the lastmost copy being really used.) // We're done here. Skip the compile. Point the caller at the already-compiled version. shared.Cpu.PushStack(programAddress); this.ReturnValue = true; // tell caller that it already existed. return; } } FileContent fileContent = file.ReadAll(); // filename is now guaranteed to have an extension. To make default output name, replace the extension with KSM: if (defaultOutput) outPath = path.ChangeExtension(Volume.KOS_MACHINELANGUAGE_EXTENSION); if (path.Equals(outPath)) throw new KOSFileException("Input and output paths must differ."); if (shared.VolumeMgr == null) return; if (shared.VolumeMgr.CurrentVolume == null) throw new KOSFileException("Volume not found"); if (shared.ScriptHandler != null) { shared.Cpu.StartCompileStopwatch(); var options = new CompilerOptions { LoadProgramsInSameAddressSpace = true, FuncManager = shared.FunctionManager }; // add this program to the address space of the parent program, // or to a file to save: if (justCompiling) { // since we've already read the file content, use the volume from outPath instead of the source path volume = shared.VolumeMgr.GetVolumeFromPath(outPath); List<CodePart> compileParts = shared.ScriptHandler.Compile(path, 1, fileContent.String, string.Empty, options); VolumeFile written = volume.SaveFile(outPath, new FileContent(compileParts)); if (written == null) { throw new KOSFileException("Can't save compiled file: not enough space or access forbidden"); } } else { var programContext = ((CPU)shared.Cpu).SwitchToProgramContext(); List<CodePart> parts; if (fileContent.Category == FileCategory.KSM) { string prefix = programContext.Program.Count.ToString(); parts = fileContent.AsParts(path, prefix); } else { parts = shared.ScriptHandler.Compile(path, 1, fileContent.String, "program", options); } int programAddress = programContext.AddObjectParts(parts, path.ToString()); // push the entry point address of the new program onto the stack shared.Cpu.PushStack(programAddress); this.ReturnValue = false; // did not already exist. } shared.Cpu.StopCompileStopwatch(); } }
/// <summary> /// Compile source text into compiled codeparts. /// </summary> /// <param name="filePath">The name that should get reported to the user on /// runtime errors in this compiled code. Even if the text is not from an /// actual file this should still be a pseudo-filename for reporting, for /// example "(commandline)" or "(socket stream)" /// </param> /// <param name="startLineNum">Assuming scriptText is a subset of some bigger buffer, line 1 of scripttext /// corresponds to line (what) of the more global something, for reporting numbers on errors.</param> /// <param name="scriptText">The text to be compiled.</param> /// <param name="contextId">The name of the runtime context (i.e. "interpreter").</param> /// <param name="options">settings for the compile</param> /// <returns>The CodeParts made from the scriptText</returns> public abstract List <CodePart> Compile(string filePath, int startLineNum, string scriptText, string contextId, CompilerOptions options);