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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 56c11c5e48960b86e262bf7b4c6dddf59cbbce51
4
- data.tar.gz: 0120fd57b88b3ec458a5a88f9d6187e37ca33190
3
+ metadata.gz: 59dc1f16d41d54f7c14d7ff68abc1ad404d7bd39
4
+ data.tar.gz: 95d1cfdfbe7ad7e35b6cc2e4be77a3cba9326a26
5
5
  SHA512:
6
- metadata.gz: 00370bf66d7d030a3aee98a670d5697b63f1a6ee2be3717db6f20968cf4ba7453c4f1ab2c6fc5bd864e7dfc9a265fb79462dd787c411dc24b389ccecadac9c71
7
- data.tar.gz: 7bd7d734dabbdeafad2d892d23a5d2387218c7505be61b0431d1c639ac8c911fb4c5a97cae05653d69a5ba1e673fa3f62d2ac4347d0818698cbb094d3a979fd4
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].value, val[0], val[2]) }
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].value) } }
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].value) }
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].value, val[0].attrs[:target]) }
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].value, val[1].attrs[:target]) ; result = val[0] }
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].value, val[2].value) ; result = val[0] }
89
+ { val[0].add_var(val[1], ArgumentNode.new(val[2])) ; result = val[0] }
90
90
  | LValue RValue
91
- { result = EnvNode.new(val[0].value, val[1].value) }
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].value) }
96
+ { result = CommandNode.new(val[0]) }
97
97
  | Command args
98
- { result = CommandNode.new(val[0].value, val[1].flatten) }
98
+ { result = CommandNode.new(val[0], val[1].flatten) }
99
99
  | LiteralCommand
100
- { result = CommandNode.new(val[0].value, literal:true) }
100
+ { result = CommandNode.new(val[0], literal:true) }
101
101
  | LiteralCommand args
102
- { result = CommandNode.new(val[0].value, val[1].flatten, literal:true) }
102
+ { result = CommandNode.new(val[0], val[1].flatten, literal:true) }
103
103
 
104
104
  args : Argument
105
- { result = [ArgumentNode.new(val[0].value)] }
105
+ { result = [ArgumentNode.new(val[0])] }
106
106
  | args Argument
107
- { result = [val[0], ArgumentNode.new(val[1].value)].flatten }
107
+ { result = [val[0], ArgumentNode.new(val[1])].flatten }
108
108
 
109
109
  internal_eval : InternalEval
110
- { result = InternalEvalNode.new(val[0].value) }
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 = /\A\(((\d+)\.\.(\d+))\)(\.each)?\s*:\s*/
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
- @str = str
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
- # binding.pry
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
- token :Argument, str
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 string_argument_token
418
+ def string_token
394
419
  if %w(' ").include?(@chunk[0])
395
420
  result = process_string @chunk[0..-1], @chunk[0]
396
- token :Argument, result.str
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(lvalue)
23
- @lvalue = lvalue
22
+ def initialize(token)
23
+ @lvalue = token.value
24
+ @attrs = token.attrs
24
25
  end
25
26
 
26
- def inspect
27
- to_s
27
+ def quoted?
28
+ double_quoted? || single_quoted?
28
29
  end
29
30
 
30
- def to_s
31
- "ArgumentNode(#{lvalue.inspect})"
31
+ def double_quoted?
32
+ @attrs[:quoted_by] == '"'
32
33
  end
33
- end
34
34
 
35
- class AssignmentNode
36
- include Visitor
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
- "A(#{lvalue.inspect}=#{rvalue.inspect})"
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(command, *args, literal:false, heredoc:nil)
60
- @command = 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(lvalue, rvalue)
106
+ def initialize(token, argument_node)
112
107
  @env = {}
113
- @env[lvalue] = rvalue
108
+ @env[token.value] = argument_node
114
109
  end
115
110
 
116
- def add_var(lvalue, rvalue)
117
- @env[lvalue] = rvalue
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(operator, expr1, expr2)
169
- @operator = 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(src)
186
- @src = 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(kind, target)
235
- @kind = 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)
@@ -3,7 +3,7 @@ require 'yap/shell/parser'
3
3
  module Yap
4
4
  module Shell
5
5
  module Parser
6
- VERSION = "0.4.0"
6
+ VERSION = "0.5.0"
7
7
  end
8
8
  end
9
9
  end
@@ -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].value, val[0], val[2])
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].value) }
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].value)
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].value, val[0].attrs[:target])
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].value, val[1].attrs[:target]) ; result = val[0]
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].value, val[2].value) ; result = val[0]
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].value, val[1].value)
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].value)
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].value, val[1].flatten)
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].value, literal:true)
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].value, val[1].flatten, literal:true)
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].value)]
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].value)].flatten
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].value)
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 { should eq [
76
- t(:Command, "ls", lineno:0),
77
- t(:BlockBegin, '{', lineno: 0),
78
- t(:Command, "echo", lineno:0),
79
- t(:Argument, "n", lineno:0),
80
- t(:BlockEnd, '}', lineno: 0)
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 "fails to lex" do
86
- expect { subject }.to raise_error
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.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-03-25 00:00:00.000000000 Z
11
+ date: 2016-05-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler