利用者:Ef3/forth.rb
< 利用者:Ef3
- forth.rb
# frozen_string_literal: true class Forth < Array Call = Struct.new :addr class Return end class ReturnFalse end Branch = Struct.new :offset BranchFalse = Struct.new :offset Branch2 = Struct.new :offset class Case end Of = Struct.new :offset class EndCase end class Do end class CountUp end class Loop end def initialize super # ループカウンタスタック(I/J/Kを記憶) @lcstack = [] # ループ先頭ワードスタック do/?do - loop/+loop で使用 @lwstack = [] # 中間コード列 @codespace = [] # Rスタック @rstack = [] # 定数辞書 @constants = {} # 変数辞書 @variables = {} @varnames = [] @words = { '.' => ->(x) { print "#{x} " }, # Print the top value of the stack '.s' => -> { p self }, # Print the entire stack 'clear' => -> { clear }, 'cr' => -> { puts }, 'depth' => -> { push size }, 'drop' => ->(x) {}, 'dup' => -> { push peek }, 'nip' => ->(_x1, x2) { push(x2) }, # ( x1 x2 -- x2 ) Drop the second item on the stack 'over' => ->(x1, x2) { push(x1, x2, x1) }, # ( x1 x2 -- x1 x2 x1 )Copy the second item on the stack to the top 'rot' => lambda { |x1, x2, x3| push(x2, x3, x1) }, # ( x1 x2 x3 -- x2 x3 x1 ) Rotate the top three values on the stack '-rot' => lambda { |x1, x2, x3| push(x3, x1, x2) }, # ( x1 x2 x3 -- x3 x1 x2 ) Reverse rotate the top three values on the stack 'swap' => ->(x1, x2) { push(x2, x1) }, # ( x1 x2 -- x2 x1) Swap the top two values of the stack 'tuck' => lambda { |x1, x2| push(x2, x1, x2) }, # ( x1 x2 -- x2 x1 x2 ) Insert a copy of the top item below the second item on the stack '2drop' => ->(x1, x2) {}, # ( x1 x2 -- ) スタックの上位2つの値を削除します。 '2dup' => ->(x1, x2) { push(x1, x2, x1, x2) }, # ( x1 x2 -- x1 x2 x1 x2 ) スタックの上位2つの値を複製します。 '2nip' => ->(_x1, _x2, x3) { push(x3) }, # ( x1 x2 x3 -- x3 ) スタックの上位2つの値を削除し、3番目の値を残します。 '2over' => lambda { |x1, x2, x3, x4| # ( x1 x2 x3 x4 -- x1 x2 x3 x4 x1 x2 ) スタックの3番目と4番目の値をコピーして最上位に置きます。 push(x1, x2, x3, x4, x1, x2) }, '2rot' => lambda { |x1, x2, x3, x4, x5, x6| # ( x1 x2 x3 x4 x5 x6 -- x3 x4 x5 x6 x1 x2 ) スタックの上位6つの値を回転させます。 push(x3, x4, x5, x6, x1, x2) }, '2swap' => lambda { |x1, x2, x3, x4| # ( x1 x2 x3 x4 -- x3 x4 x1 x2 ) スタックの上位4つの値を交換します。 push(x3, x4, x1, x2) }, '2tuck' => lambda { |x1, x2, x3, x4| # ( x1 x2 x3 x4 -- x3 x4 x1 x2 x3 x4 ) スタックの3番目と4番目の値を2番目と1番目の値の間に挿入します。 push(x3, x4, x1, x2, x3, x4) }, 'min' => ->(x1, x2) { push [x1, x2].min }, 'max' => ->(x1, x2) { push [x1, x2].max }, 'nan?' => ->(x) { push x.nan? }, 'words' => lambda do @words.select { |k| k.instance_of? String }.keys.sort.each do |k| v = @words[k] case v in Proc if nil puts "#{k}: ( #{v.parameters.map { |x| case x in [:req, Symbol => x] x.to_s else nil end } * ' ' } )" end else puts "#{k}: ( #{v.class} )" end end @words.select { |k| k.instance_of? Proc }.keys.each_with_index do |k, index| v = @words[k] puts "<lamda #{index}>: ( #{v.parameters.map { |x| case x in [:req, Symbol => x] x.to_s else nil end } * ' ' } )" end end, 'I' => -> { push @lcstack[-1][:count] }, 'J' => -> { push @lcstack[-2][:count] }, 'K' => -> { push @lcstack[-3][:count] }, 'INFINITY' => -> { push Float::INFINITY }, '-INFINITY' => -> { push(-Float::INFINITY) }, 'NAN' => -> { push Float::NAN }, 'EPSILON' => -> { push Float::EPSILON }, 'PI' => -> { push Math::PI }, 'constant' => lambda { @evalstack.push(lambda { |word| if @constants.include? word puts "duplicate define #{word}" else @constants[word] = pop end @evalstack.pop }) }, 'variable' => lambda { @evalstack.push(lambda { |word| @variables[word] = nil @evalstack.pop }) }, ':' => lambda { # ワード定義 push ':' define_word } } # Import from Numeric %w[negative? zero? [] abs rationalize class to_c to_f to_i to_r ! != == < <= > >= -@ ~ + - * / % divmod **].each do |word| m = word.to_sym ary_ = [1.0, 1, 1r, 1i] q = ary_.each { |n| break n if n.respond_to? m } next if q == ary_ case q.method(m).parameters in [] then @words[word] = ->(x) { push x.send(m) } in [[:rest]] => a then @words[word] = ->(x1, x2) { push x1.send(m, x2) } in [[:req]] then @words[word] = ->(x1, x2) { push x1.send(m, x2) } else end end # Import from Math module %w[sin cos tan sqrt cbrt].sort.each do |word| m = word.to_sym case Math.method(m).parameters in [[:req]] then @words[word] = ->(x) { push Math.send(m, x) } in [[:rest]] then @words[word] = ->(x) { push Math.send(m, x) } in [[:req], [:req]] then @words[word] = ->(x1, x2) { push Math.send(node, x1, x2) } else end end # ワードに別名を定義 { 'negate' => '-@', 'invert' => '~', '=' => '==', '<>' => '!=' }.each { |key, value| @words[key] = @words[value] } # ワード定義中に有効なワード @keywords = { ';' => lambda { name, *body = slice!(rindex(':')..-1).drop(1) start = @codespace.size body.each { |word| @codespace.push word } @codespace.push Return.new @words[name] = Call.new(start) enddef_word }, 'if' => lambda { push 'if' define_word }, 'then' => lambda { body = slice!(rindex('if')..-1).drop(1) _then = body.take_while { |word| word != 'else' } _else = body.drop_while { |word| word != 'else' }.drop_while { |word| word == 'else' } start = @codespace.size if _else.size.positive? _then.push Return.new @codespace.push BranchFalse.new _then.size _then.each { |word| @codespace.push word } _else.each { |word| @codespace.push word } else @codespace.push ReturnFalse.new _then.each { |word| @codespace.push word } end @codespace.push Return.new name = Call.new(start) push(name) @words[name] = name # XXX enddef_word }, 'case' => lambda { push 'case' @casestack ||= [] @casestack.push [] define_word }, 'of' => lambda { stmt = @casestack.last.empty? ? 'case' : 'endof' _when = slice!(rindex(stmt)..-1).drop(1) @casestack.last << _when push 'of' }, 'endof' => lambda { body = slice!(rindex('of')..-1).drop(1) @casestack.last.last[1] = body push 'endof' }, 'endcase' => lambda { default_ = slice!(rindex('endof')..-1).drop(1) tbl = @casestack.pop start = @codespace.size @codespace.push Case.new tbl.each do |w, body| @codespace.push w body.push EndCase.new @codespace.push Of.new body.size body.each { |word| @codespace.push word } end default_.each { |word| @codespace.push(word) } @codespace.push EndCase.new word = Call.new start push(word) @words[word] = word enddef_word }, 'do' => lambda { push 'do' @lwstack.push 'do' define_word }, '?do' => lambda { push '?do' @lwstack.push '?do' define_word }, 'loop' => lambda { stmt = @lwstack.pop body = slice!(rindex(stmt)..-1).drop(1) start = @codespace.size @codespace.push Do.new case stmt in 'do' body.push '1' body.push CountUp.new body.push Branch2.new(-body.size - 1) in '?do' body.push '1' body.push CountUp.new body.unshift Branch body.size + 1 body.push Branch2.new(-body.size - 1) end body.each { |word| @codespace.push word } @codespace.push Loop.new # warn [__method__, @codespace.inspect] word = Call.new start push(word) @words[word] = word enddef_word }, '+loop' => lambda { stmt = @lwstack.pop body = slice!(rindex(stmt)..-1).drop(1) start = @codespace.size @codespace.push Do.new case stmt in 'do' body.push CountUp.new body.push Branch2.new(-body.size - 1) in '?do' body.push CountUp.new body.unshift Branch body.size + 1 body.push Branch2.new(-body.size - 1) end body.each { |word| @codespace.push word } @codespace.push Loop.new word = Call.new start push(word) @words[word] = word enddef_word }, '."' => lambda { push '."' @evalstack.push(lambda do |word| case word when /(.*)"$/ push ::Regexp.last_match(1) body = slice!(rindex('."')..-1).drop(1) word = -> { push body * ' ' } push(word) @words[word] = word @evalstack.pop else push(word) end end) }, '(' => lambda { push '(' @evalstack.push(lambda do |word| case word when ')' slice!(rindex('(')..-1).drop(1) @evalstack.pop else push(word) end end) } } # キーワードに別名を定義 { 'endif' => 'then' }.each { |key, value| @keywords[key] = @keywords[value] } # evaluator stack @evalstack = [method(:eval)] end alias peek last def eval(word) ip = nil lc = 0 while word # $stderr.puts "word #{word}" next_word = nil case word in /\A-?\d+\z/ # Decimal integer push Integer(word) in /\A[+-]?0[Bb][01]+\z/ # Binary integer push Integer(word) in /\A[+-]?0[Oo][0-7]+\z/ # Octal integer push Integer(word) in /\A[+-]?0[Xx][0-9A-Fa-f]+\z/ # Hexadecimal integer push Integer(word) in /\A[+-]?\d+(\.\d+)?([eE][-+]?\d+)?\z/ # Floating point number push Float(word) in String if @constants.include? word push @constants[word] elsif @variables.include? word @varnames.push word @evalstack.push(lambda { |word| case word in '!' @variables[@varnames.pop] = pop in '@' push @variables[@varnames.pop] end @evalstack.pop }) else next_word = @words[word] end in Proc => proc then n = proc.parameters.reduce(0) do |result, el| el.first == :req ? result + 1 : result end proc[*pop(n)] in Call => call then @rstack.push ip ip = call.addr in Return then ip = @rstack.pop in ReturnFalse then ip = @rstack.pop unless pop in BranchFalse => bf then ip += bf.offset unless pop in Case => _case then n = pop # warn [:case, n].inspect @rstack.push n in Of => of then n = pop # warn [:of, n].inspect ip += of.offset unless @rstack.last == n in EndCase => endcase then @rstack.pop ip = @rstack.pop # break if @rstack.empty? if ip.nil? p [EndCase, word, ip] warn [ip, word, self].inspect @codespace.each_with_index do |word, i| p [EndCase, i, word] end # exit end in Do => _do then limit, count = *pop(2) @lcstack.push({limit: limit, count: count}) in Branch2 => branch2 limit, count = @lcstack.last.fetch_values(:limit, :count) ip += branch2.offset if limit > count in CountUp => countup @lcstack.last[:count] += pop in Loop then pair = @lcstack.pop ip = @rstack.pop else raise "Unknown word #{word}(#{wird.class})" end word = nil if next_word word = next_word elsif ip word = @codespace[ip] ip += 1 end # break if !@rstack.empty? lc += 1 if lc > 9999 [[:inf - loop]] exit end # p self # XXX end end def eval_line(line) line.split.each { |word| @evalstack.last[word] } self end def repl while (line = gets) eval_line(line) end end private def define_word @evalstack.push(lambda do |word| @keywords[word] ? @keywords[word][] : push(word) end) end def enddef_word = @evalstack.pop end require 'minitest/spec' # 標準出力をキャプチャして文字列として返す def capture_stdout original_stdout = $stdout $stdout = StringIO.new yield $stdout.string ensure $stdout = original_stdout end describe Forth do before do @forth = Forth.new end describe 'basic operation' do it 'initialize' do _(@forth.eval_line('')).must_equal [] end it '.' do actual_output = capture_stdout { @forth.eval_line('123 .') } _(actual_output).must_equal '123 ' end it '.s' do actual_output = capture_stdout { @forth.eval_line('123 .s') } _(actual_output).must_equal "[123]\n" end it 'words' do skip @forth.eval_line(': cube dup dup * * ;') @forth.eval_line(%(: sign dup 0 < if -1 else dup 0 > if 1 else 0 then then ;)) @forth.eval_line(%(: d34 3 1 do 4 1 do I J loop loop ; d34)) actual_output = capture_stdout { @forth.eval_line('words') } _(actual_output).must_equal '' end it 'stack op.' do _(@forth.eval_line('1')).must_equal [1] _(@forth.eval_line('2')).must_equal [1, 2] _(@forth.eval_line('+')).must_equal [3] end it 'to_i' do _(@forth.eval_line(%(3.1415926536))).must_equal [3.1415926536] _(@forth.eval_line(%(to_i))).must_equal [3] end it 'to_f' do _(@forth.eval_line(%(3))).must_equal [3] _(@forth.eval_line(%(to_f))).must_equal [3.0] end it 'to_c' do _(@forth.eval_line(%(-2))).must_equal [-2] _(@forth.eval_line(%(to_c))).must_equal [-2] _(@forth.eval_line(%(0.5 **))).must_equal [(0.0 + 1.4142135623730951i)] end it 'to_r' do _(@forth.eval_line(%(12))).must_equal [12] _(@forth.eval_line(%(to_r))).must_equal [12] _(@forth.eval_line(%(18 /))).must_equal [Rational(2, 3)] _(@forth.eval_line(%(0.0 /))).must_equal [Float::INFINITY] _(@forth.eval_line(%(0.0 to_r 0.0 / nan?))).must_equal [Float::INFINITY, true] _ { @forth.eval_line(%(12 to_r 0 /)) }.must_raise ZeroDivisionError end it 'extensive func.' do _(@forth.eval_line(%(9 5 divmod))).must_equal [[1, 4]] _(@forth.eval_line(%(clear 27 cbrt))).must_equal [3] _(@forth.eval_line(%(clear PI 4 / sin))).must_equal [0.7071067811865475] _(@forth.eval_line(%(clear 0.0 zero?))).must_equal [true] _(@forth.eval_line(%(clear 0b1111 0 []))).must_equal [1] _(@forth.eval_line(%(clear 0b1111 1 []))).must_equal [1] _(@forth.eval_line(%(clear 0b1111 2 []))).must_equal [1] _(@forth.eval_line(%(clear 0b1111 3 []))).must_equal [1] _(@forth.eval_line(%(clear 0b1111 4 []))).must_equal [0] _(@forth.eval_line(%(clear 73 42 rationalize class))).must_equal [Rational] end end describe 'Basic words' do it 'should execute . correctly' do output = capture_stdout { @forth.eval_line(%(123 .)) } _(output).must_equal '123 ' end it 'should execute .s correctly' do output = capture_stdout { @forth.eval_line(%(1 2 3 .s)) } _(output).must_equal "[1, 2, 3]\n" end it 'should execute clear correctly' do @forth.eval_line(%(1 2 3 clear)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[]\n" end it 'should execute depth correctly' do @forth.eval_line(%(1 2 3 depth)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2, 3, 3]\n" end it 'should execute drop correctly' do @forth.eval_line(%(1 2 3 drop)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2]\n" end it 'should execute dup correctly' do @forth.eval_line(%(1 2 dup)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2, 2]\n" end it 'should execute nip correctly' do @forth.eval_line(%(1 2 3 nip)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 3]\n" end it 'should execute over correctly' do @forth.eval_line(%(1 2 over)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2, 1]\n" end it 'should execute rot correctly' do @forth.eval_line(%(1 2 3 rot)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[2, 3, 1]\n" end it 'should execute -rot correctly' do @forth.eval_line(%(1 2 3 -rot)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[3, 1, 2]\n" end it 'should execute swap correctly' do @forth.eval_line(%(1 2 swap)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[2, 1]\n" end it 'should execute tuck correctly' do @forth.eval_line(%(1 2 tuck)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[2, 1, 2]\n" end it 'should execute 2drop correctly' do @forth.eval_line(%(1 2 3 4 2drop)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2]\n" end it 'should execute 2dup correctly' do @forth.eval_line(%(1 2 2dup)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2, 1, 2]\n" end it 'should execute 2nip correctly' do @forth.eval_line(%(1 2 3 4 2nip)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 4]\n" end it 'should execute 2over correctly' do @forth.eval_line(%(1 2 3 4 2over)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[1, 2, 3, 4, 1, 2]\n" end it 'should execute 2rot correctly' do @forth.eval_line(%(1 2 3 4 5 6 2rot)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[3, 4, 5, 6, 1, 2]\n" end it 'should execute 2swap correctly' do @forth.eval_line(%(1 2 3 4 2swap)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[3, 4, 1, 2]\n" end it 'should execute 2tuck correctly' do @forth.eval_line(%(1 2 3 4 2tuck)) _(capture_stdout { @forth.eval_line('.s') }).must_equal "[3, 4, 1, 2, 3, 4]\n" end end describe 'arithmetic operations' do it 'should perform arithmetic operations correctly' do _(@forth.eval_line(%(5 3 + 2 *))).must_equal [16] end it '!' do _(@forth.eval_line(%(clear 1 !))).must_equal [false] _(@forth.eval_line(%(clear 0 !))).must_equal [false] _(@forth.eval_line(%(clear 0 1 < !))).must_equal [false] end it '!= ==' do _(@forth.eval_line(%(clear 1 1 !=))).must_equal [false] _(@forth.eval_line(%(clear 1 1 ==))).must_equal [true] _(@forth.eval_line(%(clear 0 1 !=))).must_equal [true] _(@forth.eval_line(%(clear 0 1 ==))).must_equal [false] _(@forth.eval_line(%(clear 1 1.0 !=))).must_equal [false] _(@forth.eval_line(%(clear 1 1.0 ==))).must_equal [true] _(@forth.eval_line(%(clear 0 1.0 !=))).must_equal [true] _(@forth.eval_line(%(clear 0 1.0 ==))).must_equal [false] _(@forth.eval_line(%(clear 1.0 1 !=))).must_equal [false] _(@forth.eval_line(%(clear 1.0 1 ==))).must_equal [true] _(@forth.eval_line(%(clear 0.0 1 !=))).must_equal [true] _(@forth.eval_line(%(clear 0.0 1 ==))).must_equal [false] _(@forth.eval_line(%(clear NAN 1 ==))).must_equal [false] _(@forth.eval_line(%(clear NAN 1 !=))).must_equal [true] _(@forth.eval_line(%(clear NAN NAN ==))).must_equal [false] _(@forth.eval_line(%(clear NAN NAN !=))).must_equal [true] end it 'abs' do _(@forth.eval_line(%(4 abs))).must_equal [4] _(@forth.eval_line(%(clear -4 abs))).must_equal [4] _(@forth.eval_line(%(clear -9 to_c 0.5 ** 4 + abs))).must_equal [5] end it 'min max' do _(@forth.eval_line(%(4 1 min))).must_equal [1] _(@forth.eval_line(%(4 max))).must_equal [4] end it 'negate invert' do _(@forth.eval_line(%(1 negate))).must_equal [-1] _(@forth.eval_line(%(negate))).must_equal [1] _(@forth.eval_line(%(4 invert))).must_equal [1, -5] end it 'negative?' do _(@forth.eval_line(%(clear -1 negative?))).must_equal [true] _(@forth.eval_line(%(clear 0 negative?))).must_equal [false] _(@forth.eval_line(%(clear 1 negative?))).must_equal [false] _(@forth.eval_line(%(clear -1.0 negative?))).must_equal [true] _(@forth.eval_line(%(clear 0.0 negative?))).must_equal [false] _(@forth.eval_line(%(clear 1.0 negative?))).must_equal [false] _(@forth.eval_line(%(clear -0.0 negative?))).must_equal [false] end end describe 'comparison operations' do it 'should compare values correctly' do _(@forth.eval_line(%(5 3 < 5 3 > 5 5 ==))).must_equal [false, true, true] end end describe 'dup' do it 'should duplicate the top value of the stack' do _(@forth.eval_line(%(5 dup))).must_equal [5, 5] end end describe 'drop' do it 'should remove the top value from the stack' do _(@forth.eval_line(%(5 drop))).must_equal [] end end describe 'swap' do it '( x1 x2 -- x2 x1 ) should swap the top two values of the stack' do _(@forth.eval_line(%(5 10 swap))).must_equal [10, 5] end end describe 'over' do it 'should copy the second item on the stack to the top' do _(@forth.eval_line(%(5 10 over))).must_equal [5, 10, 5] end end describe 'rot' do it 'should rotate the top three values on the stack' do _(@forth.eval_line(%(5 10 15 rot))).must_equal [10, 15, 5] end end describe '-rot' do it 'should reverse rotate the top three values on the stack' do _(@forth.eval_line(%(5 10 15 -rot))).must_equal [15, 5, 10] end end describe 'nip' do it '( x1 x2 -- x2 ) should drop the second item on the stack' do _(@forth.eval_line(%(5 10 nip))).must_equal [10] end end describe 'tuck' do it 'should insert a copy of the top item below the second item on the stack' do _(@forth.eval_line(%(5 10 tuck))).must_equal [10, 5, 10] end end describe 'constant' do it 'define constant' do _(@forth.eval_line(%(42 constant C1))).must_equal [] _(@forth.eval_line(%(12 C1))).must_equal [12, 42] actual_output = capture_stdout do _(@forth.eval_line(%(34 constant C1))).must_equal [12, 42, 34] end _(actual_output).must_equal "duplicate define C1\n" end end describe 'variable' do it 'define variable' do _(@forth.eval_line(%(variable v1))).must_equal [] _(@forth.eval_line(%(12 v1 !))).must_equal [] _(@forth.eval_line(%(v1 @ ))).must_equal [12] end end describe 'if-then-else' do it 'should execute if-then blocks correctly' do _(@forth.eval_line(%(: xxx if 2 + then ;))).must_equal [] _(@forth.eval_line(%(0 5 3 > xxx))).must_equal [2] _(@forth.eval_line(%(5 3 < xxx))).must_equal [2] end it 'should execute if-else-then blocks correctly' do _(@forth.eval_line(%(: ttt 5 3 > if 2 2 + else 3 3 + then ;))).must_equal [] _(@forth.eval_line(%(ttt))).must_equal [4] _(@forth.eval_line(%[clear : sss ( n -- ) if ." true" else ." false" then ;])).must_equal [] _(@forth.eval_line(%(1 2 < sss))).must_equal ['true'] end it 'should execute if-else-endif blocks correctly' do _(@forth.eval_line(%(: ttt 5 3 > if 2 2 + else 3 3 + endif ;))).must_equal [] _(@forth.eval_line(%(ttt))).must_equal [4] _(@forth.eval_line(%[clear : sss ( n -- ) if ." true" else ." false" endif ;])).must_equal [] _(@forth.eval_line(%(1 2 < sss))).must_equal ['true'] end it 'should execute if-then-else nested blocks correctly' do _(@forth.eval_line(%(: c dup dup * * ; 3 c))).must_equal [27] _(@forth.eval_line(%(clear))).must_equal [] _(@forth.eval_line(%(: sign dup 0 < if -1 else dup 0 > if 1 else 0 then then ;))).must_equal [] _(@forth.eval_line(%(123 sign))).must_equal [123, 1] _(@forth.eval_line(%(clear -123 sign))).must_equal [-123, -1] _(@forth.eval_line(%(clear 0.0 sign))).must_equal [0.0, 0] _(@forth.eval_line(%(clear 0 sign))).must_equal [0.0, 0] end end describe 'case _ of ... endof _ of ... endof default endcase' do it 'should execute case-endcase correctly' do _(@forth.eval_line(%(: t case 1 of 111 endof 2 of 222 endof 3 of 333 endof 999 endcase ;))).must_equal [] _(@forth.eval_line(%(clear 2 t ))).must_equal [222] _(@forth.eval_line(%(clear 1 t ))).must_equal [111] _(@forth.eval_line(%(clear 3 t ))).must_equal [333] _(@forth.eval_line(%(clear 0 t ))).must_equal [999] _(@forth.eval_line(%(clear 2.0 t ))).must_equal [222] _(@forth.eval_line(%(clear 1.0 t ))).must_equal [111] _(@forth.eval_line(%(clear 3.0 t ))).must_equal [333] _(@forth.eval_line(%(clear 0.0 t ))).must_equal [999] end end describe 'do loop' do it 'should execute do loops correctly' do _(@forth.eval_line(%(: d3 3 1 do I loop ; d3))).must_equal [1, 2] end it 'should execute do +loops correctly' do _(@forth.eval_line(%(: d15 15 1 do I 5 +loop ;))).must_equal [] end it 'should execute do loops nested correctly' do _(@forth.eval_line(%(: d34 3 1 do 4 1 do I J loop loop ; d34))).must_equal [1, 1, 2, 1, 3, 1, 1, 2, 2, 2, 3, 2] end it 'should execute do loops nested correctly w/ stdout' do actual_output = capture_stdout do @forth.eval_line(%(: d34 3 1 do 4 1 do I . J . ." ," . loop cr loop ; d34)) end _(actual_output).must_equal <<~EOS 1 1 , 2 1 , 3 1 ,#{' '} 1 2 , 2 2 , 3 2 ,#{' '} EOS end it 'should execute do do +loop loop nested correctly' do _(@forth.eval_line(%(: d34x 3 1 do 5 1 do I J 99 2 +loop loop ; d34x ))).must_equal [1, 1, 99, 3, 1, 99, 1, 2, 99, 3, 2, 99] end end end Minitest.run # Forth.new.repl