yap-shell-parser 0.2.2 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 8e9067d2b8034ad2ca424fbdb61b5c939be1470e
4
- data.tar.gz: 1ae4cf2f162e5c19bd8419619034759e069836d5
3
+ metadata.gz: b1f871f25358079c9bb5bfda0d59b77504b36b88
4
+ data.tar.gz: ae41ab9e03b55318f48c9fd739448600c30e545c
5
5
  SHA512:
6
- metadata.gz: 7bfc31b24ff9925175ded8555a3a8462c4d88fca804f035432c572c2a623069fe370437fa8e14097494fc53e40eccfe97396034763ec7c476a8326d8a99c455d
7
- data.tar.gz: 8534df6ecc65ec92307d491e3dd760ac3129cc3808df6e2f07b2d8a733cafc1bfce0ecb56148c6ea4fa40d5233095af6fb28740109d24b7eb9fee43429ebf1ba
6
+ metadata.gz: 2b6dc9b5ebfee853ef445c1f8a60ed9e9ca8d81365497f2ae5fda0e64721ffa215c451ebd1cd95c8485759fa918d0085c7b8dfc75a1cbf7be823d2969e6f8356
7
+ data.tar.gz: fd2670a3b93ec221a664a71e0280ef79b5cf25f7a968abde4d2655188f89c5bbcf2de784f574f3a8ecd4355ab007ea1fcadc7d7253477b12e02962d0e37a4c34
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 2.2.3
@@ -3,7 +3,7 @@
3
3
  # convert Array-like string into Ruby's Array.
4
4
 
5
5
  class Yap::Shell::ParserImpl
6
- token Command LiteralCommand Argument Heredoc InternalEval Separator Conditional Pipe Redirection LValue RValue BeginCommandSubstitution EndCommandSubstitution
6
+ token Command LiteralCommand Argument Heredoc InternalEval Separator Conditional Pipe Redirection LValue RValue BeginCommandSubstitution EndCommandSubstitution Range BlockBegin BlockEnd BlockParams
7
7
  #
8
8
  # prechigh
9
9
  # # left '**' '*' '/' '%'
@@ -24,10 +24,24 @@ stmts : stmts Separator stmt
24
24
  { result = StatementsNode.new(val[0], val[2]) }
25
25
  | stmt
26
26
  { result = StatementsNode.new(val[0]) }
27
+ | range_stmt
27
28
 
28
29
  stmt : stmt Conditional pipeline
29
30
  { result = ConditionalNode.new(val[1].value, val[0], val[2]) }
30
31
  | pipeline
32
+ | block_stmt
33
+
34
+ block_stmt : range_stmt BlockBegin stmts BlockEnd
35
+ { result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[2]) } }
36
+ | range_stmt BlockBegin BlockParams stmts BlockEnd
37
+ { result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[3], params: val[2].value) } }
38
+ | stmt BlockBegin stmts BlockEnd
39
+ { result = BlockNode.new(val[0], val[2]) }
40
+ | stmt BlockBegin BlockParams stmts BlockEnd
41
+ { result = BlockNode.new(val[0], val[3], params: val[2].value) }
42
+
43
+ range_stmt : Range
44
+ { result = RangeNode.new(val[0]) }
31
45
 
32
46
  pipeline : pipeline Pipe stmts2
33
47
  { result = PipelineNode.new(val[0], val[2]) }
@@ -105,6 +119,7 @@ end
105
119
  @yydebug = true
106
120
 
107
121
  @q = Yap::Shell::Parser::Lexer.new.tokenize(str)
122
+ pp @q if ENV["DEBUG"]
108
123
  # @q.push [false, '$'] # is optional from Racc 1.3.7
109
124
  # puts @q.inspect
110
125
  # puts "---- parse tree follows ----"
@@ -119,6 +134,7 @@ end
119
134
  ---- footer
120
135
 
121
136
  if $0 == __FILE__
137
+ require 'pry'
122
138
  $LOAD_PATH.unshift File.dirname(__FILE__) + "/lib/"
123
139
  require 'yap/shell/parser/lexer'
124
140
  require 'yap/shell/parser/nodes'
@@ -127,7 +143,9 @@ if $0 == __FILE__
127
143
  # "`git cbranch`",
128
144
  # "`git cbranch`.bak",
129
145
  # "echo `echo hi`",
130
- "echo `echo hi` foo bar baz",
146
+ # "ls *.rb te* { |f| f }",
147
+ # "f { |a, b, c| echo foo }"
148
+ "(0..3) as n : echo hi",
131
149
  # "`hi``bye` `what`",
132
150
  # "echo && `what` && where is `that`thing | `you know`",
133
151
  ].each do |src|
@@ -2,6 +2,9 @@ require 'ostruct'
2
2
 
3
3
  module Yap::Shell
4
4
  class Parser::Lexer
5
+ class Error < ::StandardError ; end
6
+ class HeredocMissingEndDelimiter < Error ; end
7
+
5
8
  class Token
6
9
  include Comparable
7
10
 
@@ -37,18 +40,29 @@ module Yap::Shell
37
40
  ARG = /[^\s;\|\(\)\{\}\[\]\&\!\\\<`][^\s;\|\(\)\{\}\[\]\&\>\<`]*/
38
41
  COMMAND = /\A(#{ARG})/
39
42
  LITERAL_COMMAND = /\A\\(#{ARG})/
40
- WHITESPACE = /\A[^\n\S]+/
43
+ WHITESPACE = /\A\s+/
41
44
  LH_ASSIGNMENT = /\A(([A-z_][\w]*)=)/
42
45
  RH_VALUE = /\A(\S+)/
43
46
  STATEMENT_TERMINATOR = /\A(;)/
44
47
  PIPE_TERMINATOR = /\A(\|)/
45
48
  CONDITIONAL_TERMINATOR = /\A(&&|\|\|)/
46
- HEREDOC = /\A<<-?([A-z0-9]+)\s*^(.*)?(^\s*\1\s*$)/m
49
+ HEREDOC_START = /\A<<-?([A-z0-9]+)\s*\n/
47
50
  INTERNAL_EVAL = /\A(?:(\!)|([0-9]+))/
48
51
  SUBGROUP = /\A(\(|\))/
49
52
  REDIRECTION = /\A(([12]?>&?[12]?)\s*(?![12]>)(#{ARG})?)/
50
53
  REDIRECTION2 = /\A((&>|<)\s*(#{ARG}))/
51
54
 
55
+ NUMERIC_RANGE = /\A\(((\d+)\.\.(\d+))\)(\.each)?/
56
+ NUMERIC_RANGE_W_CALL = /\A\(((\d+)\.\.(\d+))\)(\.each)?\s*:\s*/
57
+ NUMERIC_RANGE_W_PARAM = /\A(\((\d+)\.\.(\d+))\)\s+as\s+([A-z0-9,\s]+)\s*:\s*/
58
+ NUMERIC_REPETITION = /\A((\d+)(\.times))\s*/
59
+ NUMERIC_REPETITION_2 = /\A((\d+)(\.times))\s*:\s*/
60
+ NUMERIC_REPETITION_W_PARAM = /\A((\d+)(\.times))\s+as\s+([A-z0-9,\s]+)\s*:\s*/
61
+
62
+ BLOCK_BEGIN = /\A\s*(\{)\s*(?:\|\s*([A-z0-9,\s]+)\s*\|)?/
63
+ BLOCK_END = /\A\s*(\})\s*/
64
+
65
+ SPLIT_BLOCK_PARAMS_RGX = /\s*,\s*|\s*/
52
66
 
53
67
  # Loop over the given input and yield command substitutions. This yields
54
68
  # an object that responds to #str, and #position.
@@ -88,6 +102,7 @@ module Yap::Shell
88
102
  @tokens = []
89
103
  @lineno = 0
90
104
  @looking_for_args = false
105
+ @tokens_to_add_when_done = []
91
106
 
92
107
  max = 100
93
108
  count = 0
@@ -96,6 +111,8 @@ module Yap::Shell
96
111
 
97
112
  while process_next_chunk.call
98
113
  result =
114
+ block_token ||
115
+ numerical_range_token ||
99
116
  command_substitution_token ||
100
117
  subgroup_token ||
101
118
  assignment_token ||
@@ -115,6 +132,10 @@ module Yap::Shell
115
132
  @current_position += result.to_i
116
133
  end
117
134
 
135
+ @tokens_to_add_when_done.each do |args|
136
+ token *args
137
+ end
138
+
118
139
  @tokens
119
140
  end
120
141
 
@@ -124,6 +145,71 @@ module Yap::Shell
124
145
  @tokens.push [tag, Token.new(tag, value, lineno:@lineno, attrs:attrs)]
125
146
  end
126
147
 
148
+ def block_token
149
+ if md=@chunk.match(BLOCK_BEGIN)
150
+ @looking_for_args = false
151
+ token :BlockBegin, md[1]
152
+ if md[2]
153
+ params = md[2].split(SPLIT_BLOCK_PARAMS_RGX)
154
+ token :BlockParams, params
155
+ end
156
+ md[0].length
157
+ elsif md=@chunk.match(BLOCK_END)
158
+ @looking_for_args = false
159
+ token :BlockEnd, md[1]
160
+ md[0].length
161
+ end
162
+ end
163
+
164
+ def numerical_range_token
165
+ return if @looking_for_args
166
+
167
+ if md=@chunk.match(NUMERIC_RANGE_W_CALL)
168
+ start, stop = md[2].to_i, md[3].to_i
169
+ token :Range, (start..stop)
170
+ token :BlockBegin, '{'
171
+ @tokens_to_add_when_done << [:BlockEnd, '}']
172
+ md[0].length
173
+
174
+ elsif md=@chunk.match(NUMERIC_RANGE_W_PARAM)
175
+ start, stop = md[2].to_i, md[3].to_i
176
+ token :Range, (start..stop)
177
+ token :BlockBegin, '{'
178
+ params = md[4].split(SPLIT_BLOCK_PARAMS_RGX)
179
+ token :BlockParams, params
180
+ @tokens_to_add_when_done << [:BlockEnd, '}']
181
+ md[0].length
182
+
183
+ elsif md=@chunk.match(NUMERIC_REPETITION_2)
184
+ start, stop = 1, md[2].to_i
185
+ token :Range, (start..stop)
186
+ token :BlockBegin, '{'
187
+ @tokens_to_add_when_done << [:BlockEnd, '}']
188
+ md[0].length
189
+
190
+ elsif md=@chunk.match(NUMERIC_REPETITION_W_PARAM)
191
+ start, stop = 1, md[2].to_i
192
+ token :Range, (start..stop)
193
+ token :BlockBegin, '{'
194
+ params = md[4].split(SPLIT_BLOCK_PARAMS_RGX)
195
+ token :BlockParams, params
196
+ @tokens_to_add_when_done << [:BlockEnd, '}']
197
+ md[0].length
198
+
199
+ elsif md=@chunk.match(NUMERIC_REPETITION)
200
+ start, stop = 1, md[2].to_i
201
+ token :Range, (start..stop)
202
+ md[0].length
203
+
204
+ elsif md=@chunk.match(NUMERIC_RANGE)
205
+ start, stop = md[2].to_i, md[3].to_i
206
+ token :Range, (start..stop)
207
+ md[0].length
208
+
209
+ end
210
+
211
+ end
212
+
127
213
  def command_token
128
214
  if !@looking_for_args && md=@chunk.match(COMMAND)
129
215
  @looking_for_args = true
@@ -149,9 +235,31 @@ module Yap::Shell
149
235
  end
150
236
 
151
237
  def heredoc_token
152
- if md=@chunk.match(HEREDOC)
153
- token :Heredoc, md[2]
154
- md[0].length
238
+ if md=@chunk.match(HEREDOC_START)
239
+ delimiter = md[1]
240
+ str = @chunk[md[0].length..-1]
241
+ consumed_length = md[0].length
242
+
243
+ delimeter_regex = Regexp.escape(delimiter)
244
+
245
+ contents = ""
246
+ found_ending_delimiter = false
247
+ str.lines.each do |line|
248
+ if md=line.match(/^(.*?)\s*#{delimeter_regex}\s*$/m)
249
+ contents << $1
250
+ found_ending_delimiter = true
251
+ else
252
+ contents << line
253
+ end
254
+ consumed_length += line.length
255
+ end
256
+
257
+ unless found_ending_delimiter
258
+ raise HeredocMissingEndDelimiter, "Missing end delimiter on #{@chunk}"
259
+ end
260
+
261
+ token :Heredoc, contents
262
+ consumed_length
155
263
  end
156
264
  end
157
265
 
@@ -207,7 +315,7 @@ module Yap::Shell
207
315
  result = process_string @chunk[characters_read..-1], ch
208
316
  str << result.str
209
317
  characters_read += result.consumed_length
210
- elsif prev_char != '\\' && ch =~ /[\s\|;&\)]/
318
+ elsif prev_char != '\\' && ch =~ /[\s\|;&\)\}]/
211
319
  break
212
320
  else
213
321
  str << ch
@@ -258,5 +258,47 @@ module Yap::Shell
258
258
  end
259
259
  end
260
260
 
261
+ class BlockNode
262
+ include Visitor
263
+
264
+ attr_accessor :head, :tail, :params
265
+
266
+ def initialize(head, tail, params: [])
267
+ @head = head
268
+ @tail = tail
269
+ @params = params
270
+ end
271
+
272
+ def to_s(indent:0)
273
+ if @counter_reference
274
+ "BlockNode(#{@head.inspect}, tail: #{@tail.inspect} params: #{@params.inspect})"
275
+ else
276
+ "BlockNode(#{@head.inspect}, tail: #{@tail.inspect} params: #{@params.inspect})"
277
+ end
278
+ end
279
+
280
+ def inspect
281
+ to_s
282
+ end
283
+ end
284
+
285
+ class RangeNode
286
+ include Visitor
287
+
288
+ attr_accessor :head, :tail
289
+
290
+ def initialize(head, tail=nil)
291
+ @head = head
292
+ @tail = tail
293
+ end
294
+
295
+ def to_s(indent:0)
296
+ "(#{@head.inspect}, tail: #{tail.inspect})"
297
+ end
298
+
299
+ def inspect
300
+ to_s
301
+ end
302
+ end
261
303
  end
262
304
  end
@@ -3,7 +3,7 @@ require 'yap/shell/parser'
3
3
  module Yap
4
4
  module Shell
5
5
  module Parser
6
- VERSION = "0.2.2"
6
+ VERSION = "0.3.1"
7
7
  end
8
8
  end
9
9
  end