Class: BSV::Script::Stack
- Inherits:
-
Object
- Object
- BSV::Script::Stack
- Defined in:
- lib/bsv/script/interpreter/stack.rb
Overview
Script execution stack providing push/pop/peek operations for bytes, integers (ScriptNumber), and booleans.
Implements Forth-like stack manipulation operations (dup, drop, swap, rot, over, pick, roll, tuck) parameterised by count for the multi-element opcodes (OP_2DUP, OP_2SWAP, etc.).
A 32 MB aggregate memory cap is enforced on every push, matching the ts-sdk behaviour. This provides a natural bound for O(n²) opcodes such as OP_MUL operating on large script numbers.
Constant Summary collapse
- MAX_MEMORY_BYTES =
Maximum total bytes held across all stack items (32 MB).
32 * 1024 * 1024
Class Method Summary collapse
-
.cast_bool(bytes) ⇒ Object
Bitcoin consensus boolean: false if empty, all-zero, or negative zero (0x80 last byte).
Instance Method Summary collapse
- #clear ⇒ Object
-
#depth ⇒ Object
— Info —.
-
#drop_n(count) ⇒ Object
Remove the top N items.
-
#dup_n(count) ⇒ Object
Duplicate the top N items.
- #empty? ⇒ Boolean
-
#initialize ⇒ Stack
constructor
A new instance of Stack.
-
#nip_n(idx) ⇒ Object
Remove item at offset idx from top (0 = top).
-
#over_n(count) ⇒ Object
Copy N items from 2N depth to top.
- #peek_bool(idx = 0) ⇒ Object
-
#peek_bytes(idx = 0) ⇒ Object
— Peek —.
-
#peek_int(idx = 0, max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ⇒ Object
Decode a stack item as a ScriptNumber without removing it.
-
#pick_n(idx) ⇒ Object
Copy item at index n to top (0 = top).
- #pop_bool ⇒ Object
-
#pop_bytes ⇒ Object
— Pop —.
-
#pop_int(max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ⇒ Object
Decode the top stack item as a ScriptNumber.
- #push_bool(val) ⇒ Object
-
#push_bytes(data) ⇒ Object
— Push —.
- #push_int(script_number) ⇒ Object
-
#roll_n(idx) ⇒ Object
Move item at index n to top (0 = top).
-
#rot_n(count) ⇒ Object
Rotate: move the bottom N of the top 3N items to the top.
-
#swap_n(count) ⇒ Object
Swap the top N items with the next N.
- #to_a ⇒ Object
-
#tuck ⇒ Object
Copy top and insert before second: [x1 x2] -> [x2 x1 x2].
Constructor Details
#initialize ⇒ Stack
Returns a new instance of Stack.
22 23 24 25 |
# File 'lib/bsv/script/interpreter/stack.rb', line 22 def initialize @items = [] @memory_usage = 0 end |
Class Method Details
.cast_bool(bytes) ⇒ Object
Bitcoin consensus boolean: false if empty, all-zero, or negative zero (0x80 last byte).
219 220 221 222 223 224 225 226 227 228 229 230 |
# File 'lib/bsv/script/interpreter/stack.rb', line 219 def self.cast_bool(bytes) return false if bytes.nil? || bytes.empty? bytes.each_byte.with_index do |byte, i| next if byte.zero? # Negative zero: last byte is exactly 0x80 return !(i == bytes.bytesize - 1 && byte == 0x80) end false end |
Instance Method Details
#clear ⇒ Object
105 106 107 108 |
# File 'lib/bsv/script/interpreter/stack.rb', line 105 def clear @items.clear @memory_usage = 0 end |
#depth ⇒ Object
— Info —
97 98 99 |
# File 'lib/bsv/script/interpreter/stack.rb', line 97 def depth @items.length end |
#drop_n(count) ⇒ Object
Remove the top N items.
130 131 132 133 134 135 136 137 |
# File 'lib/bsv/script/interpreter/stack.rb', line 130 def drop_n(count) check_count!(count, 'drop_n') stack_error!("stack too small for drop_n(#{count})") if @items.length < count removed = @items.pop(count) @memory_usage -= removed.sum(&:bytesize) nil end |
#dup_n(count) ⇒ Object
Duplicate the top N items.
117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/bsv/script/interpreter/stack.rb', line 117 def dup_n(count) check_count!(count, 'dup_n') stack_error!("stack too small for dup_n(#{count})") if @items.length < count start = @items.length - count added = @items[start..].sum(&:bytesize) check_memory!(added) copies = Array.new(count) { |i| @items[start + i].dup } @memory_usage += added @items.concat(copies) end |
#empty? ⇒ Boolean
101 102 103 |
# File 'lib/bsv/script/interpreter/stack.rb', line 101 def empty? @items.empty? end |
#nip_n(idx) ⇒ Object
Remove item at offset idx from top (0 = top).
140 141 142 143 144 145 146 |
# File 'lib/bsv/script/interpreter/stack.rb', line 140 def nip_n(idx) check_index!(idx) removed = @items.delete_at(@items.length - 1 - idx) @memory_usage -= removed.bytesize removed end |
#over_n(count) ⇒ Object
Copy N items from 2N depth to top. OP_OVER (n=1): [x1 x2] -> [x1 x2 x1] OP_2OVER (n=2): [x1 x2 x3 x4] -> [x1 x2 x3 x4 x1 x2]
176 177 178 179 180 181 182 183 184 185 186 |
# File 'lib/bsv/script/interpreter/stack.rb', line 176 def over_n(count) check_count!(count, 'over_n') stack_error!("stack too small for over_n(#{count})") if @items.length < (2 * count) start = @items.length - (2 * count) added = @items[start, count].sum(&:bytesize) check_memory!(added) copies = Array.new(count) { |i| @items[start + i].dup } @memory_usage += added @items.concat(copies) end |
#peek_bool(idx = 0) ⇒ Object
91 92 93 |
# File 'lib/bsv/script/interpreter/stack.rb', line 91 def peek_bool(idx = 0) self.class.cast_bool(peek_bytes(idx)) end |
#peek_bytes(idx = 0) ⇒ Object
— Peek —
73 74 75 76 77 |
# File 'lib/bsv/script/interpreter/stack.rb', line 73 def peek_bytes(idx = 0) check_index!(idx) @items[@items.length - 1 - idx] end |
#peek_int(idx = 0, max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ⇒ Object
Decode a stack item as a BSV::Script::ScriptNumber without removing it.
87 88 89 |
# File 'lib/bsv/script/interpreter/stack.rb', line 87 def peek_int(idx = 0, max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ScriptNumber.from_bytes(peek_bytes(idx), max_length: max_length, require_minimal: require_minimal) end |
#pick_n(idx) ⇒ Object
Copy item at index n to top (0 = top).
189 190 191 192 193 194 195 196 |
# File 'lib/bsv/script/interpreter/stack.rb', line 189 def pick_n(idx) check_index!(idx) copy = @items[@items.length - 1 - idx].dup check_memory!(copy.bytesize) @memory_usage += copy.bytesize @items.push(copy) end |
#pop_bool ⇒ Object
67 68 69 |
# File 'lib/bsv/script/interpreter/stack.rb', line 67 def pop_bool self.class.cast_bool(pop_bytes) end |
#pop_bytes ⇒ Object
— Pop —
46 47 48 49 50 51 52 |
# File 'lib/bsv/script/interpreter/stack.rb', line 46 def pop_bytes stack_error!('stack empty') if @items.empty? item = @items.pop @memory_usage -= item.bytesize item end |
#pop_int(max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ⇒ Object
Decode the top stack item as a BSV::Script::ScriptNumber.
63 64 65 |
# File 'lib/bsv/script/interpreter/stack.rb', line 63 def pop_int(max_length: ScriptNumber::MAX_BYTE_LENGTH, require_minimal: true) ScriptNumber.from_bytes(pop_bytes, max_length: max_length, require_minimal: require_minimal) end |
#push_bool(val) ⇒ Object
40 41 42 |
# File 'lib/bsv/script/interpreter/stack.rb', line 40 def push_bool(val) push_bytes(val ? "\x01".b : ''.b) end |
#push_bytes(data) ⇒ Object
— Push —
29 30 31 32 33 34 |
# File 'lib/bsv/script/interpreter/stack.rb', line 29 def push_bytes(data) check_memory!(data.bytesize) bytes = data.b @memory_usage += bytes.bytesize @items.push(bytes) end |
#push_int(script_number) ⇒ Object
36 37 38 |
# File 'lib/bsv/script/interpreter/stack.rb', line 36 def push_int(script_number) push_bytes(script_number.to_bytes) end |
#roll_n(idx) ⇒ Object
Move item at index n to top (0 = top). Memory usage is unchanged.
199 200 201 202 203 204 |
# File 'lib/bsv/script/interpreter/stack.rb', line 199 def roll_n(idx) check_index!(idx) val = @items.delete_at(@items.length - 1 - idx) @items.push(val) end |
#rot_n(count) ⇒ Object
Rotate: move the bottom N of the top 3N items to the top. OP_ROT (n=1): [x1 x2 x3] -> [x2 x3 x1] OP_2ROT (n=2): [x1 x2 x3 x4 x5 x6] -> [x3 x4 x5 x6 x1 x2]
151 152 153 154 155 156 157 |
# File 'lib/bsv/script/interpreter/stack.rb', line 151 def rot_n(count) check_count!(count, 'rot_n') stack_error!("stack too small for rot_n(#{count})") if @items.length < (3 * count) removed = @items.slice!(@items.length - (3 * count), count) @items.concat(removed) end |
#swap_n(count) ⇒ Object
Swap the top N items with the next N. OP_SWAP (n=1): [x1 x2] -> [x2 x1] OP_2SWAP (n=2): [x1 x2 x3 x4] -> [x3 x4 x1 x2]
162 163 164 165 166 167 168 169 170 171 |
# File 'lib/bsv/script/interpreter/stack.rb', line 162 def swap_n(count) check_count!(count, 'swap_n') stack_error!("stack too small for swap_n(#{count})") if @items.length < (2 * count) count.times do |i| a = @items.length - count + i b = @items.length - (2 * count) + i @items[a], @items[b] = @items[b], @items[a] end end |
#to_a ⇒ Object
110 111 112 |
# File 'lib/bsv/script/interpreter/stack.rb', line 110 def to_a @items.dup end |
#tuck ⇒ Object
Copy top and insert before second: [x1 x2] -> [x2 x1 x2]
207 208 209 210 211 212 213 214 |
# File 'lib/bsv/script/interpreter/stack.rb', line 207 def tuck stack_error!('stack too small for tuck') if @items.length < 2 copy = @items.last.dup check_memory!(copy.bytesize) @memory_usage += copy.bytesize @items.insert(@items.length - 2, copy) end |