From 0f924355ca3e2a2752e892ac174e4e62a16f3773 Mon Sep 17 00:00:00 2001 From: Gwenhael Le Moine Date: Wed, 10 Nov 2021 16:20:47 +0100 Subject: [PATCH] implement and test part of the language; exit using quit --- lib/core.rb | 29 ++++++ lib/dictionary.rb | 173 ++++++++++++++++++++++++++++++++++-- lib/language/general.rb | 12 +++ lib/language/operations.rb | 93 +++++++++++++++++++ lib/language/stack.rb | 122 +++++++++++++++++++++++++ repl.rb | 3 +- spec/language_stack_spec.rb | 158 ++++++++++++++++++++++++++++++++ 7 files changed, 582 insertions(+), 8 deletions(-) create mode 100644 lib/core.rb create mode 100644 lib/language/general.rb create mode 100644 lib/language/operations.rb create mode 100644 lib/language/stack.rb create mode 100644 spec/language_stack_spec.rb diff --git a/lib/core.rb b/lib/core.rb new file mode 100644 index 0000000..a8909f6 --- /dev/null +++ b/lib/core.rb @@ -0,0 +1,29 @@ +require_relative './language/general' +require_relative './language/operations' +require_relative './language/stack' + +module Rpn + module Core + module_function + + def stack_extract( stack, needs ) + raise 'Not enough elements' if stack.size < needs.size + + args = [] + needs.each do |need| + elt = stack.pop + + raise "Type Error, needed #{need} got #{elt[:type]}" if need != :any && !need.include?( elt[:type] ) + + args << elt + end + + [stack, args.reverse] + end + + def __todo( stack ) + puts '__NOT IMPLEMENTED__' + stack + end + end +end diff --git a/lib/dictionary.rb b/lib/dictionary.rb index 85eed64..4e8d802 100644 --- a/lib/dictionary.rb +++ b/lib/dictionary.rb @@ -4,15 +4,174 @@ module Rpn class Dictionary def initialize @parser = Parser.new - @words = { - '+' => proc { |stack| - stack + @parser.parse_input( (stack.pop[:value] + stack.pop[:value]).to_s ) - }, - } + @words = {} + + # GENERAL + add( 'nop', proc { |stack| Rpn::Core::General.nop( stack ) } ) + add( 'help', proc { |stack| Rpn::Core.__todo( stack ) } ) # this help message + add( 'quit', proc { |stack| Rpn::Core.__todo( stack ) } ) # quit software + add( 'version', proc { |stack| Rpn::Core.__todo( stack ) } ) # show rpn version + add( 'uname', proc { |stack| Rpn::Core.__todo( stack ) } ) # show rpn complete identification string + add( 'history', proc { |stack| Rpn::Core.__todo( stack ) } ) # see commands history + + # USUAL OPERATIONS ON REALS AND COMPLEXES + add( '+', proc { |stack| Rpn::Core::Operations.add( stack ) } ) + add( '-', proc { |stack| Rpn::Core::Operations.subtract( stack ) } ) + add( 'chs', proc { |stack| Rpn::Core::Operations.negate( stack ) } ) + add( '*', proc { |stack| Rpn::Core::Operations.multiply( stack ) } ) + add( '/', proc { |stack| Rpn::Core::Operations.divide( stack ) } ) + add( 'inv', proc { |stack| Rpn::Core::Operations.inverse( stack ) } ) + add( '^', proc { |stack| Rpn::Core::Operations.power( stack ) } ) + add( 'sqrt', proc { |stack| Rpn::Core.__todo( stack ) } ) # rpn_square root + add( 'sq', proc { |stack| Rpn::Core.__todo( stack ) } ) # rpn_square + add( 'abs', proc { |stack| Rpn::Core.__todo( stack ) } ) # absolute value + add( 'dec', proc { |stack| Rpn::Core.__todo( stack ) } ) # decimal representation + add( 'hex', proc { |stack| Rpn::Core.__todo( stack ) } ) # hexadecimal representation + add( 'bin', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary representation + add( 'base', proc { |stack| Rpn::Core.__todo( stack ) } ) # arbitrary base representation + add( 'sign', proc { |stack| Rpn::Core.__todo( stack ) } ) # 1 if number at stack level 1 is > 0, 0 if == 0, -1 if <= 0 + + # OPERATIONS ON REALS + add( '%', proc { |stack| Rpn::Core.__todo( stack ) } ) # percent + add( '%CH', proc { |stack| Rpn::Core.__todo( stack ) } ) # inverse percent + add( 'mod', proc { |stack| Rpn::Core.__todo( stack ) } ) # modulo + add( 'fact', proc { |stack| Rpn::Core.__todo( stack ) } ) # n! for integer n or Gamma(x+1) for fractional x + add( 'mant', proc { |stack| Rpn::Core.__todo( stack ) } ) # mantissa of a real number + add( 'xpon', proc { |stack| Rpn::Core.__todo( stack ) } ) # exponant of a real number + add( 'floor', proc { |stack| Rpn::Core.__todo( stack ) } ) # largest number <= + add( 'ceil', proc { |stack| Rpn::Core.__todo( stack ) } ) # smallest number >= + add( 'ip', proc { |stack| Rpn::Core.__todo( stack ) } ) # integer part + add( 'fp', proc { |stack| Rpn::Core.__todo( stack ) } ) # fractional part + add( 'min', proc { |stack| Rpn::Core.__todo( stack ) } ) # min of 2 real numbers + add( 'max', proc { |stack| Rpn::Core.__todo( stack ) } ) # max of 2 real numbers + + # OPERATIONS ON COMPLEXES + add( 're', proc { |stack| Rpn::Core.__todo( stack ) } ) # complex real part + add( 'im', proc { |stack| Rpn::Core.__todo( stack ) } ) # complex imaginary part + add( 'conj', proc { |stack| Rpn::Core.__todo( stack ) } ) # complex conjugate + add( 'arg', proc { |stack| Rpn::Core.__todo( stack ) } ) # complex argument in radians + add( 'c->r', proc { |stack| Rpn::Core.__todo( stack ) } ) # transform a complex in 2 reals + add( 'r->c', proc { |stack| Rpn::Core.__todo( stack ) } ) # transform 2 reals in a complex + add( 'p->r', proc { |stack| Rpn::Core.__todo( stack ) } ) # cartesian to polar + add( 'r->p', proc { |stack| Rpn::Core.__todo( stack ) } ) # polar to cartesian + + # MODE + add( 'std', proc { |stack| Rpn::Core.__todo( stack ) } ) # standard floating numbers representation. ex: std + add( 'fix', proc { |stack| Rpn::Core.__todo( stack ) } ) # fixed point representation. ex: 6 fix + add( 'sci', proc { |stack| Rpn::Core.__todo( stack ) } ) # scientific floating point representation. ex: 20 sci + add( 'prec', proc { |stack| Rpn::Core.__todo( stack ) } ) # set float precision in bits. ex: 256 prec + add( 'round', proc { |stack| Rpn::Core.__todo( stack ) } ) # set float rounding mode. ex: ["nearest", "toward zero", "toward +inf", "toward -inf", "away from zero"] round + add( 'default', proc { |stack| Rpn::Core.__todo( stack ) } ) # set float representation and precision to default + add( 'type', proc { |stack| Rpn::Core.__todo( stack ) } ) # show type of stack first entry + + # TEST + add( '>', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary operator > + add( '>=', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary operator >= + add( '<', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary operator < + add( '<=', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary operator <= + add( '!=', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary operator != (different) + add( '==', proc { |stack| Rpn::Core.__todo( stack ) } ) # binary operator == (equal) + add( 'and', proc { |stack| Rpn::Core.__todo( stack ) } ) # boolean operator and + add( 'or', proc { |stack| Rpn::Core.__todo( stack ) } ) # boolean operator or + add( 'xor', proc { |stack| Rpn::Core.__todo( stack ) } ) # boolean operator xor + add( 'not', proc { |stack| Rpn::Core.__todo( stack ) } ) # boolean operator not + add( 'same', proc { |stack| Rpn::Core.__todo( stack ) } ) # boolean operator same (equal) + + # STACK + add( 'swap', proc { |stack| Rpn::Core::Stack.swap( stack ) } ) + add( 'drop', proc { |stack| Rpn::Core::Stack.drop( stack ) } ) + add( 'drop2', proc { |stack| Rpn::Core::Stack.drop2( stack ) } ) + add( 'dropn', proc { |stack| Rpn::Core::Stack.dropn( stack ) } ) + add( 'del', proc { |stack| Rpn::Core::Stack.del( stack ) } ) + add( 'rot', proc { |stack| Rpn::Core::Stack.rot( stack ) } ) + add( 'dup', proc { |stack| Rpn::Core::Stack.dup( stack ) } ) + add( 'dup2', proc { |stack| Rpn::Core::Stack.dup2( stack ) } ) + add( 'dupn', proc { |stack| Rpn::Core::Stack.dupn( stack ) } ) + add( 'pick', proc { |stack| Rpn::Core::Stack.pick( stack ) } ) + add( 'depth', proc { |stack| Rpn::Core::Stack.depth( stack ) } ) + add( 'roll', proc { |stack| Rpn::Core::Stack.roll( stack ) } ) + add( 'rolld', proc { |stack| Rpn::Core::Stack.rolld( stack ) } ) + add( 'over', proc { |stack| Rpn::Core::Stack.over( stack ) } ) + + # STRING + add( '->str', proc { |stack| Rpn::Core.__todo( stack ) } ) # convert an object into a string + add( 'str->', proc { |stack| Rpn::Core.__todo( stack ) } ) # convert a string into an object + add( 'chr', proc { |stack| Rpn::Core.__todo( stack ) } ) # convert ASCII character code in stack level 1 into a string + add( 'num', proc { |stack| Rpn::Core.__todo( stack ) } ) # return ASCII code of the first character of the string in stack level 1 as a real number + add( 'size', proc { |stack| Rpn::Core.__todo( stack ) } ) # return the length of the string + add( 'pos', proc { |stack| Rpn::Core.__todo( stack ) } ) # seach for the string in level 1 within the string in level 2 + add( 'sub', proc { |stack| Rpn::Core.__todo( stack ) } ) # return a substring of the string in level 3 + + # BRANCH + add( 'if', proc { |stack| Rpn::Core.__todo( stack ) } ) # if then else end + add( 'then', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with if + add( 'else', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with if + add( 'end', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with various branch instructions + add( 'start', proc { |stack| Rpn::Core.__todo( stack ) } ) # start next| step + add( 'for', proc { |stack| Rpn::Core.__todo( stack ) } ) # for next| step + add( 'next', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with start and for + add( 'step', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with start and for + add( 'ift', proc { |stack| Rpn::Core.__todo( stack ) } ) # similar to if-then-end, ift + add( 'ifte', proc { |stack| Rpn::Core.__todo( stack ) } ) # similar to if-then-else-end, ifte + add( 'do', proc { |stack| Rpn::Core.__todo( stack ) } ) # do until end + add( 'until', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with do + add( 'while', proc { |stack| Rpn::Core.__todo( stack ) } ) # while repeat end + add( 'repeat', proc { |stack| Rpn::Core.__todo( stack ) } ) # used with while + + # STORE + add( 'sto', proc { |stack| Rpn::Core.__todo( stack ) } ) # store a variable. ex: 1 'name' sto + add( 'rcl', proc { |stack| Rpn::Core.__todo( stack ) } ) # recall a variable. ex: 'name' rcl + add( 'purge', proc { |stack| Rpn::Core.__todo( stack ) } ) # delete a variable. ex: 'name' purge + add( 'vars', proc { |stack| Rpn::Core.__todo( stack ) } ) # list all variables + add( 'clusr', proc { |stack| Rpn::Core.__todo( stack ) } ) # erase all variables + add( 'edit', proc { |stack| Rpn::Core.__todo( stack ) } ) # edit a variable content + add( 'sto+', proc { |stack| Rpn::Core.__todo( stack ) } ) # add to a stored variable. ex: 1 'name' sto+ 'name' 2 sto+ + add( 'sto-', proc { |stack| Rpn::Core.__todo( stack ) } ) # substract to a stored variable. ex: 1 'name' sto- 'name' 2 sto- + add( 'sto*', proc { |stack| Rpn::Core.__todo( stack ) } ) # multiply a stored variable. ex: 3 'name' sto* 'name' 2 sto* + add( 'sto/', proc { |stack| Rpn::Core.__todo( stack ) } ) # divide a stored variable. ex: 3 'name' sto/ 'name' 2 sto/ + add( 'sneg', proc { |stack| Rpn::Core.__todo( stack ) } ) # negate a variable. ex: 'name' sneg + add( 'sinv', proc { |stack| Rpn::Core.__todo( stack ) } ) # inverse a variable. ex: 1 'name' sinv + + # PROGRAM + add( 'eval', proc { |stack| Rpn::Core.__todo( stack ) } ) # evaluate (run) a program, or recall a variable. ex: 'my_prog' eval + add( '->', proc { |stack| Rpn::Core.__todo( stack ) } ) # load program local variables. ex: << -> n m << 0 n m for i i + next >> >> + + # TRIG ON REALS AND COMPLEXES + add( 'pi', proc { |stack| Rpn::Core.__todo( stack ) } ) # pi constant + add( 'sin', proc { |stack| Rpn::Core.__todo( stack ) } ) # sinus + add( 'asin', proc { |stack| Rpn::Core.__todo( stack ) } ) # arg sinus + add( 'cos', proc { |stack| Rpn::Core.__todo( stack ) } ) # cosinus + add( 'acos', proc { |stack| Rpn::Core.__todo( stack ) } ) # arg cosinus + add( 'tan', proc { |stack| Rpn::Core.__todo( stack ) } ) # tangent + add( 'atan', proc { |stack| Rpn::Core.__todo( stack ) } ) # arg tangent + add( 'd->r', proc { |stack| Rpn::Core.__todo( stack ) } ) # convert degrees to radians + add( 'r->d', proc { |stack| Rpn::Core.__todo( stack ) } ) # convert radians to degrees + + # LOGS ON REALS AND COMPLEXES + add( 'e', proc { |stack| Rpn::Core.__todo( stack ) } ) # Euler constant + add( 'ln', proc { |stack| Rpn::Core.__todo( stack ) } ) # logarithm base e + add( 'lnp1', proc { |stack| Rpn::Core.__todo( stack ) } ) # ln(1+x) which is useful when x is close to 0 + add( 'exp', proc { |stack| Rpn::Core.__todo( stack ) } ) # exponential + add( 'expm', proc { |stack| Rpn::Core.__todo( stack ) } ) # exp(x)-1 which is useful when x is close to 0 + add( 'log10', proc { |stack| Rpn::Core.__todo( stack ) } ) # logarithm base 10 + add( 'alog10', proc { |stack| Rpn::Core.__todo( stack ) } ) # exponential base 10 + add( 'log2', proc { |stack| Rpn::Core.__todo( stack ) } ) # logarithm base 2 + add( 'alog2', proc { |stack| Rpn::Core.__todo( stack ) } ) # exponential base 2 + add( 'sinh', proc { |stack| Rpn::Core.__todo( stack ) } ) # hyperbolic sine + add( 'asinh', proc { |stack| Rpn::Core.__todo( stack ) } ) # inverse hyperbolic sine + add( 'cosh', proc { |stack| Rpn::Core.__todo( stack ) } ) # hyperbolic cosine + add( 'acosh', proc { |stack| Rpn::Core.__todo( stack ) } ) # inverse hyperbolic cosine + add( 'tanh', proc { |stack| Rpn::Core.__todo( stack ) } ) # hyperbolic tangent + add( 'atanh', proc { |stack| Rpn::Core.__todo( stack ) } ) # inverse hyperbolic tangent + + # TIME AND DATE + add( 'time', proc { |stack| Rpn::Core.__todo( stack ) } ) # time in local format + add( 'date', proc { |stack| Rpn::Core.__todo( stack ) } ) # date in local format + add( 'ticks', proc { |stack| Rpn::Core.__todo( stack ) } ) # system tick in µs end - def add( word ) - @words[ word[:name] ] = word[:value] + def add( name, implementation ) + @words[ name ] = implementation end def lookup( name ) diff --git a/lib/language/general.rb b/lib/language/general.rb new file mode 100644 index 0000000..fd1fd91 --- /dev/null +++ b/lib/language/general.rb @@ -0,0 +1,12 @@ +module Rpn + module Core + module General + module_function + + # no operation + def nop( stack ) + stack + end + end + end +end diff --git a/lib/language/operations.rb b/lib/language/operations.rb new file mode 100644 index 0000000..6421ab5 --- /dev/null +++ b/lib/language/operations.rb @@ -0,0 +1,93 @@ +module Rpn + module Core + module Operations + module_function + + # addition + def add( stack ) + addable = %i[numeric string name] + stack, args = Rpn::Core.stack_extract( stack, [addable, addable] ) + + result = { type: case args[0][:type] + when :name + :name + when :string + :string + when :numeric + if args[1][:type] == :numeric + :numeric + else + :string + end + end } + + args.each do |elt| + elt[:value] = elt[:value][1..-2] unless elt[:type] == :numeric + end + + result[:value] = case result[:type] + when :name + "'#{args[0][:value]}#{args[1][:value]}'" + when :string + "\"#{args[0][:value]}#{args[1][:value]}\"" + when :numeric + args[0][:value] + args[1][:value] + end + + stack << result + end + + # substraction + def subtract( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric], %i[numeric]] ) + + stack << { type: :numeric, + value: args[0][:value] - args[1][:value] } + end + + # negation + def negate( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + + stack << { type: :numeric, + value: args[0][:value] * -1 } + end + + # multiplication + def multiply( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric], %i[numeric]] ) + + stack << { type: :numeric, + value: args[0][:value] * args[1][:value] } + end + + # division + def divide( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric], %i[numeric]] ) + + raise 'Division by 0' if args[0][:value].zero? + + stack << { type: :numeric, + value: args[0][:value] / args[1][:value] } + end + + # inverse + def inverse( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + + raise 'Division by 0' if args[0][:value].zero? + + stack << { type: :numeric, + value: 1.0 / args[0][:value] } + end + + # power + def power( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric], %i[numeric]] ) + + stack << { type: :numeric, + value: args[0][:value]**args[1][:value] } + end + end + end +end diff --git a/lib/language/stack.rb b/lib/language/stack.rb new file mode 100644 index 0000000..0ce6f20 --- /dev/null +++ b/lib/language/stack.rb @@ -0,0 +1,122 @@ +module Rpn + module Core + module Stack + module_function + + # swap 2 first stack entries + def swap( stack ) + stack, args = Rpn::Core.stack_extract( stack, %i[any any] ) + + stack << args[1] << args[0] + end + + # drop first stack entry + def drop( stack ) + dropn( stack << { type: :numeric, value: 1 } ) + end + + # drop 2 first stack entries + def drop2( stack ) + dropn( stack << { type: :numeric, value: 2 } ) + end + + # drop n first stack entries + def dropn( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + stack, _args = Rpn::Core.stack_extract( stack, %i[any] * args[0][:value] ) + + stack + end + + # drop all stack entries + def del( _stack ) + [] + end + + # rotate 3 first stack entries + def rot( stack ) + stack, args = Rpn::Core.stack_extract( stack, %i[any any any] ) + + stack << args[1] << args[2] << args[0] + end + + # duplicate first stack entry + def dup( stack ) + dupn( stack << { type: :numeric, value: 1 } ) + end + + # duplicate 2 first stack entries + def dup2( stack ) + dupn( stack << { type: :numeric, value: 2 } ) + end + + # duplicate n first stack entries + def dupn( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + n = args[0][:value] + stack, args = Rpn::Core.stack_extract( stack, %i[any] * args[0][:value] ) + + 2.times do + n.times.each do |i| + stack << args[ i ] + end + end + + stack + end + + # push a copy of the given stack level onto the stack + def pick( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + n = args[0][:value] + stack, args = Rpn::Core.stack_extract( stack, %i[any] * args[0][:value] ) + + n.times.each do |i| + stack << args[ i ] + end + stack << args[0] + + stack + end + + # give stack depth + def depth( stack ) + stack << { type: :numeric, value: stack.size } + end + + # move a stack entry to the top of the stack + def roll( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + n = args[0][:value] + stack, args = Rpn::Core.stack_extract( stack, %i[any] * args[0][:value] ) + + (1..(n - 1)).each do |i| + stack << args[ i ] + end + stack << args[0] + + stack + end + + # move the element on top of the stack to a higher stack position + def rolld( stack ) + stack, args = Rpn::Core.stack_extract( stack, [%i[numeric]] ) + n = args[0][:value] + stack, args = Rpn::Core.stack_extract( stack, %i[any] * args[0][:value] ) + + stack << args[n - 1] + + (0..(n - 2)).each do |i| + stack << args[ i ] + end + + stack + end + + # push a copy of the element in stack level 2 onto the stack + def over( stack ) + pick( stack << { type: :numeric, value: 2 } ) + end + end + end +end diff --git a/repl.rb b/repl.rb index bf1878e..f9b8636 100644 --- a/repl.rb +++ b/repl.rb @@ -3,6 +3,7 @@ require 'readline' +require './lib/core' require './lib/dictionary' require './lib/parser' require './lib/runner' @@ -29,7 +30,7 @@ module Rpn loop do input = Readline.readline( ' ', true ) - break if input.nil? || input == 'exit' + break if input.nil? || input == 'quit' # Remove blank lines from history Readline::HISTORY.pop if input.empty? diff --git a/spec/language_stack_spec.rb b/spec/language_stack_spec.rb new file mode 100644 index 0000000..d5ff30e --- /dev/null +++ b/spec/language_stack_spec.rb @@ -0,0 +1,158 @@ +# coding: utf-8 +# frozen_string_literal: true + +require 'test/unit' + +require_relative '../lib/core' + +class TestParser < Test::Unit::TestCase + def test_swap + stack = Rpn::Core::Stack.swap [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [{ value: 2, type: :numeric }, + { value: 1, type: :numeric }], + stack + end + + def test_drop + stack = Rpn::Core::Stack.drop [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }], + stack + end + + def test_drop2 + stack = Rpn::Core::Stack.drop2 [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [], + stack + end + + def test_dropn + stack = Rpn::Core::Stack.dropn [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 3, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }], + stack + end + + def test_del + stack = Rpn::Core::Stack.del [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [], + stack + end + + def test_rot + stack = Rpn::Core::Stack.rot [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }] + assert_equal [{ value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 1, type: :numeric }], + stack + end + + def test_dup + stack = Rpn::Core::Stack.dup [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 2, type: :numeric }], + stack + end + + def test_dup2 + stack = Rpn::Core::Stack.dup2 [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 1, type: :numeric }, + { value: 2, type: :numeric }], + stack + end + + def test_dupn + stack = Rpn::Core::Stack.dupn [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 3, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }], + stack + end + + def test_pick + stack = Rpn::Core::Stack.pick [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 3, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 2, type: :numeric }], + stack + end + + def test_depth + stack = Rpn::Core::Stack.depth [] + assert_equal [{ value: 0, type: :numeric }], + stack + + stack = Rpn::Core::Stack.depth [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 2, type: :numeric }], + stack + end + + def test_roll + stack = Rpn::Core::Stack.roll [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 3, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 2, type: :numeric }], + stack + end + + def test_rolld + stack = Rpn::Core::Stack.rolld [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 4, type: :numeric }, + { value: 3, type: :numeric }, + { value: 2, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }], + stack + end + + def test_over + stack = Rpn::Core::Stack.over [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }] + assert_equal [{ value: 1, type: :numeric }, + { value: 2, type: :numeric }, + { value: 3, type: :numeric }, + { value: 4, type: :numeric }, + { value: 3, type: :numeric }], + stack + end +end