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