ripper_ruby_parser 1.1.2 → 1.2.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.
Files changed (40) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +19 -0
  3. data/README.md +2 -2
  4. data/Rakefile +1 -1
  5. data/lib/ripper_ruby_parser.rb +0 -7
  6. data/lib/ripper_ruby_parser/commenting_ripper_parser.rb +112 -34
  7. data/lib/ripper_ruby_parser/parser.rb +26 -12
  8. data/lib/ripper_ruby_parser/sexp_handlers.rb +4 -1
  9. data/lib/ripper_ruby_parser/sexp_handlers/arguments.rb +7 -6
  10. data/lib/ripper_ruby_parser/sexp_handlers/arrays.rb +4 -2
  11. data/lib/ripper_ruby_parser/sexp_handlers/assignment.rb +39 -43
  12. data/lib/ripper_ruby_parser/sexp_handlers/blocks.rb +93 -69
  13. data/lib/ripper_ruby_parser/sexp_handlers/conditionals.rb +30 -24
  14. data/lib/ripper_ruby_parser/sexp_handlers/hashes.rb +7 -9
  15. data/lib/ripper_ruby_parser/sexp_handlers/helper_methods.rb +51 -71
  16. data/lib/ripper_ruby_parser/sexp_handlers/literals.rb +72 -56
  17. data/lib/ripper_ruby_parser/sexp_handlers/loops.rb +14 -13
  18. data/lib/ripper_ruby_parser/sexp_handlers/method_calls.rb +19 -13
  19. data/lib/ripper_ruby_parser/sexp_handlers/methods.rb +19 -22
  20. data/lib/ripper_ruby_parser/sexp_handlers/operators.rb +47 -35
  21. data/lib/ripper_ruby_parser/sexp_processor.rb +72 -85
  22. data/lib/ripper_ruby_parser/version.rb +1 -1
  23. data/test/end_to_end/line_numbering_test.rb +1 -1
  24. data/test/end_to_end/samples_comparison_test.rb +0 -1
  25. data/test/pt_testcase/pt_test.rb +4 -6
  26. data/test/{unit → ripper_ruby_parser}/commenting_ripper_parser_test.rb +82 -25
  27. data/test/{unit → ripper_ruby_parser}/parser_test.rb +37 -170
  28. data/test/{unit/parser_assignment_test.rb → ripper_ruby_parser/sexp_handlers/assignment_test.rb} +1 -1
  29. data/test/{unit/parser_blocks_test.rb → ripper_ruby_parser/sexp_handlers/blocks_test.rb} +267 -2
  30. data/test/{unit/parser_conditionals_test.rb → ripper_ruby_parser/sexp_handlers/conditionals_test.rb} +125 -17
  31. data/test/{unit/parser_literals_test.rb → ripper_ruby_parser/sexp_handlers/literals_test.rb} +10 -12
  32. data/test/{unit/parser_loops_test.rb → ripper_ruby_parser/sexp_handlers/loops_test.rb} +1 -1
  33. data/test/{unit/parser_method_calls_test.rb → ripper_ruby_parser/sexp_handlers/method_calls_test.rb} +10 -10
  34. data/test/{unit/parser_operators_test.rb → ripper_ruby_parser/sexp_handlers/operators_test.rb} +22 -2
  35. data/test/{unit → ripper_ruby_parser}/sexp_processor_test.rb +49 -48
  36. data/test/{unit → ripper_ruby_parser}/version_test.rb +0 -0
  37. data/test/samples/misc.rb +4 -0
  38. data/test/test_helper.rb +4 -4
  39. metadata +28 -42
  40. data/test/end_to_end/error_conditions_test.rb +0 -51
@@ -2,7 +2,7 @@ require File.expand_path('../test_helper.rb', File.dirname(__FILE__))
2
2
  require 'ruby_parser'
3
3
 
4
4
  describe 'Using RipperRubyParser and RubyParser' do
5
- def to_line_numbers exp
5
+ def to_line_numbers(exp)
6
6
  exp.map! do |sub_exp|
7
7
  if sub_exp.is_a? Sexp
8
8
  to_line_numbers sub_exp
@@ -21,7 +21,6 @@ describe 'Using RipperRubyParser and RubyParser' do
21
21
  end
22
22
 
23
23
  let :imitation do
24
- newparser.extra_compatible = true
25
24
  newparser.parse program
26
25
  end
27
26
 
@@ -2,7 +2,7 @@ require File.expand_path('../test_helper.rb', File.dirname(__FILE__))
2
2
  require 'pt_testcase'
3
3
 
4
4
  class TestParser < RipperRubyParser::Parser
5
- def process input
5
+ def process(input)
6
6
  parse input
7
7
  end
8
8
  end
@@ -10,11 +10,11 @@ end
10
10
  SKIPPED_TESTS = ['dstr_heredoc_windoze_sucks'].freeze
11
11
 
12
12
  class RubyParserTestCase < ParseTreeTestCase
13
- def self.previous _key
13
+ def self.previous(_key)
14
14
  'Ruby'
15
15
  end
16
16
 
17
- def self.generate_test klass, node, data, input_name, output_name
17
+ def self.generate_test(klass, node, data, input_name, _output_name)
18
18
  if data['Ruby'].is_a? Array
19
19
  klass.send :define_method, "test_#{node}" do
20
20
  skip 'Not a parser test'
@@ -29,9 +29,7 @@ class RubyParserTestCase < ParseTreeTestCase
29
29
  return
30
30
  end
31
31
 
32
- output_name = 'ParseTree'
33
-
34
- super
32
+ super klass, node, data, input_name, 'ParseTree'
35
33
  end
36
34
  end
37
35
 
@@ -1,119 +1,176 @@
1
1
  require File.expand_path('../test_helper.rb', File.dirname(__FILE__))
2
2
 
3
3
  describe RipperRubyParser::CommentingRipperParser do
4
- def parse_with_builder str
4
+ def parse_with_builder(str)
5
5
  builder = RipperRubyParser::CommentingRipperParser.new str
6
6
  builder.parse
7
7
  end
8
8
 
9
9
  def empty_params_list
10
- @empty_params_list ||= begin
11
- num_params = RUBY_VERSION < '2.0.0' ? 5 : 7
12
- s(:params, *([nil] * num_params))
13
- end
10
+ @empty_params_list ||= s(:params, *([nil] * 7))
14
11
  end
15
12
 
16
13
  describe 'handling comments' do
17
14
  it 'produces a comment node surrounding a commented def' do
18
15
  result = parse_with_builder "# Foo\ndef foo; end"
19
16
  result.must_equal s(:program,
20
- s(s(:comment,
17
+ s(:stmts,
18
+ s(:comment,
21
19
  "# Foo\n",
22
20
  s(:def,
23
21
  s(:@ident, 'foo', s(2, 4)),
24
22
  empty_params_list,
25
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
23
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
26
24
  end
27
25
 
28
26
  it 'produces a blank comment node surrounding a def that has no comment' do
29
27
  result = parse_with_builder 'def foo; end'
30
28
  result.must_equal s(:program,
31
- s(s(:comment,
29
+ s(:stmts,
30
+ s(:comment,
32
31
  '',
33
32
  s(:def,
34
33
  s(:@ident, 'foo', s(1, 4)),
35
34
  empty_params_list,
36
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
35
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
37
36
  end
38
37
 
39
38
  it 'produces a comment node surrounding a commented class' do
40
39
  result = parse_with_builder "# Foo\nclass Foo; end"
41
40
  result.must_equal s(:program,
42
- s(s(:comment,
41
+ s(:stmts,
42
+ s(:comment,
43
43
  "# Foo\n",
44
44
  s(:class,
45
45
  s(:const_ref, s(:@const, 'Foo', s(2, 6))),
46
46
  nil,
47
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
47
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
48
48
  end
49
49
 
50
50
  it 'produce a blank comment node surrounding a class that has no comment' do
51
51
  result = parse_with_builder 'class Foo; end'
52
52
  result.must_equal s(:program,
53
- s(s(:comment,
53
+ s(:stmts,
54
+ s(:comment,
54
55
  '',
55
56
  s(:class,
56
57
  s(:const_ref, s(:@const, 'Foo', s(1, 6))),
57
58
  nil,
58
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
59
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
59
60
  end
60
61
 
61
62
  it 'produces a comment node surrounding a commented module' do
62
63
  result = parse_with_builder "# Foo\nmodule Foo; end"
63
64
  result.must_equal s(:program,
64
- s(s(:comment,
65
+ s(:stmts,
66
+ s(:comment,
65
67
  "# Foo\n",
66
68
  s(:module,
67
69
  s(:const_ref, s(:@const, 'Foo', s(2, 7))),
68
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
70
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
69
71
  end
70
72
 
71
73
  it 'produces a blank comment node surrounding a module that has no comment' do
72
74
  result = parse_with_builder 'module Foo; end'
73
75
  result.must_equal s(:program,
74
- s(s(:comment,
76
+ s(:stmts,
77
+ s(:comment,
75
78
  '',
76
79
  s(:module,
77
80
  s(:const_ref, s(:@const, 'Foo', s(1, 7))),
78
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
81
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
79
82
  end
80
83
 
81
84
  it 'is not confused by a symbol containing a keyword' do
82
85
  result = parse_with_builder ':class; def foo; end'
83
86
  result.must_equal s(:program,
84
- s(s(:symbol_literal, s(:symbol, s(:@kw, 'class', s(1, 1)))),
87
+ s(:stmts,
88
+ s(:symbol_literal, s(:symbol, s(:@kw, 'class', s(1, 1)))),
85
89
  s(:comment,
86
90
  '',
87
91
  s(:def,
88
92
  s(:@ident, 'foo', s(1, 12)),
89
93
  empty_params_list,
90
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
94
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
91
95
  end
92
96
 
93
97
  it 'is not confused by a dynamic symbol' do
94
98
  result = parse_with_builder ":'foo'; def bar; end"
95
99
  result.must_equal s(:program,
96
- s(s(:dyna_symbol, s(s(:@tstring_content, 'foo', s(1, 2)))),
100
+ s(:stmts,
101
+ s(:dyna_symbol,
102
+ s(:xstring, s(:@tstring_content, 'foo', s(1, 2)))),
97
103
  s(:comment,
98
104
  '',
99
105
  s(:def,
100
106
  s(:@ident, 'bar', s(1, 12)),
101
107
  empty_params_list,
102
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))
108
+ s(:bodystmt, s(:stmts, s(:void_stmt)), nil, nil, nil)))))
103
109
  end
104
110
 
105
111
  it 'is not confused by a dynamic symbol containing a class definition' do
106
112
  result = parse_with_builder ":\"foo\#{class Bar;end}\""
107
113
  result.must_equal s(:program,
108
- s(s(:dyna_symbol,
109
- s(s(:@tstring_content, 'foo', s(1, 2)),
114
+ s(:stmts,
115
+ s(:dyna_symbol,
116
+ s(:xstring,
117
+ s(:@tstring_content, 'foo', s(1, 2)),
110
118
  s(:string_embexpr,
111
- s(s(:comment,
119
+ s(:stmts,
120
+ s(:comment,
112
121
  '',
113
122
  s(:class,
114
123
  s(:const_ref, s(:@const, 'Bar', s(1, 13))),
115
124
  nil,
116
- s(:bodystmt, s(s(:void_stmt)), nil, nil, nil)))))))))
125
+ s(:bodystmt,
126
+ s(:stmts, s(:void_stmt)),
127
+ nil,
128
+ nil,
129
+ nil)))))))))
130
+ end
131
+ end
132
+
133
+ describe 'handling syntax errors' do
134
+ it 'raises an error for an incomplete source' do
135
+ proc {
136
+ parse_with_builder 'def foo'
137
+ }.must_raise RipperRubyParser::SyntaxError
138
+ end
139
+
140
+ it 'raises an error for an invalid class name' do
141
+ proc {
142
+ parse_with_builder 'class foo; end'
143
+ }.must_raise RipperRubyParser::SyntaxError
144
+ end
145
+
146
+ it 'raises an error aliasing $1 as foo' do
147
+ proc {
148
+ parse_with_builder 'alias foo $1'
149
+ }.must_raise RipperRubyParser::SyntaxError
150
+ end
151
+
152
+ it 'raises an error aliasing foo as $1' do
153
+ proc {
154
+ parse_with_builder 'alias $1 foo'
155
+ }.must_raise RipperRubyParser::SyntaxError
156
+ end
157
+
158
+ it 'raises an error aliasing $2 as $1' do
159
+ proc {
160
+ parse_with_builder 'alias $1 $2'
161
+ }.must_raise RipperRubyParser::SyntaxError
162
+ end
163
+
164
+ it 'raises an error assigning to $1' do
165
+ proc {
166
+ parse_with_builder '$1 = foo'
167
+ }.must_raise RipperRubyParser::SyntaxError
168
+ end
169
+
170
+ it 'raises an error using an invalid parameter name' do
171
+ proc {
172
+ parse_with_builder 'def foo(BAR); end'
173
+ }.must_raise RipperRubyParser::SyntaxError
117
174
  end
118
175
  end
119
176
  end
@@ -8,19 +8,6 @@ describe RipperRubyParser::Parser do
8
8
  result.must_be_instance_of Sexp
9
9
  end
10
10
 
11
- it 'post-processes its result with the passed sexp processor' do
12
- sexp_p = MiniTest::Mock.new
13
- sexp_p.expect :process, s(:result), [Sexp]
14
- sexp_p.expect :filename=, nil, ['(string)']
15
- sexp_p.expect :extra_compatible=, nil, [false]
16
-
17
- parser = RipperRubyParser::Parser.new sexp_p
18
- result = parser.parse 'any code'
19
-
20
- result.must_equal s(:result)
21
- sexp_p.verify
22
- end
23
-
24
11
  describe 'for an empty program' do
25
12
  it 'returns nil' do
26
13
  ''.must_be_parsed_as nil
@@ -48,7 +35,7 @@ describe RipperRubyParser::Parser do
48
35
  end
49
36
  end
50
37
 
51
- describe 'for empty brackets' do
38
+ describe 'for empty parentheses' do
52
39
  it 'works with lone ()' do
53
40
  '()'.must_be_parsed_as s(:nil)
54
41
  end
@@ -293,7 +280,7 @@ describe RipperRubyParser::Parser do
293
280
  s(:nil))
294
281
  end
295
282
 
296
- it 'works with brackets around the parameter list' do
283
+ it 'works with parentheses around the parameter list' do
297
284
  'def foo(bar); end'.
298
285
  must_be_parsed_as s(:defn, :foo, s(:args, :bar), s(:nil))
299
286
  end
@@ -390,7 +377,6 @@ describe RipperRubyParser::Parser do
390
377
  end
391
378
 
392
379
  it 'works with a named argument with no default value' do
393
- skip 'Default values are required in Ruby 2.0' if RUBY_VERSION < '2.1.0'
394
380
  'def foo bar:; end'.
395
381
  must_be_parsed_as s(:defn,
396
382
  :foo,
@@ -414,181 +400,35 @@ describe RipperRubyParser::Parser do
414
400
  end
415
401
  end
416
402
 
417
- describe 'for blocks' do
418
- it 'works with no statements in the block body' do
419
- 'foo do; end'.
420
- must_be_parsed_as s(:iter,
421
- s(:call, nil, :foo),
422
- 0)
423
- end
424
-
425
- it 'works with next with no arguments' do
426
- 'foo do; next; end'.
427
- must_be_parsed_as s(:iter,
428
- s(:call, nil, :foo),
429
- 0,
430
- s(:next))
431
- end
432
-
433
- it 'works with next with one argument' do
434
- 'foo do; next bar; end'.
435
- must_be_parsed_as s(:iter,
436
- s(:call, nil, :foo),
437
- 0,
438
- s(:next, s(:call, nil, :bar)))
439
- end
440
-
441
- it 'works with next with several arguments' do
442
- 'foo do; next bar, baz; end'.
443
- must_be_parsed_as s(:iter,
444
- s(:call, nil, :foo),
445
- 0,
446
- s(:next,
447
- s(:array,
448
- s(:call, nil, :bar),
449
- s(:call, nil, :baz))))
450
- end
451
-
452
- it 'works with next with a function call with parentheses' do
453
- 'foo do; next foo(bar); end'.
454
- must_be_parsed_as s(:iter,
455
- s(:call, nil, :foo),
456
- 0,
457
- s(:next,
458
- s(:call, nil, :foo,
459
- s(:call, nil, :bar))))
460
- end
461
-
462
- it 'works with next with a function call without parentheses' do
463
- 'foo do; next foo bar; end'.
464
- must_be_parsed_as s(:iter,
465
- s(:call, nil, :foo),
466
- 0,
467
- s(:next,
468
- s(:call, nil, :foo,
469
- s(:call, nil, :bar))))
470
- end
471
-
472
- it 'works with break with no arguments' do
473
- 'foo do; break; end'.
474
- must_be_parsed_as s(:iter,
475
- s(:call, nil, :foo),
476
- 0,
477
- s(:break))
478
- end
479
-
480
- it 'works with break with one argument' do
481
- 'foo do; break bar; end'.
482
- must_be_parsed_as s(:iter,
483
- s(:call, nil, :foo),
484
- 0,
485
- s(:break, s(:call, nil, :bar)))
486
- end
487
-
488
- it 'works with break with several arguments' do
489
- 'foo do; break bar, baz; end'.
490
- must_be_parsed_as s(:iter,
491
- s(:call, nil, :foo),
492
- 0,
493
- s(:break,
494
- s(:array,
495
- s(:call, nil, :bar),
496
- s(:call, nil, :baz))))
497
- end
498
-
499
- it 'works with break with a function call with parentheses' do
500
- 'foo do; break foo(bar); end'.
501
- must_be_parsed_as s(:iter,
502
- s(:call, nil, :foo),
503
- 0,
504
- s(:break,
505
- s(:call, nil, :foo,
506
- s(:call, nil, :bar))))
507
- end
508
-
509
- it 'works with break with a function call without parentheses' do
510
- 'foo do; break foo bar; end'.
511
- must_be_parsed_as s(:iter,
512
- s(:call, nil, :foo),
513
- 0,
514
- s(:break,
515
- s(:call, nil, :foo,
516
- s(:call, nil, :bar))))
517
- end
518
-
519
- it 'works with redo' do
520
- 'foo do; redo; end'.
521
- must_be_parsed_as s(:iter,
522
- s(:call, nil, :foo),
523
- 0,
524
- s(:redo))
525
- end
526
-
527
- it 'works with zero arguments' do
528
- 'foo do ||; end'.
529
- must_be_parsed_as s(:iter,
530
- s(:call, nil, :foo),
531
- s(:args))
532
- end
533
-
534
- it 'works with one argument' do
535
- 'foo do |bar|; end'.
536
- must_be_parsed_as s(:iter,
537
- s(:call, nil, :foo),
538
- s(:args, :bar))
539
- end
540
-
541
- it 'works with multiple arguments' do
542
- 'foo do |bar, baz|; end'.
543
- must_be_parsed_as s(:iter,
544
- s(:call, nil, :foo),
545
- s(:args, :bar, :baz))
546
- end
547
-
548
- it 'works with a single splat argument' do
549
- 'foo do |*bar|; end'.
550
- must_be_parsed_as s(:iter,
551
- s(:call, nil, :foo),
552
- s(:args, :"*bar"))
553
- end
554
-
555
- it 'works with a combination of regular arguments and a splat argument' do
556
- 'foo do |bar, *baz|; end'.
557
- must_be_parsed_as s(:iter,
558
- s(:call, nil, :foo),
559
- s(:args, :bar, :"*baz"))
560
- end
561
- end
562
-
563
403
  describe 'for yield' do
564
- it 'works with no arguments and no brackets' do
404
+ it 'works with no arguments and no parentheses' do
565
405
  'yield'.
566
406
  must_be_parsed_as s(:yield)
567
407
  end
568
408
 
569
- it 'works with brackets but no arguments' do
409
+ it 'works with parentheses but no arguments' do
570
410
  'yield()'.
571
411
  must_be_parsed_as s(:yield)
572
412
  end
573
413
 
574
- it 'works with one argument and no brackets' do
414
+ it 'works with one argument and no parentheses' do
575
415
  'yield foo'.
576
416
  must_be_parsed_as s(:yield, s(:call, nil, :foo))
577
417
  end
578
418
 
579
- it 'works with one argument and brackets' do
419
+ it 'works with one argument and parentheses' do
580
420
  'yield(foo)'.
581
421
  must_be_parsed_as s(:yield, s(:call, nil, :foo))
582
422
  end
583
423
 
584
- it 'works with multiple arguments and no brackets' do
424
+ it 'works with multiple arguments and no parentheses' do
585
425
  'yield foo, bar'.
586
426
  must_be_parsed_as s(:yield,
587
427
  s(:call, nil, :foo),
588
428
  s(:call, nil, :bar))
589
429
  end
590
430
 
591
- it 'works with multiple arguments and brackets' do
431
+ it 'works with multiple arguments and parentheses' do
592
432
  'yield(foo, bar)'.
593
433
  must_be_parsed_as s(:yield,
594
434
  s(:call, nil, :foo),
@@ -617,6 +457,13 @@ describe RipperRubyParser::Parser do
617
457
  end
618
458
  end
619
459
 
460
+ describe 'for the __ENCODING__ keyword' do
461
+ it 'evaluates to the equivalent of Encoding::UTF_8' do
462
+ '__ENCODING__'.
463
+ must_be_parsed_as s(:colon2, s(:const, :Encoding), :UTF_8)
464
+ end
465
+ end
466
+
620
467
  describe 'for the __FILE__ keyword' do
621
468
  describe 'when not passing a file name' do
622
469
  it "creates a string sexp with value '(string)'" do
@@ -871,7 +718,7 @@ describe RipperRubyParser::Parser do
871
718
  s(:call, nil, :qux)))
872
719
  end
873
720
 
874
- it 'works with brackets around the left-hand side' do
721
+ it 'works with parentheses around the left-hand side' do
875
722
  '(foo, bar) = baz'.
876
723
  must_be_parsed_as s(:masgn,
877
724
  s(:array, s(:lasgn, :foo), s(:lasgn, :bar)),
@@ -1167,7 +1014,7 @@ describe RipperRubyParser::Parser do
1167
1014
  result.line.must_equal 1
1168
1015
  end
1169
1016
 
1170
- it 'works for a method call with brackets' do
1017
+ it 'works for a method call with parentheses' do
1171
1018
  result = parser.parse 'foo()'
1172
1019
  result.line.must_equal 1
1173
1020
  end
@@ -1308,4 +1155,24 @@ describe RipperRubyParser::Parser do
1308
1155
  end
1309
1156
  end
1310
1157
  end
1158
+
1159
+ describe '#trickle_up_line_numbers' do
1160
+ it 'works through several nested levels' do
1161
+ inner = s(:foo)
1162
+ outer = s(:bar, s(:baz, s(:qux, inner)))
1163
+ outer.line = 42
1164
+ parser.send :trickle_down_line_numbers, outer
1165
+ inner.line.must_equal 42
1166
+ end
1167
+ end
1168
+
1169
+ describe '#trickle_down_line_numbers' do
1170
+ it 'works through several nested levels' do
1171
+ inner = s(:foo)
1172
+ inner.line = 42
1173
+ outer = s(:bar, s(:baz, s(:qux, inner)))
1174
+ parser.send :trickle_up_line_numbers, outer
1175
+ outer.line.must_equal 42
1176
+ end
1177
+ end
1311
1178
  end