parser 1.0.1 → 1.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
  #