ruby_list_comprehension 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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