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
(also: #length, #size)
— 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).
222 223 224 225 226 227 228 229 230 231 232 233 |
# File 'lib/bsv/script/interpreter/stack.rb', line 222 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
108 109 110 111 |
# File 'lib/bsv/script/interpreter/stack.rb', line 108 def clear @items.clear @memory_usage = 0 end |
#depth ⇒ Object Also known as: length, size
— 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.
133 134 135 136 137 138 139 140 |
# File 'lib/bsv/script/interpreter/stack.rb', line 133 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.
120 121 122 123 124 125 126 127 128 129 130 |
# File 'lib/bsv/script/interpreter/stack.rb', line 120 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
104 105 106 |
# File 'lib/bsv/script/interpreter/stack.rb', line 104 def empty? @items.empty? end |
#nip_n(idx) ⇒ Object
Remove item at offset idx from top (0 = top).
143 144 145 146 147 148 149 |
# File 'lib/bsv/script/interpreter/stack.rb', line 143 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]
179 180 181 182 183 184 185 186 187 188 189 |
# File 'lib/bsv/script/interpreter/stack.rb', line 179 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).
192 193 194 195 196 197 198 199 |
# File 'lib/bsv/script/interpreter/stack.rb', line 192 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.
202 203 204 205 206 207 |
# File 'lib/bsv/script/interpreter/stack.rb', line 202 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]
154 155 156 157 158 159 160 |
# File 'lib/bsv/script/interpreter/stack.rb', line 154 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]
165 166 167 168 169 170 171 172 173 174 |
# File 'lib/bsv/script/interpreter/stack.rb', line 165 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
113 114 115 |
# File 'lib/bsv/script/interpreter/stack.rb', line 113 def to_a @items.dup end |
#tuck ⇒ Object
Copy top and insert before second: [x1 x2] -> [x2 x1 x2]
210 211 212 213 214 215 216 217 |
# File 'lib/bsv/script/interpreter/stack.rb', line 210 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 |