From 99fe7a35700a9ae6051f5642cf72de727261add2 Mon Sep 17 00:00:00 2001 From: DigitalCodeCrafter Date: Sun, 2 Nov 2025 15:35:27 +0100 Subject: [PATCH 1/6] Added 6 new instructions about pointers storename, peek, poke, allocate, instptr, stackptr --- src/kOS.Safe/Compilation/Opcode.cs | 282 ++++++++++++++++++++++++++--- src/kOS.Safe/Execution/CPU.cs | 11 ++ src/kOS.Safe/Execution/ICpu.cs | 1 + src/kOS.Safe/Execution/IStack.cs | 1 + src/kOS.Safe/Execution/Stack.cs | 36 ++++ 5 files changed, 308 insertions(+), 23 deletions(-) diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index d7a65a8d17..335981a4f9 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -88,6 +88,12 @@ public enum ByteCode :byte TESTARGBOTTOM = 0x61, TESTCANCELLED = 0x62, JUMPSTACK = 0x63, + PEEK = 0x64, + POKE = 0x65, + STORENAME = 0x66, + ALLOCATE = 0x67, + INSTPTR = 0x68, + STACKPTR = 0x69, // Augmented bogus placeholder versions of the normal // opcodes: These only exist in the program temporarily @@ -694,6 +700,39 @@ public override void Execute(ICpu cpu) } } + /// + /// + /// Consumes the identifier atop the stack and + /// stores the value beneath the identifier into that + /// variable name
+ /// Note that the ident atop the stack must be formatted like a variable + /// name (i.e. have the leading '$'). + ///
+ /// + /// storename + /// ... value ident -- ... + /// + ///
+ public class OpcodeStoreName : Opcode + { + protected override string Name { get { return "storename"; } } + public override ByteCode Code { get { return ByteCode.STORENAME; } } + + public OpcodeStoreName() + { + } + + public override void Execute(ICpu cpu) + { + string ident = Convert.ToString(cpu.PopArgumentStack()); + Structure value = PopStructureAssertEncapsulated(cpu); + if (ident != null) + { + cpu.SetValue(ident, value); + } + } + } + /// /// Consumes the topmost value of the stack as an identifier, unsetting /// the variable referenced by this identifier. This will remove the @@ -714,7 +753,7 @@ public override void Execute(ICpu cpu) } else { - throw new KOSObsoletionException("0.17","UNSET ALL", "", ""); + throw new KOSObsoletionException("0.17", "UNSET ALL", "", ""); } } } @@ -2278,12 +2317,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 +2340,151 @@ public override void Execute(ICpu cpu) } } + /// + /// + /// Pops an index/pointer from the stack and duplicates the value at that stack slot onto the top of the stack. + /// Default indexing is top-to-bottom, can be set to bottom-to-tob using the FromBottom MLField. + /// + /// + /// peek fromBottom + /// ... val ... ptr -- ... val ... val + /// + public class OpcodePeek : Opcode + { + protected override string Name { get { return "peek"; } } + public override ByteCode Code { get { return ByteCode.PEEK; } } + + [MLField(0, false)] + public bool FromBottom { get; set; } + + public OpcodePeek(bool fromBottom) + { + FromBottom = fromBottom; + } + + protected OpcodePeek() + { + } + + public override void PopulateFromMLFields(List fields) + { + if (fields == null || fields.Count < 1) + throw new Exception("Saved field in ML file for OpcodePeek seems to be missing. Version mismatch?"); + FromBottom = (bool)(fields[0]); + } + + public override void Execute(ICpu cpu) + { + int idx = Convert.ToInt32(cpu.PopValueArgument()); + + int depth = FromBottom + ? cpu.GetArgumentStackSize() - 1 - idx + : idx; + + if (depth < 0 || depth >= cpu.GetArgumentStackSize()) + throw KOSException($"Invalid peek index {idx}"); + + object value = cpu.PeekRawArgument(depth, out bool ok); + if (!ok) + throw new KOSException("Peek failed"); + + cpu.PushArgumentStack(value); + } + } + + /// + /// + /// Pops an index/pointer and a value from the stack and pokes the value into that stack slot. + /// Default indexing is top-to-bottom, can be set to bottom-to-tob using the FromBottom MLField. + /// + /// + /// poke fromBottom + /// ... val ptr -- ... val ... + /// + public class OpcodePoke : Opcode + { + protected override string Name { get { return "poke"; } } + public override ByteCode Code { get { return ByteCode.POKE; } } + + [MLField(0, false)] + public bool FromBottom { get; set; } + + public OpcodePoke(bool fromBottom) + { + FromBottom = fromBottom; + } + + protected OpcodePoke() + { + } + + public override void PopulateFromMLFields(List fields) + { + if (fields == null || fields.Count < 1) + throw new Exception("Saved field in ML file for OpcodePoke seems to be missing. Version mismatch?"); + FromBottom = (bool)(fields[0]); + } + + public override void Execute(ICpu cpu) + { + int idx = Convert.ToInt32(cpu.PopValueArgument()); + object value = cpu.PopArgumentStack(); + + int depth = FromBottom + ? cpu.GetArgumentStackSize() - 1 - idx + : idx; + + if (depth < 0 || depth >= cpu.GetArgumentStackSize()) + throw KOSException($"Invalid poke index {idx}"); + + object value = cpu.PokeArgumentStack(depth, value, out bool ok); + if (!ok) + throw new KOSException("Poke failed"); + } + } + + /// + /// + /// 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 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); + } + } + } + /// /// 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 +2493,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 +2521,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 +2554,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 +2573,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 +2582,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 +2601,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 +2666,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 +2688,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]); diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index d6d35cd1dd..285bcfd2e4 100644 --- a/src/kOS.Safe/Execution/CPU.cs +++ b/src/kOS.Safe/Execution/CPU.cs @@ -1142,6 +1142,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 51c03c75cb..6e7b4338ab 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 f349030d5b..75c7bf0aa3 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 f8c4176fb4..2f78d3c363 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 From 3ad9fb8a5dc9a645ff371b2a12e34ce8602bb6ef Mon Sep 17 00:00:00 2001 From: DigitalCodeCrafter Date: Sun, 2 Nov 2025 16:23:38 +0100 Subject: [PATCH 2/6] fixed doc on CPU.SetNewLocal(..) and switched to local variables for OpcodeStoreName --- src/kOS.Safe/Compilation/Opcode.cs | 2 +- src/kOS.Safe/Execution/CPU.cs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 335981a4f9..99ce7d26a8 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -728,7 +728,7 @@ public override void Execute(ICpu cpu) Structure value = PopStructureAssertEncapsulated(cpu); if (ident != null) { - cpu.SetValue(ident, value); + cpu.SetNewLocal(ident, value); } } } diff --git a/src/kOS.Safe/Execution/CPU.cs b/src/kOS.Safe/Execution/CPU.cs index 285bcfd2e4..459632319c 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.
From 19b4788b1de3b7bc2047af058679edc446223717 Mon Sep 17 00:00:00 2001 From: DigitalCodeCrafter Date: Fri, 7 Nov 2025 15:00:35 +0100 Subject: [PATCH 3/6] changed order of arguments for pointer writes (poke and storename): pointer now beneath value --- src/kOS.Safe/Compilation/Opcode.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 99ce7d26a8..bf6d44e89a 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -702,15 +702,14 @@ public override void Execute(ICpu cpu) /// /// - /// Consumes the identifier atop the stack and - /// stores the value beneath the identifier into that - /// variable name
+ /// Consumes a value and identifier and stores the value + /// into that a local variable under that identifier
/// Note that the ident atop the stack must be formatted like a variable /// name (i.e. have the leading '$'). ///
/// /// storename - /// ... value ident -- ... + /// ... ident value -- ... /// ///
public class OpcodeStoreName : Opcode @@ -724,8 +723,8 @@ public OpcodeStoreName() public override void Execute(ICpu cpu) { - string ident = Convert.ToString(cpu.PopArgumentStack()); Structure value = PopStructureAssertEncapsulated(cpu); + string ident = Convert.ToString(cpu.PopArgumentStack()); if (ident != null) { cpu.SetNewLocal(ident, value); @@ -2394,12 +2393,12 @@ public override void Execute(ICpu cpu) /// /// - /// Pops an index/pointer and a value from the stack and pokes the value into that stack slot. + /// Pops a value and an index/pointer from the stack and pokes the value into that stack slot. /// Default indexing is top-to-bottom, can be set to bottom-to-tob using the FromBottom MLField. /// /// /// poke fromBottom - /// ... val ptr -- ... val ... + /// ... ptr val -- ... val ... /// public class OpcodePoke : Opcode { @@ -2427,8 +2426,8 @@ public override void PopulateFromMLFields(List fields) public override void Execute(ICpu cpu) { - int idx = Convert.ToInt32(cpu.PopValueArgument()); object value = cpu.PopArgumentStack(); + int idx = Convert.ToInt32(cpu.PopValueArgument()); int depth = FromBottom ? cpu.GetArgumentStackSize() - 1 - idx From aeee2b06aa70148e6bff2670c75131e0851605e9 Mon Sep 17 00:00:00 2001 From: DigitalCodeCrafter Date: Fri, 7 Nov 2025 18:07:49 +0100 Subject: [PATCH 4/6] added deallocation instruction --- src/kOS.Safe/Compilation/Opcode.cs | 46 ++++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index bf6d44e89a..2a66631de1 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -92,8 +92,9 @@ public enum ByteCode :byte POKE = 0x65, STORENAME = 0x66, ALLOCATE = 0x67, - INSTPTR = 0x68, - STACKPTR = 0x69, + DEALLOCATE = 0x68, + INSTPTR = 0x69, + STACKPTR = 0x6A, // Augmented bogus placeholder versions of the normal // opcodes: These only exist in the program temporarily @@ -2484,6 +2485,47 @@ public override void Execute(ICpu cpu) } } + /// + /// + /// Pops N entries from the stack, quickly freeing the space that ALLOCATE has created + /// + /// + /// deallocate n + /// ... null * N -- .. + /// + public class OpcodeDeallocate : Opcode + { + protected override string Name { get { return "deallocate"; } } + public override ByteCode Code { get { return ByteCode.DEALLOCATE; } } + + [MLField(0, false)] + public Int32 Count { get; set; } + + public OpcodeDeallocate(int count) + { + Count = count; + } + + protected OpcodeDeallocate() + { + } + + public override void PopulateFromMLFields(List fields) + { + if (fields == null || fields.Count < 1) + throw Exception("Saved field in ML file for OpcodeDeallocate 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#.) From ec29e86d44d94248b3720f5aac3095fba8590534 Mon Sep 17 00:00:00 2001 From: DigitalCodeCrafter Date: Sun, 16 Nov 2025 17:34:05 +0100 Subject: [PATCH 5/6] re-did pointer logic, renamed dealloc to free --- src/kOS.Safe/Compilation/Opcode.cs | 366 ++++++++++++--------- src/kOS.Safe/Encapsulation/PointerValue.cs | 23 ++ 2 files changed, 241 insertions(+), 148 deletions(-) create mode 100644 src/kOS.Safe/Encapsulation/PointerValue.cs diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 2a66631de1..660ec0757c 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -8,6 +8,7 @@ using kOS.Safe.Exceptions; using kOS.Safe.Utilities; using kOS.Safe.Persistence; +using kOS.Safe.Compilation.KS; namespace kOS.Safe.Compilation { @@ -88,11 +89,11 @@ public enum ByteCode :byte TESTARGBOTTOM = 0x61, TESTCANCELLED = 0x62, JUMPSTACK = 0x63, - PEEK = 0x64, - POKE = 0x65, - STORENAME = 0x66, + CREATEPTR = 0x64, + GETPTR = 0x65, + SETPTR = 0x66, ALLOCATE = 0x67, - DEALLOCATE = 0x68, + FREE = 0x68, INSTPTR = 0x69, STACKPTR = 0x6A, @@ -532,7 +533,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.
@@ -701,38 +701,6 @@ public override void Execute(ICpu cpu) } } - /// - /// - /// Consumes a value and identifier and stores the value - /// into that a local variable under that identifier
- /// Note that the ident atop the stack must be formatted like a variable - /// name (i.e. have the leading '$'). - ///
- /// - /// storename - /// ... ident value -- ... - /// - ///
- public class OpcodeStoreName : Opcode - { - protected override string Name { get { return "storename"; } } - public override ByteCode Code { get { return ByteCode.STORENAME; } } - - public OpcodeStoreName() - { - } - - public override void Execute(ICpu cpu) - { - Structure value = PopStructureAssertEncapsulated(cpu); - string ident = Convert.ToString(cpu.PopArgumentStack()); - if (ident != null) - { - cpu.SetNewLocal(ident, value); - } - } - } - /// /// Consumes the topmost value of the stack as an identifier, unsetting /// the variable referenced by this identifier. This will remove the @@ -2340,109 +2308,6 @@ public override void Execute(ICpu cpu) } } - /// - /// - /// Pops an index/pointer from the stack and duplicates the value at that stack slot onto the top of the stack. - /// Default indexing is top-to-bottom, can be set to bottom-to-tob using the FromBottom MLField. - /// - /// - /// peek fromBottom - /// ... val ... ptr -- ... val ... val - /// - public class OpcodePeek : Opcode - { - protected override string Name { get { return "peek"; } } - public override ByteCode Code { get { return ByteCode.PEEK; } } - - [MLField(0, false)] - public bool FromBottom { get; set; } - - public OpcodePeek(bool fromBottom) - { - FromBottom = fromBottom; - } - - protected OpcodePeek() - { - } - - public override void PopulateFromMLFields(List fields) - { - if (fields == null || fields.Count < 1) - throw new Exception("Saved field in ML file for OpcodePeek seems to be missing. Version mismatch?"); - FromBottom = (bool)(fields[0]); - } - - public override void Execute(ICpu cpu) - { - int idx = Convert.ToInt32(cpu.PopValueArgument()); - - int depth = FromBottom - ? cpu.GetArgumentStackSize() - 1 - idx - : idx; - - if (depth < 0 || depth >= cpu.GetArgumentStackSize()) - throw KOSException($"Invalid peek index {idx}"); - - object value = cpu.PeekRawArgument(depth, out bool ok); - if (!ok) - throw new KOSException("Peek failed"); - - cpu.PushArgumentStack(value); - } - } - - /// - /// - /// Pops a value and an index/pointer from the stack and pokes the value into that stack slot. - /// Default indexing is top-to-bottom, can be set to bottom-to-tob using the FromBottom MLField. - /// - /// - /// poke fromBottom - /// ... ptr val -- ... val ... - /// - public class OpcodePoke : Opcode - { - protected override string Name { get { return "poke"; } } - public override ByteCode Code { get { return ByteCode.POKE; } } - - [MLField(0, false)] - public bool FromBottom { get; set; } - - public OpcodePoke(bool fromBottom) - { - FromBottom = fromBottom; - } - - protected OpcodePoke() - { - } - - public override void PopulateFromMLFields(List fields) - { - if (fields == null || fields.Count < 1) - throw new Exception("Saved field in ML file for OpcodePoke seems to be missing. Version mismatch?"); - FromBottom = (bool)(fields[0]); - } - - public override void Execute(ICpu cpu) - { - object value = cpu.PopArgumentStack(); - int idx = Convert.ToInt32(cpu.PopValueArgument()); - - int depth = FromBottom - ? cpu.GetArgumentStackSize() - 1 - idx - : idx; - - if (depth < 0 || depth >= cpu.GetArgumentStackSize()) - throw KOSException($"Invalid poke index {idx}"); - - object value = cpu.PokeArgumentStack(depth, value, out bool ok); - if (!ok) - throw new KOSException("Poke failed"); - } - } - /// /// /// Pushes N nulls onto the stack to use for storage using OpcodePoke @@ -2471,7 +2336,7 @@ protected OpcodeAllocate() public override void PopulateFromMLFields(List fields) { if (fields == null || fields.Count < 1) - throw Exception("Saved field in ML file for OpcodeAllocate seems to be missing. Version mismatch?"); + throw new Exception("Saved field in ML file for OpcodeAllocate seems to be missing. Version mismatch?"); Count = (Int32)(fields[0]); } @@ -2490,30 +2355,30 @@ public override void Execute(ICpu cpu) /// Pops N entries from the stack, quickly freeing the space that ALLOCATE has created /// /// - /// deallocate n + /// free n /// ... null * N -- .. /// - public class OpcodeDeallocate : Opcode + public class OpcodeFree : Opcode { - protected override string Name { get { return "deallocate"; } } - public override ByteCode Code { get { return ByteCode.DEALLOCATE; } } + protected override string Name { get { return "free"; } } + public override ByteCode Code { get { return ByteCode.FREE; } } [MLField(0, false)] public Int32 Count { get; set; } - public OpcodeDeallocate(int count) + public OpcodeFree(int count) { Count = count; } - protected OpcodeDeallocate() + protected OpcodeFree() { } public override void PopulateFromMLFields(List fields) { if (fields == null || fields.Count < 1) - throw Exception("Saved field in ML file for OpcodeDeallocate seems to be missing. Version mismatch?"); + throw new Exception("Saved field in ML file for OpcodeFree seems to be missing. Version mismatch?"); Count = (Int32)(fields[0]); } @@ -2782,6 +2647,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 0000000000..f07679f62e --- /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 From 8910e2b3891a8ebd908613f86b1d4775b83b022e Mon Sep 17 00:00:00 2001 From: DigitalCodeCrafter Date: Sun, 16 Nov 2025 17:34:41 +0100 Subject: [PATCH 6/6] removed unused line --- src/kOS.Safe/Compilation/Opcode.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/src/kOS.Safe/Compilation/Opcode.cs b/src/kOS.Safe/Compilation/Opcode.cs index 660ec0757c..e86219bc13 100644 --- a/src/kOS.Safe/Compilation/Opcode.cs +++ b/src/kOS.Safe/Compilation/Opcode.cs @@ -8,7 +8,6 @@ using kOS.Safe.Exceptions; using kOS.Safe.Utilities; using kOS.Safe.Persistence; -using kOS.Safe.Compilation.KS; namespace kOS.Safe.Compilation {