carray 3.0.1 → 3.0.3
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 +4 -4
- data/CHANGELOG.md +824 -0
- data/README.md +5 -6
- data/carray.gemspec +1 -1
- data/ext/ca_axis_dispatch.c +258 -4
- data/ext/ca_axis_group.c +368 -184
- data/ext/ca_binop_dispatch.c +54 -32
- data/ext/ca_binop_dispatch.h +1 -0
- data/ext/ca_categorical_iterator.c +90 -41
- data/ext/ca_compare.c +8 -2
- data/ext/ca_for_buffer.h +21 -68
- data/ext/ca_group_iter.c +27 -11
- data/ext/ca_kernel_iterator.c +960 -245
- data/ext/ca_kernel_iterator.h +352 -181
- data/ext/ca_monop_dispatch.c +4 -4
- data/ext/ca_monop_dispatch.h +4 -0
- data/ext/ca_obj_array.c +194 -42
- data/ext/ca_obj_bincmp.c +152 -121
- data/ext/ca_obj_binop.c +107 -210
- data/ext/ca_obj_bitarray.c +37 -24
- data/ext/ca_obj_bitfield.c +95 -62
- data/ext/ca_obj_block.c +5 -4
- data/ext/ca_obj_byte_swap.c +40 -40
- data/ext/ca_obj_const_string.c +180 -50
- data/ext/ca_obj_face.c +428 -62
- data/ext/ca_obj_face.h +107 -47
- data/ext/ca_obj_fake.c +21 -50
- data/ext/ca_obj_farray.c +1 -0
- data/ext/ca_obj_field.c +16 -3
- data/ext/ca_obj_fixlen_string.c +39 -26
- data/ext/ca_obj_grid.c +181 -124
- data/ext/ca_obj_meld.c +235 -55
- data/ext/ca_obj_moncmp.c +61 -61
- data/ext/ca_obj_monop.c +125 -107
- data/ext/ca_obj_object.c +551 -233
- data/ext/ca_obj_record.c +51 -16
- data/ext/ca_obj_reduce.c +5 -8
- data/ext/ca_obj_refer.c +117 -33
- data/ext/ca_obj_remap.c +57 -17
- data/ext/ca_obj_repeat.c +21 -5
- data/ext/ca_obj_roll.c +115 -11
- data/ext/ca_obj_select.c +88 -75
- data/ext/ca_obj_select_axis.c +58 -60
- data/ext/ca_obj_shift.c +22 -102
- data/ext/ca_obj_stack.c +223 -123
- data/ext/ca_obj_stride.c +267 -35
- data/ext/ca_obj_string.c +25 -22
- data/ext/ca_obj_tile.c +32 -16
- data/ext/ca_obj_time.c +23 -25
- data/ext/ca_obj_timedelta.c +21 -23
- data/ext/ca_obj_transpose.c +12 -3
- data/ext/ca_obj_triop.c +110 -117
- data/ext/ca_obj_window.c +157 -29
- data/ext/ca_op_ipower.c +25 -13
- data/ext/ca_op_powi.h +57 -30
- data/ext/ca_pow5_table.h +656 -0
- data/ext/ca_rng_normal.h +42 -0
- data/ext/ca_rng_xoshiro256pp.h +105 -0
- data/ext/ca_sort_kernels.h +70 -0
- data/ext/ca_sweep_engine.c +385 -198
- data/ext/ca_sweep_engine.h +31 -23
- data/ext/ca_triop_dispatch.c +4 -1
- data/ext/ca_triop_dispatch.h +5 -2
- data/ext/caframe_csv_reader.c +463 -0
- data/ext/carray.h +90 -28
- data/ext/carray_access.c +487 -231
- data/ext/carray_address_basis.c +731 -0
- data/ext/carray_attribute.c +45 -3
- data/ext/carray_bincount.c +32 -2
- data/ext/carray_broadcast.c +49 -17
- data/ext/carray_call_cfunc.c +667 -511
- data/ext/carray_cast.c +1556 -247
- data/ext/carray_cast_func.rb +34 -21
- data/ext/carray_conversion.c +197 -73
- data/ext/carray_copy.c +84 -38
- data/ext/carray_core.c +591 -119
- data/ext/carray_count.c +148 -96
- data/ext/carray_element.c +18 -27
- data/ext/carray_factorize.c +604 -291
- data/ext/carray_generate.c +76 -40
- data/ext/carray_histogram.c +152 -94
- data/ext/carray_hold.c +2 -1
- data/ext/carray_index_classifier.c +63 -96
- data/ext/carray_internal.h +400 -1
- data/ext/carray_lazy.c +834 -79
- data/ext/carray_loop.c +88 -66
- data/ext/carray_mask.c +705 -324
- data/ext/carray_math_kernel.h +1 -0
- data/ext/carray_median_percentile.c +263 -63
- data/ext/carray_memory_view.c +254 -248
- data/ext/carray_operator.c +1093 -363
- data/ext/carray_order.c +133 -69
- data/ext/carray_partition.c +102 -56
- data/ext/carray_random.c +582 -204
- data/ext/carray_scatter.c +308 -65
- data/ext/carray_segment.c +257 -0
- data/ext/carray_slab.c +183 -70
- data/ext/carray_sort.c +235 -240
- data/ext/carray_sort_kernel.c +8 -1
- data/ext/carray_struct.c +99 -21
- data/ext/carray_test.c +167 -64
- data/ext/carray_time_text.c +1182 -0
- data/ext/carray_utils.c +363 -98
- data/ext/extconf.rb +27 -2
- data/ext/mk_call_cfunc.rb +103 -118
- data/ext/mk_pow5_table.rb +35 -0
- data/ext/mkkernel.rb +1349 -851
- data/ext/ruby_carray.c +17 -2
- data/ext/version.h +4 -4
- data/ext/version.rb +4 -2
- data/lib/carray/attribute.rb +39 -0
- data/lib/carray/autoload_carray.rb +24 -3
- data/lib/carray/autoload_method_extension.rb +18 -0
- data/lib/carray/axis_group.rb +245 -20
- data/lib/carray/basics.rb +95 -25
- data/lib/carray/bincount_nd.rb +6 -0
- data/lib/carray/block_iterator.rb +200 -54
- data/lib/carray/boolean_reduce.rb +21 -29
- data/lib/carray/categorical.rb +244 -59
- data/lib/carray/categorical_iterator.rb +391 -757
- data/lib/carray/complex.rb +36 -29
- data/lib/carray/conditional.rb +94 -7
- data/lib/carray/const_string.rb +177 -27
- data/lib/carray/construct.rb +40 -0
- data/lib/carray/data_type_extension.rb +3 -0
- data/lib/carray/data_type_limits.rb +91 -0
- data/lib/carray/fixlen_string.rb +1 -1
- data/lib/carray/frame/concat.rb +157 -6
- data/lib/carray/frame/convert.rb +13 -7
- data/lib/carray/frame/csv_parser.rb +590 -76
- data/lib/carray/frame/describe.rb +79 -0
- data/lib/carray/frame/frame.rb +205 -35
- data/lib/carray/frame/group.rb +199 -40
- data/lib/carray/frame/io.rb +452 -60
- data/lib/carray/frame/join.rb +134 -46
- data/lib/carray/frame/records.rb +54 -19
- data/lib/carray/frame/reshape.rb +448 -0
- data/lib/carray/frame/sort.rb +26 -2
- data/lib/carray/frame/verbs.rb +726 -56
- data/lib/carray/frame.rb +2 -0
- data/lib/carray/fuse_source.rb +111 -2
- data/lib/carray/fusion.rb +229 -38
- data/lib/carray/inspect.rb +71 -20
- data/lib/carray/iterator.rb +157 -2
- data/lib/carray/lazy.rb +54 -67
- data/lib/carray/mask_gap_fill.rb +66 -50
- data/lib/carray/math.rb +32 -19
- data/lib/carray/meld_reduce.rb +40 -7
- data/lib/carray/methods/align_addr.rb +8 -4
- data/lib/carray/methods/bincount.rb +2 -2
- data/lib/carray/methods/bit_string.rb +14 -3
- data/lib/carray/methods/composition.rb +16 -10
- data/lib/carray/methods/discovery_along.rb +74 -0
- data/lib/carray/methods/factorize.rb +50 -0
- data/lib/carray/methods/gather_nd.rb +13 -2
- data/lib/carray/methods/insert_block.rb +9 -14
- data/lib/carray/methods/is_in.rb +130 -6
- data/lib/carray/methods/join.rb +1 -1
- data/lib/carray/methods/locate_addr.rb +235 -21
- data/lib/carray/methods/mask_duplicates.rb +35 -1
- data/lib/carray/methods/mode.rb +18 -1
- data/lib/carray/methods/nunique.rb +22 -1
- data/lib/carray/methods/repeat.rb +91 -0
- data/lib/carray/methods/resize.rb +21 -23
- data/lib/carray/methods/snap.rb +34 -12
- data/lib/carray/methods/unique.rb +52 -3
- data/lib/carray/record_order.rb +208 -0
- data/lib/carray/rng.rb +86 -0
- data/lib/carray/runtime.rb +14 -11
- data/lib/carray/segment_iterator.rb +990 -0
- data/lib/carray/serialize.rb +197 -27
- data/lib/carray/slab_iterator.rb +109 -40
- data/lib/carray/stack.rb +10 -4
- data/lib/carray/string_operation_extension.rb +9 -4
- data/lib/carray/struct.rb +142 -51
- data/lib/carray/struct_builder.rb +110 -14
- data/lib/carray/table.rb +2 -2
- data/lib/carray/time.rb +535 -105
- data/lib/carray/window_iterator.rb +259 -82
- data/lib/carray.rb +4 -2
- data/yard-stubs/ca_obj_array.rb +4 -4
- data/yard-stubs/ca_obj_bitarray.rb +7 -0
- data/yard-stubs/ca_obj_bitfield.rb +23 -12
- data/yard-stubs/ca_obj_block.rb +2 -7
- data/yard-stubs/ca_obj_fake.rb +1 -1
- data/yard-stubs/ca_obj_field.rb +1 -1
- data/yard-stubs/ca_obj_refer.rb +3 -3
- data/yard-stubs/ca_obj_roll.rb +0 -1
- data/yard-stubs/ca_obj_stride.rb +10 -7
- data/yard-stubs/ca_obj_transpose.rb +1 -1
- data/yard-stubs/ca_obj_window.rb +11 -4
- data/yard-stubs/carray_access.rb +6 -5
- data/yard-stubs/carray_broadcast.rb +2 -2
- data/yard-stubs/carray_cast.rb +78 -43
- data/yard-stubs/carray_conversion.rb +13 -1
- data/yard-stubs/carray_copy.rb +8 -3
- data/yard-stubs/carray_core.rb +3 -83
- data/yard-stubs/carray_count.rb +16 -2
- data/yard-stubs/carray_lazy.rb +210 -1
- data/yard-stubs/carray_loop.rb +4 -1
- data/yard-stubs/carray_mask.rb +34 -10
- data/yard-stubs/carray_math.rb +1499 -4
- data/yard-stubs/carray_median_percentile.rb +26 -8
- data/yard-stubs/carray_order.rb +43 -82
- data/yard-stubs/carray_scatter.rb +10 -3
- data/yard-stubs/carray_segment.rb +72 -0
- data/yard-stubs/carray_slab.rb +11 -8
- data/yard-stubs/carray_sort.rb +50 -8
- data/yard-stubs/carray_test.rb +10 -0
- data/yard-stubs/carray_utils.rb +0 -26
- metadata +19 -19
- data/ext/ca_for_each_element.h +0 -239
- data/ext/carray_build_flags.h +0 -3
- data/ext/carray_config.h +0 -28
- data/ext/carray_kernels_bincmp.c +0 -4446
- data/ext/carray_kernels_binop.c +0 -11001
- data/ext/carray_kernels_init.c +0 -1131
- data/ext/carray_kernels_map.c +0 -3467
- data/ext/carray_kernels_moncmp.c +0 -2097
- data/ext/carray_kernels_monop.c +0 -18313
- data/ext/carray_kernels_reduce_aggregate.c +0 -25837
- data/ext/carray_kernels_reduce_boolean.c +0 -330
- data/ext/carray_kernels_reduce_cumulative.c +0 -14593
- data/ext/carray_kernels_reduce_extreme.c +0 -16948
- data/ext/carray_kernels_reduce_variance.c +0 -3910
- data/ext/carray_kernels_scan.c +0 -3693
- data/ext/carray_kernels_search.c +0 -32138
- data/ext/carray_kernels_sort.c +0 -10626
- data/ext/carray_kernels_triop.c +0 -1392
data/lib/carray/frame.rb
CHANGED
data/lib/carray/fuse_source.rb
CHANGED
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
# shadows back -- `fuse(a, b) { |x, y| ... }` -- which names each of them
|
|
12
12
|
# twice. Julia writes `@.` for the same reason and does the same thing to
|
|
13
13
|
# the expression underneath.
|
|
14
|
+
#
|
|
15
|
+
# Reading and rewriting a block gives the same text every time it is
|
|
16
|
+
# called, so it is done once per block -- per place in the source -- and
|
|
17
|
+
# kept. What is kept is a lambda that takes the block's free locals as
|
|
18
|
+
# arguments and is run with the block's `self`; a call only reads the
|
|
19
|
+
# locals.
|
|
14
20
|
# ---------------------------------------------------------------------------
|
|
15
21
|
|
|
16
22
|
require "prism"
|
|
@@ -26,13 +32,110 @@ class CArray
|
|
|
26
32
|
end
|
|
27
33
|
|
|
28
34
|
def self.evaluate (block)
|
|
29
|
-
result =
|
|
30
|
-
*block.source_location)
|
|
35
|
+
result = compiled(block).call(block)
|
|
31
36
|
# An expression that is just an array is that array; the shadow put
|
|
32
37
|
# around it has nothing to fuse.
|
|
33
38
|
result.is_a?(CALazyMarker) ? result.parent : result
|
|
34
39
|
end
|
|
35
40
|
|
|
41
|
+
# -- one compiled form per block --------------------------------------
|
|
42
|
+
|
|
43
|
+
# Keyed by the block's instruction sequence, which is the same object
|
|
44
|
+
# every time the same block is called and a different one for each
|
|
45
|
+
# block, even two on one line. Where Ruby has weak keys, a block whose
|
|
46
|
+
# code is unloaded takes its entry with it.
|
|
47
|
+
SITES = defined?(ObjectSpace::WeakKeyMap) ? ObjectSpace::WeakKeyMap.new
|
|
48
|
+
: {}.compare_by_identity
|
|
49
|
+
|
|
50
|
+
def self.compiled (block)
|
|
51
|
+
site = RubyVM::InstructionSequence.of(block)
|
|
52
|
+
return compile(block) unless site
|
|
53
|
+
SITES[site] || (SITES[site] = compile(block))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# The block's free locals, handed to a lambda that was compiled without
|
|
57
|
+
# them. `self` is the block's own, so instance variables and methods
|
|
58
|
+
# are what they were.
|
|
59
|
+
Compiled = Struct.new(:locals, :lambda) do
|
|
60
|
+
def call (block)
|
|
61
|
+
scope = block.binding
|
|
62
|
+
values = locals.map { |name| scope.local_variable_get(name) }
|
|
63
|
+
scope.receiver.instance_exec(*values, &lambda)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# A block that needs its own frame -- it assigns to a local outside it,
|
|
68
|
+
# or yields, or asks for its binding -- is evaluated there every time;
|
|
69
|
+
# only the rewrite is kept.
|
|
70
|
+
InFrame = Struct.new(:text, :file, :line) do
|
|
71
|
+
def call (block)
|
|
72
|
+
eval(text, block.binding, file, line)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Calls that mean something only in the frame they are made from.
|
|
77
|
+
FRAME_BOUND = %i[binding local_variables block_given? __method__ __dir__
|
|
78
|
+
eval].freeze
|
|
79
|
+
|
|
80
|
+
FRAME_BOUND_NODES = [Prism::YieldNode, Prism::SuperNode,
|
|
81
|
+
Prism::ForwardingSuperNode, Prism::ReturnNode,
|
|
82
|
+
Prism::BreakNode, Prism::NextNode,
|
|
83
|
+
Prism::RedoNode].freeze
|
|
84
|
+
|
|
85
|
+
def self.compile (block)
|
|
86
|
+
body = body_source(block)
|
|
87
|
+
text = rewrite(body)
|
|
88
|
+
file, line = block.source_location
|
|
89
|
+
scope = block.binding
|
|
90
|
+
locals, needs_frame = free_locals(body, scope.local_variables)
|
|
91
|
+
return InFrame.new(text, file, line) if needs_frame
|
|
92
|
+
Compiled.new(locals, compile_lambda(text, locals, scope, file, line))
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# Read on its own, a body names a local from outside as a call with no
|
|
96
|
+
# receiver and no arguments, so every one of them is found this way. A
|
|
97
|
+
# body that assigns to one has to keep its frame.
|
|
98
|
+
def self.free_locals (body, outer)
|
|
99
|
+
reads = []
|
|
100
|
+
needs_frame = false
|
|
101
|
+
Prism.parse(body).value.breadth_first_search do |node|
|
|
102
|
+
case node
|
|
103
|
+
when Prism::CallNode
|
|
104
|
+
reads << node.name if node.variable_call? && outer.include?(node.name)
|
|
105
|
+
needs_frame = true if node.receiver.nil? && FRAME_BOUND.include?(node.name)
|
|
106
|
+
when *FRAME_BOUND_NODES
|
|
107
|
+
needs_frame = true
|
|
108
|
+
else
|
|
109
|
+
if node.class.name.match?(/\APrism::LocalVariable\w*(Write|Target)Node\z/) &&
|
|
110
|
+
outer.include?(node.name)
|
|
111
|
+
needs_frame = true
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
false
|
|
115
|
+
end
|
|
116
|
+
[reads.uniq, needs_frame]
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Constants resolve as they do where the block was written: each module
|
|
120
|
+
# it sits in is reopened around the lambda, outermost first. The
|
|
121
|
+
# refinements active there are activated again. None of it sees the
|
|
122
|
+
# frame of the call that compiles it.
|
|
123
|
+
def self.compile_lambda (text, locals, scope, file, line)
|
|
124
|
+
nesting = scope.eval("Module.nesting").reverse
|
|
125
|
+
refinements = scope.eval("Module.used_modules")
|
|
126
|
+
Thread.current[:__carray_fuse_scope__] = [nesting, refinements]
|
|
127
|
+
code = refinements.each_index.map { |i|
|
|
128
|
+
"using Thread.current[:__carray_fuse_scope__][1][#{i}];"
|
|
129
|
+
}.join + "lambda { |#{locals.join(", ")}| #{text}\n}"
|
|
130
|
+
(nesting.size - 1).downto(0) do |i|
|
|
131
|
+
code = "Thread.current[:__carray_fuse_scope__][0][#{i}]" \
|
|
132
|
+
".module_eval(#{code.dump}, #{file.dump}, #{line})"
|
|
133
|
+
end
|
|
134
|
+
EMPTY_SCOPE.call.eval(code, file, line)
|
|
135
|
+
ensure
|
|
136
|
+
Thread.current[:__carray_fuse_scope__] = nil
|
|
137
|
+
end
|
|
138
|
+
|
|
36
139
|
# -- the block's own text ---------------------------------------------
|
|
37
140
|
|
|
38
141
|
def self.body_source (block)
|
|
@@ -121,3 +224,9 @@ class CArray
|
|
|
121
224
|
end
|
|
122
225
|
end
|
|
123
226
|
end
|
|
227
|
+
|
|
228
|
+
# A scope with no locals and no refinements in it, for compiling a block's
|
|
229
|
+
# expression where it can neither see nor hold on to the frame of the call
|
|
230
|
+
# that happens to compile it. Both are true only of the top of a file.
|
|
231
|
+
CArray::FuseSource::EMPTY_SCOPE = -> { binding }
|
|
232
|
+
CArray::FuseSource.private_constant :EMPTY_SCOPE
|
data/lib/carray/fusion.rb
CHANGED
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
# Turning a lazy expression into a plan a compiler can read.
|
|
3
3
|
#
|
|
4
4
|
# A lazy view is already a typed, closed expression graph, and the kernels
|
|
5
|
-
# already carry the C that computes each operation (CArray.__kernel_body__
|
|
5
|
+
# already carry the C that computes each operation (CArray.__kernel_body__,
|
|
6
|
+
# with CArray.__kernel_helpers__ for the functions a body calls beyond the
|
|
7
|
+
# C standard library).
|
|
6
8
|
# What is missing between them is the reading: which operation each node is,
|
|
7
9
|
# what its mask does, and where the leaves are. That is what a plan holds.
|
|
8
10
|
#
|
|
@@ -17,12 +19,49 @@ class CArray
|
|
|
17
19
|
|
|
18
20
|
# One value per node, in evaluation order; the last is the result.
|
|
19
21
|
#
|
|
20
|
-
# Op
|
|
21
|
-
# Leaf
|
|
22
|
-
#
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
# Op an operation, reading the nodes named in `args`
|
|
23
|
+
# Leaf an array, the `index`-th of plan.leaves, read at the cell
|
|
24
|
+
# being computed
|
|
25
|
+
# Shifted the same, read `offset` cells away along each axis -- what
|
|
26
|
+
# CArray#shift makes. A cell that falls outside the array is
|
|
27
|
+
# `fill` where `bounds` says :fill and masked where it says
|
|
28
|
+
# :mask. `masked` says whether the array itself has a mask.
|
|
29
|
+
# Const a scalar written into the expression
|
|
30
|
+
#
|
|
31
|
+
# A Shifted node reads cells other than the one being computed, so an
|
|
32
|
+
# evaluator that writes its output over one of the plan's leaves must
|
|
33
|
+
# not compute one that is read shifted: a cell it reads may already
|
|
34
|
+
# have been written.
|
|
35
|
+
#
|
|
36
|
+
# A comparison's body is the one for the data type it compares; what it
|
|
37
|
+
# answers is boolean, which is the node's data_type.
|
|
38
|
+
# An Op's fields:
|
|
39
|
+
#
|
|
40
|
+
# kind :monop, :binop, :triop, :moncmp or :bincmp
|
|
41
|
+
# name the operation as the kernels name it (:add, :sqrt, :ipow)
|
|
42
|
+
# args indices of the nodes it reads, in operand order
|
|
43
|
+
# body its C, from CArray.__kernel_body__: `#1`, `#2`, ... are
|
|
44
|
+
# the operands and the next number is the result; `<type>`
|
|
45
|
+
# is CArray's name for the type (float64_t)
|
|
46
|
+
# mask how the result is masked: :pass (as its one operand),
|
|
47
|
+
# :union (where any operand is), :kleene_or / :kleene_and
|
|
48
|
+
# (boolean | and &, unmasked where the known side settles it),
|
|
49
|
+
# :select (where the first operand is, else where the
|
|
50
|
+
# operand it chooses is: the second where it is true, the
|
|
51
|
+
# third where it is false)
|
|
52
|
+
# trapping true where a masked cell must not be computed at all
|
|
53
|
+
#
|
|
54
|
+
# What a reader may rely on: the node classes, the kinds, the names, the
|
|
55
|
+
# mask rules and the fields above keep the meaning they have here. New
|
|
56
|
+
# ones are added -- a node class, a kind, an operation, a mask rule --
|
|
57
|
+
# and an evaluator that meets one it does not know declines the plan
|
|
58
|
+
# (returns false). It does not raise: raising takes it out of service
|
|
59
|
+
# for the rest of the process. Declining is what lets CArray add to
|
|
60
|
+
# the plan without a new evaluator being released at the same time.
|
|
61
|
+
Op = Struct.new(:kind, :name, :data_type, :args, :body, :mask, :trapping)
|
|
62
|
+
Leaf = Struct.new(:index, :data_type, :masked)
|
|
63
|
+
Shifted = Struct.new(:index, :data_type, :masked, :offset, :bounds, :fill)
|
|
64
|
+
Const = Struct.new(:value, :data_type)
|
|
26
65
|
|
|
27
66
|
Plan = Struct.new(:nodes, :leaves, :data_type, :dim, :masked, :signature)
|
|
28
67
|
|
|
@@ -40,14 +79,18 @@ class CArray
|
|
|
40
79
|
TRIOP_NAMES = { :__clip_ki__ => :clip }.freeze
|
|
41
80
|
|
|
42
81
|
MONOP_BY_ID = CArray::LAZY_MONOP_OP_IDS.invert.freeze
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
#
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
82
|
+
# A Float or Complex array to an Integer power is its own node, ipow,
|
|
83
|
+
# which `**` makes and no table above names.
|
|
84
|
+
BINOP_BY_ID = CArray::LAZY_BINOP_OP_IDS.invert
|
|
85
|
+
.merge(CABinOp::OP_IPOWER => :ipow).freeze
|
|
86
|
+
# The lazy then_else is its own triop, select, which no method names.
|
|
87
|
+
TRIOP_BY_ID = CArray::LAZY_TRIOP_OP_IDS.invert
|
|
88
|
+
.merge(CATriOp::OP_SELECT => :select).freeze
|
|
89
|
+
MONCMP_BY_ID = CArray::LAZY_MONCMP_OP_IDS.invert.freeze
|
|
90
|
+
# The table holds both spellings of each comparison (`lt` and `<`); the
|
|
91
|
+
# kernels are named by the word.
|
|
92
|
+
BINCMP_BY_ID = CArray::LAZY_BINCMP_OP_IDS
|
|
93
|
+
.select { |name, _| name.to_s.match?(/\A[a-z]/) }.invert.freeze
|
|
51
94
|
|
|
52
95
|
class Refused < StandardError; end
|
|
53
96
|
|
|
@@ -74,29 +117,82 @@ class CArray
|
|
|
74
117
|
|
|
75
118
|
# Returns the array, or nil where nothing computed it.
|
|
76
119
|
def self.evaluate (view)
|
|
77
|
-
|
|
120
|
+
plan = worth_asking(view) or return nil
|
|
78
121
|
out = CArray.__alloc_uninit__(view.data_type, view.dim)
|
|
79
|
-
|
|
122
|
+
ask(plan, out) ? out : nil
|
|
80
123
|
end
|
|
81
124
|
|
|
82
125
|
# Fills an array the caller already has. Called from the store as well,
|
|
83
126
|
# where making one and copying it over would be most of the work.
|
|
84
127
|
# Returns true when something computed it.
|
|
85
128
|
def self.evaluate_into (view, out)
|
|
129
|
+
plan = worth_asking(view) or return false
|
|
130
|
+
ask(plan, out)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
# The plan to hand over, or nil. A marker over an array, or anything
|
|
134
|
+
# else with nothing to compute, is not worth handing over.
|
|
135
|
+
def self.worth_asking (view)
|
|
136
|
+
return nil unless askable?(view)
|
|
137
|
+
plan = plan(view) or return nil
|
|
138
|
+
plan.nodes.any? { |n| n.is_a?(Op) } ? plan : nil
|
|
139
|
+
rescue StandardError => error
|
|
140
|
+
retire(error)
|
|
141
|
+
nil
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def self.ask (plan, out)
|
|
86
145
|
evaluator = CArray.expression_evaluator or return false
|
|
87
|
-
return false
|
|
88
|
-
|
|
89
|
-
#
|
|
90
|
-
#
|
|
91
|
-
|
|
92
|
-
|
|
146
|
+
return false if overlaps?(plan, out)
|
|
147
|
+
# The walk leaves the destination masked where the expression is, so
|
|
148
|
+
# a destination that arrives masked loses its mask to an expression
|
|
149
|
+
# that has none.
|
|
150
|
+
if plan.masked
|
|
151
|
+
out.mask = 0 unless out.has_mask?
|
|
152
|
+
elsif out.has_mask?
|
|
153
|
+
out.mask = 0
|
|
154
|
+
end
|
|
93
155
|
evaluator.call(plan, out) ? true : false
|
|
94
156
|
rescue StandardError => error
|
|
157
|
+
retire(error)
|
|
158
|
+
false
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
# Whether the destination shares storage with a leaf it would read. An
|
|
162
|
+
# evaluator computes cell by cell, so writing a cell another leaf reads
|
|
163
|
+
# later -- `z[] = y + y` with z and y overlapping slices of one array --
|
|
164
|
+
# reads what it has just written. The walk reads every operand before
|
|
165
|
+
# it writes. The destination itself as a leaf is fine as long as it is
|
|
166
|
+
# read at the cell being written, not shifted.
|
|
167
|
+
def self.overlaps? (plan, out)
|
|
168
|
+
mine = storages(out)
|
|
169
|
+
shifted = plan.nodes.grep(Shifted).map(&:index)
|
|
170
|
+
plan.leaves.each_with_index.any? do |leaf, index|
|
|
171
|
+
if leaf.equal?(out)
|
|
172
|
+
shifted.include?(index)
|
|
173
|
+
else
|
|
174
|
+
storages(leaf).any? { |r| mine.any? { |m| m.equal?(r) } }
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
# The arrays that hold an array's cells: its root, or each parent's
|
|
180
|
+
# where a view stands over several.
|
|
181
|
+
def self.storages (array, found = [])
|
|
182
|
+
root = array.root_array
|
|
183
|
+
if ! root.entity? && root.respond_to?(:parents)
|
|
184
|
+
root.parents.each { |parent| storages(parent, found) }
|
|
185
|
+
else
|
|
186
|
+
found << root
|
|
187
|
+
end
|
|
188
|
+
found
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def self.retire (error)
|
|
95
192
|
CArray.expression_evaluator = nil
|
|
96
193
|
warn "CArray: the registered expression evaluator raised " \
|
|
97
194
|
"(#{error.class}: #{error.message}); expressions will be walked " \
|
|
98
195
|
"from here on"
|
|
99
|
-
false
|
|
100
196
|
end
|
|
101
197
|
|
|
102
198
|
def self.askable? (view)
|
|
@@ -115,8 +211,9 @@ class CArray
|
|
|
115
211
|
raise Refused, "not a lazy expression" unless lazy?(view)
|
|
116
212
|
w = Walk.new
|
|
117
213
|
w.visit(view)
|
|
118
|
-
|
|
119
|
-
w.
|
|
214
|
+
masked = w.leaves.any? { |a| a.has_mask? } ||
|
|
215
|
+
w.nodes.any? { |n| n.is_a?(Shifted) && n.bounds.include?(:mask) }
|
|
216
|
+
Plan.new(w.nodes, w.leaves, view.data_type, view.dim, masked, w.signature)
|
|
120
217
|
end
|
|
121
218
|
|
|
122
219
|
def self.lazy? (x)
|
|
@@ -131,6 +228,7 @@ class CArray
|
|
|
131
228
|
def initialize
|
|
132
229
|
@nodes = []
|
|
133
230
|
@leaves = []
|
|
231
|
+
@leaf_index = {}
|
|
134
232
|
@seen = {}
|
|
135
233
|
@signature = +""
|
|
136
234
|
end
|
|
@@ -147,6 +245,9 @@ class CArray
|
|
|
147
245
|
when CAMonOp then unary(n)
|
|
148
246
|
when CABinOp then binary(n)
|
|
149
247
|
when CATriOp then ternary(n)
|
|
248
|
+
when CAMonCmp then unary_comparison(n)
|
|
249
|
+
when CABinCmp then binary_comparison(n)
|
|
250
|
+
when CAShift then shifted(n)
|
|
150
251
|
when CScalar then constant(n)
|
|
151
252
|
when CArray then leaf(n)
|
|
152
253
|
else raise Refused, "#{n.class} in an expression"
|
|
@@ -154,11 +255,24 @@ class CArray
|
|
|
154
255
|
end
|
|
155
256
|
|
|
156
257
|
def unary (n)
|
|
258
|
+
return conversion(n) if n.__op_id__ >= CAMonOp::CAST_BASE
|
|
157
259
|
name = spell(MONOP_BY_ID, n.__op_id__, {})
|
|
158
260
|
args = [visit(n.parent)]
|
|
159
261
|
# A view over one array is masked exactly where that array is
|
|
160
262
|
# (ca_obj_monop.c).
|
|
161
|
-
op(:monop, name, n.data_type, args, :pass)
|
|
263
|
+
op(:monop, name, n.data_type, args, :pass, n.__trapping__)
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
# A cast node is named by the type it converts to, cast_<data_type>,
|
|
267
|
+
# and its body is keyed by the type it converts from.
|
|
268
|
+
def conversion (n)
|
|
269
|
+
from = n.parent.data_type
|
|
270
|
+
name = :"cast_#{n.data_type}"
|
|
271
|
+
body = CArray.__kernel_body__(:monop, name, from) or
|
|
272
|
+
raise Refused, "monop #{name} has no body at #{from}"
|
|
273
|
+
args = [visit(n.parent)]
|
|
274
|
+
note("c", name, from, args.join(","))
|
|
275
|
+
push Op.new(:monop, name, n.data_type, args, body, :pass, n.__trapping__)
|
|
162
276
|
end
|
|
163
277
|
|
|
164
278
|
def binary (n)
|
|
@@ -170,32 +284,107 @@ class CArray
|
|
|
170
284
|
elsif n.data_type == :boolean && name == :bit_and_i then :kleene_and
|
|
171
285
|
else :union
|
|
172
286
|
end
|
|
173
|
-
op(:binop, name, n.data_type, args, rule)
|
|
287
|
+
op(:binop, name, n.data_type, args, rule, n.__trapping__)
|
|
174
288
|
end
|
|
175
289
|
|
|
176
290
|
def ternary (n)
|
|
177
291
|
name = spell(TRIOP_BY_ID, n.__op_id__, TRIOP_NAMES)
|
|
178
292
|
args = [visit(n.parent), visit(n.__triop_op2__), visit(n.__triop_op3__)]
|
|
179
|
-
|
|
293
|
+
# select is masked where its condition is, or where the branch the
|
|
294
|
+
# condition chooses is (ca_obj_triop.c).
|
|
295
|
+
rule = name == :select ? :select : :union
|
|
296
|
+
op(:triop, name, n.data_type, args, rule, n.__trapping__)
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
# A comparison is masked where its operands are (ca_obj_moncmp.c,
|
|
300
|
+
# ca_obj_bincmp.c): there is no three-valued comparison.
|
|
301
|
+
def unary_comparison (n)
|
|
302
|
+
name = spell(MONCMP_BY_ID, n.__op_id__, {})
|
|
303
|
+
operand = n.parent
|
|
304
|
+
args = [visit(operand)]
|
|
305
|
+
comparison(:moncmp, name, operand.data_type, args, :pass, n.__trapping__)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def binary_comparison (n)
|
|
309
|
+
name = spell(BINCMP_BY_ID, n.__op_id__, {})
|
|
310
|
+
right = n.__bincmp_right__
|
|
311
|
+
# Both sides are already of the type compared (CABinCmp casts one
|
|
312
|
+
# to the other); a pair that is not has no single body.
|
|
313
|
+
unless n.parent.data_type == right.data_type
|
|
314
|
+
raise Refused, "#{name} between #{n.parent.data_type} and #{right.data_type}"
|
|
315
|
+
end
|
|
316
|
+
args = [visit(n.parent), visit(right)]
|
|
317
|
+
comparison(:bincmp, name, n.parent.data_type, args, :union, n.__trapping__)
|
|
180
318
|
end
|
|
181
319
|
|
|
182
|
-
|
|
320
|
+
# `trapping`: the node skips the cells its operands mask rather than
|
|
321
|
+
# computing them -- one that can raise on a cell or calls Ruby for it.
|
|
322
|
+
# Each lazy view answers it from the rule its own kernel follows.
|
|
323
|
+
def comparison (kind, name, compared, args, mask, trapping)
|
|
324
|
+
body = CArray.__kernel_body__(kind, name, compared) or
|
|
325
|
+
raise Refused, "#{kind} #{name} has no body at #{compared}"
|
|
326
|
+
# A body written against its compared type or a tolerance (`feq`)
|
|
327
|
+
# needs more than its operands substituted.
|
|
328
|
+
if body.match?(/<\w+>|\btol\b/)
|
|
329
|
+
raise Refused, "#{kind} #{name} needs more than its operands"
|
|
330
|
+
end
|
|
331
|
+
note(kind.to_s[0..3], name, compared, args.join(","))
|
|
332
|
+
push Op.new(kind, name, :boolean, args, body, mask, trapping)
|
|
333
|
+
end
|
|
334
|
+
|
|
335
|
+
def op (kind, name, type, args, mask, trapping)
|
|
183
336
|
body = CArray.__kernel_body__(kind, name, type) or
|
|
184
337
|
raise Refused, "#{kind} #{name} has no body at #{type}"
|
|
185
|
-
note(kind.to_s[0], name, type)
|
|
186
|
-
push Op.new(kind, name, type, args, body, mask,
|
|
187
|
-
TRAPPING.include?(name) && INTEGERS.include?(type))
|
|
338
|
+
note(kind.to_s[0], name, type, args.join(","))
|
|
339
|
+
push Op.new(kind, name, type, args, body, mask, trapping)
|
|
188
340
|
end
|
|
189
341
|
|
|
190
342
|
def leaf (n)
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
push Leaf.new(
|
|
343
|
+
index = leaf_index(n)
|
|
344
|
+
note("a", index, n.data_type, n.has_mask? ? 1 : 0)
|
|
345
|
+
push Leaf.new(index, n.data_type, n.has_mask?)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
# A shift of an array is that array read elsewhere, so it is the
|
|
349
|
+
# array that becomes a leaf. A shift of anything else -- an
|
|
350
|
+
# expression, say -- has no array to read and is taken as a whole.
|
|
351
|
+
def shifted (n)
|
|
352
|
+
array = n.parent
|
|
353
|
+
bounds = n.__axis_bounds__
|
|
354
|
+
if Fusion.lazy?(array) || array.dim != n.dim ||
|
|
355
|
+
! bounds.all? { |b| b == :fill || b == :mask }
|
|
356
|
+
return leaf(n)
|
|
357
|
+
end
|
|
358
|
+
offset = n.start
|
|
359
|
+
# The fill as a cell of the array holds it: a boolean array is
|
|
360
|
+
# filled with true or false, not with the Integer it was given.
|
|
361
|
+
fill = bounds.include?(:fill) ? as_cell(n.fill_value, array.data_type) : nil
|
|
362
|
+
index = leaf_index(array)
|
|
363
|
+
note("s", index, array.data_type, array.has_mask? ? 1 : 0,
|
|
364
|
+
offset.join(","), bounds.join(","), fill.inspect)
|
|
365
|
+
push Shifted.new(index, array.data_type, array.has_mask?,
|
|
366
|
+
offset, bounds, fill)
|
|
194
367
|
end
|
|
195
368
|
|
|
369
|
+
def leaf_index (array)
|
|
370
|
+
@leaf_index.fetch(array.object_id) do
|
|
371
|
+
@leaves << array
|
|
372
|
+
@leaf_index[array.object_id] = @leaves.size - 1
|
|
373
|
+
end
|
|
374
|
+
end
|
|
375
|
+
|
|
376
|
+
# A masked scalar has no value to write into the expression.
|
|
196
377
|
def constant (n)
|
|
197
|
-
|
|
198
|
-
|
|
378
|
+
raise Refused, "a masked scalar" if n.has_mask? && n.is_masked[0]
|
|
379
|
+
value = n[0]
|
|
380
|
+
note("k", value.inspect, n.data_type)
|
|
381
|
+
push Const.new(value, n.data_type)
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def as_cell (value, data_type)
|
|
385
|
+
cell = CScalar.new(data_type)
|
|
386
|
+
cell[0] = value
|
|
387
|
+
cell[0]
|
|
199
388
|
end
|
|
200
389
|
|
|
201
390
|
def push (node)
|
|
@@ -210,6 +399,8 @@ class CArray
|
|
|
210
399
|
|
|
211
400
|
# Two expressions of the same shape compute alike, whatever arrays
|
|
212
401
|
# they are over, so a consumer can keep one compiled kernel for both.
|
|
402
|
+
# The shape includes which node each operation reads and which leaf
|
|
403
|
+
# each read is: `(a - b) - b` and `(a - b) - a` differ only there.
|
|
213
404
|
def note (*parts)
|
|
214
405
|
@signature << parts.join(":") << ";"
|
|
215
406
|
end
|
data/lib/carray/inspect.rb
CHANGED
|
@@ -7,18 +7,22 @@ class CArray::Inspector # :nodoc:
|
|
|
7
7
|
end
|
|
8
8
|
|
|
9
9
|
# @!visibility private
|
|
10
|
-
|
|
10
|
+
#
|
|
11
|
+
# `abbrev` false renders every element instead of eliding with `...`.
|
|
12
|
+
# It is the one difference between #inspect and #inspect_full: the
|
|
13
|
+
# header and the layout are the same, so there is one renderer rather
|
|
14
|
+
# than two that could drift apart.
|
|
15
|
+
def inspect_string (abbrev: true)
|
|
11
16
|
if @carray.ndim == 0
|
|
12
17
|
raise "can't inspect CArray of ndim == 0"
|
|
13
18
|
end
|
|
14
19
|
formatter = get_formatter()
|
|
15
|
-
|
|
16
|
-
type_name = get_type_name()
|
|
20
|
+
label = get_label()
|
|
17
21
|
shape = get_shape()
|
|
18
|
-
data_spec = get_data_spec(0, Array.new(@carray.ndim){0}, formatter)
|
|
22
|
+
data_spec = get_data_spec(0, Array.new(@carray.ndim){0}, formatter, abbrev)
|
|
19
23
|
info_list = get_info_list()
|
|
20
24
|
output = ["<",
|
|
21
|
-
format("%s
|
|
25
|
+
format("%s(%s)", label, shape.join(",")),
|
|
22
26
|
": ",
|
|
23
27
|
info_list.join(" "),
|
|
24
28
|
"\n",
|
|
@@ -30,6 +34,16 @@ class CArray::Inspector # :nodoc:
|
|
|
30
34
|
|
|
31
35
|
private
|
|
32
36
|
|
|
37
|
+
# The name before the shape: the class and the storage type, or what a
|
|
38
|
+
# Face says instead through a private inspect_label (CATime gives its
|
|
39
|
+
# tick, which the storage type does not tell).
|
|
40
|
+
def get_label
|
|
41
|
+
if @carray.respond_to?(:inspect_label, true)
|
|
42
|
+
return @carray.send(:inspect_label)
|
|
43
|
+
end
|
|
44
|
+
return format("%s.%s", get_class_name, get_type_name)
|
|
45
|
+
end
|
|
46
|
+
|
|
33
47
|
def get_class_name
|
|
34
48
|
return @carray.class.to_s
|
|
35
49
|
end
|
|
@@ -105,15 +119,27 @@ class CArray::Inspector # :nodoc:
|
|
|
105
119
|
end
|
|
106
120
|
|
|
107
121
|
def get_formatter
|
|
108
|
-
# A Face
|
|
122
|
+
# A Face can write its cells itself through a private
|
|
123
|
+
# inspect_cell_formatter, a lambda taking the decoded cell.
|
|
124
|
+
if @carray.respond_to?(:inspect_cell_formatter, true)
|
|
125
|
+
return @carray.send(:inspect_cell_formatter)
|
|
126
|
+
end
|
|
127
|
+
# A Face that defines storage_to_element decodes each cell into a surface
|
|
109
128
|
# value (CATime::Element, a String, a category label, ...) that has nothing
|
|
110
129
|
# to do with the storage data_type, so the formatter must follow the decoded
|
|
111
130
|
# value, not the storage. Faces without the hook (CAString) hand back the
|
|
112
131
|
# stored value itself and fall through to the storage formatters below.
|
|
113
|
-
if @carray.face? and @carray.respond_to?(:
|
|
132
|
+
if @carray.face? and @carray.respond_to?(:storage_to_element)
|
|
114
133
|
return lambda { |x| x.inspect }
|
|
115
134
|
end
|
|
116
|
-
|
|
135
|
+
data_type = @carray.data_type
|
|
136
|
+
# The same Faces behind a gated (fixlen) surface hand back the value of
|
|
137
|
+
# their storage, so they take the storage's formatter.
|
|
138
|
+
if @carray.face? and data_type == CA_FIXLEN and not @carray.data_class
|
|
139
|
+
storage = @carray.parent.data_type
|
|
140
|
+
data_type = storage unless storage == CA_FIXLEN
|
|
141
|
+
end
|
|
142
|
+
case data_type
|
|
117
143
|
when CA_BOOLEAN
|
|
118
144
|
# Boolean cells fetch as true/false; show compact 1/0 (masked = _).
|
|
119
145
|
# The type name in the inspect header distinguishes this from an int array.
|
|
@@ -129,11 +155,8 @@ class CArray::Inspector # :nodoc:
|
|
|
129
155
|
x.real.inspect, (x.imag >= 0) ? "+" : "-", x.imag.abs.inspect) }
|
|
130
156
|
when CA_FIXLEN
|
|
131
157
|
if @carray.data_class
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
else
|
|
135
|
-
lambda{|x| "%s" % (x.encode.chomp[0, 5]+"...").dump }
|
|
136
|
-
end
|
|
158
|
+
# A record shows its members, as the element's own inspect does.
|
|
159
|
+
lambda{|x| x.inspect }
|
|
137
160
|
else
|
|
138
161
|
if @carray.bytes <= 6
|
|
139
162
|
lambda{|x| "%s" % x.dump }
|
|
@@ -146,7 +169,7 @@ class CArray::Inspector # :nodoc:
|
|
|
146
169
|
end
|
|
147
170
|
end
|
|
148
171
|
|
|
149
|
-
def get_data_spec (level, idx, formatter)
|
|
172
|
+
def get_data_spec (level, idx, formatter, abbrev = true)
|
|
150
173
|
io = +"[ " # mutable buffer; `<<` below appends into it
|
|
151
174
|
ndim = @carray.ndim
|
|
152
175
|
dim = @carray.shape
|
|
@@ -163,7 +186,7 @@ class CArray::Inspector # :nodoc:
|
|
|
163
186
|
if i != dim[level] - 1
|
|
164
187
|
io << ", "
|
|
165
188
|
end
|
|
166
|
-
if io.length > 48 - 2*level
|
|
189
|
+
if abbrev and io.length > 48 - 2*level
|
|
167
190
|
if i < dim[level] - 1
|
|
168
191
|
io << "..."
|
|
169
192
|
over = true
|
|
@@ -182,24 +205,24 @@ class CArray::Inspector # :nodoc:
|
|
|
182
205
|
end
|
|
183
206
|
else
|
|
184
207
|
over = false
|
|
185
|
-
show = [dim[level], 5].min
|
|
208
|
+
show = abbrev ? [dim[level], 5].min : dim[level]
|
|
186
209
|
show.times do |i|
|
|
187
210
|
idx[level] = i
|
|
188
|
-
io << get_data_spec(level+1, idx, formatter)
|
|
211
|
+
io << get_data_spec(level+1, idx, formatter, abbrev)
|
|
189
212
|
if i < show - 1
|
|
190
213
|
io << ",\n" + " " * (level+1)
|
|
191
214
|
end
|
|
192
|
-
if i >= 2 and dim[level] > 5
|
|
215
|
+
if abbrev and i >= 2 and dim[level] > 5
|
|
193
216
|
break
|
|
194
217
|
end
|
|
195
218
|
end
|
|
196
|
-
if dim[level] > 5
|
|
219
|
+
if abbrev and dim[level] > 5
|
|
197
220
|
io << "... ... ..."
|
|
198
221
|
over = true
|
|
199
222
|
end
|
|
200
223
|
if over
|
|
201
224
|
idx[level] = dim[level] - 1
|
|
202
|
-
io << "\n"+ " " * (level+1) + get_data_spec(level+1, idx, formatter)
|
|
225
|
+
io << "\n"+ " " * (level+1) + get_data_spec(level+1, idx, formatter, abbrev)
|
|
203
226
|
end
|
|
204
227
|
end
|
|
205
228
|
io << " ]"
|
|
@@ -219,6 +242,34 @@ class CArray
|
|
|
219
242
|
return CArray::Inspector.new(self).inspect_string
|
|
220
243
|
end
|
|
221
244
|
|
|
245
|
+
# @overload inspect_full
|
|
246
|
+
# The same description as {#inspect}, with every element rendered
|
|
247
|
+
# instead of the `...` preview.
|
|
248
|
+
#
|
|
249
|
+
# `inspect` abbreviates on purpose -- it is what `p`, `irb` and an
|
|
250
|
+
# error message call, and a million-cell array has to stay readable
|
|
251
|
+
# there. `inspect_full` is for the other moment, when the whole
|
|
252
|
+
# array is the thing you came to look at:
|
|
253
|
+
#
|
|
254
|
+
# puts a.inspect_full
|
|
255
|
+
#
|
|
256
|
+
# The header, the layout and the `_` for a masked cell are
|
|
257
|
+
# `inspect`'s; only the eliding is dropped, so for an array small
|
|
258
|
+
# enough that `inspect` was not abbreviating anything the two give
|
|
259
|
+
# the same string.
|
|
260
|
+
#
|
|
261
|
+
# Note that neither of these is `to_s`, which returns the **raw
|
|
262
|
+
# bytes** of the storage rather than anything printable.
|
|
263
|
+
#
|
|
264
|
+
# The result is one String holding every element, so it is as large
|
|
265
|
+
# as the array is: nothing here is streamed, and a line is as long
|
|
266
|
+
# as the last axis makes it.
|
|
267
|
+
#
|
|
268
|
+
# @return [String]
|
|
269
|
+
def inspect_full
|
|
270
|
+
return CArray::Inspector.new(self).inspect_string(abbrev: false)
|
|
271
|
+
end
|
|
272
|
+
|
|
222
273
|
private
|
|
223
274
|
|
|
224
275
|
def desc
|