diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index d7a65a8d1..e86219bc1 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -88,6 +88,13 @@ public enum ByteCode :byte TESTARGBOTTOM = 0x61, TESTCANCELLED = 0x62, JUMPSTACK = 0x63, + CREATEPTR = 0x64, + GETPTR = 0x65, + SETPTR = 0x66, + ALLOCATE = 0x67, + FREE = 0x68, + INSTPTR = 0x69, + STACKPTR = 0x6A, // Augmented bogus placeholder versions of the normal // opcodes: These only exist in the program temporarily @@ -525,7 +532,6 @@ public override string ToString() #region General - /// /// Consumes the topmost value of the stack, storing it into /// a variable named by the Identifier MLField of this opcode.
@@ -714,7 +720,7 @@ public override void Execute(ICpu cpu) } else { - throw new KOSObsoletionException("0.17","UNSET ALL", "", ""); + throw new KOSObsoletionException("0.17", "UNSET ALL", "", ""); } } } @@ -2278,12 +2284,12 @@ public class OpcodeEval : Opcode protected override string Name { get { return "eval"; } } public override ByteCode Code { get { return ByteCode.EVAL; } } private bool barewordOkay; - + public OpcodeEval() { barewordOkay = false; } - + /// /// Eval top thing on the stack and replace it with its dereferenced /// value. If you want to allow bare words like filenames then set argument bareOkay to true @@ -2301,6 +2307,89 @@ public override void Execute(ICpu cpu) } } + /// + /// + /// Pushes N nulls onto the stack to use for storage using OpcodePoke + /// + /// + /// allocate n + /// ... -- ... null * N + /// + public class OpcodeAllocate : Opcode + { + protected override string Name { get { return "allocate"; } } + public override ByteCode Code { get { return ByteCode.ALLOCATE; } } + + [MLField(0, false)] + public Int32 Count { get; set; } + + public OpcodeAllocate(int count) + { + Count = count; + } + + protected OpcodeAllocate() + { + } + + public override void PopulateFromMLFields(List fields) + { + if (fields == null || fields.Count < 1) + throw new Exception("Saved field in ML file for OpcodeAllocate seems to be missing. Version mismatch?"); + Count = (Int32)(fields[0]); + } + + public override void Execute(ICpu cpu) + { + object nul = new PseudoNull(); // no modification possible on Null so no possibility of shared state modification + for (int i = 0; i < Count; i++) + { + cpu.PushArgumentStack(nul); + } + } + } + + /// + /// + /// Pops N entries from the stack, quickly freeing the space that ALLOCATE has created + /// + /// + /// free n + /// ... null * N -- .. + /// + public class OpcodeFree : Opcode + { + protected override string Name { get { return "free"; } } + public override ByteCode Code { get { return ByteCode.FREE; } } + + [MLField(0, false)] + public Int32 Count { get; set; } + + public OpcodeFree(int count) + { + Count = count; + } + + protected OpcodeFree() + { + } + + public override void PopulateFromMLFields(List fields) + { + if (fields == null || fields.Count < 1) + throw new Exception("Saved field in ML file for OpcodeFree seems to be missing. Version mismatch?"); + Count = (Int32)(fields[0]); + } + + public override void Execute(ICpu cpu) + { + for (int i = 0; i < Count; i++) + { + cpu.PopArgumentStack(); + } + } + } + /// /// Pushes a new variable namespace scope (for example, when a "{" is encountered /// in a block-scoping language like C++ or Java or C#.) @@ -2309,10 +2398,10 @@ public override void Execute(ICpu cpu) /// public class OpcodePushScope : Opcode { - [MLField(1,true)] - public Int16 ScopeId {get;set;} - [MLField(2,true)] - public Int16 ParentScopeId {get;set;} + [MLField(1, true)] + public Int16 ScopeId { get; set; } + [MLField(2, true)] + public Int16 ParentScopeId { get; set; } /// /// Push a scope frame that knows the id of its lexical parent scope. @@ -2337,25 +2426,25 @@ protected OpcodePushScope() public override void PopulateFromMLFields(List fields) { // Expect fields in the same order as the [MLField] properties of this class: - if (fields == null || fields.Count<2) + if (fields == null || fields.Count < 2) throw new Exception("Saved field in ML file for OpcodePushScope seems to be missing. Version mismatch?"); - ScopeId = (Int16)( fields[0] ); - ParentScopeId = (Int16)( fields[1] ); + ScopeId = (Int16)(fields[0]); + ParentScopeId = (Int16)(fields[1]); } protected override string Name { get { return "pushscope"; } } public override ByteCode Code { get { return ByteCode.PUSHSCOPE; } } - + public override void Execute(ICpu cpu) { - cpu.PushNewScope(ScopeId,ParentScopeId); + cpu.PushNewScope(ScopeId, ParentScopeId); } public override string ToString() { return String.Format("{0} {1} {2}", Name, ScopeId, ParentScopeId); } - + } /// @@ -2370,17 +2459,17 @@ public override string ToString() /// public class OpcodePopScope : Opcode { - [MLField(1,true)] - public Int16 NumLevels {get;set;} // Are we really going to have recursion more than 32767 levels? Int16 is fine. + [MLField(1, true)] + public Int16 NumLevels { get; set; } // Are we really going to have recursion more than 32767 levels? Int16 is fine. protected override string Name { get { return "popscope"; } } public override ByteCode Code { get { return ByteCode.POPSCOPE; } } - + public OpcodePopScope(int numLevels) { NumLevels = (Int16)numLevels; } - + public OpcodePopScope() { NumLevels = 1; @@ -2389,7 +2478,7 @@ public OpcodePopScope() public override void PopulateFromMLFields(List fields) { // Expect fields in the same order as the [MLField] properties of this class: - if (fields == null || fields.Count<1) + if (fields == null || fields.Count < 1) throw new Exception("Saved field in ML file for OpcodePopScope seems to be missing. Version mismatch?"); NumLevels = (Int16)(fields[0]); // should throw error if it's not an int. } @@ -2398,7 +2487,7 @@ public override void Execute(ICpu cpu) { DoPopScope(cpu, NumLevels); } - + /// /// Do the actual work of the Execute() method. This was pulled out /// to a separate static method so that others can call it without needing @@ -2417,7 +2506,59 @@ public override string ToString() { return Name + " " + NumLevels; } - + + } + + /// + /// + /// Pushes the value of the stack pointer onto the stack. + /// The stack pointer points to value where the next push will go to. + /// As result of this, the returned value will point to itself (absolute) + /// + /// + /// stackptr + /// ... -- ... ptr + /// + public class OpcodeStackPointer : Opcode + { + protected override string Name { get { return "stackptr"; } } + public override ByteCode Code { get { return ByteCode.STACKPTR; } } + + public OpcodeStackPointer() + { + } + + public override void Execute(ICpu cpu) + { + int ptr = cpu.GetArgumentStackSize(); + cpu.PushArgumentStack(ptr); + } + } + + /// + /// + /// Pushes the value of the instruction pointer onto the stack. + /// The instruction pointer points to the current instruction to. + /// As result of this, the returned value will point to this instruction (absolute) + /// + /// + /// instptr + /// ... -- ... ptr + /// + public class OpcodeInstructionPointer : Opcode + { + protected override string Name { get { return "instptr"; } } + public override ByteCode Code { get { return ByteCode.INSTPTR; } } + + public OpcodeInstructionPointer() + { + } + + public override void Execute(ICpu cpu) + { + int ptr = cpu.InstructionPointer; + cpu.PushArgumentStack(ptr); + } } /// @@ -2430,9 +2571,9 @@ public override string ToString() /// public class OpcodePushDelegate : Opcode { - [MLField(1,false)] + [MLField(1, false)] private int EntryPoint { get; set; } - [MLField(2,false)] + [MLField(2, false)] private bool WithClosure { get; set; } protected override string Name { get { return "pushdelegate"; } } @@ -2452,7 +2593,7 @@ protected OpcodePushDelegate() { } public override void PopulateFromMLFields(List fields) { // Expect fields in the same order as the [MLField] properties of this class: - if (fields == null || fields.Count<2) + if (fields == null || fields.Count < 2) throw new Exception("Saved field in ML file for OpcodePushDelegate seems to be missing. Version mismatch?"); EntryPoint = Convert.ToInt32(fields[0]); WithClosure = Convert.ToBoolean(fields[1]); @@ -2505,6 +2646,211 @@ public override void PopulateFromMLFields(List fields) #endregion + #region Pointers + + /// + /// + /// Consumes N elements from the stack and returns a PointerValue + /// + /// + /// createptr n + /// ... seg1 seg2 -- ... ptr + /// + public class OpcodeCreatePointer : Opcode + { + [MLField(0, false)] + private Int32 Count { get; set; } + + protected override string Name { get { return "createptr"; } } + public override ByteCode Code { get { return ByteCode.CREATEPTR; } } + + protected OpcodeCreatePointer() { } + + public OpcodeCreatePointer(int count) + { + Count = count; + } + + public override void PopulateFromMLFields(List fields) + { + if (fields == null || fields.Count < 1) + throw new Exception("Saved field in ML file for OpcodeCreatePointer seems to be missing. Version mismatch?"); + Count = (Int32)(fields[0]); + if (Count < 1) + throw new Exception("CREATEPTR requires at least one segment."); + } + + public override void Execute(ICpu cpu) + { + if (cpu.GetArgumentStackSize() < Count) + throw new KOSException($"CREATEPTR {Count} requires {Count} stack values but only {cpu.GetArgumentStackSize()} present."); + + List segments = new List(Count); + + for (int i = 0; i < Count; i++) + { + segments.Add(cpu.PopStructureEncapsulatedArgument()); + } + + segments.Reverse(); + + // validate + if (!(segments[0] is ScalarIntValue) && + !(segments[0] is StringValue str && str.StartsWith("$"))) + { + throw new KOSException("Invalid pointer root: must be int (stack index) or variable name ('$' prefixed string)."); + } + + cpu.PushArgumentStack(new PointerValue(segments)); + } + } + + /// + /// + /// Pops a PointerValue from the stack and pushes its resolution + /// + /// + /// getptr + /// ... ptr -- ... value + /// + public class OpcodeGetPointer : Opcode + { + protected override string Name { get { return "getptr"; } } + public override ByteCode Code { get { return ByteCode.GETPTR; } } + + public OpcodeGetPointer() { } + + public override void Execute(ICpu cpu) + { + Structure ptrStruct = cpu.PopStructureEncapsulatedArgument(); + if (!(ptrStruct is PointerValue ptr)) + throw new KOSException("GETPTR expects a PointerValue on the stack."); + + // validate + if (ptr.Segments.Count < 1) + throw new KOSException("GETPTR: PointerValue must have at least one segment."); + + // Resolve first segment + Structure current; + Structure firstSeg = ptr.Segments[0]; + if (firstSeg is ScalarIntValue idx) + { + int absIndex = idx.GetIntValue(); + if (absIndex < 0 || absIndex >= cpu.GetArgumentStackSize()) + throw new KOSException($"GETPTR: stack index {absIndex} out of range."); + current = Structure.FromPrimitiveWithAssert(cpu.PeekRawArgument(cpu.GetArgumentStackSize() - 1 - absIndex, out bool ok)); + if (!ok) + throw new KOSException("GETPTR: stack indexing failed"); + } + else if (firstSeg is StringValue varName && varName.StartsWith("$")) + { + current = Structure.FromPrimitiveWithAssert(cpu.GetValue(varName.ToString())); + } + else + { + throw new KOSException("GETPTR: first segment must be a stack index or variable name."); + } + + // Traverse remaining segments + for (int i = 1; i < ptr.Segments.Count; i++) + { + Structure seg = ptr.Segments[i]; + + if (!(current is IIndexable indexable)) + throw new KOSException($"GETPTR: segment {i} encountered non-indexable value."); + + current = indexable.GetIndex(seg); + } + + cpu.PushArgumentStack(current); + } + } + + /// + /// + /// Pops a Value and a PointerValue from the stack and writes the value into the pointer's resolution + /// + /// + /// setptr + /// ... ptr value -- ... + /// + public class OpcodeSetPointer : Opcode + { + protected override string Name { get { return "setptr"; } } + public override ByteCode Code { get { return ByteCode.SETPTR; } } + + public OpcodeSetPointer() { } + + public override void Execute(ICpu cpu) + { + Structure value = cpu.PopStructureEncapsulatedArgument(); + Structure ptrStruct = cpu.PopStructureEncapsulatedArgument(); + if (!(ptrStruct is PointerValue ptr)) + throw new KOSException("SETPTR expects a PointerValue on the stack."); + + // validate + if (ptr.Segments.Count < 1) + throw new KOSException("SETPTR: PointerValue must have at least one segment."); + + // Resolve first segment + Structure current; + Structure firstSeg = ptr.Segments[0]; + if (firstSeg is ScalarIntValue idx) + { + int absIndex = idx.GetIntValue(); + if (absIndex < 0 || absIndex >= cpu.GetArgumentStackSize()) + throw new KOSException($"SETPTR: stack index {absIndex} out of range."); + + int depth = cpu.GetArgumentStackSize() - 1 - absIndex; + + if (ptr.Segments.Count == 1) + { + cpu.PokeArgumentStack(depth, value, out bool ok); + if (!ok) + throw new KOSException("SETPTR: stack indexing failed"); + return; + } + + current = Structure.FromPrimitiveWithAssert(cpu.PeekRawArgument(depth, out bool ok)); + if (!ok) + throw new KOSException("SETPTR: stack indexing failed"); + } + else if (firstSeg is StringValue varName && varName.StartsWith("$")) + { + if (ptr.Segments.Count == 1) + { + cpu.SetValueExists(varName.ToString(), value); + return; + } + current = Structure.FromPrimitiveWithAssert(cpu.GetValue(varName.ToString())); + } + else + { + throw new KOSException("SETPTR: first segment must be a stack index or variable name."); + } + + // Traverse remaining segments + for (int i = 1; i < ptr.Segments.Count - 1; i++) + { + Structure seg = ptr.Segments[i]; + + if (!(current is IIndexable indexable)) + throw new KOSException($"SETPTR: segment {i} encountered non-indexable value."); + + current = indexable.GetIndex(seg); + } + + // set last segment + Structure lastSeg = ptr.Segments[^1]; + if (!(current is IIndexable lastIndexable)) + throw new KOSException("SETPTR: final target is not indexable."); + + lastIndexable.SetIndex(lastSeg, value); + } + } + + #endregion + #region Wait / Trigger /// diff --git a/src/kOS.Safe/Encapsulation/PointerValue.cs b/src/kOS.Safe/Encapsulation/PointerValue.cs new file mode 100644 index 000000000..f07679f62 --- /dev/null +++ b/src/kOS.Safe/Encapsulation/PointerValue.cs @@ -0,0 +1,23 @@ + +namespace kOS.Safe.Encapsulation +{ + /// + /// A Pointer class that stores a "path" to a value using keys and indecies.
+ /// Used by the Pointer instructions "CREATEPTR", "GETPTR", "SETPTR".
+ ///
+ [kOS.Safe.Utilities.KOSNomenclature("Pointer")] + public class PointerValue : Structure + { + public readonly List Segments; + + public PointerValue(IEnumerable segments) + { + Segments = new List(segments); + } + + public override string ToString() + { + return ""; + } + } +} \ No newline at end of file diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index d6d35cd1d..459632319 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -914,8 +914,7 @@ public object GetValue(object testValue, bool barewordOkay = false) /// /// Try to make a new local variable at the localmost scoping level and - /// give it a starting value. It errors out of there is already one there - /// by the same name.
+ /// give it a starting value.
///
/// This does NOT scan up the scoping stack like SetValue() does. /// It operates at the local level only.
@@ -1142,6 +1141,17 @@ public object PeekRawArgument(int digDepth, out bool checkOkay) return returnValue; } + /// + /// Poke a value into the argument stack without evaluating it to get the variable's value. + /// + /// Poke the element this far down the stack (0 means top, 1 means just under the top, etc) + /// The object to write to that depth + /// Tells you whether or not the stack was exhausted. If it's false, then the poke went too deep. + public void PokeArgumentStack(int digDepth, object item, out bool checkOkay) + { + checkOkay = stack.PokeCheckArgument(digDepth, item); + } + /// /// Peek at a value atop the scope stack without popping it. /// diff --git a/src/kOS.Safe/Execution/ICpu.cs b/src/kOS.Safe/Execution/ICpu.cs index 51c03c75c..6e7b4338a 100644 --- a/src/kOS.Safe/Execution/ICpu.cs +++ b/src/kOS.Safe/Execution/ICpu.cs @@ -19,6 +19,7 @@ public interface ICpu : IFixedUpdateObserver object PopValueArgument(bool barewordOkay = false); object PeekValueArgument(int digDepth, bool barewordOkay = false); object PeekRawArgument(int digDepth, out bool checkOkay); + void PokeArgumentStack(int digDepth, object item, out bool checkOkay); object PeekRawScope(int digDepth, out bool checkOkay); object PopValueEncapsulatedArgument(bool barewordOkay = false); object PeekValueEncapsulatedArgument(int digDepth, bool barewordOkay = false); diff --git a/src/kOS.Safe/Execution/IStack.cs b/src/kOS.Safe/Execution/IStack.cs index f349030d5..75c7bf0aa 100644 --- a/src/kOS.Safe/Execution/IStack.cs +++ b/src/kOS.Safe/Execution/IStack.cs @@ -9,6 +9,7 @@ public interface IStack object PopArgument(); object PeekArgument(int digDepth); bool PeekCheckArgument(int digDepth, out object item); + bool PokeCheckArgument(int digDepth, object item); object PeekScope(int digDepth); bool PeekCheckScope(int digDepth, out object item); void PushScope(object item); diff --git a/src/kOS.Safe/Execution/Stack.cs b/src/kOS.Safe/Execution/Stack.cs index f8c4176fb..2f78d3c36 100644 --- a/src/kOS.Safe/Execution/Stack.cs +++ b/src/kOS.Safe/Execution/Stack.cs @@ -232,6 +232,42 @@ public bool PeekCheckArgument(int digDepth, out object item) return returnVal; } + /// + /// Pokes a value into the argument stack. + /// Slightly "cheats" and breaks out of the 'stack' model by allowing you to replace the contents of + /// somewhere on the stack that is underneath the topmost thing. You can only replace, but not pop + /// values this way. It a boolean for whether or not your poke attempt went out of bounds of the stack. + /// + /// How far underneath the top to look. Zero means poke at the top, + /// 1 means replace the item just under the top, 2 means replace the item just under that, and + /// so on. + /// The object to write to that depth + /// Returns true if your poke was within the bounds of the stack, or false if you tried + /// to poke too far and went past the top or bottom of the stack. + public bool PokeCheckArgument(int digDepth, object item) + { + ThrowIfInvalid(item); + + if (digDepth < 0) + { + return false; + } + + int index = argumentCount - digDepth - 1; // 0 means top + if (index < 0) + { + return false; + } + + object prev = argumentStack[index]; + AdjustTriggerCountIfNeeded(prev, -1); + + argumentStack[index] = ProcessItem(item); + AdjustTriggerCountIfNeeded(item, +1); + + return true; + } + /// /// Peeks at a value in the scope stack. /// Slightly "cheats" and breaks out of the 'stack' model by allowing you to view the contents of