parser 1.0.1 → 1.1.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.
@@ -1,9 +1,53 @@
1
+ # encoding:ascii-8bit
2
+
1
3
  module Parser
2
4
  module Source
3
5
 
4
6
  class Buffer
5
7
  attr_reader :name, :first_line
6
8
 
9
+ def self.recognize_encoding(string)
10
+ if string.empty?
11
+ return Encoding::UTF_8
12
+ end
13
+
14
+ # TODO: Make this more efficient.
15
+ first_line, second_line = string.lines.first(2)
16
+ first_line.force_encoding(Encoding::ASCII_8BIT)
17
+
18
+ if first_line =~ /\A\xef\xbb\xbf/ # BOM
19
+ return Encoding::UTF_8
20
+ elsif first_line[0, 2] == '#!'
21
+ encoding_line = second_line
22
+ else
23
+ encoding_line = first_line
24
+ end
25
+
26
+ encoding_line.force_encoding(Encoding::ASCII_8BIT)
27
+
28
+ if encoding_line =~ /coding[:=]?\s*([a-z0-9_-]+)/
29
+ Encoding.find($1)
30
+ else
31
+ string.encoding
32
+ end
33
+ end
34
+
35
+ # Lexer expects UTF-8 input. This method processes the input
36
+ # in an arbitrary valid Ruby encoding and returns an UTF-8 encoded
37
+ # string.
38
+ #
39
+ def self.reencode_string(string)
40
+ encoding = recognize_encoding(string)
41
+
42
+ unless encoding.ascii_compatible?
43
+ raise RuntimeError, "Encoding #{encoding} is not ASCII-compatible"
44
+ end
45
+
46
+ string.
47
+ force_encoding(encoding).
48
+ encode(Encoding::UTF_8)
49
+ end
50
+
7
51
  def initialize(name, first_line = 1)
8
52
  @name = name
9
53
  @first_line = first_line
@@ -11,7 +55,9 @@ module Parser
11
55
  end
12
56
 
13
57
  def read
14
- self.source = File.read(@name)
58
+ File.open(@name, 'rb') do |io|
59
+ self.source = io.read
60
+ end
15
61
 
16
62
  self
17
63
  end
@@ -25,7 +71,11 @@ module Parser
25
71
  end
26
72
 
27
73
  def source=(source)
28
- @source = source.freeze
74
+ if source.respond_to? :encoding
75
+ source = self.class.reencode_string(source)
76
+ end
77
+
78
+ @source = source.freeze
29
79
 
30
80
  freeze
31
81
  end
data/lib/parser.rb CHANGED
@@ -50,8 +50,6 @@ module Parser
50
50
  :regexp_options => "unknown regexp options: %{options}",
51
51
  :cvar_name => "`%{name}' is not allowed as a class variable name",
52
52
  :ivar_name => "`%{name}' is not allowed as an instance variable name",
53
- :ambiguous_literal => "ambiguous first argument; parenthesize arguments or add whitespace to the right",
54
- :ambiguous_prefix => "`%{prefix}' interpreted as argument prefix",
55
53
  :trailing_underscore => "trailing `_' in number",
56
54
  :empty_numeric => "numeric literal without digits",
57
55
  :invalid_octal => "invalid octal digit",
@@ -60,10 +58,13 @@ module Parser
60
58
  :unexpected => "unexpected %{character}",
61
59
  :embedded_document => "embedded document meats end of file (and they embark on a romantic journey)",
62
60
 
61
+ # Lexer warnings
62
+ :ambiguous_literal => "ambiguous first argument; parenthesize arguments or add whitespace to the right",
63
+ :ambiguous_prefix => "`%{prefix}' interpreted as argument prefix",
64
+
63
65
  # Parser errors
64
66
  :nth_ref_alias => "cannot define an alias for a back-reference variable",
65
67
  :begin_in_method => "BEGIN in method",
66
- :end_in_method => "END in method; use at_exit",
67
68
  :backref_assignment => "cannot assign to a back-reference variable",
68
69
  :invalid_assignment => "cannot assign to a keyword",
69
70
  :module_name_const => "class or module name must be a constant literal",
@@ -72,16 +73,26 @@ module Parser
72
73
  :argument_ivar => "formal argument cannot be an instance variable",
73
74
  :argument_gvar => "formal argument cannot be a global variable",
74
75
  :argument_cvar => "formal argument cannot be a class variable",
76
+ :duplicate_argument => "duplicate argument name",
75
77
  :empty_symbol => "empty symbol literal",
76
78
  :odd_hash => "odd number of entries for a hash",
77
79
  :singleton_literal => "cannot define a singleton method for a literal",
78
80
  :dynamic_const => "dynamic constant assignment",
79
81
  :module_in_def => "module definition in method body",
80
82
  :class_in_def => "class definition in method body",
81
- :space_before_lparen => "don't put space before argument parentheses",
82
83
  :unexpected_percent_str => "%{type}: unknown type of percent-literal",
83
- :useless_else => "else without rescue is useless",
84
84
  :block_and_blockarg => "both block argument and literal block are passed",
85
85
  :masgn_as_condition => "multiple assignment in conditional context",
86
+
87
+ # Parser warnings
88
+ :end_in_method => "END in method; use at_exit",
89
+ :space_before_lparen => "don't put space before argument parentheses",
90
+ :useless_else => "else without rescue is useless",
86
91
  }.freeze
92
+
93
+ def self.check_for_encoding_support
94
+ unless defined?(Encoding)
95
+ raise RuntimeError, "Parsing 1.9 and later versions of Ruby is not supported on 1.8 due to the lack of Encoding support"
96
+ end
97
+ end
87
98
  end
data/parser.gemspec CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |spec|
4
4
  spec.name = 'parser'
5
- spec.version = '1.0.1'
5
+ spec.version = '1.1.0'
6
6
  spec.authors = ['Peter Zotov']
7
7
  spec.email = ['whitequark@whitequark.org']
8
8
  spec.description = %q{A Ruby parser written in pure Ruby.}
@@ -15,6 +15,7 @@ Gem::Specification.new do |spec|
15
15
  lib/parser/ruby18.rb
16
16
  lib/parser/ruby19.rb
17
17
  lib/parser/ruby20.rb
18
+ lib/parser/ruby21.rb
18
19
  )
19
20
  spec.executables = %w()
20
21
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
data/test/helper.rb CHANGED
@@ -7,7 +7,7 @@ if SimpleCov.usable?
7
7
  if defined?(TracePoint)
8
8
  require_relative 'racc_coverage_helper'
9
9
 
10
- RaccCoverage.start(%w(ruby18.y ruby19.y ruby20.y),
10
+ RaccCoverage.start(%w(ruby18.y ruby19.y ruby20.y ruby21.y),
11
11
  File.expand_path('../../lib/parser', __FILE__))
12
12
 
13
13
  # Report results faster.
data/test/parse_helper.rb CHANGED
@@ -1,9 +1,16 @@
1
- require 'parser/all'
2
-
3
1
  module ParseHelper
4
2
  include AST::Sexp
5
3
 
6
- ALL_VERSIONS = %w(1.8 1.9 2.0)
4
+ if RUBY_VERSION == '1.8.7'
5
+ require 'parser/ruby18'
6
+
7
+ ALL_VERSIONS = %w(1.8)
8
+ else
9
+ require 'parser/all'
10
+ require 'parser/ruby21'
11
+
12
+ ALL_VERSIONS = %w(1.8 1.9 2.0 2.1)
13
+ end
7
14
 
8
15
  def setup
9
16
  @diagnostics = []
@@ -16,6 +23,7 @@ module ParseHelper
16
23
  when '1.8'; parser = Parser::Ruby18.new
17
24
  when '1.9'; parser = Parser::Ruby19.new
18
25
  when '2.0'; parser = Parser::Ruby20.new
26
+ when '2.1'; parser = Parser::Ruby21.new
19
27
  else raise "Unrecognized Ruby version #{version}"
20
28
  end
21
29
 
@@ -27,7 +35,7 @@ module ParseHelper
27
35
  end
28
36
 
29
37
  def with_versions(code, versions)
30
- versions.each do |version|
38
+ (versions & ALL_VERSIONS).each do |version|
31
39
  @diagnostics.clear
32
40
 
33
41
  parser = parser_for_ruby_version(version)
@@ -0,0 +1,17 @@
1
+ require 'helper'
2
+ require 'parser/current'
3
+
4
+ class TestCurrent < MiniTest::Unit::TestCase
5
+ def test_current
6
+ case RUBY_VERSION
7
+ when '1.8.7'
8
+ assert_equal Parser::Ruby18, Parser::CurrentRuby
9
+ when '1.9.2', '1.9.3'
10
+ assert_equal Parser::Ruby19, Parser::CurrentRuby
11
+ when '2.0.0'
12
+ assert_equal Parser::Ruby20, Parser::CurrentRuby
13
+ else
14
+ flunk "Update test_parser_current for #{RUBY_VERSION}"
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,31 @@
1
+ # encoding: binary
2
+
3
+ require 'helper'
4
+
5
+ class TestEncoding < MiniTest::Unit::TestCase
6
+ def recognize(string)
7
+ Parser::Source::Buffer.recognize_encoding(string)
8
+ end
9
+
10
+ if defined?(Encoding)
11
+ def test_default
12
+ assert_equal Encoding::BINARY, recognize("foobar")
13
+ end
14
+
15
+ def test_bom
16
+ assert_equal Encoding::UTF_8, recognize("\xef\xbb\xbffoobar")
17
+ end
18
+
19
+ def test_magic_comment
20
+ assert_equal Encoding::KOI8_R, recognize("# coding:koi8-r\nfoobar")
21
+ end
22
+
23
+ def test_shebang
24
+ assert_equal Encoding::KOI8_R, recognize("#!/bin/foo\n# coding:koi8-r\nfoobar")
25
+ end
26
+
27
+ def test_empty
28
+ assert_equal Encoding::UTF_8, recognize("")
29
+ end
30
+ end
31
+ end
data/test/test_lexer.rb CHANGED
@@ -50,10 +50,10 @@ class TestLexer < MiniTest::Unit::TestCase
50
50
  end
51
51
  end
52
52
 
53
- def util_lex_fname(name, type, end_state = :expr_end)
53
+ def util_lex_fname(name, type)
54
54
  util_lex_token("def #{name} ", :kDEF, 'def', type, name)
55
55
 
56
- assert_equal end_state, @lex.state
56
+ assert_equal :expr_endfn, @lex.state
57
57
  end
58
58
 
59
59
  def util_lex_token(input, *args)
@@ -149,14 +149,12 @@ class TestLexer < MiniTest::Unit::TestCase
149
149
  :tIDENTIFIER, "m",
150
150
  :tUMINUS_NUM, "-",
151
151
  :tINTEGER, 3)
152
- # TODO: verify warning
153
152
  end
154
153
 
155
154
  def test_ambiguous_uplus
156
155
  util_lex_token("m +3",
157
156
  :tIDENTIFIER, "m",
158
157
  :tINTEGER, 3)
159
- # TODO: verify warning
160
158
  end
161
159
 
162
160
  def test_and
@@ -319,7 +317,7 @@ class TestLexer < MiniTest::Unit::TestCase
319
317
  def test_backtick_method
320
318
  @lex.state = :expr_fname
321
319
  util_lex_token("`", :tBACK_REF2, "`")
322
- assert_equal :expr_end, @lex.state
320
+ assert_equal :expr_endfn, @lex.state
323
321
  end
324
322
 
325
323
  def test_bad_char
@@ -822,7 +820,7 @@ class TestLexer < MiniTest::Unit::TestCase
822
820
  end
823
821
 
824
822
  def test_identifier_def
825
- util_lex_fname "identifier", :tIDENTIFIER, :expr_end
823
+ util_lex_fname "identifier", :tIDENTIFIER
826
824
  end
827
825
 
828
826
  def test_identifier_eh
@@ -854,7 +852,7 @@ class TestLexer < MiniTest::Unit::TestCase
854
852
  end
855
853
 
856
854
  def test_identifier_equals_def
857
- util_lex_fname "identifier=", :tIDENTIFIER, :expr_end
855
+ util_lex_fname "identifier=", :tIDENTIFIER
858
856
  end
859
857
 
860
858
  def test_identifier_equals_def2
@@ -8,11 +8,16 @@ class TestParseHelper < MiniTest::Unit::TestCase
8
8
  assert_instance_of Parser::Ruby18,
9
9
  parser_for_ruby_version('1.8')
10
10
 
11
- assert_instance_of Parser::Ruby19,
12
- parser_for_ruby_version('1.9')
11
+ unless RUBY_VERSION == '1.8.7'
12
+ assert_instance_of Parser::Ruby19,
13
+ parser_for_ruby_version('1.9')
13
14
 
14
- # assert_instance_of Parser::Ruby20,
15
- # parser_for_ruby_version('2.0')
15
+ assert_instance_of Parser::Ruby20,
16
+ parser_for_ruby_version('2.0')
17
+
18
+ assert_instance_of Parser::Ruby21,
19
+ parser_for_ruby_version('2.1')
20
+ end
16
21
  end
17
22
 
18
23
  def parse_maps(what)
data/test/test_parser.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # encoding:utf-8
2
+
1
3
  require 'helper'
2
4
  require 'parse_helper'
3
5
 
@@ -1042,6 +1044,54 @@ class TestParser < MiniTest::Unit::TestCase
1042
1044
  %q{~~~ location})
1043
1045
  end
1044
1046
 
1047
+ def test_const_op_asgn
1048
+ assert_parses(
1049
+ s(:op_asgn,
1050
+ s(:cdecl, nil, :A), :+,
1051
+ s(:int, 1)),
1052
+ %q{A += 1})
1053
+
1054
+ assert_parses(
1055
+ s(:op_asgn,
1056
+ s(:cdecl, s(:cbase), :A), :+,
1057
+ s(:int, 1)),
1058
+ %q{::A += 1},
1059
+ %q{},
1060
+ ALL_VERSIONS - %w(1.8 1.9))
1061
+
1062
+ assert_parses(
1063
+ s(:op_asgn,
1064
+ s(:cdecl, s(:const, nil, :B), :A), :+,
1065
+ s(:int, 1)),
1066
+ %q{B::A += 1},
1067
+ %q{},
1068
+ ALL_VERSIONS - %w(1.8 1.9))
1069
+ end
1070
+
1071
+ def test_const_op_asgn_invalid
1072
+ assert_diagnoses(
1073
+ [:error, :dynamic_const],
1074
+ %q{Foo::Bar += 1},
1075
+ %q{ ~~~ location},
1076
+ %w(1.8 1.9))
1077
+
1078
+ assert_diagnoses(
1079
+ [:error, :dynamic_const],
1080
+ %q{::Bar += 1},
1081
+ %q{ ~~~ location},
1082
+ %w(1.8 1.9))
1083
+
1084
+ assert_diagnoses(
1085
+ [:error, :dynamic_const],
1086
+ %q{def foo; Foo::Bar += 1; end},
1087
+ %q{ ~~~ location})
1088
+
1089
+ assert_diagnoses(
1090
+ [:error, :dynamic_const],
1091
+ %q{def foo; ::Bar += 1; end},
1092
+ %q{ ~~~ location})
1093
+ end
1094
+
1045
1095
  # Method binary operator-assignment
1046
1096
 
1047
1097
  def test_op_asgn
@@ -1133,16 +1183,6 @@ class TestParser < MiniTest::Unit::TestCase
1133
1183
  [:error, :backref_assignment],
1134
1184
  %q{$+ |= m foo},
1135
1185
  %q{~~ location})
1136
-
1137
- assert_diagnoses(
1138
- [:error, :dynamic_const],
1139
- %q{Foo::Bar += 1; end},
1140
- %q{ ~~~ location})
1141
-
1142
- assert_diagnoses(
1143
- [:error, :dynamic_const],
1144
- %q{::Bar += 1; end},
1145
- %q{ ~~~ location})
1146
1186
  end
1147
1187
 
1148
1188
  # Variable logical operator-assignment
@@ -1716,6 +1756,14 @@ class TestParser < MiniTest::Unit::TestCase
1716
1756
  ALL_VERSIONS - %w(1.8 1.9))
1717
1757
  end
1718
1758
 
1759
+ def test_kwarg_no_paren
1760
+ assert_parses_args(
1761
+ s(:args,
1762
+ s(:kwarg, :foo)),
1763
+ %Q{foo:\n},
1764
+ ALL_VERSIONS - %w(1.8 1.9 2.0))
1765
+ end
1766
+
1719
1767
  def assert_parses_margs(ast, code, versions=ALL_VERSIONS - %w(1.8))
1720
1768
  assert_parses_args(
1721
1769
  s(:args, ast),
@@ -2085,6 +2133,105 @@ class TestParser < MiniTest::Unit::TestCase
2085
2133
  %q{ ~~~~~ location})
2086
2134
  end
2087
2135
 
2136
+ def test_arg_duplicate
2137
+ assert_diagnoses(
2138
+ [:error, :duplicate_argument],
2139
+ %q{def foo(aa, aa); end},
2140
+ %q{ ^^ location
2141
+ | ~~ highlights (0)})
2142
+
2143
+ assert_diagnoses(
2144
+ [:error, :duplicate_argument],
2145
+ %q{def foo(aa, aa=1); end},
2146
+ %q{ ^^ location
2147
+ | ~~ highlights (0)})
2148
+
2149
+ assert_diagnoses(
2150
+ [:error, :duplicate_argument],
2151
+ %q{def foo(aa, *aa); end},
2152
+ %q{ ^^ location
2153
+ | ~~ highlights (0)})
2154
+
2155
+ assert_diagnoses(
2156
+ [:error, :duplicate_argument],
2157
+ %q{def foo(aa, &aa); end},
2158
+ %q{ ^^ location
2159
+ | ~~ highlights (0)})
2160
+
2161
+ assert_diagnoses(
2162
+ [:error, :duplicate_argument],
2163
+ %q{def foo(aa, (bb, aa)); end},
2164
+ %q{ ^^ location
2165
+ | ~~ highlights (0)},
2166
+ ALL_VERSIONS - %w(1.8))
2167
+
2168
+ assert_diagnoses(
2169
+ [:error, :duplicate_argument],
2170
+ %q{def foo(aa, *r, aa); end},
2171
+ %q{ ^^ location
2172
+ | ~~ highlights (0)},
2173
+ ALL_VERSIONS - %w(1.8))
2174
+
2175
+
2176
+ assert_diagnoses(
2177
+ [:error, :duplicate_argument],
2178
+ %q{lambda do |aa; aa| end},
2179
+ %q{ ^^ location
2180
+ | ~~ highlights (0)},
2181
+ ALL_VERSIONS - %w(1.8))
2182
+
2183
+ assert_diagnoses(
2184
+ [:error, :duplicate_argument],
2185
+ %q{def foo(aa, aa: 1); end},
2186
+ %q{ ^^ location
2187
+ | ~~ highlights (0)},
2188
+ ALL_VERSIONS - %w(1.8 1.9))
2189
+
2190
+ assert_diagnoses(
2191
+ [:error, :duplicate_argument],
2192
+ %q{def foo(aa, **aa); end},
2193
+ %q{ ^^ location
2194
+ | ~~ highlights (0)},
2195
+ ALL_VERSIONS - %w(1.8 1.9))
2196
+
2197
+ assert_diagnoses(
2198
+ [:error, :duplicate_argument],
2199
+ %q{def foo(aa, aa:); end},
2200
+ %q{ ^^ location
2201
+ | ~~ highlights (0)},
2202
+ ALL_VERSIONS - %w(1.8 1.9 2.0))
2203
+ end
2204
+
2205
+ def test_arg_duplicate_ignored
2206
+ assert_diagnoses(
2207
+ [:error, :duplicate_argument],
2208
+ %q{def foo(_, _); end},
2209
+ %q{},
2210
+ %w(1.8))
2211
+
2212
+ assert_parses(
2213
+ s(:def, :foo,
2214
+ s(:args, s(:arg, :_), s(:arg, :_)),
2215
+ s(:nil)),
2216
+ %q{def foo(_, _); end},
2217
+ %q{},
2218
+ ALL_VERSIONS - %w(1.8))
2219
+
2220
+ assert_diagnoses(
2221
+ [:error, :duplicate_argument],
2222
+ %q{def foo(_a, _a); end},
2223
+ %q{},
2224
+ %w(1.8 1.9))
2225
+
2226
+ assert_parses(
2227
+ s(:def, :foo,
2228
+ s(:args, s(:arg, :_a), s(:arg, :_a)),
2229
+ s(:nil)),
2230
+ %q{def foo(_a, _a); end},
2231
+ %q{},
2232
+ ALL_VERSIONS - %w(1.8 1.9))
2233
+ end
2234
+
2088
2235
  def test_kwarg_invalid
2089
2236
  assert_diagnoses(
2090
2237
  [:error, :argument_const],
@@ -3922,6 +4069,21 @@ class TestParser < MiniTest::Unit::TestCase
3922
4069
  ALL_VERSIONS - %w(1.8 1.9))
3923
4070
  end
3924
4071
 
4072
+ if defined?(Encoding)
4073
+ def test_magic_encoding_comment
4074
+ assert_parses(
4075
+ s(:begin,
4076
+ s(:lvasgn, :"проверка", s(:int, 42)),
4077
+ s(:send, nil, :puts, s(:lvar, :"проверка"))),
4078
+ %Q{# coding:koi8-r
4079
+ \xd0\xd2\xcf\xd7\xc5\xd2\xcb\xc1 = 42
4080
+ puts \xd0\xd2\xcf\xd7\xc5\xd2\xcb\xc1}.
4081
+ force_encoding(Encoding::BINARY),
4082
+ %q{},
4083
+ %w(1.9 2.0 2.1))
4084
+ end
4085
+ end
4086
+
3925
4087
  #
3926
4088
  # Error recovery
3927
4089
  #