yap-shell-parser 0.4.0 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/yap/shell/parser/grammar.y +14 -14
- data/lib/yap/shell/parser/lexer.rb +39 -9
- data/lib/yap/shell/parser/nodes.rb +25 -30
- data/lib/yap/shell/parser/version.rb +1 -1
- data/lib/yap/shell/parser_impl.rb +14 -14
- data/spec/yap/shell/lexer_spec.rb +124 -24
- data/spec/yap/shell/parser_spec.rb +3 -0
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 59dc1f16d41d54f7c14d7ff68abc1ad404d7bd39
|
4
|
+
data.tar.gz: 95d1cfdfbe7ad7e35b6cc2e4be77a3cba9326a26
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 0f324a8808805a0f4d64c0f63248040639e4dd06c48e6e818341fc6d1a895eee6330a71f4db3d1d3def9f12f6cf00605bc863b1c75d8003aa45f023678db1ee4
|
7
|
+
data.tar.gz: b7a672e1cfabd50b07871b2f67f8042349cbd367cd53e1d8d3eafd23eaad7655c254f10484c5824893152e3be1711759e67cd07efe8a47aa3e7381ba0b8f3e5c
|
@@ -29,18 +29,18 @@ stmts : stmts Separator stmt
|
|
29
29
|
| range_stmt
|
30
30
|
|
31
31
|
stmt : stmt Conditional pipeline
|
32
|
-
{ result = ConditionalNode.new(val[1]
|
32
|
+
{ result = ConditionalNode.new(val[1], val[0], val[2]) }
|
33
33
|
| pipeline
|
34
34
|
| block_stmt
|
35
35
|
|
36
36
|
block_stmt : range_stmt BlockBegin stmts BlockEnd
|
37
37
|
{ result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[2]) } }
|
38
38
|
| range_stmt BlockBegin BlockParams stmts BlockEnd
|
39
|
-
{ result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[3], params: val[2]
|
39
|
+
{ result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[3], params: val[2]) } }
|
40
40
|
| stmt BlockBegin stmts BlockEnd
|
41
41
|
{ result = BlockNode.new(val[0], val[2]) }
|
42
42
|
| stmt BlockBegin BlockParams stmts BlockEnd
|
43
|
-
{ result = BlockNode.new(val[0], val[3], params: val[2]
|
43
|
+
{ result = BlockNode.new(val[0], val[3], params: val[2]) }
|
44
44
|
|
45
45
|
range_stmt : Range
|
46
46
|
{ result = RangeNode.new(val[0]) }
|
@@ -74,10 +74,10 @@ command_w_heredoc : command_w_redirects Heredoc
|
|
74
74
|
{ val[0].heredoc = val[1] ; result = val[0] }
|
75
75
|
| command_w_redirects
|
76
76
|
| Redirection
|
77
|
-
{ result = RedirectionNode.new(val[0]
|
77
|
+
{ result = RedirectionNode.new(val[0]) }
|
78
78
|
|
79
79
|
command_w_redirects : command_w_redirects Redirection
|
80
|
-
{ val[0].redirects << RedirectionNode.new(val[1]
|
80
|
+
{ val[0].redirects << RedirectionNode.new(val[1]) ; result = val[0] }
|
81
81
|
| command_w_vars
|
82
82
|
| command
|
83
83
|
| vars
|
@@ -86,28 +86,28 @@ command_w_vars : vars command
|
|
86
86
|
{ result = EnvWrapperNode.new(val[0], val[1]) }
|
87
87
|
|
88
88
|
vars : vars LValue RValue
|
89
|
-
{ val[0].add_var(val[1]
|
89
|
+
{ val[0].add_var(val[1], ArgumentNode.new(val[2])) ; result = val[0] }
|
90
90
|
| LValue RValue
|
91
|
-
{ result = EnvNode.new(val[0]
|
91
|
+
{ result = EnvNode.new(val[0], ArgumentNode.new(val[1])) }
|
92
92
|
|
93
93
|
command : command2
|
94
94
|
|
95
95
|
command2: Command
|
96
|
-
{ result = CommandNode.new(val[0]
|
96
|
+
{ result = CommandNode.new(val[0]) }
|
97
97
|
| Command args
|
98
|
-
{ result = CommandNode.new(val[0]
|
98
|
+
{ result = CommandNode.new(val[0], val[1].flatten) }
|
99
99
|
| LiteralCommand
|
100
|
-
{ result = CommandNode.new(val[0]
|
100
|
+
{ result = CommandNode.new(val[0], literal:true) }
|
101
101
|
| LiteralCommand args
|
102
|
-
{ result = CommandNode.new(val[0]
|
102
|
+
{ result = CommandNode.new(val[0], val[1].flatten, literal:true) }
|
103
103
|
|
104
104
|
args : Argument
|
105
|
-
{ result = [ArgumentNode.new(val[0]
|
105
|
+
{ result = [ArgumentNode.new(val[0])] }
|
106
106
|
| args Argument
|
107
|
-
{ result = [val[0], ArgumentNode.new(val[1]
|
107
|
+
{ result = [val[0], ArgumentNode.new(val[1])].flatten }
|
108
108
|
|
109
109
|
internal_eval : InternalEval
|
110
|
-
{ result = InternalEvalNode.new(val[0]
|
110
|
+
{ result = InternalEvalNode.new(val[0]) }
|
111
111
|
|
112
112
|
---- header
|
113
113
|
if $0 ==__FILE__
|
@@ -4,6 +4,8 @@ module Yap::Shell
|
|
4
4
|
class Parser::Lexer
|
5
5
|
class Error < ::StandardError ; end
|
6
6
|
class HeredocMissingEndDelimiter < Error ; end
|
7
|
+
class LineContinuationFound < Error ; end
|
8
|
+
class NonterminatedString < Error ; end
|
7
9
|
|
8
10
|
class Token
|
9
11
|
include Comparable
|
@@ -54,7 +56,7 @@ module Yap::Shell
|
|
54
56
|
REDIRECTION2 = /\A((&>|<)\s*(#{ARG}))/
|
55
57
|
|
56
58
|
NUMERIC_RANGE = /\A\(((\d+)\.\.(\d+))\)(\.each)?/
|
57
|
-
NUMERIC_RANGE_W_CALL
|
59
|
+
NUMERIC_RANGE_W_CALL = /\A\(((\d+)\.\.(\d+))\)(\.each)?\s*:\s*/
|
58
60
|
NUMERIC_RANGE_W_PARAM = /\A(\((\d+)\.\.(\d+))\)\s+as\s+([A-z0-9,\s]+)\s*:\s*/
|
59
61
|
NUMERIC_REPETITION = /\A((\d+)(\.times))/
|
60
62
|
NUMERIC_REPETITION_2 = /\A((\d+)(\.times))\s*:\s*/
|
@@ -63,6 +65,8 @@ module Yap::Shell
|
|
63
65
|
BLOCK_BEGIN = /\A\s+(\{)\s*(?:\|\s*([A-z0-9,\s]+)\s*\|)?/
|
64
66
|
BLOCK_END = /\A\s+(\})\s*/
|
65
67
|
|
68
|
+
LINE_CONTINUATION = /.*\\\Z/
|
69
|
+
|
66
70
|
SPLIT_BLOCK_PARAMS_RGX = /\s*,\s*|\s*/
|
67
71
|
|
68
72
|
# Loop over the given input and yield command substitutions. This yields
|
@@ -99,7 +103,7 @@ module Yap::Shell
|
|
99
103
|
end
|
100
104
|
|
101
105
|
def tokenize(str)
|
102
|
-
@
|
106
|
+
@chunk = str
|
103
107
|
@tokens = []
|
104
108
|
@lineno = 0
|
105
109
|
@looking_for_args = false
|
@@ -111,6 +115,9 @@ module Yap::Shell
|
|
111
115
|
last_position = -1
|
112
116
|
process_next_chunk = -> { @chunk = str.slice(@current_position..-1) ; @chunk != "" }
|
113
117
|
|
118
|
+
strip_line_continutations_before_newlines
|
119
|
+
line_continuation_token
|
120
|
+
|
114
121
|
while process_next_chunk.call
|
115
122
|
result =
|
116
123
|
comment_token ||
|
@@ -120,12 +127,12 @@ module Yap::Shell
|
|
120
127
|
subgroup_token ||
|
121
128
|
assignment_token ||
|
122
129
|
literal_command_token ||
|
130
|
+
string_token ||
|
123
131
|
command_token ||
|
124
132
|
whitespace_token ||
|
125
133
|
terminator_token ||
|
126
134
|
redirection_token ||
|
127
135
|
heredoc_token ||
|
128
|
-
string_argument_token ||
|
129
136
|
argument_token ||
|
130
137
|
internal_eval_token
|
131
138
|
|
@@ -325,15 +332,16 @@ module Yap::Shell
|
|
325
332
|
prev_char = ''
|
326
333
|
loop do
|
327
334
|
ch = @chunk[characters_read]
|
328
|
-
|
335
|
+
|
329
336
|
if %w(' ").include?(ch)
|
337
|
+
@quoted_by = ch
|
330
338
|
result = process_string @chunk[characters_read..-1], ch
|
331
339
|
str << result.str
|
332
340
|
characters_read += result.consumed_length
|
333
341
|
elsif ch == '\\'
|
334
342
|
# no-op
|
335
343
|
characters_read += 1
|
336
|
-
elsif prev_char != '\\' && ch =~ /[\s\|;&\)
|
344
|
+
elsif prev_char != '\\' && ch =~ /[\s\|;&\(\)]/
|
337
345
|
break
|
338
346
|
else
|
339
347
|
str << ch
|
@@ -346,7 +354,11 @@ module Yap::Shell
|
|
346
354
|
end
|
347
355
|
|
348
356
|
if characters_read > 0
|
349
|
-
|
357
|
+
attrs = {}
|
358
|
+
if @quoted_by
|
359
|
+
attrs.merge!(quoted_by: @quoted_by)
|
360
|
+
end
|
361
|
+
token :Argument, str, attrs: attrs
|
350
362
|
characters_read
|
351
363
|
else
|
352
364
|
nil
|
@@ -363,7 +375,7 @@ module Yap::Shell
|
|
363
375
|
@chunk = @chunk[i..-1]
|
364
376
|
if %w(' ").include?(@chunk[0])
|
365
377
|
result = process_string @chunk[0..-1], @chunk[0]
|
366
|
-
token :RValue, result.str
|
378
|
+
token :RValue, result.str, attrs: { quoted_by: @chunk[0] }
|
367
379
|
consumed_length += result.consumed_length
|
368
380
|
elsif md=@chunk.match(RH_VALUE)
|
369
381
|
token :RValue, md[1]
|
@@ -373,6 +385,19 @@ module Yap::Shell
|
|
373
385
|
end
|
374
386
|
end
|
375
387
|
|
388
|
+
def line_continuation_token
|
389
|
+
if @chunk.match(LINE_CONTINUATION)
|
390
|
+
raise(
|
391
|
+
LineContinuationFound,
|
392
|
+
"Expected more input, line continutation found"
|
393
|
+
)
|
394
|
+
end
|
395
|
+
end
|
396
|
+
|
397
|
+
def strip_line_continutations_before_newlines
|
398
|
+
@chunk.gsub!(/\\\n/, '\\')
|
399
|
+
end
|
400
|
+
|
376
401
|
def terminator_token
|
377
402
|
if md=@chunk.match(CONDITIONAL_TERMINATOR)
|
378
403
|
@looking_for_args = false
|
@@ -390,10 +415,14 @@ module Yap::Shell
|
|
390
415
|
end
|
391
416
|
|
392
417
|
# Matches single and double quoted strings
|
393
|
-
def
|
418
|
+
def string_token
|
394
419
|
if %w(' ").include?(@chunk[0])
|
395
420
|
result = process_string @chunk[0..-1], @chunk[0]
|
396
|
-
|
421
|
+
if @looking_for_args
|
422
|
+
token :Argument, result.str, attrs: { quoted_by: @chunk[0] }
|
423
|
+
else
|
424
|
+
token :Command, result.str
|
425
|
+
end
|
397
426
|
return result.consumed_length
|
398
427
|
end
|
399
428
|
end
|
@@ -490,6 +519,7 @@ module Yap::Shell
|
|
490
519
|
|
491
520
|
if i >= input_str.length
|
492
521
|
puts "#{' '*indent}C-yah: result:#{result_str.inspect} length: #{input_str.length}" if ENV["DEBUG"]
|
522
|
+
raise NonterminatedString, "Expected to find #{delimiter} in:\n #{input_str}"
|
493
523
|
return OpenStruct.new(str:result_str, consumed_length: input_str.length)
|
494
524
|
end
|
495
525
|
|
@@ -19,26 +19,21 @@ module Yap::Shell
|
|
19
19
|
|
20
20
|
attr_reader :lvalue
|
21
21
|
|
22
|
-
def initialize(
|
23
|
-
@lvalue =
|
22
|
+
def initialize(token)
|
23
|
+
@lvalue = token.value
|
24
|
+
@attrs = token.attrs
|
24
25
|
end
|
25
26
|
|
26
|
-
def
|
27
|
-
|
27
|
+
def quoted?
|
28
|
+
double_quoted? || single_quoted?
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
"
|
31
|
+
def double_quoted?
|
32
|
+
@attrs[:quoted_by] == '"'
|
32
33
|
end
|
33
|
-
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
attr_reader :lvalue, :rvalue
|
39
|
-
|
40
|
-
def initialize(lvalue, rvalue)
|
41
|
-
@lvalue, @rvalue = lvalue, rvalue
|
35
|
+
def single_quoted?
|
36
|
+
@attrs[:quoted_by] == "'"
|
42
37
|
end
|
43
38
|
|
44
39
|
def inspect
|
@@ -46,7 +41,7 @@ module Yap::Shell
|
|
46
41
|
end
|
47
42
|
|
48
43
|
def to_s
|
49
|
-
"
|
44
|
+
"ArgumentNode(#{lvalue.inspect})"
|
50
45
|
end
|
51
46
|
end
|
52
47
|
|
@@ -56,8 +51,8 @@ module Yap::Shell
|
|
56
51
|
attr_reader :command, :args
|
57
52
|
attr_accessor :heredoc, :redirects
|
58
53
|
|
59
|
-
def initialize(
|
60
|
-
@command =
|
54
|
+
def initialize(token, *args, literal:false, heredoc:nil)
|
55
|
+
@command = token.value
|
61
56
|
@args = args.flatten
|
62
57
|
@literal = literal
|
63
58
|
@heredoc = nil
|
@@ -108,13 +103,13 @@ module Yap::Shell
|
|
108
103
|
|
109
104
|
attr_reader :env
|
110
105
|
|
111
|
-
def initialize(
|
106
|
+
def initialize(token, argument_node)
|
112
107
|
@env = {}
|
113
|
-
@env[
|
108
|
+
@env[token.value] = argument_node
|
114
109
|
end
|
115
110
|
|
116
|
-
def add_var(
|
117
|
-
@env[
|
111
|
+
def add_var(token, argument_node)
|
112
|
+
@env[token.value] = argument_node
|
118
113
|
end
|
119
114
|
end
|
120
115
|
|
@@ -165,8 +160,8 @@ module Yap::Shell
|
|
165
160
|
|
166
161
|
attr_reader :operator, :expr1, :expr2
|
167
162
|
|
168
|
-
def initialize(
|
169
|
-
@operator =
|
163
|
+
def initialize(token, expr1, expr2)
|
164
|
+
@operator = token.value
|
170
165
|
@expr1 = expr1
|
171
166
|
@expr2 = expr2
|
172
167
|
end
|
@@ -182,8 +177,8 @@ module Yap::Shell
|
|
182
177
|
attr_reader :src
|
183
178
|
alias_method :command, :src
|
184
179
|
|
185
|
-
def initialize(
|
186
|
-
@src =
|
180
|
+
def initialize(token)
|
181
|
+
@src = token.value
|
187
182
|
end
|
188
183
|
|
189
184
|
def args
|
@@ -231,9 +226,9 @@ module Yap::Shell
|
|
231
226
|
|
232
227
|
attr_reader :kind, :target
|
233
228
|
|
234
|
-
def initialize(
|
235
|
-
@kind =
|
236
|
-
@target = target
|
229
|
+
def initialize(token)
|
230
|
+
@kind = token.value
|
231
|
+
@target = token.attrs[:target]
|
237
232
|
end
|
238
233
|
|
239
234
|
def to_s(indent:0)
|
@@ -289,10 +284,10 @@ module Yap::Shell
|
|
289
284
|
|
290
285
|
attr_accessor :head, :tail, :params
|
291
286
|
|
292
|
-
def initialize(head, tail, params:
|
287
|
+
def initialize(head, tail, params: nil)
|
293
288
|
@head = head
|
294
289
|
@tail = tail
|
295
|
-
@params = params
|
290
|
+
@params = params ? params.value : []
|
296
291
|
end
|
297
292
|
|
298
293
|
def to_s(indent:0)
|
@@ -293,7 +293,7 @@ module_eval(<<'.,.,', 'grammar.y', 27)
|
|
293
293
|
|
294
294
|
module_eval(<<'.,.,', 'grammar.y', 31)
|
295
295
|
def _reduce_6(val, _values, result)
|
296
|
-
result = ConditionalNode.new(val[1]
|
296
|
+
result = ConditionalNode.new(val[1], val[0], val[2])
|
297
297
|
result
|
298
298
|
end
|
299
299
|
.,.,
|
@@ -311,7 +311,7 @@ module_eval(<<'.,.,', 'grammar.y', 36)
|
|
311
311
|
|
312
312
|
module_eval(<<'.,.,', 'grammar.y', 38)
|
313
313
|
def _reduce_10(val, _values, result)
|
314
|
-
result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[3], params: val[2]
|
314
|
+
result = val[0].tap { |range_node| range_node.tail = BlockNode.new(nil, val[3], params: val[2]) }
|
315
315
|
result
|
316
316
|
end
|
317
317
|
.,.,
|
@@ -325,7 +325,7 @@ module_eval(<<'.,.,', 'grammar.y', 40)
|
|
325
325
|
|
326
326
|
module_eval(<<'.,.,', 'grammar.y', 42)
|
327
327
|
def _reduce_12(val, _values, result)
|
328
|
-
result = BlockNode.new(val[0], val[3], params: val[2]
|
328
|
+
result = BlockNode.new(val[0], val[3], params: val[2])
|
329
329
|
result
|
330
330
|
end
|
331
331
|
.,.,
|
@@ -409,14 +409,14 @@ module_eval(<<'.,.,', 'grammar.y', 73)
|
|
409
409
|
|
410
410
|
module_eval(<<'.,.,', 'grammar.y', 76)
|
411
411
|
def _reduce_29(val, _values, result)
|
412
|
-
result = RedirectionNode.new(val[0]
|
412
|
+
result = RedirectionNode.new(val[0])
|
413
413
|
result
|
414
414
|
end
|
415
415
|
.,.,
|
416
416
|
|
417
417
|
module_eval(<<'.,.,', 'grammar.y', 79)
|
418
418
|
def _reduce_30(val, _values, result)
|
419
|
-
val[0].redirects << RedirectionNode.new(val[1]
|
419
|
+
val[0].redirects << RedirectionNode.new(val[1]) ; result = val[0]
|
420
420
|
result
|
421
421
|
end
|
422
422
|
.,.,
|
@@ -436,14 +436,14 @@ module_eval(<<'.,.,', 'grammar.y', 85)
|
|
436
436
|
|
437
437
|
module_eval(<<'.,.,', 'grammar.y', 88)
|
438
438
|
def _reduce_35(val, _values, result)
|
439
|
-
val[0].add_var(val[1]
|
439
|
+
val[0].add_var(val[1], ArgumentNode.new(val[2])) ; result = val[0]
|
440
440
|
result
|
441
441
|
end
|
442
442
|
.,.,
|
443
443
|
|
444
444
|
module_eval(<<'.,.,', 'grammar.y', 90)
|
445
445
|
def _reduce_36(val, _values, result)
|
446
|
-
result = EnvNode.new(val[0]
|
446
|
+
result = EnvNode.new(val[0], ArgumentNode.new(val[1]))
|
447
447
|
result
|
448
448
|
end
|
449
449
|
.,.,
|
@@ -452,49 +452,49 @@ module_eval(<<'.,.,', 'grammar.y', 90)
|
|
452
452
|
|
453
453
|
module_eval(<<'.,.,', 'grammar.y', 95)
|
454
454
|
def _reduce_38(val, _values, result)
|
455
|
-
result = CommandNode.new(val[0]
|
455
|
+
result = CommandNode.new(val[0])
|
456
456
|
result
|
457
457
|
end
|
458
458
|
.,.,
|
459
459
|
|
460
460
|
module_eval(<<'.,.,', 'grammar.y', 97)
|
461
461
|
def _reduce_39(val, _values, result)
|
462
|
-
result = CommandNode.new(val[0]
|
462
|
+
result = CommandNode.new(val[0], val[1].flatten)
|
463
463
|
result
|
464
464
|
end
|
465
465
|
.,.,
|
466
466
|
|
467
467
|
module_eval(<<'.,.,', 'grammar.y', 99)
|
468
468
|
def _reduce_40(val, _values, result)
|
469
|
-
result = CommandNode.new(val[0]
|
469
|
+
result = CommandNode.new(val[0], literal:true)
|
470
470
|
result
|
471
471
|
end
|
472
472
|
.,.,
|
473
473
|
|
474
474
|
module_eval(<<'.,.,', 'grammar.y', 101)
|
475
475
|
def _reduce_41(val, _values, result)
|
476
|
-
result = CommandNode.new(val[0]
|
476
|
+
result = CommandNode.new(val[0], val[1].flatten, literal:true)
|
477
477
|
result
|
478
478
|
end
|
479
479
|
.,.,
|
480
480
|
|
481
481
|
module_eval(<<'.,.,', 'grammar.y', 104)
|
482
482
|
def _reduce_42(val, _values, result)
|
483
|
-
result = [ArgumentNode.new(val[0]
|
483
|
+
result = [ArgumentNode.new(val[0])]
|
484
484
|
result
|
485
485
|
end
|
486
486
|
.,.,
|
487
487
|
|
488
488
|
module_eval(<<'.,.,', 'grammar.y', 106)
|
489
489
|
def _reduce_43(val, _values, result)
|
490
|
-
result = [val[0], ArgumentNode.new(val[1]
|
490
|
+
result = [val[0], ArgumentNode.new(val[1])].flatten
|
491
491
|
result
|
492
492
|
end
|
493
493
|
.,.,
|
494
494
|
|
495
495
|
module_eval(<<'.,.,', 'grammar.y', 109)
|
496
496
|
def _reduce_44(val, _values, result)
|
497
|
-
result = InternalEvalNode.new(val[0]
|
497
|
+
result = InternalEvalNode.new(val[0])
|
498
498
|
result
|
499
499
|
end
|
500
500
|
.,.,
|
@@ -72,18 +72,25 @@ describe Yap::Shell::Parser::Lexer do
|
|
72
72
|
end
|
73
73
|
|
74
74
|
describe "ls {echo n }" do
|
75
|
-
it
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
it "parses as a block" do
|
76
|
+
should eq [
|
77
|
+
t(:Command, "ls", lineno:0),
|
78
|
+
t(:BlockBegin, '{', lineno: 0),
|
79
|
+
t(:Command, "echo", lineno:0),
|
80
|
+
t(:Argument, "n", lineno:0),
|
81
|
+
t(:BlockEnd, '}', lineno: 0)
|
82
|
+
]
|
83
|
+
end
|
82
84
|
end
|
83
85
|
|
84
86
|
describe "ls s{ echo n}" do
|
85
|
-
it "
|
86
|
-
|
87
|
+
it "parses as arguments" do
|
88
|
+
should eq [
|
89
|
+
t(:Command, "ls", lineno:0),
|
90
|
+
t(:Argument, "s{", lineno:0),
|
91
|
+
t(:Argument, "echo", lineno:0),
|
92
|
+
t(:Argument, "n}", lineno:0)
|
93
|
+
]
|
87
94
|
end
|
88
95
|
end
|
89
96
|
end
|
@@ -468,7 +475,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
468
475
|
let(:str){ "ls 'hello there'" }
|
469
476
|
it { should eq [
|
470
477
|
t(:Command, "ls", lineno:0),
|
471
|
-
t(:Argument, "hello there", lineno:0)
|
478
|
+
t(:Argument, "hello there", lineno:0, attrs: { quoted_by: "'" })
|
472
479
|
]}
|
473
480
|
end
|
474
481
|
|
@@ -476,7 +483,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
476
483
|
let(:str){ "ls 'hello \\'there\\''" }
|
477
484
|
it { should eq [
|
478
485
|
t(:Command, "ls", lineno:0),
|
479
|
-
t(:Argument, "hello 'there'", lineno:0)
|
486
|
+
t(:Argument, "hello 'there'", lineno:0, attrs: { quoted_by: "'" })
|
480
487
|
]}
|
481
488
|
end
|
482
489
|
|
@@ -484,7 +491,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
484
491
|
let(:str){ %|ls 'hello "there"'| }
|
485
492
|
it { should eq [
|
486
493
|
t(:Command, "ls", lineno:0),
|
487
|
-
t(:Argument, 'hello "there"', lineno:0)
|
494
|
+
t(:Argument, 'hello "there"', lineno:0, attrs: { quoted_by: "'" })
|
488
495
|
]}
|
489
496
|
end
|
490
497
|
|
@@ -492,7 +499,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
492
499
|
let(:str){ "ls 'hello \\'there \\\\'guy\\\\' \\''" }
|
493
500
|
it { should eq [
|
494
501
|
t(:Command, "ls", lineno:0),
|
495
|
-
t(:Argument, "hello 'there \\'guy\\' '", lineno:0)
|
502
|
+
t(:Argument, "hello 'there \\'guy\\' '", lineno:0, attrs: { quoted_by: "'" })
|
496
503
|
]}
|
497
504
|
end
|
498
505
|
|
@@ -500,8 +507,8 @@ describe Yap::Shell::Parser::Lexer do
|
|
500
507
|
let(:str){ "ls 'hello \\'there\\'' 'how are \\'you\\'?'" }
|
501
508
|
it { should eq [
|
502
509
|
t(:Command, "ls", lineno:0),
|
503
|
-
t(:Argument, "hello 'there'", lineno:0),
|
504
|
-
t(:Argument, "how are 'you'?", lineno:0)
|
510
|
+
t(:Argument, "hello 'there'", lineno:0, attrs: { quoted_by: "'" }),
|
511
|
+
t(:Argument, "how are 'you'?", lineno:0, attrs: { quoted_by: "'" })
|
505
512
|
]}
|
506
513
|
end
|
507
514
|
|
@@ -509,7 +516,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
509
516
|
let(:str){ "alias z='echo hi'" }
|
510
517
|
it { should eq [
|
511
518
|
t(:Command, "alias", lineno:0),
|
512
|
-
t(:Argument, "z=echo hi", lineno:0),
|
519
|
+
t(:Argument, "z=echo hi", lineno:0, attrs: { quoted_by: "'" }),
|
513
520
|
]}
|
514
521
|
end
|
515
522
|
end
|
@@ -519,7 +526,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
519
526
|
let(:str){ 'ls "hello there"' }
|
520
527
|
it { should eq [
|
521
528
|
t(:Command, 'ls', lineno:0),
|
522
|
-
t(:Argument, 'hello there', lineno:0)
|
529
|
+
t(:Argument, 'hello there', lineno:0, attrs: { quoted_by: '"' })
|
523
530
|
]}
|
524
531
|
end
|
525
532
|
|
@@ -527,7 +534,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
527
534
|
let(:str){ %|ls "hello 'there'"| }
|
528
535
|
it { should eq [
|
529
536
|
t(:Command, 'ls', lineno:0),
|
530
|
-
t(:Argument, "hello 'there'", lineno:0)
|
537
|
+
t(:Argument, "hello 'there'", lineno:0, attrs: { quoted_by: '"' })
|
531
538
|
]}
|
532
539
|
end
|
533
540
|
|
@@ -535,7 +542,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
535
542
|
let(:str){ 'ls "hello \\"there\\""' }
|
536
543
|
it { should eq [
|
537
544
|
t(:Command, 'ls', lineno:0),
|
538
|
-
t(:Argument, 'hello "there"', lineno:0)
|
545
|
+
t(:Argument, 'hello "there"', lineno:0, attrs: { quoted_by: '"' })
|
539
546
|
]}
|
540
547
|
end
|
541
548
|
|
@@ -543,7 +550,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
543
550
|
let(:str){ 'ls "hello \\"there \\\\"guy\\\\" \\""' }
|
544
551
|
it { should eq [
|
545
552
|
t(:Command, 'ls', lineno:0),
|
546
|
-
t(:Argument, 'hello "there \\"guy\\" "', lineno:0)
|
553
|
+
t(:Argument, 'hello "there \\"guy\\" "', lineno:0, attrs: { quoted_by: '"' })
|
547
554
|
]}
|
548
555
|
end
|
549
556
|
|
@@ -551,8 +558,8 @@ describe Yap::Shell::Parser::Lexer do
|
|
551
558
|
let(:str){ 'ls "hello \\"there\\"" "how are \\"you\\"?"' }
|
552
559
|
it { should eq [
|
553
560
|
t(:Command, 'ls', lineno:0),
|
554
|
-
t(:Argument, 'hello "there"', lineno:0),
|
555
|
-
t(:Argument, 'how are "you"?', lineno:0)
|
561
|
+
t(:Argument, 'hello "there"', lineno:0, attrs: { quoted_by: '"' }),
|
562
|
+
t(:Argument, 'how are "you"?', lineno:0, attrs: { quoted_by: '"' })
|
556
563
|
]}
|
557
564
|
end
|
558
565
|
|
@@ -560,10 +567,28 @@ describe Yap::Shell::Parser::Lexer do
|
|
560
567
|
let(:str){ "alias z=\"echo hi\"" }
|
561
568
|
it { should eq [
|
562
569
|
t(:Command, "alias", lineno:0),
|
563
|
-
t(:Argument, "z=echo hi", lineno:0)
|
570
|
+
t(:Argument, "z=echo hi", lineno:0, attrs: { quoted_by: '"' })
|
564
571
|
]}
|
565
572
|
end
|
566
573
|
end
|
574
|
+
|
575
|
+
context 'args with special characters' do
|
576
|
+
describe 'echo @{u}' do
|
577
|
+
it { should eq [
|
578
|
+
t(:Command, "echo", lineno:0),
|
579
|
+
t(:Argument, "@{u}", lineno:0)
|
580
|
+
]}
|
581
|
+
end
|
582
|
+
|
583
|
+
describe 'echo @\(u\)' do
|
584
|
+
it "parentheses must be escaped to be treated as arguments" do
|
585
|
+
should eq [
|
586
|
+
t(:Command, "echo", lineno:0),
|
587
|
+
t(:Argument, "@(u)", lineno:0)
|
588
|
+
]
|
589
|
+
end
|
590
|
+
end
|
591
|
+
end
|
567
592
|
end
|
568
593
|
|
569
594
|
context "statements" do
|
@@ -1063,7 +1088,7 @@ describe Yap::Shell::Parser::Lexer do
|
|
1063
1088
|
t(:LValue, "FOO", lineno: 0),
|
1064
1089
|
t(:RValue, "abc", lineno: 0),
|
1065
1090
|
t(:LValue, "BAR", lineno: 0),
|
1066
|
-
t(:RValue, "hello world", lineno: 0),
|
1091
|
+
t(:RValue, "hello world", lineno: 0, attrs: { quoted_by: "'"}),
|
1067
1092
|
t(:Command, "ls", lineno: 0),
|
1068
1093
|
t(:Argument, "-l", lineno: 0)
|
1069
1094
|
]}
|
@@ -1332,4 +1357,79 @@ describe Yap::Shell::Parser::Lexer do
|
|
1332
1357
|
end
|
1333
1358
|
end
|
1334
1359
|
end
|
1360
|
+
|
1361
|
+
describe 'strings' do
|
1362
|
+
describe 'non-terminated strings' do
|
1363
|
+
it 'raises an error on non-terminated double quotes' do
|
1364
|
+
expect do
|
1365
|
+
described_class.new.tokenize('"nonterminated')
|
1366
|
+
end.to raise_error(
|
1367
|
+
Yap::Shell::Parser::Lexer::NonterminatedString,
|
1368
|
+
%|Expected to find " in:\n "nonterminated|
|
1369
|
+
)
|
1370
|
+
end
|
1371
|
+
|
1372
|
+
it 'raises an error on non-terminated single quotes' do
|
1373
|
+
expect do
|
1374
|
+
described_class.new.tokenize("'nonterminated")
|
1375
|
+
end.to raise_error(
|
1376
|
+
Yap::Shell::Parser::Lexer::NonterminatedString,
|
1377
|
+
%|Expected to find ' in:\n 'nonterminated|
|
1378
|
+
)
|
1379
|
+
end
|
1380
|
+
end
|
1381
|
+
end
|
1382
|
+
|
1383
|
+
describe 'line continutations' do
|
1384
|
+
describe 'backward slashes' do
|
1385
|
+
it 'raises an error when found at the end of the single line string' do
|
1386
|
+
expect do
|
1387
|
+
described_class.new.tokenize('echo hello \\')
|
1388
|
+
end.to raise_error(
|
1389
|
+
Yap::Shell::Parser::Lexer::LineContinuationFound,
|
1390
|
+
%|Expected more input, line continutation found|
|
1391
|
+
)
|
1392
|
+
end
|
1393
|
+
|
1394
|
+
it 'raises an error when found at the end of a multiline string' do
|
1395
|
+
expect do
|
1396
|
+
described_class.new.tokenize("echo hello \n world\\")
|
1397
|
+
end.to raise_error(
|
1398
|
+
Yap::Shell::Parser::Lexer::LineContinuationFound,
|
1399
|
+
%|Expected more input, line continutation found|
|
1400
|
+
)
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
it 'does not raise when found before the end of a single line string' do
|
1404
|
+
expect do
|
1405
|
+
described_class.new.tokenize("echo hello \\ world")
|
1406
|
+
end.to_not raise_error
|
1407
|
+
end
|
1408
|
+
|
1409
|
+
it 'does not raise when found before the end of a multiline string' do
|
1410
|
+
expect do
|
1411
|
+
described_class.new.tokenize("echo hello \n world\\ word")
|
1412
|
+
end.to_not raise_error
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
context 'newlines that follow line continuations inside a string are stripped' do
|
1416
|
+
let(:str){ "echo foo\\\nbar" }
|
1417
|
+
it { should eq [
|
1418
|
+
t(:Command, "echo", lineno:0),
|
1419
|
+
t(:Argument, "foobar", lineno:0)
|
1420
|
+
]}
|
1421
|
+
end
|
1422
|
+
|
1423
|
+
context 'newlines that follow line continuations at the end of a string are not stripped' do
|
1424
|
+
it 'raises an error when found at the end of a multiline string' do
|
1425
|
+
expect do
|
1426
|
+
described_class.new.tokenize("echo foo\\\nbar\\\n" )
|
1427
|
+
end.to raise_error(
|
1428
|
+
Yap::Shell::Parser::Lexer::LineContinuationFound,
|
1429
|
+
%|Expected more input, line continutation found|
|
1430
|
+
)
|
1431
|
+
end
|
1432
|
+
end
|
1433
|
+
end
|
1434
|
+
end
|
1335
1435
|
end
|
@@ -61,6 +61,9 @@ describe Yap::Shell::Parser do
|
|
61
61
|
it { is_expected.to parse "echo bar && > foo.txt" }
|
62
62
|
it { is_expected.to parse "#this is a comment" }
|
63
63
|
it { is_expected.to parse "echo foo #this last part is a comment" }
|
64
|
+
it { is_expected.to parse "git reset --hard @{u}" }
|
64
65
|
|
65
66
|
it { is_expected.to fail_parsing "ls ()" }
|
67
|
+
it { is_expected.to parse("echo 'hi'")}
|
68
|
+
it { is_expected.to parse("ls > foo.txt")}
|
66
69
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: yap-shell-parser
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zach Dennis
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-05-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|