raheui 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +15 -0
- data/.gitignore +19 -0
- data/.rspec +1 -0
- data/.rubocop.yml +14 -0
- data/.rubocop_todo.yml +24 -0
- data/.travis.yml +17 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +8 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +16 -0
- data/bin/raheui +8 -0
- data/lib/raheui/cli.rb +27 -0
- data/lib/raheui/code.rb +106 -0
- data/lib/raheui/io.rb +60 -0
- data/lib/raheui/option.rb +44 -0
- data/lib/raheui/port.rb +14 -0
- data/lib/raheui/queue.rb +18 -0
- data/lib/raheui/runner.rb +212 -0
- data/lib/raheui/stack.rb +13 -0
- data/lib/raheui/store.rb +41 -0
- data/lib/raheui/version.rb +7 -0
- data/lib/raheui.rb +16 -0
- data/raheui.gemspec +26 -0
- data/spec/raheui/cli_spec.rb +39 -0
- data/spec/raheui/code_spec.rb +119 -0
- data/spec/raheui/io_spec.rb +94 -0
- data/spec/raheui/option_spec.rb +43 -0
- data/spec/raheui/port_spec.rb +6 -0
- data/spec/raheui/queue_spec.rb +148 -0
- data/spec/raheui/runner_spec.rb +516 -0
- data/spec/raheui/stack_spec.rb +145 -0
- data/spec/raheui/store_spec.rb +93 -0
- data/spec/raheui/version_spec.rb +8 -0
- data/spec/spec_helper.rb +75 -0
- data/spec/support/exit_code_matchers.rb +27 -0
- data/spec/support/file_helper.rb +38 -0
- data/spec/support/isolated_environment.rb +13 -0
- data/spec/support/shared_store.rb +47 -0
- metadata +168 -0
@@ -0,0 +1,212 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Raheui
|
3
|
+
# Run Aheui code.
|
4
|
+
class Runner
|
5
|
+
# Point class for execution cursor position and delta of it.
|
6
|
+
Point = Struct.new(:x, :y)
|
7
|
+
|
8
|
+
# Numbers of required elements for each command.
|
9
|
+
REQUIRED_STORE_SIZE = [
|
10
|
+
0, 0, 2, 2, 2, 2, 1, 0, 1, 0, # ㄱ ㄲ ㄴ ㄷ ㄸ ㄹ ㅁ ㅂ ㅃ ㅅ
|
11
|
+
1, 0, 2, 0, 1, 0, 2, 2, 0 # ㅆ ㅇ ㅈ ㅉ ㅊ ㅋ ㅌ ㅍ ㅎ
|
12
|
+
]
|
13
|
+
|
14
|
+
# Delta values of each medial consonant.
|
15
|
+
MEDIAL_DELTAS = [
|
16
|
+
[1, 0], nil, [2, 0], nil, # ㅏ ㅐ ㅑ ㅒ
|
17
|
+
[-1, 0], nil, [-2, 0], nil, # ㅓ ㅔ ㅕ ㅖ
|
18
|
+
[0, -1], nil, nil, nil, [0, -2], # ㅗ ㅘ ㅙ ㅚ ㅛ
|
19
|
+
[0, 1], nil, nil, nil, [0, 2], # ㅜ ㅝ ㅞ ㅟ ㅠ
|
20
|
+
[:+, :-], [:-, :-], [:-, :+] # ㅡ ㅢ ㅣ
|
21
|
+
]
|
22
|
+
|
23
|
+
# Numbers of strokes of each final consonant.
|
24
|
+
FINAL_STROKES = [
|
25
|
+
0, # No final consonant.
|
26
|
+
2, 4, 4, 2, 5, 5, 3, 5, 7, 9, # ㄱ ㄲ ㄳ ㄴ ㄵ ㄶ ㄷ ㄹ ㄺ ㄻ
|
27
|
+
9, 7, 9, 9, 8, 4, 4, 6, 2, 4, # ㄼ ㄽ ㄾ ㄿ ㅀ ㅁ ㅂ ㅄ ㅅ ㅆ
|
28
|
+
1, 3, 4, 3, 4, 4, 3 # ㅇ ㅈ ㅊ ㅋ ㅌ ㅍ ㅎ
|
29
|
+
]
|
30
|
+
|
31
|
+
private_constant :REQUIRED_STORE_SIZE, :MEDIAL_DELTAS, :FINAL_STROKES
|
32
|
+
|
33
|
+
# Initialize a Runner. Get a Code instance and initialize Stores.
|
34
|
+
#
|
35
|
+
# code - The Code instance to execute.
|
36
|
+
def initialize(code)
|
37
|
+
@code = code
|
38
|
+
end
|
39
|
+
|
40
|
+
# Run the Aheui Code.
|
41
|
+
#
|
42
|
+
# Returns the Integer exit code.
|
43
|
+
def run
|
44
|
+
reset
|
45
|
+
step until @finished
|
46
|
+
@selected_store.pop || 0
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
# Reset cursor and Stores. Select first Store.
|
52
|
+
#
|
53
|
+
# Returns nothing.
|
54
|
+
def reset
|
55
|
+
@cursor = Point.new(0, 0)
|
56
|
+
@delta = Point.new(0, 1)
|
57
|
+
@stores = Code::FINAL_CONSONANTS.times.map do |consonant|
|
58
|
+
case consonant
|
59
|
+
when 21 then Queue.new # ㅇ
|
60
|
+
when 27 then Port.new # ㅎ
|
61
|
+
else Stack.new
|
62
|
+
end
|
63
|
+
end
|
64
|
+
@selected_store = @stores[0]
|
65
|
+
@finished = false
|
66
|
+
end
|
67
|
+
|
68
|
+
# Process current character which cursor points to and move cursor.
|
69
|
+
#
|
70
|
+
# Returns nothing.
|
71
|
+
def step
|
72
|
+
consonants = @code[@cursor.x, @cursor.y]
|
73
|
+
unless consonants.empty?
|
74
|
+
initial, medial, final = consonants
|
75
|
+
@delta.x, @delta.y = delta(medial)
|
76
|
+
if @selected_store.size < REQUIRED_STORE_SIZE[initial]
|
77
|
+
@delta.x, @delta.y = -@delta.x, -@delta.y
|
78
|
+
else
|
79
|
+
process(initial, final)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
move
|
83
|
+
end
|
84
|
+
|
85
|
+
# Process a Korean alphabet.
|
86
|
+
#
|
87
|
+
# initial - An Integer index of initial consonant of the Korean alphabet.
|
88
|
+
# final - An Integer index of final consonant of the Korean alphabet.
|
89
|
+
#
|
90
|
+
# Returns nothing.
|
91
|
+
def process(initial, final)
|
92
|
+
case initial
|
93
|
+
when 2 # ㄴ
|
94
|
+
operate(:/)
|
95
|
+
when 3 # ㄷ
|
96
|
+
operate(:+)
|
97
|
+
when 4 # ㄸ
|
98
|
+
operate(:*)
|
99
|
+
when 5 # ㄹ
|
100
|
+
operate(:%)
|
101
|
+
when 6 # ㅁ
|
102
|
+
op = @selected_store.pop
|
103
|
+
if final == 21 # ㅇ
|
104
|
+
IO.print_int(op)
|
105
|
+
elsif final == 27 # ㅎ
|
106
|
+
IO.print_chr(op)
|
107
|
+
end
|
108
|
+
when 7 # ㅂ
|
109
|
+
op = if final == 21 # ㅇ
|
110
|
+
IO.read_int
|
111
|
+
elsif final == 27 # ㅎ
|
112
|
+
IO.read_chr
|
113
|
+
else
|
114
|
+
FINAL_STROKES[final]
|
115
|
+
end
|
116
|
+
@selected_store.push(op)
|
117
|
+
when 8 # ㅃ
|
118
|
+
@selected_store.push_dup
|
119
|
+
when 9 # ㅅ
|
120
|
+
@selected_store = @stores[final]
|
121
|
+
when 10 # ㅆ
|
122
|
+
op = @selected_store.pop
|
123
|
+
@stores[final].push(op)
|
124
|
+
when 12 # ㅈ
|
125
|
+
op1 = @selected_store.pop
|
126
|
+
op2 = @selected_store.pop
|
127
|
+
@selected_store.push(op2 >= op1 ? 1 : 0)
|
128
|
+
when 14 # ㅊ
|
129
|
+
op = @selected_store.pop
|
130
|
+
@delta.x, @delta.y = -@delta.x, -@delta.y if op == 0
|
131
|
+
when 16 # ㅌ
|
132
|
+
operate(:-)
|
133
|
+
when 17 # ㅍ
|
134
|
+
@selected_store.swap
|
135
|
+
when 18 # ㅎ
|
136
|
+
@finished = true
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
# Helper method for basic operators. +, -, *, / and % can be processed.
|
141
|
+
#
|
142
|
+
# method - A Symbol method to execute.
|
143
|
+
#
|
144
|
+
# Examples
|
145
|
+
#
|
146
|
+
# operate(:+)
|
147
|
+
#
|
148
|
+
# operate(:-)
|
149
|
+
#
|
150
|
+
# operate(:*)
|
151
|
+
#
|
152
|
+
# operate(:/)
|
153
|
+
#
|
154
|
+
# operate(:%)
|
155
|
+
#
|
156
|
+
# Returns nothing.
|
157
|
+
def operate(method)
|
158
|
+
op1 = @selected_store.pop
|
159
|
+
op2 = @selected_store.pop
|
160
|
+
@selected_store.push([op2, op1].reduce(method))
|
161
|
+
end
|
162
|
+
|
163
|
+
# Get delta x and y position for next move.
|
164
|
+
#
|
165
|
+
# medial - An Integer index of medial consonant of the Korean alphabet.
|
166
|
+
#
|
167
|
+
# Examples
|
168
|
+
#
|
169
|
+
# delta(0)
|
170
|
+
# # => [1, 0]
|
171
|
+
#
|
172
|
+
# Returns an Array of Integer delta x and y position.
|
173
|
+
def delta(medial)
|
174
|
+
delta = MEDIAL_DELTAS[medial]
|
175
|
+
if delta
|
176
|
+
x, y = delta
|
177
|
+
x = x == :+ ? @delta.x : -@delta.x if x.is_a? Symbol
|
178
|
+
y = y == :+ ? @delta.y : -@delta.y if y.is_a? Symbol
|
179
|
+
[x, y]
|
180
|
+
else
|
181
|
+
[@delta.x, @delta.y]
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
# Move cursor to proper position. Wrap the position if it goes to outside of
|
186
|
+
# the code.
|
187
|
+
#
|
188
|
+
# Returns nothing.
|
189
|
+
def move
|
190
|
+
@cursor.x = wrap(@cursor.x + @delta.x, @code.width)
|
191
|
+
@cursor.y = wrap(@cursor.y + @delta.y, @code.height)
|
192
|
+
end
|
193
|
+
|
194
|
+
# Wrap a number to be between 0 and max value excluding max value. If the
|
195
|
+
# number is negative, it goes to max - 1. If the number is bigger than or
|
196
|
+
# equal to max value, it goes to zero.
|
197
|
+
#
|
198
|
+
# num - An Integer to be wrapped.
|
199
|
+
# max - An Integer max value.
|
200
|
+
#
|
201
|
+
# Returns an Integer between 0 and max - 1.
|
202
|
+
def wrap(num, max)
|
203
|
+
if num < 0
|
204
|
+
max - 1
|
205
|
+
elsif num >= max
|
206
|
+
0
|
207
|
+
else
|
208
|
+
num
|
209
|
+
end
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
data/lib/raheui/stack.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Raheui
|
3
|
+
# Stack class for Aheui.
|
4
|
+
class Stack < Store
|
5
|
+
# Delegates push, pop to @store.
|
6
|
+
delegate [:push, :pop] => :@store
|
7
|
+
|
8
|
+
# Swap the last two elements of Stack.
|
9
|
+
def swap
|
10
|
+
@store[-1], @store[-2] = @store[-2], @store[-1] if size > 1
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/raheui/store.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
module Raheui
|
3
|
+
# Base Store class for Aheui. Every child classes should implement push, pop
|
4
|
+
# and swap method.
|
5
|
+
class Store
|
6
|
+
extend Forwardable
|
7
|
+
|
8
|
+
BASE_METHODS = [:push, :pop, :swap]
|
9
|
+
private_constant :BASE_METHODS
|
10
|
+
|
11
|
+
# Delegates size to @store.
|
12
|
+
def_delegator :@store, :size
|
13
|
+
|
14
|
+
# Initialize a Stack.
|
15
|
+
def initialize
|
16
|
+
check_base_methods
|
17
|
+
@store = []
|
18
|
+
end
|
19
|
+
|
20
|
+
# Push the last element to Store.
|
21
|
+
def push_dup
|
22
|
+
push(@store.last) if size > 0
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
# Check whether base methods are implemented.
|
28
|
+
#
|
29
|
+
# Returns nothing.
|
30
|
+
# Raises NotImplementedError if base methods are not implemented.
|
31
|
+
def check_base_methods
|
32
|
+
errors = []
|
33
|
+
BASE_METHODS.each do |method|
|
34
|
+
errors << method unless respond_to?(method)
|
35
|
+
end
|
36
|
+
return if errors.empty?
|
37
|
+
fail NotImplementedError, 'base methods are not implemented:' \
|
38
|
+
" #{errors.join(', ')}"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/raheui.rb
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'raheui/version'
|
3
|
+
|
4
|
+
require 'forwardable'
|
5
|
+
require 'raheui/store'
|
6
|
+
require 'raheui/stack'
|
7
|
+
require 'raheui/queue'
|
8
|
+
require 'raheui/port'
|
9
|
+
|
10
|
+
require 'raheui/io'
|
11
|
+
require 'raheui/code'
|
12
|
+
require 'raheui/runner'
|
13
|
+
|
14
|
+
require 'optparse'
|
15
|
+
require 'raheui/option'
|
16
|
+
require 'raheui/cli'
|
data/raheui.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'raheui/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = 'raheui'
|
8
|
+
spec.version = Raheui::Version::STRING
|
9
|
+
spec.authors = ['ChaYoung You']
|
10
|
+
spec.email = ['yousbe@gmail.com']
|
11
|
+
spec.summary = 'Aheui interpreter in Ruby.'
|
12
|
+
spec.description = 'Aheui interpreter in Ruby.'
|
13
|
+
spec.homepage = ''
|
14
|
+
spec.license = 'MIT'
|
15
|
+
|
16
|
+
spec.files = `git ls-files -z`.split("\x0")
|
17
|
+
spec.executables = spec.files.grep(/^bin\//) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(/^(test|spec|features)\//)
|
19
|
+
spec.require_paths = ['lib']
|
20
|
+
|
21
|
+
spec.add_development_dependency 'bundler', '~> 1.6'
|
22
|
+
spec.add_development_dependency 'rake', '~> 10.0'
|
23
|
+
spec.add_development_dependency 'rspec', '~> 3.0'
|
24
|
+
spec.add_development_dependency 'rubocop', '~> 0.25.0'
|
25
|
+
spec.add_development_dependency 'simplecov', '~> 0.9'
|
26
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Raheui::CLI do
|
5
|
+
include FileHelper
|
6
|
+
include_context 'isolated environment'
|
7
|
+
|
8
|
+
subject(:cli) { described_class.new }
|
9
|
+
|
10
|
+
before(:example) { $stdout = StringIO.new }
|
11
|
+
after(:example) { $stdout = STDOUT }
|
12
|
+
|
13
|
+
it 'runs aheui file passed as an argument' do
|
14
|
+
create_file('helloworld.aheui', %w(밤밣따빠밣밟따뿌
|
15
|
+
빠맣파빨받밤뚜뭏
|
16
|
+
돋밬탕빠맣붏두붇
|
17
|
+
볻뫃박발뚷투뭏붖
|
18
|
+
뫃도뫃희멓뭏뭏붘
|
19
|
+
뫃봌토범더벌뿌뚜
|
20
|
+
뽑뽀멓멓더벓뻐뚠
|
21
|
+
뽀덩벐멓뻐덕더벅))
|
22
|
+
expect(cli.run(['helloworld.aheui'])).to be(0)
|
23
|
+
expect($stdout.string).to eq("Hello, world!\n")
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'runs string passed as an stdin argument' do
|
27
|
+
allow($stdin).to receive(:read).once
|
28
|
+
.and_return(%w(밤밣따빠밣밟따뿌
|
29
|
+
빠맣파빨받밤뚜뭏
|
30
|
+
돋밬탕빠맣붏두붇
|
31
|
+
볻뫃박발뚷투뭏붖
|
32
|
+
뫃도뫃희멓뭏뭏붘
|
33
|
+
뫃봌토범더벌뿌뚜
|
34
|
+
뽑뽀멓멓더벓뻐뚠
|
35
|
+
뽀덩벐멓뻐덕더벅).join("\n"))
|
36
|
+
expect(cli.run([])).to be(0)
|
37
|
+
expect($stdout.string).to eq("Hello, world!\n")
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Raheui::Code do
|
5
|
+
shared_examples 'a code' do
|
6
|
+
subject(:code) { Raheui::Code.new(code_str) }
|
7
|
+
let(:consonants) { code.method(:consonants) }
|
8
|
+
|
9
|
+
it 'has width which is maximum line length of code with minimum value 1' do
|
10
|
+
width = code_str.lines.map { |line| line.chomp.size }.max || 1
|
11
|
+
expect(code.width).to be(width)
|
12
|
+
end
|
13
|
+
|
14
|
+
it 'has height which is number of lines with minimum value 1' do
|
15
|
+
height = [code_str.lines.count, 1].max
|
16
|
+
expect(code.height).to be(height)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#[]' do
|
20
|
+
it 'returns item at given position' do
|
21
|
+
code_str.lines.each_with_index do |line, y|
|
22
|
+
line.chomp.chars.each_with_index do |ch, x|
|
23
|
+
expect(code[x, y]).to match_array(consonants.call(ch))
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'returns item at given negative position' do
|
29
|
+
code_str.lines.each_with_index do |line, y|
|
30
|
+
max_x = line.chomp.chars.count
|
31
|
+
line.chomp.chars.each_with_index do |ch, x|
|
32
|
+
expected = consonants.call(ch)
|
33
|
+
expect(code[x - max_x, y]).to match_array(expected)
|
34
|
+
expect(code[x, y - code.height]).to match_array(expected)
|
35
|
+
expect(code[x - max_x, y - code.height]).to match_array(expected)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
it 'returns an empty array if there is no item at given position' do
|
41
|
+
expect(code[0, code.height]).to match_array([])
|
42
|
+
expect(code[code.width, 0]).to match_array([])
|
43
|
+
expect(code[code.width, code.height]).to match_array([])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe Raheui::Code, 'with empty code' do
|
49
|
+
let(:code_str) { '' }
|
50
|
+
it_behaves_like 'a code'
|
51
|
+
end
|
52
|
+
|
53
|
+
describe Raheui::Code, 'with aheui code' do
|
54
|
+
let(:code_str) { '아희' }
|
55
|
+
it_behaves_like 'a code'
|
56
|
+
end
|
57
|
+
|
58
|
+
describe Raheui::Code, 'with hello world code' do
|
59
|
+
let(:code_str) do
|
60
|
+
<<-CODE
|
61
|
+
밤밣따빠밣밟따뿌
|
62
|
+
빠맣파빨받밤뚜뭏
|
63
|
+
돋밬탕빠맣붏두붇
|
64
|
+
볻뫃박발뚷투뭏붖
|
65
|
+
뫃도뫃희멓뭏뭏붘
|
66
|
+
뫃봌토범더벌뿌뚜
|
67
|
+
뽑뽀멓멓더벓뻐뚠
|
68
|
+
뽀덩벐멓뻐덕더벅
|
69
|
+
CODE
|
70
|
+
end
|
71
|
+
|
72
|
+
it_behaves_like 'a code'
|
73
|
+
end
|
74
|
+
|
75
|
+
describe '#consonants' do
|
76
|
+
subject { Raheui::Code.new('') }
|
77
|
+
let(:consonants) { subject.method(:consonants) }
|
78
|
+
before(:example) do
|
79
|
+
stub_const('INITIAL_CONSONANTS', Raheui::Code::INITIAL_CONSONANTS)
|
80
|
+
stub_const('MEDIAL_CONSONANTS', Raheui::Code::MEDIAL_CONSONANTS)
|
81
|
+
stub_const('FINAL_CONSONANTS', Raheui::Code::FINAL_CONSONANTS)
|
82
|
+
end
|
83
|
+
|
84
|
+
it 'returns consonants of Korean alphabet' do
|
85
|
+
examples = [
|
86
|
+
# 가
|
87
|
+
[0, 0, 0],
|
88
|
+
# 힣
|
89
|
+
[INITIAL_CONSONANTS - 1, MEDIAL_CONSONANTS - 1, FINAL_CONSONANTS - 1]
|
90
|
+
]
|
91
|
+
examples.concat(10.times.map do
|
92
|
+
[rand(INITIAL_CONSONANTS),
|
93
|
+
rand(MEDIAL_CONSONANTS),
|
94
|
+
rand(FINAL_CONSONANTS)]
|
95
|
+
end.uniq)
|
96
|
+
examples.each do |initial, medial, final|
|
97
|
+
alphabet = (
|
98
|
+
0xAC00 +
|
99
|
+
initial * MEDIAL_CONSONANTS * FINAL_CONSONANTS +
|
100
|
+
medial * FINAL_CONSONANTS +
|
101
|
+
final
|
102
|
+
).chr(Encoding::UTF_8)
|
103
|
+
expect(consonants.call(alphabet)).to match_array(
|
104
|
+
[initial, medial, final])
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'returns an empty array for non-Korean alphabet' do
|
109
|
+
examples = [
|
110
|
+
*0..127,
|
111
|
+
0xAC00 + INITIAL_CONSONANTS * MEDIAL_CONSONANTS * FINAL_CONSONANTS
|
112
|
+
]
|
113
|
+
examples.concat(%w(あ 漢 å ★).map(&:ord))
|
114
|
+
examples.each do |ch|
|
115
|
+
expect(consonants.call(ch)).to match_array([])
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Raheui::IO do
|
5
|
+
describe '.read_int' do
|
6
|
+
let(:value) { rand(-100..100) }
|
7
|
+
|
8
|
+
it { is_expected.to respond_to(:read_int) }
|
9
|
+
|
10
|
+
it 'reads an integer' do
|
11
|
+
allow($stdin).to receive(:gets).with(no_args).once
|
12
|
+
.and_return("#{value}\n")
|
13
|
+
expect(subject.read_int).to be(value)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '.read_chr' do
|
18
|
+
it { is_expected.to respond_to(:read_chr) }
|
19
|
+
|
20
|
+
it 'reads an ASCII code' do
|
21
|
+
[0, *32..126].each do |value|
|
22
|
+
allow($stdin).to receive(:getc).with(no_args).once
|
23
|
+
.and_return(value.chr)
|
24
|
+
expect(subject.read_chr).to be(value)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'reads an control character' do
|
29
|
+
[*1..31, *127..159].each do |value|
|
30
|
+
allow($stdin).to receive(:getc).with(no_args).once
|
31
|
+
.and_return(value.chr)
|
32
|
+
expect(subject.read_chr).to be(value)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'reads an unicode character' do
|
37
|
+
[*10.times.map { rand(160..0xD7FF).chr(Encoding::UTF_8) }.uniq,
|
38
|
+
# Range 0xD800..0xDFFF is used by UTF-16.
|
39
|
+
*10.times.map { rand(0xE000..0x10FFFF).chr(Encoding::UTF_8) }.uniq,
|
40
|
+
'가', 'あ', '漢', ' ', 'å', '★'].each do |chr|
|
41
|
+
allow($stdin).to receive(:getc).with(no_args).once
|
42
|
+
.and_return(chr)
|
43
|
+
expect(subject.read_chr).to be(chr.ord)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe '.print_int' do
|
49
|
+
let(:value) { rand(-100..100) }
|
50
|
+
|
51
|
+
it { is_expected.to respond_to(:print_int) }
|
52
|
+
|
53
|
+
it 'prints an integer' do
|
54
|
+
allow($stdout).to receive(:print).with(value).once
|
55
|
+
expect(subject.print_int(value)).to be_nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '.print_chr' do
|
60
|
+
it { is_expected.to respond_to(:print_chr) }
|
61
|
+
|
62
|
+
it 'prints an ASCII character corresponding character code' do
|
63
|
+
[0, *32..126].each do |value|
|
64
|
+
allow($stdout).to receive(:print).with(value.chr).once
|
65
|
+
expect(subject.print_chr(value)).to be_nil
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
it 'prints an control character corresponding character code' do
|
70
|
+
[*1..31, *127..159].each do |value|
|
71
|
+
allow($stdout).to receive(:print).with(value.chr(Encoding::UTF_8)).once
|
72
|
+
expect(subject.print_chr(value)).to be_nil
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
it 'prints an unicode character corresponding character code' do
|
77
|
+
[*10.times.map { rand(160..0xD7FF).chr(Encoding::UTF_8) }.uniq,
|
78
|
+
# Range 0xD800..0xDFFF is used by UTF-16.
|
79
|
+
*10.times.map { rand(0xE000..0x10FFFF).chr(Encoding::UTF_8) }.uniq,
|
80
|
+
'가', 'あ', '漢', ' ', 'å', '★'].each do |chr|
|
81
|
+
allow($stdout).to receive(:print).with(chr).once
|
82
|
+
expect(subject.print_chr(chr.ord)).to be_nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it 'prints an [U+%04X] string when RangeError is raised' do
|
87
|
+
[*10.times.map { rand(0xD800..0xDFFF) }.uniq,
|
88
|
+
0x110000].each do |value|
|
89
|
+
allow($stdout).to receive(:print).with(format('[U+%04X]', value)).once
|
90
|
+
expect(subject.print_chr(value)).to be_nil
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
require 'spec_helper'
|
3
|
+
|
4
|
+
describe Raheui::Option do
|
5
|
+
include ExitCodeMatchers
|
6
|
+
|
7
|
+
subject(:option) { described_class.new }
|
8
|
+
|
9
|
+
before(:example) { $stdout = StringIO.new }
|
10
|
+
after(:example) { $stdout = STDOUT }
|
11
|
+
|
12
|
+
describe 'option' do
|
13
|
+
describe '-h/--help' do
|
14
|
+
it 'exits cleanly' do
|
15
|
+
expect { option.parse(['-h']) }.to exit_with_code(0)
|
16
|
+
expect { option.parse(['--help']) }.to exit_with_code(0)
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'shows help text' do
|
20
|
+
begin
|
21
|
+
option.parse(['--help'])
|
22
|
+
rescue SystemExit # rubocop:disable Lint/HandleExceptions
|
23
|
+
end
|
24
|
+
|
25
|
+
expected_help = <<-END
|
26
|
+
Usage: raheui [options] [file]
|
27
|
+
-h, --help Print this message.
|
28
|
+
-v, --version Print version.
|
29
|
+
END
|
30
|
+
|
31
|
+
expect($stdout.string).to eq(expected_help)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
describe '-v/--version' do
|
36
|
+
it 'exits cleanly' do
|
37
|
+
expect { option.parse(['-v']) }.to exit_with_code(0)
|
38
|
+
expect { option.parse(['--version']) }.to exit_with_code(0)
|
39
|
+
expect($stdout.string).to eq("#{Raheui::Version::STRING}\n" * 2)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|