looksee 3.0.0-universal-java-1.8
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.
- checksums.yaml +7 -0
- data/CHANGELOG +66 -0
- data/LICENSE +22 -0
- data/README.markdown +175 -0
- data/Rakefile +13 -0
- data/ext/extconf.rb +19 -0
- data/ext/mri/1.9.2/debug.h +36 -0
- data/ext/mri/1.9.2/id.h +170 -0
- data/ext/mri/1.9.2/method.h +103 -0
- data/ext/mri/1.9.2/node.h +483 -0
- data/ext/mri/1.9.2/thread_pthread.h +27 -0
- data/ext/mri/1.9.2/vm_core.h +707 -0
- data/ext/mri/1.9.2/vm_opts.h +51 -0
- data/ext/mri/1.9.3/atomic.h +56 -0
- data/ext/mri/1.9.3/debug.h +41 -0
- data/ext/mri/1.9.3/id.h +175 -0
- data/ext/mri/1.9.3/internal.h +227 -0
- data/ext/mri/1.9.3/internal_falcon.h +248 -0
- data/ext/mri/1.9.3/method.h +105 -0
- data/ext/mri/1.9.3/node.h +503 -0
- data/ext/mri/1.9.3/thread_pthread.h +51 -0
- data/ext/mri/1.9.3/vm_core.h +755 -0
- data/ext/mri/1.9.3/vm_opts.h +51 -0
- data/ext/mri/2.0.0/internal.h +378 -0
- data/ext/mri/2.0.0/method.h +138 -0
- data/ext/mri/2.1.0/internal.h +889 -0
- data/ext/mri/2.1.0/method.h +142 -0
- data/ext/mri/2.2.0/internal.h +1182 -0
- data/ext/mri/2.2.0/method.h +141 -0
- data/ext/mri/env-1.8.h +27 -0
- data/ext/mri/eval_c-1.8.h +27 -0
- data/ext/mri/mri.c +309 -0
- data/ext/mri/node-1.9.h +35 -0
- data/ext/rbx/rbx.c +13 -0
- data/lib/looksee.rb +2 -0
- data/lib/looksee/JRuby.jar +0 -0
- data/lib/looksee/adapter.rb +8 -0
- data/lib/looksee/adapter/base.rb +105 -0
- data/lib/looksee/adapter/rubinius.rb +84 -0
- data/lib/looksee/clean.rb +169 -0
- data/lib/looksee/columnizer.rb +73 -0
- data/lib/looksee/core_ext.rb +48 -0
- data/lib/looksee/editor.rb +64 -0
- data/lib/looksee/help.rb +54 -0
- data/lib/looksee/inspector.rb +70 -0
- data/lib/looksee/lookup_path.rb +95 -0
- data/lib/looksee/rbx.bundle +0 -0
- data/lib/looksee/version.rb +11 -0
- data/spec/looksee/adapter_spec.rb +588 -0
- data/spec/looksee/clean_spec.rb +41 -0
- data/spec/looksee/columnizer_spec.rb +52 -0
- data/spec/looksee/core_ext_spec.rb +17 -0
- data/spec/looksee/editor_spec.rb +107 -0
- data/spec/looksee/inspector_spec.rb +179 -0
- data/spec/looksee/lookup_path_spec.rb +87 -0
- data/spec/spec_helper.rb +29 -0
- data/spec/support/core_ext.rb +25 -0
- data/spec/support/temporary_classes.rb +78 -0
- data/spec/support/test_adapter.rb +83 -0
- metadata +116 -0
data/ext/mri/node-1.9.h
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
/* MRI 1.9 does not install node.h. This is the part we need. */
|
2
|
+
|
3
|
+
typedef struct RNode {
|
4
|
+
unsigned long flags;
|
5
|
+
char *nd_file;
|
6
|
+
union {
|
7
|
+
struct RNode *node;
|
8
|
+
ID id;
|
9
|
+
VALUE value;
|
10
|
+
VALUE (*cfunc)(ANYARGS);
|
11
|
+
ID *tbl;
|
12
|
+
} u1;
|
13
|
+
union {
|
14
|
+
struct RNode *node;
|
15
|
+
ID id;
|
16
|
+
long argc;
|
17
|
+
VALUE value;
|
18
|
+
} u2;
|
19
|
+
union {
|
20
|
+
struct RNode *node;
|
21
|
+
ID id;
|
22
|
+
long state;
|
23
|
+
struct global_entry *entry;
|
24
|
+
long cnt;
|
25
|
+
VALUE value;
|
26
|
+
} u3;
|
27
|
+
} NODE;
|
28
|
+
|
29
|
+
#define nd_body u2.node
|
30
|
+
#define nd_noex u3.id
|
31
|
+
|
32
|
+
#define NOEX_PUBLIC 0x00
|
33
|
+
#define NOEX_PRIVATE 0x02
|
34
|
+
#define NOEX_PROTECTED 0x04
|
35
|
+
#define NOEX_MASK 0x06
|
data/ext/rbx/rbx.c
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
#include "ruby.h"
|
2
|
+
|
3
|
+
VALUE Looksee_internal_class(VALUE self, VALUE object) {
|
4
|
+
return CLASS_OF(object);
|
5
|
+
}
|
6
|
+
|
7
|
+
void Init_rbx(void) {
|
8
|
+
VALUE mLooksee = rb_const_get(rb_cObject, rb_intern("Looksee"));
|
9
|
+
VALUE mAdapter = rb_const_get(mLooksee, rb_intern("Adapter"));
|
10
|
+
VALUE mBase = rb_const_get(mAdapter, rb_intern("Base"));
|
11
|
+
VALUE mRubinius = rb_define_class_under(mAdapter, "Rubinius", mBase);
|
12
|
+
rb_define_method(mRubinius, "internal_class", Looksee_internal_class, 1);
|
13
|
+
}
|
data/lib/looksee.rb
ADDED
Binary file
|
@@ -0,0 +1,105 @@
|
|
1
|
+
module Looksee
|
2
|
+
module Adapter
|
3
|
+
class Base
|
4
|
+
#
|
5
|
+
# Return the chain of classes and modules which comprise the
|
6
|
+
# object's method lookup path.
|
7
|
+
#
|
8
|
+
def lookup_modules(object)
|
9
|
+
modules = []
|
10
|
+
klass = internal_class(object)
|
11
|
+
while klass
|
12
|
+
modules << klass
|
13
|
+
klass = internal_superclass(klass)
|
14
|
+
end
|
15
|
+
modules
|
16
|
+
end
|
17
|
+
|
18
|
+
#
|
19
|
+
# Return a description of the given module.
|
20
|
+
#
|
21
|
+
# This is used for the module labels in the Inspector output.
|
22
|
+
#
|
23
|
+
def describe_module(mod)
|
24
|
+
num_brackets = 0
|
25
|
+
object = mod
|
26
|
+
while singleton_class?(object)
|
27
|
+
num_brackets += 1
|
28
|
+
object = singleton_instance(object)
|
29
|
+
end
|
30
|
+
|
31
|
+
if included_class?(mod) || object.is_a?(Module)
|
32
|
+
description = module_name(object)
|
33
|
+
if description.empty?
|
34
|
+
is_class = real_module(object).is_a?(Class)
|
35
|
+
description = "unnamed #{is_class ? 'Class' : 'Module'}"
|
36
|
+
end
|
37
|
+
else
|
38
|
+
description = "#{module_name(object.class)} instance"
|
39
|
+
end
|
40
|
+
|
41
|
+
if num_brackets == 0
|
42
|
+
description
|
43
|
+
else
|
44
|
+
"#{'['*num_brackets}#{description}#{']'*num_brackets}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def real_module(module_or_included_class)
|
49
|
+
module_or_included_class
|
50
|
+
end
|
51
|
+
|
52
|
+
def internal_superclass(klass)
|
53
|
+
raise NotImplementedError, "abstract"
|
54
|
+
end
|
55
|
+
|
56
|
+
def internal_class(object)
|
57
|
+
raise NotImplementedError, "abstract"
|
58
|
+
end
|
59
|
+
|
60
|
+
def included_class?(object)
|
61
|
+
raise NotImplementedError, "abstract"
|
62
|
+
end
|
63
|
+
|
64
|
+
def internal_public_instance_methods(mod)
|
65
|
+
raise NotImplementedError, "abstract"
|
66
|
+
end
|
67
|
+
|
68
|
+
def internal_protected_instance_methods(mod)
|
69
|
+
raise NotImplementedError, "abstract"
|
70
|
+
end
|
71
|
+
|
72
|
+
def internal_private_instance_methods(mod)
|
73
|
+
raise NotImplementedError, "abstract"
|
74
|
+
end
|
75
|
+
|
76
|
+
def internal_undefined_instance_methods(mod)
|
77
|
+
raise NotImplementedError, "abstract"
|
78
|
+
end
|
79
|
+
|
80
|
+
def singleton_class?(object)
|
81
|
+
raise NotImplementedError, "abstract"
|
82
|
+
end
|
83
|
+
|
84
|
+
def singleton_instance(singleton_class)
|
85
|
+
raise NotImplementedError, "abstract"
|
86
|
+
end
|
87
|
+
|
88
|
+
def module_name(mod)
|
89
|
+
raise NotImplementedError, "abstract"
|
90
|
+
end
|
91
|
+
|
92
|
+
if RUBY_VERSION >= '1.9.0' || Looksee.ruby_engine == 'rbx'
|
93
|
+
def source_location(method)
|
94
|
+
method.is_a?(UnboundMethod) or
|
95
|
+
raise TypeError, "expected UnboundMethod, got #{method.class}"
|
96
|
+
method.source_location
|
97
|
+
end
|
98
|
+
else
|
99
|
+
def source_location(method)
|
100
|
+
raise NotImplementedError, 'abstract'
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
require 'looksee/adapter/base'
|
2
|
+
require 'looksee/rbx'
|
3
|
+
|
4
|
+
module Looksee
|
5
|
+
module Adapter
|
6
|
+
class Rubinius < Base
|
7
|
+
def internal_superclass(klass)
|
8
|
+
klass.direct_superclass
|
9
|
+
end
|
10
|
+
|
11
|
+
def internal_public_instance_methods(mod)
|
12
|
+
return [] if !mod.origin.equal?(mod)
|
13
|
+
mod.method_table.public_names
|
14
|
+
end
|
15
|
+
|
16
|
+
def internal_protected_instance_methods(mod)
|
17
|
+
return [] if !mod.origin.equal?(mod)
|
18
|
+
mod.method_table.protected_names
|
19
|
+
end
|
20
|
+
|
21
|
+
def internal_private_instance_methods(mod)
|
22
|
+
return [] if !mod.origin.equal?(mod)
|
23
|
+
mod.method_table.private_names
|
24
|
+
end
|
25
|
+
|
26
|
+
def internal_undefined_instance_methods(mod)
|
27
|
+
return [] if !mod.origin.equal?(mod)
|
28
|
+
names = []
|
29
|
+
mod.method_table.entries.each do |(name, method, visibility)|
|
30
|
+
names << name if visibility.equal?(:undef)
|
31
|
+
end
|
32
|
+
names
|
33
|
+
end
|
34
|
+
|
35
|
+
def included_class?(object)
|
36
|
+
object.is_a?(::Rubinius::IncludedModule)
|
37
|
+
end
|
38
|
+
|
39
|
+
def singleton_class?(object)
|
40
|
+
object.is_a?(Class) && !!::Rubinius::Type.singleton_class_object(object)
|
41
|
+
end
|
42
|
+
|
43
|
+
def singleton_instance(singleton_class)
|
44
|
+
singleton_class?(singleton_class) or
|
45
|
+
raise TypeError, "expected singleton class, got #{singleton_class.class}"
|
46
|
+
::Rubinius::Type.singleton_class_object(singleton_class)
|
47
|
+
end
|
48
|
+
|
49
|
+
def module_name(mod)
|
50
|
+
mod.is_a?(Module) or
|
51
|
+
raise TypeError, "expected module, got #{mod.class}"
|
52
|
+
|
53
|
+
if ::Rubinius::IncludedModule === mod
|
54
|
+
if Class === mod.module
|
55
|
+
"#{module_name(mod.module)} (origin)"
|
56
|
+
else
|
57
|
+
module_name(mod.module)
|
58
|
+
end
|
59
|
+
elsif ::Rubinius::Type.respond_to?(:module_name)
|
60
|
+
::Rubinius::Type.module_name(mod) || ''
|
61
|
+
else
|
62
|
+
mod.__name__
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
def source_location(method)
|
67
|
+
method.is_a?(UnboundMethod) or
|
68
|
+
raise TypeError, "expected UnboundMethod, got #{method.class}"
|
69
|
+
source_location = method.source_location and
|
70
|
+
return source_location
|
71
|
+
|
72
|
+
# #source_location doesn't always work. If it returns nil, try
|
73
|
+
# a little harder.
|
74
|
+
case (executable = method.executable)
|
75
|
+
when ::Rubinius::BlockEnvironment::AsMethod
|
76
|
+
method = executable.instance_variable_get(:@block_env).method
|
77
|
+
[method.file.to_s, method.lines[1]]
|
78
|
+
when ::Rubinius::DelegatedMethod
|
79
|
+
executable.instance_variable_get(:@receiver).source_location
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
require "rbconfig"
|
2
|
+
require 'set'
|
3
|
+
|
4
|
+
module Looksee
|
5
|
+
Config = Object.const_defined?(:RbConfig) ? ::RbConfig : ::Config
|
6
|
+
|
7
|
+
NoMethodError = Class.new(RuntimeError)
|
8
|
+
NoSourceLocationError = Class.new(RuntimeError)
|
9
|
+
NoSourceFileError = Class.new(RuntimeError)
|
10
|
+
|
11
|
+
autoload :VERSION, 'looksee/version'
|
12
|
+
autoload :Adapter, 'looksee/adapter'
|
13
|
+
autoload :Columnizer, 'looksee/columnizer'
|
14
|
+
autoload :Editor, 'looksee/editor'
|
15
|
+
autoload :Help, 'looksee/help'
|
16
|
+
autoload :Inspector, 'looksee/inspector'
|
17
|
+
autoload :LookupPath, 'looksee/lookup_path'
|
18
|
+
autoload :WirbleCompatibility, 'looksee/wirble_compatibility'
|
19
|
+
|
20
|
+
class << self
|
21
|
+
#
|
22
|
+
# The default options passed to #ls.
|
23
|
+
#
|
24
|
+
# Default: <tt>[:public, :protected, :private, :undefined,
|
25
|
+
# :overridden]</tt>
|
26
|
+
#
|
27
|
+
attr_accessor :default_specifiers
|
28
|
+
|
29
|
+
#
|
30
|
+
# The width to use for displaying output, when not available in
|
31
|
+
# the COLUMNS environment variable.
|
32
|
+
#
|
33
|
+
# Default: 80
|
34
|
+
#
|
35
|
+
attr_accessor :default_width
|
36
|
+
|
37
|
+
#
|
38
|
+
# The default styles to use for the +inspect+ strings.
|
39
|
+
#
|
40
|
+
# This is a hash with keys:
|
41
|
+
#
|
42
|
+
# * :module
|
43
|
+
# * :public
|
44
|
+
# * :protected
|
45
|
+
# * :private
|
46
|
+
# * :undefined
|
47
|
+
# * :overridden
|
48
|
+
#
|
49
|
+
# The values are format strings. They should all contain a single
|
50
|
+
# "%s", which is where the name is inserted.
|
51
|
+
#
|
52
|
+
# Default:
|
53
|
+
#
|
54
|
+
# {
|
55
|
+
# :module => "\e[1;37m%s\e[0m", # white
|
56
|
+
# :public => "\e[1;32m%s\e[0m", # green
|
57
|
+
# :protected => "\e[1;33m%s\e[0m", # yellow
|
58
|
+
# :private => "\e[1;31m%s\e[0m", # red
|
59
|
+
# :undefined => "\e[1;34m%s\e[0m", # blue
|
60
|
+
# :overridden => "\e[1;30m%s\e[0m", # black
|
61
|
+
# }
|
62
|
+
#
|
63
|
+
attr_accessor :styles
|
64
|
+
|
65
|
+
#
|
66
|
+
# The editor command, used for Object#edit.
|
67
|
+
#
|
68
|
+
# This string should contain a "%f", which is replaced with the
|
69
|
+
# file name, and/or "%l" which is replaced with the line number. A
|
70
|
+
# "%%" is replaced with "%".
|
71
|
+
#
|
72
|
+
# If the LOOKSEE_EDITOR environment variable is set, it is used as
|
73
|
+
# the default. Otherwise, we use the following heuristic:
|
74
|
+
#
|
75
|
+
# If EDITOR is set, we use that. If it looks like vi, emacs, or
|
76
|
+
# textmate, we also append options to position the cursor on the
|
77
|
+
# appropriate line. If EDITOR is not set, we use "vi +%l %f".
|
78
|
+
#
|
79
|
+
attr_accessor :editor
|
80
|
+
|
81
|
+
#
|
82
|
+
# The interpreter adapter.
|
83
|
+
#
|
84
|
+
# Encapsulates the interpreter-specific functionality.
|
85
|
+
#
|
86
|
+
attr_accessor :adapter
|
87
|
+
|
88
|
+
#
|
89
|
+
# Wrapper around RUBY_ENGINE that's always defined.
|
90
|
+
#
|
91
|
+
attr_accessor :ruby_engine
|
92
|
+
|
93
|
+
#
|
94
|
+
# Return a Looksee::Inspector for the given +object+.
|
95
|
+
#
|
96
|
+
# +args+ is an optional list of specifiers.
|
97
|
+
#
|
98
|
+
# * +:public+ - include public methods
|
99
|
+
# * +:protected+ - include public methods
|
100
|
+
# * +:private+ - include public methods
|
101
|
+
# * +:undefined+ - include public methods (see Module#undef_method)
|
102
|
+
# * +:overridden+ - include public methods
|
103
|
+
# * +:nopublic+ - include public methods
|
104
|
+
# * +:noprotected+ - include public methods
|
105
|
+
# * +:noprivate+ - include public methods
|
106
|
+
# * +:noundefined+ - include public methods (see Module#undef_method)
|
107
|
+
# * +:nooverridden+ - include public methods
|
108
|
+
# * a string - only include methods containing this string (may
|
109
|
+
# be used multiple times)
|
110
|
+
# * a regexp - only include methods matching this regexp (may
|
111
|
+
# be used multiple times)
|
112
|
+
#
|
113
|
+
# The default (if options is nil or omitted) is given by
|
114
|
+
# #default_lookup_path_options.
|
115
|
+
#
|
116
|
+
def [](object, *args)
|
117
|
+
options = {:visibilities => Set[], :filters => Set[]}
|
118
|
+
(Looksee.default_specifiers + args).each do |arg|
|
119
|
+
case arg
|
120
|
+
when String, Regexp
|
121
|
+
options[:filters] << arg
|
122
|
+
when :public, :protected, :private, :undefined, :overridden
|
123
|
+
options[:visibilities].add(arg)
|
124
|
+
when :nopublic, :noprotected, :noprivate, :noundefined, :nooverridden
|
125
|
+
visibility = arg.to_s.sub(/\Ano/, '').to_sym
|
126
|
+
options[:visibilities].delete(visibility)
|
127
|
+
else
|
128
|
+
raise ArgumentError, "invalid specifier: #{arg.inspect}"
|
129
|
+
end
|
130
|
+
end
|
131
|
+
lookup_path = LookupPath.new(object)
|
132
|
+
Inspector.new(lookup_path, options)
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Show a quick reference.
|
137
|
+
#
|
138
|
+
def help
|
139
|
+
Help.new
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
self.default_specifiers = [:public, :protected, :private, :undefined, :overridden]
|
144
|
+
self.default_width = 80
|
145
|
+
self.styles = {
|
146
|
+
:module => "\e[1;37m%s\e[0m", # white
|
147
|
+
:public => "\e[1;32m%s\e[0m", # green
|
148
|
+
:protected => "\e[1;33m%s\e[0m", # yellow
|
149
|
+
:private => "\e[1;31m%s\e[0m", # red
|
150
|
+
:undefined => "\e[1;34m%s\e[0m", # blue
|
151
|
+
:overridden => "\e[1;30m%s\e[0m", # black
|
152
|
+
}
|
153
|
+
self.editor = ENV['LOOKSEE_EDITOR'] || ENV['EDITOR'] || 'vi'
|
154
|
+
|
155
|
+
if Object.const_defined?(:RUBY_ENGINE)
|
156
|
+
self.ruby_engine = RUBY_ENGINE
|
157
|
+
else
|
158
|
+
self.ruby_engine = 'ruby'
|
159
|
+
end
|
160
|
+
|
161
|
+
case ruby_engine
|
162
|
+
when 'jruby'
|
163
|
+
self.adapter = Adapter::JRuby.new
|
164
|
+
when 'rbx'
|
165
|
+
self.adapter = Adapter::Rubinius.new
|
166
|
+
else
|
167
|
+
self.adapter = Adapter::MRI.new
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Looksee
|
2
|
+
module Columnizer
|
3
|
+
class << self
|
4
|
+
#
|
5
|
+
# Arrange the given strings in columns, restricted to the given
|
6
|
+
# width. Smart enough to ignore content in terminal control
|
7
|
+
# sequences.
|
8
|
+
#
|
9
|
+
def columnize(strings, width)
|
10
|
+
return '' if strings.empty?
|
11
|
+
|
12
|
+
num_columns = 1
|
13
|
+
layout = [strings]
|
14
|
+
loop do
|
15
|
+
break if layout.first.length <= 1
|
16
|
+
next_layout = layout_in_columns(strings, num_columns + 1)
|
17
|
+
break if layout_width(next_layout) > width
|
18
|
+
layout = next_layout
|
19
|
+
num_columns += 1
|
20
|
+
end
|
21
|
+
|
22
|
+
pad_strings(layout)
|
23
|
+
rectangularize_layout(layout)
|
24
|
+
layout.transpose.map do |row|
|
25
|
+
' ' + row.compact.join(' ')
|
26
|
+
end.join("\n") << "\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
private # -----------------------------------------------------
|
30
|
+
|
31
|
+
def layout_in_columns(strings, num_columns)
|
32
|
+
strings_per_column = (strings.length / num_columns.to_f).ceil
|
33
|
+
(0...num_columns).map{|i| strings[i*strings_per_column...(i+1)*strings_per_column] || []}
|
34
|
+
end
|
35
|
+
|
36
|
+
def layout_width(layout)
|
37
|
+
widths = layout_column_widths(layout)
|
38
|
+
widths.inject(0){|sum, w| sum + w} + 2*layout.length
|
39
|
+
end
|
40
|
+
|
41
|
+
def layout_column_widths(layout)
|
42
|
+
layout.map do |column|
|
43
|
+
column.map{|string| display_width(string)}.max || 0
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def display_width(string)
|
48
|
+
# remove terminal control sequences
|
49
|
+
string.gsub(/\e\[.*?m/, '').length
|
50
|
+
end
|
51
|
+
|
52
|
+
def pad_strings(layout)
|
53
|
+
widths = layout_column_widths(layout)
|
54
|
+
layout.each_with_index do |column, i|
|
55
|
+
column_width = widths[i]
|
56
|
+
column.each do |string|
|
57
|
+
padding = column_width - display_width(string)
|
58
|
+
string << ' '*padding
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def rectangularize_layout(layout)
|
64
|
+
return if layout.length == 1
|
65
|
+
height = layout[0].length
|
66
|
+
layout[1..-1].each do |column|
|
67
|
+
column.length == height or
|
68
|
+
column[height - 1] = nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|