yap-shell-parser 0.4.0 → 0.5.0
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/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
|