ruby_list_comprehension 0.1.4 → 0.2.0

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 (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/ruby_list_comprehension.rb +210 -110
  3. metadata +2 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 25d56bf9d1cd2645750c99f4f46678893ba00e6d4075712a5ac7b845aa479412
4
- data.tar.gz: f30a9fbd24e4595c1bde00bb344d9482f499e8ae87cb6e04035ef5123c94b8aa
3
+ metadata.gz: fa7eade3470c5cf0a30200ae86f39f05383804d6ddc0e35faa3fb18d01da7dcc
4
+ data.tar.gz: 1ce2185e0da4ee30e402ee844bd1a87d5beacb2faf263fdd13618ae6e7eba1d7
5
5
  SHA512:
6
- metadata.gz: 90526fa524e2b0377da07dd436f6011fa2d38e1363639caf19d58ed8afb36c04501708058d3ab6feb86f4c8452af9d281d07a3cbd122eba6c51855e6e2c22461
7
- data.tar.gz: 210c7af5d5a6355c7a38a9276e802c689561f299fed9ed23c24bbfe37c6b5c5b35bd27f9e4c7afc3f575cd0473d9f3b4e03ed444cad09ed0cd52a37b68b27ee2
6
+ metadata.gz: a122824fd982fdfecf007a83860b8006a4cefbeed77013be38b10284d3b6c1e3e525d45c131cce0d6dc405af581a417639ee65141bbb85cf07d053c6a23520d5
7
+ data.tar.gz: e9b2476e5f6ac2cf86910218642a21c4c2c7cbd679d70654c4b0ad5f403cac0563b81df25704f8c6392db44e57e4a2d8f56207b62b3b4542ddb762db63201cc8
@@ -1,130 +1,230 @@
1
1
  # frozen_string_literal: true
2
-
3
2
  require 'readline'
3
+ require 'singleton'
4
+ require 'set'
5
+ module RubyList
6
+ FM_REGEX = /for(?<parameter>.*)(?=in)in(?<iterable>.*)(?=do)do(?<mappable>.*)(?=if)if(?<filterable>.+)end/.freeze
7
+ F_REGEX = /for(?<parameter>.*)(?=in)in(?<iterable>.*)(.+)do(.+)(?=if)if(?<filterable>.*)end/.freeze
8
+ M_REGEX = /(?=for)for(?<parameter>.*)(?=in)in(?<iterable>.*)(?=do)do(?<mappable>.*)(?=end)end/.freeze
9
+ I_REGEX = /for(?<parameter>.*)(?=in)in(?<iterable>.*)(?=do)do(?<identity>.+)(?=end)end/.freeze
4
10
 
5
- class ListComprehension
6
- attr_accessor :cache, :caching, :mappable, :filterable, :iterable, :var, :list, :location, :line
7
- attr_reader :c, :version, :op, :cache_count, :filename
11
+ def fetch_capture(data, name)
12
+ data[name.to_sym] if data.names.include?(name.to_s)
13
+ end
8
14
 
9
- def [](list_comp)
10
- if @filename == 'pry' || @filename == 'irb'
11
- @line = Readline::HISTORY.to_a.uniq[-1].strip
12
- start = @line.index('l[') + 2
13
- ending = @line[start..-1].index('end') + 5
14
- @line = @line[start...ending]
15
+ def op_type(str)
16
+ match_map = str.match(M_REGEX)
17
+ match_filter_map = str.match(FM_REGEX)
18
+ match_filter = str.match(F_REGEX)
19
+ match_identity = str.match(I_REGEX)
20
+ parameter = fetch_capture(match_identity, :parameter)
21
+ mappable = fetch_capture(match_map, :mappable)
22
+ if match_filter_map.nil? && match_filter.nil?
23
+ if parameter.strip == mappable.strip || /\A\s*\Z/ === mappable
24
+ return :identity
25
+ else
26
+ return :map
27
+ end
28
+ end
29
+ map_condition = mappable.split('if')[0]
30
+ if fetch_capture(match_map, :mappable).include?('if') && !(parameter.strip == map_condition.strip || /\A\s*\Z/ === mappable)
31
+ return :filter_map
15
32
  else
16
- @location = caller_locations.last.to_s.scan(/\d+/).last.to_i
17
- file = File.open($PROGRAM_NAME)
18
- file_data = file.readlines.map(&:chomp)
19
- file.close
20
- @line = file_data[@location - 1].strip
21
- start = @line.index('l[')
22
- ending = @line[start..-1].index('end')
23
- @line = @line[start+2...ending + 6].chop
33
+ if match_identity[:parameter].strip == match_filter[:filterable].strip
34
+ return :identity
35
+ end
36
+ return :filter
24
37
  end
25
- # p @line
26
- c[@line]
27
38
  end
28
39
 
29
- def initialize
30
- @filename = $PROGRAM_NAME
31
- @cache = {}
32
- @count = 0
33
- @op = {}
34
- @cache_count = 0
35
- @caching = true
36
- @version = RUBY_VERSION
37
- @c = lambda { |list| # check for cached results
38
- return [] unless list.is_a? String
39
-
40
- @list = list.strip
41
- case list
42
- when '{}' then return [{}]
43
- when '[]' then return [[]]
44
- when '' then return []
45
- else list += ' end' unless list.include?('end')
46
- end
47
- raise 'syntax error in list comprehension' if list.length < 10
48
-
49
- if @caching && @cache[list]
50
- @cache_count += 1
51
- return @cache[list]
52
- end
40
+ def denest_builder(nested_list)
41
+ nested_list
42
+ nested_list
43
+ len = 0
44
+ nested_list
45
+ fin = nested_list.split('[').delete_if{|x| x == ""}
46
+ fin = fin.map{|str|str[0..-1] + " end"}
47
+ fin = fin.join.split('],')
48
+ fin = fin.join.split(' end')
49
+ fin.map{|x|"#{x} end"}
50
+ end
53
51
 
54
- copy1 = list[0..-1]
55
- arr = list.split
56
- @var = arr[1]
52
+ def denest_flattener(nested_list)
53
+ nested_list
54
+ len = nested_list.index('for')
55
+ nested_list_array = nested_list.split(" end").join
56
+ nested_list_array.split(' end')
57
+ nested_list_array = nested_list_array.split('for')[1..-1].map!{|x|"for#{x} end"}
58
+ end
57
59
 
58
- # replace initial semicolon with do if needed for parser
59
- if arr[3..-1].any? { |x| x.include?(';') } && !arr.include?('do')
60
- arr = list.to_s.sub(';', ' do ').split
61
- end
62
- arr.insert(-2, 'do') if arr.none? { |x| x.include?('do') } && arr.none? { |x| x.include?('do') }
63
-
64
- # pre-eval to check for invalid syntax
65
- begin
66
- res = instance_eval(arr.join(' '))
67
- return [] if res.nil?
68
- rescue SyntaxError => se
69
- raise 'incorrect syntax for list comprehension' + "\n" + se.to_s
60
+ def create_protocol(denested_array, operator_array)
61
+ operator_array.each_with_object({}).with_index do |(e, o), i|
62
+ case e
63
+ when :filter
64
+ o[i] = [e, denested_array[i].match(F_REGEX)]
65
+ when :filter_map
66
+ o[i] = [e, denested_array[i].match(FM_REGEX)]
67
+ when :map
68
+ o[i] = [e, denested_array[i].match(M_REGEX)]
69
+ when :identity
70
+ o[i] = [e, denested_array[i].match(I_REGEX)]
70
71
  end
72
+ end
73
+ end
74
+
75
+ def execute_comprehension(hash, flatten = true)
76
+ $process_str = ''
77
+ op_array = hash.values.map{|x,|x}
78
+ start_index = op_array.length - 1
79
+ nested = op_array.length > 1
80
+ match_data_array = hash.values.map{|_,x|x}
71
81
 
72
- # check for hash to parse csv's
73
- iterable = arr[3]
74
- return [{}] if iterable == '{}'
75
-
76
- m_data = copy1[3...copy1.rindex('do')].match(/({.+[=>:].+})/)
77
- if m_data
78
- first_hash = m_data[0].split(';')[0]
79
- if @list.index(first_hash) == 9
80
- iterable = first_hash if instance_eval(first_hash).is_a? Hash
82
+ i = 0
83
+ until op_array.empty?
84
+ current_data = match_data_array[i]
85
+ current_op = op_array[0]
86
+ $iterable_string = fetch_capture(current_data, 'iterable')
87
+ $mappable = fetch_capture(current_data, 'mappable')
88
+ $parameter = fetch_capture(current_data, 'parameter')
89
+ $filterable = fetch_capture(current_data, 'filterable')
90
+ $identity = fetch_capture(current_data, 'identity')
91
+ $iterable = Array($iterable) if $iterable.class == Set
92
+ $iterable = fetch_capture(current_data, 'iterable') if i != 0
93
+ $iterable = $iterable_string if !flatten && @nested
94
+ case op_array.shift
95
+ when :identity
96
+ flatten = @flattener
97
+ if !@nested
98
+ return *$iterable
99
+ elsif i.zero? && !@flattener
100
+ $process_str += "(#$iterable).map"
101
+ elsif i.zero? && @flattener
102
+ $process_str += "(#$iterable).flat_map"
103
+ elsif !i.zero? && i != start_index
104
+ $process_str += "{(#$iterable).map"
105
+ elsif i == start_index
106
+ $process_str += "{(#$iterable_string).map(&:itself)"
107
+ $process_str += '}' * (start_index)
108
+ elsif nested && i.zero? && !flatten
109
+ $process_str += "(#$iterable).map"
110
+ elsif i != 0 && nested
111
+ $process_str += "(#$iterable_string).map"
81
112
  end
82
- end
83
- @iterable = instance_eval(iterable)
84
- # p @iterable
85
- if_condition = arr.include?('if') ? arr[arr.index('if') + 1...-1] : ['true']
86
- map_condition = arr[arr.index('do') + 1...(arr.index('if') || arr.index('end'))]
87
- @filterable = if_condition.join(' ')
88
- @mappable = map_condition.join(' ')
89
- ### list_comprehension identity currently uses for(each) as if normal ruby
90
- # p @filterable
91
- # p @mappable
92
- return *@iterable if (@mappable == @var || @mappable == '') && (@filterable == 'true' || @filterable == @var)
93
-
94
- # define a method to handle the transformation to list_comp
95
- self.class.send(:define_method, 'lc') do |arr|
96
-
97
- if @mappable == @var
98
- @op[@count] = 'filter'
99
- @count += 1
100
- return @iterable.filter { |x| instance_eval(@filterable.gsub(@var, x.to_s)) }
113
+ when :map
114
+ if i == 0
115
+ $mappable = $parameter if /\A\s*\Z/ === $mappable
116
+ $process_str += "(#$iterable).map{|(#$parameter)|(#$mappable)}"
117
+ elsif i == 0 && flatten
118
+ $mappable = $parameter if /\A\s*\Z/ === $mappable
119
+ $process_str += "(#$iterable).flat_map{|(#$parameter)|(#$mappable)"
120
+ elsif i == 0 && !flatten
121
+ $mappable = $parameter if /\A\s*\Z/ === $mappable
122
+ $process_str += "(#$iterable).map{|(#$parameter)|(#$mappable)"
123
+ elsif i == start_index
124
+ $mappable = $parameter if /\A\s*\Z/ === $mappable
125
+ $process_str += "{(#$iterable).map{|(#$parameter)|(#$mappable)"
126
+ $process_str += '}' * (start_index+1)
127
+ elsif i != start_index
128
+ $process_str += "{(#$iterable_string).map{|(#$parameter)|(#$mappable)"
101
129
  end
102
-
103
- if @filterable == 'true' || @filterable == @var
104
- @op[@count] = 'map'
105
- @count += 1
106
- return @iterable.map { |x| instance_eval(@mappable.gsub(@var, x.to_s)) }
130
+ when :filter
131
+ if op_array.empty?
132
+ $process_str += "(#$iterable).filter{|(#$parameter)|(#$filterable)}"
107
133
  end
108
-
109
- filter_map_condition_args = "#@mappable if #@filterable"
110
- # check Ruby Version stored in @version
111
- if @version >= '2.7.0'
112
- @op[@count] = 'filter_map'
113
- @count += 1
114
- return @iterable.filter_map { |x| instance_eval(filter_map_condition_args.gsub(@var, x.to_s)) }
134
+ when :filter_map
135
+ if op_array.empty? && RUBY_VERSION >= '2.7.0'
136
+ $process_str += "(#$iterable).filter_map{|(#$parameter)|(#$mappable) if (#$filterable)}"
115
137
  else
116
- @op[@count] ="map&compact"
117
- @count += 1
118
- @iterable.map { |x| instance_eval(filter_map_condition_args.gsub!(@var, x.to_s)) }.compact!
138
+ $process_str += "(#$iterable).map{|(#$parameter)|(#$mappable) if (#$filterable)}.compact"
119
139
  end
120
140
  end
121
- list_comp = lc(arr)
122
- unless list_comp.is_a?(Array) || list_comp.is_a?(Hash)
123
- list_comp = [list_comp]
124
- end
125
- list_comp = list_comp == [nil] ? [] : list_comp
126
- # p @op
127
- @cache[list] = list_comp if @caching
128
- }
141
+ i += 1
142
+ # p $process_str
143
+ end
144
+ begin
145
+ current_op
146
+ instance_eval($process_str)
147
+ rescue SyntaxError => e
148
+ 'List imcomprehensible :' + e.backtrace_locations.to_s
149
+ end
150
+ end
151
+
152
+ def one_shot(str)
153
+ len = str.scan('for ').length
154
+ @nested = (len > 1)
155
+ nested = @nested
156
+ if nested
157
+ nest_mode = str.include?('end end') && !nested ? :flatten : :matrix
158
+ nest_array = @flattener ? denest_flattener(str) : denest_builder(str)
159
+ else
160
+ nest_array = [str]
161
+ end
162
+ op_array = nest_array.map(&method(:op_type))
163
+ hash = create_protocol(nest_array, op_array)
164
+ begin
165
+ execute_comprehension(hash, nest_mode==nested)
166
+ rescue SyntaxError => se
167
+ puts 'RESCUED!' + se.backtrace_locations.to_s
168
+ end
169
+ end
170
+
171
+ class ListComprehension
172
+ include Singleton
173
+ include RubyList
174
+ attr_accessor :cache, :caching, :mappable, :filterable, :iterable, :var, :list, :location, :line, :count
175
+ attr_accessor :flattener, :len, :nested, :nested_var, :file, :list_comp, :final_iterable_value
176
+ attr_accessor :count
177
+ REPL_LIST = %w[:irb :pry].freeze
178
+
179
+ def initialize
180
+ @cache = {}
181
+ @caching = true
182
+ @version = RUBY_VERSION
183
+ end
184
+
185
+ def [](*iterable)
186
+ return [] if iterable.empty? || iterable.nil?
187
+
188
+
189
+ @flattener = iterable.length == 1
190
+ iterable = iterable[0] if iterable.length == 1
191
+ @final_iterable_value = iterable
192
+ @list_comp = iterable
193
+ $iterable = iterable
194
+ @filename = $PROGRAM_NAME
195
+ @line = REPL_LIST.include?(@filename.to_sym) ? locate_list_repl : locate_list_file
196
+ return @cache[@line] if @cache.has_key?(@line)
197
+
198
+ @cache[@line] = one_shot(@line)
199
+ end
200
+
201
+ def locate_list_repl
202
+ @line = Readline::HISTORY.to_a.reverse.uniq.reverse[-1]
203
+ start = @line.index('l[') + 2
204
+ ending = @line[start..-1].index('end') + 5
205
+ @line = @line[start...ending]
206
+ end
207
+
208
+ def retrieve_file_data
209
+ @file = File.open($PROGRAM_NAME)
210
+ file_data = @file.readlines.map(&:chomp)
211
+ @file.close
212
+ file_data
213
+ end
214
+
215
+ def locate_list_file
216
+ @location = caller_locations.last.to_s.scan(/\d+/).last.to_i
217
+ @line = retrieve_file_data[@location - 1].strip.match(/\$l(?<line>.+)/)[:line][0...-1]
218
+
219
+ # if @line.is_a? Array
220
+ # @line.each_with_index do |list, idx|
221
+ # list.split[3] == iterable.to_s
222
+ # @line = list[0..list.index('end]') + 2] if list.split[3] == iterable.to_s
223
+ # end
224
+ # end
225
+ @line
226
+
227
+ end
129
228
  end
229
+ $l = ListComprehension.instance
130
230
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_list_comprehension
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Michael
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2010-04-28 00:00:00.000000000 Z
11
+ date: 2019-10-02 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: List Comprehensions for Ruby
14
14
  email: smichael@appacademy.io