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 +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
|