yap-shell-parser 0.2.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -0
- data/lib/yap/shell/parser/grammar.y +20 -2
- data/lib/yap/shell/parser/lexer.rb +114 -6
- data/lib/yap/shell/parser/nodes.rb +42 -0
- data/lib/yap/shell/parser/version.rb +1 -1
- data/lib/yap/shell/parser_impl.rb +196 -116
- data/spec/spec_helper.rb +27 -0
- data/spec/yap/shell/lexer_spec.rb +298 -5
- data/spec/yap/shell/parser_spec.rb +42 -55
- data/yap-shell-parser.gemspec +1 -1
- metadata +5 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b1f871f25358079c9bb5bfda0d59b77504b36b88
|
4
|
+
data.tar.gz: ae41ab9e03b55318f48c9fd739448600c30e545c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
"
|
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
|
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
|
-
|
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(
|
153
|
-
|
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
|