teek 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +21 -0
  3. data/Rakefile +121 -22
  4. data/ext/teek/extconf.rb +19 -1
  5. data/ext/teek/tcltkbridge.c +44 -2
  6. data/ext/teek/tcltkbridge.h +3 -0
  7. data/ext/teek/tkdrop.c +66 -0
  8. data/ext/teek/tkdrop.h +26 -0
  9. data/ext/teek/tkdrop_macos.m +141 -0
  10. data/ext/teek/tkdrop_win.c +232 -0
  11. data/ext/teek/tkdrop_x11.c +337 -0
  12. data/ext/teek/tkwin.c +42 -0
  13. data/lib/teek/platform.rb +29 -0
  14. data/lib/teek/version.rb +1 -1
  15. data/lib/teek.rb +248 -7
  16. data/teek.gemspec +3 -2
  17. metadata +7 -45
  18. data/sample/calculator.rb +0 -255
  19. data/sample/debug_demo.rb +0 -43
  20. data/sample/goldberg.rb +0 -1803
  21. data/sample/goldberg_helpers.rb +0 -170
  22. data/sample/minesweeper/assets/MINESWEEPER_0.png +0 -0
  23. data/sample/minesweeper/assets/MINESWEEPER_1.png +0 -0
  24. data/sample/minesweeper/assets/MINESWEEPER_2.png +0 -0
  25. data/sample/minesweeper/assets/MINESWEEPER_3.png +0 -0
  26. data/sample/minesweeper/assets/MINESWEEPER_4.png +0 -0
  27. data/sample/minesweeper/assets/MINESWEEPER_5.png +0 -0
  28. data/sample/minesweeper/assets/MINESWEEPER_6.png +0 -0
  29. data/sample/minesweeper/assets/MINESWEEPER_7.png +0 -0
  30. data/sample/minesweeper/assets/MINESWEEPER_8.png +0 -0
  31. data/sample/minesweeper/assets/MINESWEEPER_F.png +0 -0
  32. data/sample/minesweeper/assets/MINESWEEPER_M.png +0 -0
  33. data/sample/minesweeper/assets/MINESWEEPER_X.png +0 -0
  34. data/sample/minesweeper/minesweeper.rb +0 -452
  35. data/sample/optcarrot/vendor/optcarrot/apu.rb +0 -856
  36. data/sample/optcarrot/vendor/optcarrot/config.rb +0 -257
  37. data/sample/optcarrot/vendor/optcarrot/cpu.rb +0 -1162
  38. data/sample/optcarrot/vendor/optcarrot/driver.rb +0 -144
  39. data/sample/optcarrot/vendor/optcarrot/mapper/cnrom.rb +0 -14
  40. data/sample/optcarrot/vendor/optcarrot/mapper/mmc1.rb +0 -105
  41. data/sample/optcarrot/vendor/optcarrot/mapper/mmc3.rb +0 -153
  42. data/sample/optcarrot/vendor/optcarrot/mapper/uxrom.rb +0 -14
  43. data/sample/optcarrot/vendor/optcarrot/nes.rb +0 -105
  44. data/sample/optcarrot/vendor/optcarrot/opt.rb +0 -168
  45. data/sample/optcarrot/vendor/optcarrot/pad.rb +0 -92
  46. data/sample/optcarrot/vendor/optcarrot/palette.rb +0 -65
  47. data/sample/optcarrot/vendor/optcarrot/ppu.rb +0 -1468
  48. data/sample/optcarrot/vendor/optcarrot/rom.rb +0 -143
  49. data/sample/optcarrot/vendor/optcarrot.rb +0 -14
  50. data/sample/optcarrot.rb +0 -354
  51. data/sample/paint/assets/bucket.png +0 -0
  52. data/sample/paint/assets/cursor.png +0 -0
  53. data/sample/paint/assets/eraser.png +0 -0
  54. data/sample/paint/assets/pencil.png +0 -0
  55. data/sample/paint/assets/spray.png +0 -0
  56. data/sample/paint/layer.rb +0 -255
  57. data/sample/paint/layer_manager.rb +0 -179
  58. data/sample/paint/paint_demo.rb +0 -837
  59. data/sample/paint/sparse_pixel_buffer.rb +0 -202
  60. data/sample/sdl2_demo.rb +0 -318
  61. data/sample/threading_demo.rb +0 -494
@@ -1,144 +0,0 @@
1
- module Optcarrot
2
- # A manager class for drivers (user frontend)
3
- module Driver
4
- DRIVER_DB = {
5
- video: { none: :Video },
6
- audio: { none: :Audio },
7
- input: { none: :Input },
8
- }
9
-
10
- module_function
11
-
12
- def load(conf)
13
- video = load_each(conf, :video, conf.video).new(conf)
14
- audio = load_each(conf, :audio, conf.audio).new(conf)
15
- input = load_each(conf, :input, conf.input).new(conf, video)
16
- return video, audio, input
17
- end
18
-
19
- def load_each(conf, type, name)
20
- if name
21
- klass = DRIVER_DB[type][name]
22
- raise "unknown #{ type } driver: #{ name }" unless klass
23
- require_relative "driver/#{ name }_#{ type }" unless name == :none
24
- conf.debug("`#{ name }' #{ type } driver is selected")
25
- Optcarrot.const_get(klass)
26
- else
27
- selected = nil
28
- DRIVER_DB[type].each_key do |n|
29
- begin
30
- selected = load_each(conf, type, n)
31
- break
32
- rescue LoadError
33
- conf.debug("fail to use `#{ n }' #{ type } driver")
34
- end
35
- end
36
- selected
37
- end
38
- end
39
- end
40
-
41
- # A base class of video output driver
42
- class Video
43
- WIDTH = 256
44
- TV_WIDTH = 292
45
- HEIGHT = 224
46
-
47
- def initialize(conf)
48
- @conf = conf
49
- @palette_rgb = @conf.nestopia_palette ? Palette.nestopia_palette : Palette.defacto_palette
50
- @palette = [*0..4096] # dummy palette
51
- init
52
- end
53
-
54
- attr_reader :palette
55
-
56
- def init
57
- @times = []
58
- end
59
-
60
- def dispose
61
- end
62
-
63
- def tick(_output)
64
- @times << Process.clock_gettime(Process::CLOCK_MONOTONIC)
65
- @times.shift if @times.size > 10
66
- @times.size < 2 ? 0 : ((@times.last - @times.first) / (@times.size - 1)) ** -1
67
- end
68
-
69
- def change_window_size(_scale)
70
- end
71
-
72
- def on_resize(_width, _height)
73
- end
74
- end
75
-
76
- # A base class of audio output driver
77
- class Audio
78
- PACK_FORMAT = { 8 => "c*", 16 => "v*" }
79
- BUFFER_IN_FRAME = 3 # keep audio buffer during this number of frames
80
-
81
- def initialize(conf)
82
- @conf = conf
83
- @rate = conf.audio_sample_rate
84
- @bits = conf.audio_bit_depth
85
- raise "sample bits must be 8 or 16" unless @bits == 8 || @bits == 16
86
- @pack_format = PACK_FORMAT[@bits]
87
-
88
- init
89
- end
90
-
91
- def spec
92
- return @rate, @bits
93
- end
94
-
95
- def init
96
- end
97
-
98
- def dispose
99
- end
100
-
101
- def tick(_output)
102
- end
103
- end
104
-
105
- # A base class of input driver
106
- class Input
107
- def initialize(conf, video)
108
- @conf = conf
109
- @video = video
110
- init
111
- end
112
-
113
- def init
114
- end
115
-
116
- def dispose
117
- end
118
-
119
- def tick(_frame, _pads)
120
- end
121
-
122
- def event(pads, type, code, player)
123
- case code
124
- when :start then pads.send(type, player, Pad::START)
125
- when :select then pads.send(type, player, Pad::SELECT)
126
- when :a then pads.send(type, player, Pad::A)
127
- when :b then pads.send(type, player, Pad::B)
128
- when :right then pads.send(type, player, Pad::RIGHT)
129
- when :left then pads.send(type, player, Pad::LEFT)
130
- when :down then pads.send(type, player, Pad::DOWN)
131
- when :up then pads.send(type, player, Pad::UP)
132
- else
133
- return if type != :keydown
134
- case code
135
- when :screen_x1 then @video.change_window_size(1)
136
- when :screen_x2 then @video.change_window_size(2)
137
- when :screen_x3 then @video.change_window_size(3)
138
- when :screen_full then @video.change_window_size(nil)
139
- when :quit then exit
140
- end
141
- end
142
- end
143
- end
144
- end
@@ -1,14 +0,0 @@
1
- module Optcarrot
2
- # CNROM mapper: http://wiki.nesdev.com/w/index.php/CNROM
3
- class CNROM < ROM
4
- MAPPER_DB[0x03] = self
5
-
6
- def reset
7
- @cpu.add_mappings(0x8000..0xffff, @prg_ref, @chr_ram ? nil : method(:poke_8000))
8
- end
9
-
10
- def poke_8000(_addr, data)
11
- @chr_ref.replace(@chr_banks[data & 3])
12
- end
13
- end
14
- end
@@ -1,105 +0,0 @@
1
- module Optcarrot
2
- # MMC1 mapper: http://wiki.nesdev.com/w/index.php/MMC1
3
- class MMC1 < ROM
4
- MAPPER_DB[0x01] = self
5
-
6
- NMT_MODE = [:first, :second, :vertical, :horizontal]
7
- PRG_MODE = [:conseq, :conseq, :fix_first, :fix_last]
8
- CHR_MODE = [:conseq, :noconseq]
9
-
10
- def init
11
- @nmt_mode = @prg_mode = @chr_mode = nil
12
- @prg_bank = @chr_bank_0 = @chr_bank_1 = 0
13
- end
14
-
15
- def reset
16
- @shift = @shift_count = 0
17
-
18
- @chr_banks = @chr_banks.flatten.each_slice(0x1000).to_a
19
-
20
- @wrk_readable = @wrk_writable = true
21
- @cpu.add_mappings(0x6000..0x7fff, method(:peek_6000), method(:poke_6000))
22
- @cpu.add_mappings(0x8000..0xffff, @prg_ref, method(:poke_prg))
23
-
24
- update_nmt(:horizontal)
25
- update_prg(:fix_last, 0, 0)
26
- update_chr(:conseq, 0, 0)
27
- end
28
-
29
- def poke_prg(addr, val)
30
- if val[7] == 1
31
- @shift = @shift_count = 0
32
- else
33
- @shift |= val[0] << @shift_count
34
- @shift_count += 1
35
- if @shift_count == 0x05
36
- case (addr >> 13) & 0x3
37
- when 0 # control
38
- nmt_mode = NMT_MODE[@shift & 3]
39
- prg_mode = PRG_MODE[@shift >> 2 & 3]
40
- chr_mode = CHR_MODE[@shift >> 4 & 1]
41
- update_nmt(nmt_mode)
42
- update_prg(prg_mode, @prg_bank, @chr_bank_0)
43
- update_chr(chr_mode, @chr_bank_0, @chr_bank_1)
44
- when 1 # change chr_bank_0
45
- # update_prg might modify @chr_bank_0 and prevent updating chr bank,
46
- # so keep current value.
47
- bak_chr_bank_0 = @chr_bank_0
48
- update_prg(@prg_mode, @prg_bank, @shift)
49
- @chr_bank_0 = bak_chr_bank_0
50
- update_chr(@chr_mode, @shift, @chr_bank_1)
51
- when 2 # change chr_bank_1
52
- update_chr(@chr_mode, @chr_bank_0, @shift)
53
- when 3 # change png_bank
54
- update_prg(@prg_mode, @shift, @chr_bank_0)
55
- end
56
- @shift = @shift_count = 0
57
- end
58
- end
59
- end
60
-
61
- def update_nmt(nmt_mode)
62
- return if @nmt_mode == nmt_mode
63
- @nmt_mode = nmt_mode
64
- @ppu.nametables = @nmt_mode
65
- end
66
-
67
- def update_prg(prg_mode, prg_bank, chr_bank_0)
68
- return if prg_mode == @prg_mode && prg_bank == @prg_bank && chr_bank_0 == @chr_bank_0
69
- @prg_mode, @prg_bank, @chr_bank_0 = prg_mode, prg_bank, chr_bank_0
70
-
71
- high_bit = chr_bank_0 & (0x10 & (@prg_banks.size - 1))
72
- prg_bank_ex = ((@prg_bank & 0x0f) | high_bit) & (@prg_banks.size - 1)
73
- case @prg_mode
74
- when :conseq
75
- lower = prg_bank_ex & ~1
76
- upper = lower + 1
77
- when :fix_first
78
- lower = 0
79
- upper = prg_bank_ex
80
- when :fix_last
81
- lower = prg_bank_ex
82
- upper = ((@prg_banks.size - 1) & 0x0f) | high_bit
83
- end
84
- @prg_ref[0x8000, 0x4000] = @prg_banks[lower]
85
- @prg_ref[0xc000, 0x4000] = @prg_banks[upper]
86
- end
87
-
88
- def update_chr(chr_mode, chr_bank_0, chr_bank_1)
89
- return if chr_mode == @chr_mode && chr_bank_0 == @chr_bank_0 && chr_bank_1 == @chr_bank_1
90
- @chr_mode, @chr_bank_0, @chr_bank_1 = chr_mode, chr_bank_0, chr_bank_1
91
- return if @chr_ram
92
-
93
- @ppu.update(0)
94
- if @chr_mode == :conseq
95
- lower = @chr_bank_0 & 0x1e
96
- upper = lower + 1
97
- else
98
- lower = @chr_bank_0
99
- upper = @chr_bank_1
100
- end
101
- @chr_ref[0x0000, 0x1000] = @chr_banks[lower]
102
- @chr_ref[0x1000, 0x1000] = @chr_banks[upper]
103
- end
104
- end
105
- end
@@ -1,153 +0,0 @@
1
- module Optcarrot
2
- # MMC3 mapper: http://wiki.nesdev.com/w/index.php/MMC3
3
- class MMC3 < ROM
4
- MAPPER_DB[0x04] = self
5
-
6
- def init(rev = :B) # rev = :A or :B or :C
7
- @persistant = rev != :A
8
-
9
- @prg_banks = @prg_banks.flatten.each_slice(0x2000).to_a
10
- @prg_bank_swap = false
11
-
12
- @chr_banks = @chr_banks.flatten.each_slice(0x0400).to_a
13
- @chr_bank_mapping = [nil] * 8
14
- @chr_bank_swap = false
15
- end
16
-
17
- def reset
18
- @wrk_readable = true
19
- @wrk_writable = false
20
-
21
- poke_a000 = @mirroring != :FourScreen ? method(:poke_a000) : nil
22
- @cpu.add_mappings(0x6000..0x7fff, method(:peek_6000), method(:poke_6000))
23
- @cpu.add_mappings(0x8000.step(0x9fff, 2), @prg_ref, method(:poke_8000))
24
- @cpu.add_mappings(0x8001.step(0x9fff, 2), @prg_ref, method(:poke_8001))
25
- @cpu.add_mappings(0xa000.step(0xbfff, 2), @prg_ref, poke_a000)
26
- @cpu.add_mappings(0xa001.step(0xbfff, 2), @prg_ref, method(:poke_a001))
27
- @cpu.add_mappings(0xc000.step(0xdfff, 2), @prg_ref, method(:poke_c000))
28
- @cpu.add_mappings(0xc001.step(0xdfff, 2), @prg_ref, method(:poke_c001))
29
- @cpu.add_mappings(0xe000.step(0xffff, 2), @prg_ref, method(:poke_e000))
30
- @cpu.add_mappings(0xe001.step(0xffff, 2), @prg_ref, method(:poke_e001))
31
-
32
- update_prg(0x8000, 0)
33
- update_prg(0xa000, 1)
34
- update_prg(0xc000, -2)
35
- update_prg(0xe000, -1)
36
- 8.times {|i| update_chr(i * 0x400, i) }
37
-
38
- @clock = 0
39
- @hold = PPU::RP2C02_CC * 16
40
- @ppu.monitor_a12_rising_edge(self)
41
- @cpu.ppu_sync = true
42
-
43
- @count = 0
44
- @latch = 0
45
- @reload = false
46
- @enabled = false
47
- end
48
-
49
- # prg_bank_swap = F T
50
- # 0x8000..0x9fff: 0 2
51
- # 0xa000..0xbfff: 1 1
52
- # 0xc000..0xdfff: 2 0
53
- # 0xe000..0xffff: 3 3
54
- def update_prg(addr, bank)
55
- bank %= @prg_banks.size
56
- addr ^= 0x4000 if @prg_bank_swap && addr[13] == 0
57
- @prg_ref[addr, 0x2000] = @prg_banks[bank]
58
- end
59
-
60
- def update_chr(addr, bank)
61
- return if @chr_ram
62
- idx = addr / 0x400
63
- bank %= @chr_banks.size
64
- return if @chr_bank_mapping[idx] == bank
65
- addr ^= 0x1000 if @chr_bank_swap
66
- @ppu.update(0)
67
- @chr_ref[addr, 0x400] = @chr_banks[bank]
68
- @chr_bank_mapping[idx] = bank
69
- end
70
-
71
- def poke_8000(_addr, data)
72
- @reg_select = data & 7
73
- prg_bank_swap = data[6] == 1
74
- chr_bank_swap = data[7] == 1
75
-
76
- if prg_bank_swap != @prg_bank_swap
77
- @prg_bank_swap = prg_bank_swap
78
- @prg_ref[0x8000, 0x2000], @prg_ref[0xc000, 0x2000] = @prg_ref[0xc000, 0x2000], @prg_ref[0x8000, 0x2000]
79
- end
80
-
81
- if chr_bank_swap != @chr_bank_swap
82
- @chr_bank_swap = chr_bank_swap
83
- unless @chr_ram
84
- @ppu.update(0)
85
- @chr_ref.rotate!(0x1000)
86
- @chr_bank_mapping.rotate!(4)
87
- end
88
- end
89
- end
90
-
91
- def poke_8001(_addr, data)
92
- if @reg_select < 6
93
- if @reg_select < 2
94
- update_chr(@reg_select * 0x0800, data & 0xfe)
95
- update_chr(@reg_select * 0x0800 + 0x0400, data | 0x01)
96
- else
97
- update_chr((@reg_select - 2) * 0x0400 + 0x1000, data)
98
- end
99
- else
100
- update_prg((@reg_select - 6) * 0x2000 + 0x8000, data & 0x3f)
101
- end
102
- end
103
-
104
- def poke_a000(_addr, data)
105
- @ppu.nametables = data[0] == 1 ? :horizontal : :vertical
106
- end
107
-
108
- def poke_a001(_addr, data)
109
- @wrk_readable = data[7] == 1
110
- @wrk_writable = data[6] == 0 && @wrk_readable
111
- end
112
-
113
- def poke_c000(_addr, data)
114
- @ppu.update(0)
115
- @latch = data
116
- end
117
-
118
- def poke_c001(_addr, _data)
119
- @ppu.update(0)
120
- @reload = true
121
- end
122
-
123
- def poke_e000(_addr, _data)
124
- @ppu.update(0)
125
- @enabled = false
126
- @cpu.clear_irq(CPU::IRQ_EXT)
127
- end
128
-
129
- def poke_e001(_addr, _data)
130
- @ppu.update(0)
131
- @enabled = true
132
- end
133
-
134
- def vsync
135
- @clock = @clock > @cpu.next_frame_clock ? @clock - @cpu.next_frame_clock : 0
136
- end
137
-
138
- def a12_signaled(cycle)
139
- clk, @clock = @clock, cycle + @hold
140
- return if cycle < clk
141
- flag = @persistant || @count > 0
142
- if @reload
143
- @reload = false
144
- @count = @latch
145
- elsif @count == 0
146
- @count = @latch
147
- else
148
- @count -= 1
149
- end
150
- @cpu.do_irq(CPU::IRQ_EXT, cycle) if flag && @count == 0 && @enabled
151
- end
152
- end
153
- end
@@ -1,14 +0,0 @@
1
- module Optcarrot
2
- # UxROM mapper: http://wiki.nesdev.com/w/index.php/UxROM
3
- class UxROM < ROM
4
- MAPPER_DB[0x02] = self
5
-
6
- def reset
7
- @cpu.add_mappings(0x8000..0xffff, @prg_ref, method(:poke_8000))
8
- end
9
-
10
- def poke_8000(_addr, data)
11
- @prg_ref[0x8000, 0x4000] = @prg_banks[data & 7]
12
- end
13
- end
14
- end
@@ -1,105 +0,0 @@
1
- module Optcarrot
2
- FOREVER_CLOCK = 0xffffffff
3
- RP2A03_CC = 12
4
-
5
- # NES emulation main
6
- class NES
7
- FPS = 60
8
-
9
- def initialize(conf = ARGV)
10
- @conf = Config.new(conf)
11
-
12
- @video, @audio, @input = Driver.load(@conf)
13
-
14
- @cpu = CPU.new(@conf)
15
- @apu = @cpu.apu = APU.new(@conf, @cpu, *@audio.spec)
16
- @ppu = @cpu.ppu = PPU.new(@conf, @cpu, @video.palette)
17
- @rom = ROM.load(@conf, @cpu, @ppu)
18
- @pads = Pads.new(@conf, @cpu, @apu)
19
-
20
- @frame = 0
21
- @frame_target = @conf.frames == 0 ? nil : @conf.frames
22
- @fps_history = [] if save_fps_history?
23
- end
24
-
25
- def inspect
26
- "#<#{ self.class }>"
27
- end
28
-
29
- attr_reader :fps, :video, :audio, :input, :cpu, :ppu, :apu
30
-
31
- def reset
32
- @cpu.reset
33
- @apu.reset
34
- @ppu.reset
35
- @rom.reset
36
- @pads.reset
37
- @cpu.boot
38
- @rom.load_battery
39
- end
40
-
41
- def step
42
- @ppu.setup_frame
43
- @cpu.run
44
- @ppu.vsync
45
- @apu.vsync
46
- @cpu.vsync
47
- @rom.vsync
48
-
49
- @input.tick(@frame, @pads)
50
- @fps = @video.tick(@ppu.output_pixels)
51
- @fps_history << @fps if save_fps_history?
52
- @audio.tick(@apu.output)
53
-
54
- @frame += 1
55
- @conf.info("frame #{ @frame }") if @conf.loglevel >= 2
56
- end
57
-
58
- def dispose
59
- if @fps
60
- @conf.info("fps: %.2f (in the last 10 frames)" % @fps)
61
- if @conf.print_fps_history
62
- puts "frame,fps-history"
63
- @fps_history.each_with_index {|fps, frame| puts "#{ frame },#{ fps }" }
64
- end
65
- if @conf.print_p95fps
66
- puts "p95 fps: #{ @fps_history.sort[(@fps_history.length * 0.05).floor] }"
67
- end
68
- puts "fps: #{ @fps }" if @conf.print_fps
69
- end
70
- if @conf.print_video_checksum && @video.instance_of?(Video)
71
- puts "checksum: #{ @ppu.output_pixels.pack("C*").sum }"
72
- end
73
- @video.dispose
74
- @audio.dispose
75
- @input.dispose
76
- @rom.save_battery
77
- @ppu.dispose
78
- end
79
-
80
- def run
81
- reset
82
-
83
- if @conf.stackprof_mode
84
- require "stackprof"
85
- out = @conf.stackprof_output.sub("MODE", @conf.stackprof_mode)
86
- StackProf.start(mode: @conf.stackprof_mode.to_sym, out: out, raw: true)
87
- end
88
-
89
- step until @frame == @frame_target
90
-
91
- if @conf.stackprof_mode
92
- StackProf.stop
93
- StackProf.results
94
- end
95
- ensure
96
- dispose
97
- end
98
-
99
- private
100
-
101
- def save_fps_history?
102
- @conf.print_fps_history || @conf.print_p95fps
103
- end
104
- end
105
- end
@@ -1,168 +0,0 @@
1
- module Optcarrot
2
- # dirty methods manipulating and generating methods...
3
- module CodeOptimizationHelper
4
- def initialize(loglevel, enabled_opts)
5
- @loglevel = loglevel
6
- options = self.class::OPTIONS
7
- opts = {}
8
- enabled_opts ||= [:all]
9
- default =
10
- (enabled_opts == [:all] || enabled_opts != [] && enabled_opts.all? {|opt| opt.to_s.start_with?("-") })
11
- options.each {|opt| opts[opt] = default }
12
- (enabled_opts - [:none, :all]).each do |opt|
13
- val = true
14
- if opt.to_s.start_with?("-")
15
- opt = opt.to_s[1..-1].to_sym
16
- val = false
17
- end
18
- raise "unknown optimization: `#{ opt }'" unless options.include?(opt)
19
- opts[opt] = val
20
- end
21
- options.each {|opt| instance_variable_set(:"@#{ opt }", opts[opt]) }
22
- end
23
-
24
- def depends(opt, depended_opt)
25
- if instance_variable_get(:"@#{ opt }") && !instance_variable_get(:"@#{ depended_opt }")
26
- raise "`#{ opt }' depends upon `#{ depended_opt }'"
27
- end
28
- end
29
-
30
- def gen(*codes)
31
- codes.map {|code| code.to_s.chomp }.join("\n") + "\n"
32
- end
33
-
34
- # change indent
35
- def indent(i, code)
36
- if i > 0
37
- code.gsub(/^(.+)$/) { " " * i + $1 }
38
- elsif i < 0
39
- code.gsub(/^ {#{ -i }}/, "")
40
- else
41
- code
42
- end
43
- end
44
-
45
- # generate a branch
46
- def branch(cond, code1, code2)
47
- gen(
48
- "if #{ cond }",
49
- indent(2, code1),
50
- "else",
51
- indent(2, code2),
52
- "end",
53
- )
54
- end
55
-
56
- MethodDef = Struct.new(:params, :body)
57
-
58
- METHOD_DEFINITIONS_RE = /
59
- ^(\ +)def\s+(\w+)(?:\((.*)\))?\n
60
- ^((?:\1\ +.*\n|\n)*)
61
- ^\1end$
62
- /x
63
- # extract all method definitions
64
- def parse_method_definitions(file)
65
- src = File.read(file)
66
- mdefs = {}
67
- src.scan(METHOD_DEFINITIONS_RE) do |indent, meth, params, body|
68
- body = indent(-indent.size - 2, body)
69
-
70
- # noramlize: break `when ... then`
71
- body = body.gsub(/^( *)when +(.*?) +then +(.*)/) { $1 + "when #{ $2 }\n" + $1 + " " + $3 }
72
-
73
- # normalize: return unless
74
- body = "if " + $1 + indent(2, $') + "end\n" if body =~ /\Areturn unless (.*)/
75
-
76
- # normalize: if modifier -> if statement
77
- nil while body.gsub!(/^( *)((?!#)\S.*) ((?:if|unless) .*\n)/) { indent($1.size, gen($3, " " + $2, "end")) }
78
-
79
- mdefs[meth.to_sym] = MethodDef[params ? params.split(", ") : nil, body]
80
- end
81
- mdefs
82
- end
83
-
84
- # inline method calls with no arguments
85
- def expand_methods(code, mdefs, meths = mdefs.keys)
86
- code.gsub(/^( *)\b(#{ meths * "|" })\b(?:\((.*?)\))?\n/) do
87
- indent, meth, args = $1, $2, $3
88
- body = mdefs[meth.to_sym]
89
- body = body.body if body.is_a?(MethodDef)
90
- if args
91
- mdefs[meth.to_sym].params.zip(args.split(", ")) do |param, arg|
92
- body = replace_var(body, param, arg)
93
- end
94
- end
95
- indent(indent.size, body)
96
- end
97
- end
98
-
99
- def expand_inline_methods(code, meth, mdef)
100
- code.gsub(/\b#{ meth }\b(?:\(((?:@?\w+, )*@?\w+)\))?/) do
101
- args = $1
102
- b = "(#{ mdef.body.chomp.gsub(/ *#.*/, "").gsub("\n", "; ") })"
103
- if args
104
- mdef.params.zip(args.split(", ")) do |param, arg|
105
- b = replace_var(b, param, arg)
106
- end
107
- end
108
- b
109
- end
110
- end
111
-
112
- def replace_var(code, var, bool)
113
- re = var.start_with?("@") ? /#{ var }\b/ : /\b#{ var }\b/
114
- code.gsub(re) { bool }
115
- end
116
-
117
- def replace_cond_var(code, var, bool)
118
- code.gsub(/(if|unless)\s#{ var }\b/) { $1 + " " + bool }
119
- end
120
-
121
- TRIVIAL_BRANCH_RE = /
122
- ^(\ *)(if|unless)\ (true|false)\n
123
- ^((?:\1\ +.*\n|\n)*)
124
- (?:
125
- \1else\n
126
- ((?:\1\ +.*\n|\n)*)
127
- )?
128
- ^\1end\n
129
- /x
130
- # remove "if true" or "if false"
131
- def remove_trivial_branches(code)
132
- code = code.dup
133
- nil while
134
- code.gsub!(TRIVIAL_BRANCH_RE) do
135
- if ($2 == "if") == ($3 == "true")
136
- indent(-2, $4)
137
- else
138
- $5 ? indent(-2, $5) : ""
139
- end
140
- end
141
- code
142
- end
143
-
144
- # replace instance variables with temporal local variables
145
- # CAUTION: the instance variable must not be accessed out of CPU#run
146
- def localize_instance_variables(code, ivars = code.scan(/@\w+/).uniq.sort)
147
- ivars = ivars.map {|ivar| ivar.to_s[1..-1] }
148
-
149
- inits, finals = [], []
150
- ivars.each do |ivar|
151
- lvar = "__#{ ivar }__"
152
- inits << "#{ lvar } = @#{ ivar }"
153
- finals << "@#{ ivar } = #{ lvar }"
154
- end
155
-
156
- code = code.gsub(/@(#{ ivars * "|" })\b/) { "__#{ $1 }__" }
157
-
158
- gen(
159
- "begin",
160
- indent(2, inits.join("\n")),
161
- indent(2, code),
162
- "ensure",
163
- indent(2, finals.join("\n")),
164
- "end",
165
- )
166
- end
167
- end
168
- end