parser 0.9.alpha1 → 0.9.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 (43) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +4 -3
  3. data/AST_FORMAT.md +1338 -0
  4. data/README.md +58 -3
  5. data/Rakefile +32 -12
  6. data/bin/benchmark +47 -0
  7. data/bin/explain-parse +14 -0
  8. data/bin/parse +6 -0
  9. data/lib/parser.rb +84 -0
  10. data/lib/parser/all.rb +2 -0
  11. data/lib/parser/ast/node.rb +11 -0
  12. data/lib/parser/ast/processor.rb +8 -0
  13. data/lib/parser/base.rb +116 -0
  14. data/lib/parser/builders/default.rb +654 -0
  15. data/lib/parser/compatibility/ruby1_8.rb +13 -0
  16. data/lib/parser/diagnostic.rb +44 -0
  17. data/lib/parser/diagnostic/engine.rb +44 -0
  18. data/lib/parser/lexer.rl +335 -245
  19. data/lib/parser/lexer/explanation.rb +37 -0
  20. data/lib/parser/{lexer_literal.rb → lexer/literal.rb} +22 -12
  21. data/lib/parser/lexer/stack_state.rb +38 -0
  22. data/lib/parser/ruby18.y +1957 -0
  23. data/lib/parser/ruby19.y +2154 -0
  24. data/lib/parser/source/buffer.rb +78 -0
  25. data/lib/parser/source/map.rb +20 -0
  26. data/lib/parser/source/map/operator.rb +15 -0
  27. data/lib/parser/source/map/variable_assignment.rb +15 -0
  28. data/lib/parser/source/range.rb +66 -0
  29. data/lib/parser/static_environment.rb +12 -6
  30. data/parser.gemspec +23 -13
  31. data/test/helper.rb +45 -0
  32. data/test/parse_helper.rb +204 -0
  33. data/test/racc_coverage_helper.rb +130 -0
  34. data/test/test_diagnostic.rb +47 -0
  35. data/test/test_diagnostic_engine.rb +58 -0
  36. data/test/test_lexer.rb +601 -357
  37. data/test/test_lexer_stack_state.rb +69 -0
  38. data/test/test_parse_helper.rb +74 -0
  39. data/test/test_parser.rb +3654 -0
  40. data/test/test_source_buffer.rb +80 -0
  41. data/test/test_source_range.rb +51 -0
  42. data/test/test_static_environment.rb +1 -4
  43. metadata +137 -12
data/README.md CHANGED
@@ -2,8 +2,9 @@
2
2
 
3
3
  [![Build Status](https://travis-ci.org/whitequark/parser.png?branch=master)](https://travis-ci.org/whitequark/parser)
4
4
  [![Code Climate](https://codeclimate.com/github/whitequark/parser.png)](https://codeclimate.com/github/whitequark/parser)
5
+ [![Coverage Status](https://coveralls.io/repos/whitequark/parser/badge.png?branch=master)](https://coveralls.io/r/whitequark/parser)
5
6
 
6
- Parser is a Ruby parser written in pure Ruby.
7
+ _Parser_ is a Ruby parser written in pure Ruby.
7
8
 
8
9
  ## Installation
9
10
 
@@ -11,11 +12,65 @@ Parser is a Ruby parser written in pure Ruby.
11
12
 
12
13
  ## Usage
13
14
 
14
- TODO: Write usage instructions here
15
+ Parse a chunk of code:
16
+ ``` ruby
17
+ require 'parser/ruby19'
18
+
19
+ p Parser::Ruby19.parse("2 + 2")
20
+ # (send
21
+ # (int 2) :+
22
+ # (int 2))
23
+ ```
24
+
25
+ Parse a chunk of code and display all diagnostics:
26
+ ``` ruby
27
+ parser = Parser::Ruby19.new
28
+ parser.diagnostics.consumer = lambda do |diag|
29
+ puts diag.render
30
+ end
31
+
32
+ buffer = Parser::Source::Buffer.new('(string)')
33
+ buffer.source = "foo *bar"
34
+
35
+ p parser.parse(buffer)
36
+ # (string):1:5: warning: `*' interpreted as argument prefix
37
+ # foo *bar
38
+ # ^
39
+ # (send nil :foo
40
+ # (splat
41
+ # (send nil :bar)))
42
+ ```
43
+
44
+ ## Features
45
+
46
+ * Precise source location reporting.
47
+ * [Documented](AST_FORMAT.md) AST format which is convenient to work with.
48
+ * A simple interface and a powerful, tweakable one.
49
+ * Parses 1.8, 1.9 and 2.0 syntax with backwards-compatible AST formats (WIP, only 1.8 & 1.9 yet).
50
+ * Parsing error recovery.
51
+ * Improved [clang-like][] diagnostic messages with location information.
52
+ * Written in pure Ruby, runs on MRI 1.8.7 or >=1.9.2, JRuby and Rubinius in 1.8 and 1.9 mode.
53
+ * Single runtime dependency: the [ast][] gem.
54
+ * RubyParser compatibility (WIP, no, not really yet).
55
+ * [Insane][insane-lexer] Ruby lexer rewritten from scratch in Ragel.
56
+ * 100% test coverage for Bison grammars (except error recovery).
57
+ * Readable, commented source code.
58
+
59
+ [clang-like]: http://clang.llvm.org/diagnostics.html
60
+ [ast]: http://rubygems.org/gems/ast
61
+ [insane-lexer]: http://whitequark.org/blog/2013/04/01/ruby-hacking-guide-ch-11-finite-state-lexer/
62
+
63
+ ## Contributors
64
+
65
+ * Peter Zotov ([whitequark][])
66
+ * Magnus Holm ([judofyr][])
67
+
68
+ [whitequark]: https://github.com/whitequark
69
+ [judofyr]: https://github.com/judofyr
15
70
 
16
71
  ## Acknowledgements
17
72
 
18
- The lexer testsuite is derived from [ruby_parser](http://github.com/seattlerb/ruby_parser).
73
+ The lexer testsuite and ruby_parser compatibility testsuite are derived from [ruby_parser](http://github.com/seattlerb/ruby_parser).
19
74
 
20
75
  The Bison parser rules are derived from [Ruby MRI](http://github.com/ruby/ruby) parse.y.
21
76
 
data/Rakefile CHANGED
@@ -1,25 +1,45 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+ require 'rake/clean'
2
4
 
3
5
  task :default => [:generate, :test]
4
6
 
5
- task :test do
6
- $LOAD_PATH << File.expand_path('../lib/', __FILE__)
7
- Dir["test/test_*.rb"].each do |file|
8
- load file
9
- end
7
+ Rake::TestTask.new do |t|
8
+ t.libs = %w(test/ lib/)
9
+ t.test_files = FileList["test/**/test_*.rb"]
10
10
  end
11
11
 
12
- desc "Generate the Ragel lexer and Bison parser."
13
- task :generate => %w(lib/parser/lexer.rb)
14
- #lib/parser/ruby18.rb
15
- #lib/parser/ruby19.rb)
12
+ task :build => :generate_release
16
13
 
17
- task :build => :generate
14
+ GENERATED_FILES = %w(lib/parser/lexer.rb
15
+ lib/parser/ruby18.rb
16
+ lib/parser/ruby19.rb)
17
+
18
+ CLEAN.include(GENERATED_FILES)
19
+
20
+ desc 'Generate the Ragel lexer and Bison parser.'
21
+ task :generate => GENERATED_FILES
22
+
23
+ task :regenerate => [:clean, :generate]
24
+
25
+ desc 'Generate the Ragel lexer and Bison parser in release mode.'
26
+ task :generate_release => [:clean_env, :regenerate]
27
+
28
+ task :clean_env do
29
+ ENV.delete 'RACC_DEBUG'
30
+ end
18
31
 
19
32
  rule '.rb' => '.rl' do |t|
20
33
  sh "ragel -R #{t.source} -o #{t.name}"
21
34
  end
22
35
 
23
36
  rule '.rb' => '.y' do |t|
24
- sh "racc #{t.source} -o #{t.name} -O"
37
+ opts = [ "--superclass=Parser::Base",
38
+ t.source,
39
+ "-o", t.name
40
+ ]
41
+ opts << "--debug" if ENV['RACC_DEBUG']
42
+
43
+ sh "racc", *opts
25
44
  end
45
+
@@ -0,0 +1,47 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'benchmark'
4
+ require 'ruby_parser'
5
+
6
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
7
+ require 'parser/all'
8
+
9
+ def measure(what)
10
+ rp_failures = 0
11
+ p_failures = 0
12
+
13
+ Benchmark.bm(10) do |x|
14
+ x.report "ruby_parser" do
15
+ what.each do |file, src|
16
+ begin
17
+ Ruby18Parser.new.parse(src)
18
+ rescue Exception => e
19
+ puts file
20
+ puts "RP: #{e.class}: #{e.message}"
21
+ rp_failures += 1
22
+ end
23
+ end
24
+ end
25
+
26
+ x.report "parser" do
27
+ what.each do |file, src|
28
+ begin
29
+ Parser::Ruby18.parse(src, file)
30
+ rescue Parser::SyntaxError
31
+ p_failures += 1
32
+ rescue Exception => e
33
+ puts file
34
+ puts "P: #{e.class}: #{e.message}"
35
+ puts e.backtrace
36
+ p_failures += 1
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ puts
43
+ puts "Failures: RP: #{rp_failures} P: #{p_failures}"
44
+ end
45
+
46
+ files = Hash[ARGV.map { |f| [f, File.read(f)] }]
47
+ measure(files)
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
4
+ require 'parser/all'
5
+
6
+ ENV['RACC_DEBUG'] = '1'
7
+
8
+ class Parser::Base
9
+ def next_token
10
+ @lexer.advance_and_explain
11
+ end
12
+ end
13
+
14
+ Parser::Ruby18.parse(ARGF.read)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH.unshift(File.expand_path('../../lib', __FILE__))
4
+ require 'parser/all'
5
+
6
+ p Parser::Ruby18.parse(ARGF.read)
@@ -1,4 +1,88 @@
1
+ require 'set'
2
+ require 'racc/parser'
3
+
4
+ require 'ast'
5
+
6
+ if RUBY_VERSION < '1.9'
7
+ require 'parser/compatibility/ruby1_8'
8
+ end
9
+
10
+ # Library namespace
1
11
  module Parser
12
+ require 'parser/ast/node'
13
+ require 'parser/ast/processor'
14
+
15
+ require 'parser/source/buffer'
16
+ require 'parser/source/range'
17
+
18
+ require 'parser/source/map'
19
+ require 'parser/source/map/operator'
20
+ require 'parser/source/map/variable_assignment'
21
+
22
+ require 'parser/syntax_error'
23
+ require 'parser/diagnostic'
24
+ require 'parser/diagnostic/engine'
25
+
2
26
  require 'parser/static_environment'
27
+
3
28
  require 'parser/lexer'
29
+ require 'parser/lexer/literal'
30
+ require 'parser/lexer/stack_state'
31
+ require 'parser/lexer/explanation'
32
+
33
+ module Builders
34
+ require 'parser/builders/default'
35
+ end
36
+
37
+ require 'parser/base'
38
+
39
+ ERRORS = {
40
+ # Lexer errors
41
+ :unicode_point_too_large => "invalid Unicode codepoint (too large)",
42
+ :invalid_escape => "invalid escape character syntax",
43
+ :invalid_escape_use => "invalid character syntax; use ?%{escape}",
44
+ :incomplete_escape => "incomplete character syntax",
45
+ :invalid_hex_escape => "invalid hex escape",
46
+ :invalid_unicode_escape => "invalid Unicode escape",
47
+ :unterminated_unicode => "unterminated Unicode escape",
48
+ :escape_eof => "escape sequence meets end of file",
49
+ :string_eof => "unterminated string meets end of file",
50
+ :regexp_options => "unknown regexp options: %{options}",
51
+ :cvar_name => "`%{name}' is not allowed as a class variable name",
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
+ :trailing_underscore => "trailing `_' in number",
56
+ :empty_numeric => "numeric literal without digits",
57
+ :invalid_octal => "invalid octal digit",
58
+ :no_dot_digit_literal => "no .<digit> floating literal anymore; put 0 before dot",
59
+ :bare_backslash => "bare backslash only allowed before newline",
60
+ :unexpected => "unexpected %{character}",
61
+ :embedded_document => "embedded document meats end of file (and they embark on a romantic journey)",
62
+
63
+ # Parser errors
64
+ :nth_ref_alias => "cannot define an alias for a back-reference variable",
65
+ :begin_in_method => "BEGIN in method",
66
+ :end_in_method => "END in method; use at_exit",
67
+ :backref_assignment => "cannot assign to a back-reference variable",
68
+ :invalid_assignment => "cannot assign to a keyword",
69
+ :module_name_const => "class or module name must be a constant literal",
70
+ :unexpected_token => "unexpected token %{token}",
71
+ :argument_const => "formal argument cannot be a constant",
72
+ :argument_ivar => "formal argument cannot be an instance variable",
73
+ :argument_gvar => "formal argument cannot be a global variable",
74
+ :argument_cvar => "formal argument cannot be a class variable",
75
+ :empty_symbol => "empty symbol literal",
76
+ :odd_hash => "odd number of entries for a hash",
77
+ :singleton_literal => "cannot define a singleton method for a literal",
78
+ :dynamic_const => "dynamic constant assignment",
79
+ :module_in_def => "module definition in method body",
80
+ :class_in_def => "class definition in method body",
81
+ :grouped_expression => "(...) interpreted as grouped expression",
82
+ :space_before_lparen => "don't put space before argument parentheses",
83
+ :unexpected_percent_str => "%{type}: unknown type of percent-literal",
84
+ :useless_else => "else without rescue is useless",
85
+ :block_and_blockarg => "both block argument and literal block are passed",
86
+ :masgn_as_condition => "multiple assignment in conditional context",
87
+ }.freeze
4
88
  end
@@ -0,0 +1,2 @@
1
+ require 'parser/ruby18'
2
+ require 'parser/ruby19'
@@ -0,0 +1,11 @@
1
+ module Parser
2
+ module AST
3
+
4
+ class Node < ::AST::Node
5
+ attr_reader :source_map
6
+
7
+ alias src source_map
8
+ end
9
+
10
+ end
11
+ end
@@ -0,0 +1,8 @@
1
+ module Parser
2
+ module AST
3
+
4
+ class Processor < ::AST::Processor
5
+ end
6
+
7
+ end
8
+ end
@@ -0,0 +1,116 @@
1
+ module Parser
2
+
3
+ class Base < Racc::Parser
4
+ def self.parse(string, file='(string)', line=1)
5
+ parser = new
6
+
7
+ parser.diagnostics.all_errors_are_fatal = true
8
+ parser.diagnostics.ignore_warnings = true
9
+
10
+ # Temporary, for manual testing convenience
11
+ parser.diagnostics.consumer = lambda do |diagnostic|
12
+ $stderr.puts(diagnostic.render)
13
+ end
14
+
15
+ source_buffer = Source::Buffer.new(file, line)
16
+ source_buffer.source = string
17
+
18
+ parser.parse(source_buffer)
19
+ end
20
+
21
+ def self.parse_file(filename)
22
+ parse(File.read(filename), filename)
23
+ end
24
+
25
+ attr_reader :diagnostics
26
+ attr_reader :static_env
27
+
28
+ # The source file currently being parsed.
29
+ attr_reader :source_buffer
30
+
31
+ def initialize(builder=Parser::Builders::Default.new)
32
+ @diagnostics = Diagnostic::Engine.new
33
+
34
+ @static_env = StaticEnvironment.new
35
+
36
+ @comments = []
37
+
38
+ @lexer = Lexer.new(version)
39
+ @lexer.diagnostics = @diagnostics
40
+ @lexer.static_env = @static_env
41
+
42
+ @builder = builder
43
+ @builder.parser = self
44
+
45
+ if self.class::Racc_debug_parser && ENV['RACC_DEBUG']
46
+ @yydebug = true
47
+ end
48
+
49
+ reset
50
+ end
51
+
52
+ def reset
53
+ @source_buffer = nil
54
+ @def_level = 0 # count of nested def's.
55
+
56
+ @lexer.reset
57
+ @static_env.reset
58
+
59
+ self
60
+ end
61
+
62
+ def parse(source_buffer)
63
+ @source_buffer = source_buffer
64
+ @lexer.source_buffer = source_buffer
65
+
66
+ do_parse
67
+ ensure
68
+ # Don't keep references to the source file.
69
+ @source_buffer = nil
70
+ @lexer.source_buffer = nil
71
+ end
72
+
73
+ def in_def?
74
+ @def_level > 0
75
+ end
76
+
77
+ protected
78
+
79
+ def value_expr(v)
80
+ #p 'value_expr', v
81
+ v
82
+ end
83
+
84
+ def next_token
85
+ @lexer.advance
86
+ end
87
+
88
+ def diagnostic(level, kind, location_tok, highlights_tok=[])
89
+ _, location = location_tok
90
+
91
+ highlights = highlights_tok.map do |token|
92
+ _, range = token
93
+ range
94
+ end
95
+
96
+ message = ERRORS[kind]
97
+ @diagnostics.process(
98
+ Diagnostic.new(level, message, location, highlights))
99
+
100
+ if level == :error
101
+ yyerror
102
+ end
103
+ end
104
+
105
+ def on_error(error_token_id, error_value, value_stack)
106
+ token_name = token_to_str(error_token_id)
107
+ _, location = error_value
108
+
109
+ # TODO add "expected: ..." here
110
+ message = ERRORS[:unexpected_token] % { :token => token_name }
111
+ @diagnostics.process(
112
+ Diagnostic.new(:error, message, location))
113
+ end
114
+ end
115
+
116
+ end
@@ -0,0 +1,654 @@
1
+ module Parser
2
+
3
+ class Builders::Default
4
+ attr_accessor :parser
5
+
6
+ #
7
+ # Literals
8
+ #
9
+
10
+ # Singletons
11
+
12
+ def nil(token); t(token, :nil); end
13
+ def true(token); t(token, :true); end
14
+ def false(token); t(token, :false); end
15
+
16
+ # Numerics
17
+
18
+ def integer(token, negate=false)
19
+ val = value(token)
20
+ val = -val if negate
21
+
22
+ t(token, :int, val)
23
+ end
24
+
25
+ def __LINE__(token)
26
+ t(token, :__LINE__)
27
+ end
28
+
29
+ def float(token, negate=false)
30
+ val = value(token)
31
+ val = -val if negate
32
+
33
+ t(token, :float, val)
34
+ end
35
+
36
+ # Strings
37
+
38
+ def string(token)
39
+ t(token, :str, value(token))
40
+ end
41
+
42
+ def string_compose(begin_t, parts, end_t)
43
+ if parts.one?
44
+ parts.first
45
+ else
46
+ s(:dstr, *parts)
47
+ end
48
+ end
49
+
50
+ def __FILE__(token)
51
+ t(token, :__FILE__)
52
+ end
53
+
54
+ # Symbols
55
+
56
+ def symbol(token)
57
+ t(token, :sym, value(token).to_sym)
58
+ end
59
+
60
+ def symbol_compose(begin_t, parts, end_t)
61
+ s(:dsym, *parts)
62
+ end
63
+
64
+ # Executable strings
65
+
66
+ def xstring_compose(begin_t, parts, end_t)
67
+ s(:xstr, *parts)
68
+ end
69
+
70
+ # Regular expressions
71
+
72
+ def regexp_options(token)
73
+ t(token, :regopt, *value(token).each_char.sort.uniq.map(&:to_sym))
74
+ end
75
+
76
+ def regexp_compose(begin_t, parts, end_t, options)
77
+ s(:regexp, *(parts << options))
78
+ end
79
+
80
+ # Arrays
81
+
82
+ def array(begin_t, elements, end_t)
83
+ s(:array, *elements)
84
+ end
85
+
86
+ def splat(star_t, arg=nil)
87
+ if arg.nil?
88
+ s(:splat)
89
+ else
90
+ s(:splat, arg)
91
+ end
92
+ end
93
+
94
+ def words_compose(begin_t, parts, end_t)
95
+ s(:array, *parts)
96
+ end
97
+
98
+ # Hashes
99
+
100
+ def pair(key, assoc_t, value)
101
+ s(:pair, key, value)
102
+ end
103
+
104
+ def pair_list_18(list)
105
+ if list.size % 2 != 0
106
+ # TODO better location info here
107
+ message = ERRORS[:odd_hash]
108
+ diagnostic :error, message, list.last.src.expression
109
+ else
110
+ list.
111
+ each_slice(2).map do |key, value|
112
+ s(:pair, key, value)
113
+ end
114
+ end
115
+ end
116
+
117
+ def associate(begin_t, pairs, end_t)
118
+ s(:hash, *pairs)
119
+ end
120
+
121
+ # Ranges
122
+
123
+ def range_inclusive(lhs, token, rhs)
124
+ s(:irange, lhs, rhs)
125
+ end
126
+
127
+ def range_exclusive(lhs, token, rhs)
128
+ s(:erange, lhs, rhs)
129
+ end
130
+
131
+ #
132
+ # Expression grouping
133
+ #
134
+
135
+ def parenthesize(begin_t, expr, end_t)
136
+ expr
137
+ end
138
+
139
+ #
140
+ # Access
141
+ #
142
+
143
+ def self(token); t(token, :self); end
144
+
145
+ def ident(token); t(token, :ident, value(token).to_sym); end
146
+ def ivar(token); t(token, :ivar, value(token).to_sym); end
147
+ def gvar(token); t(token, :gvar, value(token).to_sym); end
148
+ def cvar(token); t(token, :cvar, value(token).to_sym); end
149
+
150
+ def back_ref(token); t(token, :back_ref, value(token).to_sym); end
151
+ def nth_ref(token); t(token, :nth_ref, value(token)); end
152
+
153
+ def accessible(node)
154
+ case node.type
155
+ when :__FILE__
156
+ node.updated(:str, [ node.src.expression.source_buffer.name ])
157
+
158
+ when :__LINE__
159
+ node.updated(:int, [ node.src.expression.line ])
160
+
161
+ when :__ENCODING__
162
+ s(:const, s(:const, nil, :Encoding), :UTF_8)
163
+
164
+ when :ident
165
+ name, = *node
166
+
167
+ if @parser.static_env.declared?(name)
168
+ node.updated(:lvar)
169
+ else
170
+ name, = *node
171
+ node.updated(:send, [ nil, name ])
172
+ end
173
+
174
+ else
175
+ node
176
+ end
177
+ end
178
+
179
+ def const(token)
180
+ t(token, :const, nil, value(token).to_sym)
181
+ end
182
+
183
+ def const_global(t_colon3, token)
184
+ s(:const, s(:cbase), value(token).to_sym)
185
+ end
186
+
187
+ def const_fetch(scope, t_colon2, token)
188
+ s(:const, scope, value(token).to_sym)
189
+ end
190
+
191
+ def __ENCODING__(token)
192
+ t(token, :__ENCODING__)
193
+ end
194
+
195
+ #
196
+ # Assignment
197
+ #
198
+
199
+ def assignable(node)
200
+ case node.type
201
+ when :cvar
202
+ if @parser.in_def?
203
+ node.updated(:cvasgn)
204
+ else
205
+ node.updated(:cvdecl)
206
+ end
207
+
208
+ when :ivar
209
+ node.updated(:ivasgn)
210
+
211
+ when :gvar
212
+ node.updated(:gvasgn)
213
+
214
+ when :const
215
+ if @parser.in_def?
216
+ message = ERRORS[:dynamic_const]
217
+ diagnostic :error, message, node.src.expression
218
+ end
219
+
220
+ node.updated(:cdecl)
221
+
222
+ when :ident
223
+ name, = *node
224
+ @parser.static_env.declare(name)
225
+
226
+ node.updated(:lvasgn)
227
+
228
+ when :nil, :self, :true, :false,
229
+ :__FILE__, :__LINE__, :__ENCODING__
230
+ message = ERRORS[:invalid_assignment]
231
+ diagnostic :error, message, node.src.expression
232
+
233
+ when :back_ref, :nth_ref
234
+ message = ERRORS[:backref_assignment]
235
+ diagnostic :error, message, node.src.expression
236
+
237
+ else
238
+ raise NotImplementedError, "build_assignable #{node.inspect}"
239
+ end
240
+ end
241
+
242
+ def assign(lhs, token, rhs)
243
+ case lhs.type
244
+ when :lvasgn, :masgn, :gvasgn, :ivasgn, :cvdecl,
245
+ :cvasgn, :cdecl,
246
+ :send
247
+ lhs << rhs
248
+
249
+ when :const
250
+ (lhs << rhs).updated(:cdecl)
251
+
252
+ else
253
+ raise NotImplementedError, "build assign #{lhs.inspect}"
254
+ end
255
+ end
256
+
257
+ def op_assign(lhs, operator_t, rhs)
258
+ case lhs.type
259
+ when :gvasgn, :ivasgn, :lvasgn, :cvasgn, :cvdecl, :send
260
+ operator = value(operator_t)[0..-1].to_sym
261
+
262
+ case operator
263
+ when :'&&'
264
+ s(:and_asgn, lhs, rhs)
265
+ when :'||'
266
+ s(:or_asgn, lhs, rhs)
267
+ else
268
+ s(:op_asgn, lhs, operator, rhs)
269
+ end
270
+
271
+ when :back_ref, :nth_ref
272
+ message = ERRORS[:backref_assignment]
273
+ diagnostic :error, message, lhs.src.expression
274
+
275
+ else
276
+ raise NotImplementedError, "build op_assign #{lhs.inspect}"
277
+ end
278
+ end
279
+
280
+ def multi_lhs(begin_t, items, end_t)
281
+ s(:mlhs, *items)
282
+ end
283
+
284
+ def multi_assign(lhs, eql_t, rhs)
285
+ s(:masgn, lhs, rhs)
286
+ end
287
+
288
+ #
289
+ # Class and module definition
290
+ #
291
+
292
+ def def_class(class_t, name,
293
+ lt_t, superclass,
294
+ body, end_t)
295
+ s(:class, name, superclass, body)
296
+ end
297
+
298
+ def def_sclass(class_t, lshft_t, expr,
299
+ body, end_t)
300
+ s(:sclass, expr, body)
301
+ end
302
+
303
+ def def_module(module_t, name,
304
+ body, end_t)
305
+ s(:module, name, body)
306
+ end
307
+
308
+ #
309
+ # Method (un)definition
310
+ #
311
+
312
+ def def_method(def_t, name, args,
313
+ body, end_t, comments)
314
+ s(:def, value(name).to_sym, args, body)
315
+ end
316
+
317
+ def def_singleton(def_t, definee, dot_t,
318
+ name, args,
319
+ body, end_t, comments)
320
+ case definee.type
321
+ when :int, :str, :dstr, :sym, :dsym,
322
+ :regexp, :array, :hash
323
+
324
+ message = ERRORS[:singleton_literal]
325
+ diagnostic :error, message, nil # TODO definee.src.expression
326
+
327
+ else
328
+ s(:defs, definee, value(name).to_sym, args, body)
329
+ end
330
+ end
331
+
332
+ def undef_method(token, names)
333
+ s(:undef, *names)
334
+ end
335
+
336
+ #
337
+ # Aliasing
338
+ #
339
+
340
+ def alias(token, to, from)
341
+ t(token, :alias, to, from)
342
+ end
343
+
344
+ def keyword_cmd(type, token, lparen_t=nil, args=[], rparen_t=nil)
345
+ case type
346
+ when :return,
347
+ :break, :next, :redo,
348
+ :retry,
349
+ :super, :zsuper, :yield,
350
+ :defined?
351
+
352
+ t(token, type, *args)
353
+
354
+ else
355
+ raise NotImplementedError, "build_keyword_cmd #{type} #{args.inspect}"
356
+ end
357
+ end
358
+
359
+ #
360
+ # Formal arguments
361
+ #
362
+
363
+ def args(begin_t, args, end_t)
364
+ s(:args, *args)
365
+ end
366
+
367
+ def arg(token)
368
+ t(token, :arg, value(token).to_sym)
369
+ end
370
+
371
+ def optarg(token, eql_t, value)
372
+ s(:optarg, value(token).to_sym, value)
373
+ end
374
+
375
+ def splatarg(star_t, token=nil)
376
+ if token
377
+ s(:splatarg, value(token).to_sym)
378
+ else
379
+ t(star_t, :splatarg)
380
+ end
381
+ end
382
+
383
+ def shadowarg(token)
384
+ s(:shadowarg, value(token).to_sym)
385
+ end
386
+
387
+ def blockarg(amper_t, token)
388
+ s(:blockarg, value(token).to_sym)
389
+ end
390
+
391
+ # Ruby 1.8 block arguments
392
+
393
+ def arg_expr(expr)
394
+ if expr.type == :lvasgn
395
+ expr.updated(:arg)
396
+ else
397
+ s(:arg_expr, expr)
398
+ end
399
+ end
400
+
401
+ def splatarg_expr(star_t, expr=nil)
402
+ if expr.nil?
403
+ t(star_t, :splatarg)
404
+ elsif expr.type == :lvasgn
405
+ expr.updated(:splatarg)
406
+ else
407
+ s(:splatarg_expr, expr)
408
+ end
409
+ end
410
+
411
+ def blockarg_expr(amper_t, expr)
412
+ if expr.type == :lvasgn
413
+ expr.updated(:blockarg)
414
+ else
415
+ s(:blockarg_expr, expr)
416
+ end
417
+ end
418
+
419
+ #
420
+ # Method calls
421
+ #
422
+
423
+ def call_method(receiver, dot_t, selector_t,
424
+ begin_t=nil, args=[], end_t=nil)
425
+ if selector_t.nil?
426
+ s(:send, receiver, :call, *args)
427
+ else
428
+ s(:send, receiver, value(selector_t).to_sym, *args)
429
+ end
430
+ end
431
+
432
+ def call_lambda(lambda_t)
433
+ s(:send, nil, :lambda)
434
+ end
435
+
436
+ def block(method_call, begin_t, args, body, end_t)
437
+ receiver, selector, *call_args = *method_call
438
+ last_arg = call_args.last
439
+
440
+ if last_arg && last_arg.type == :block_pass
441
+ # TODO uncomment when source maps are ready
442
+ # diagnostic :error, :block_and_blockarg,
443
+ # last_arg.src.expression
444
+
445
+ diagnostic :error, ERRORS[:block_and_blockarg],
446
+ last_arg.children.last.src.expression
447
+ end
448
+
449
+ s(:block, method_call, args, body)
450
+ end
451
+
452
+ def block_pass(amper_t, arg)
453
+ s(:block_pass, arg)
454
+ end
455
+
456
+ def attr_asgn(receiver, dot_t, selector_t)
457
+ method_name = (value(selector_t) + '=').to_sym
458
+
459
+ # Incomplete method call.
460
+ s(:send, receiver, method_name)
461
+ end
462
+
463
+ def index(receiver, lbrack_t, indexes, rbrack_t)
464
+ s(:send, receiver, :[], *indexes)
465
+ end
466
+
467
+ def index_asgn(receiver, lbrack_t, indexes, rbrack_t)
468
+ # Incomplete method call.
469
+ s(:send, receiver, :[]=, *indexes)
470
+ end
471
+
472
+ def binary_op(receiver, token, arg)
473
+ if @parser.version == 18
474
+ if value(token) == '!='
475
+ return s(:not, s(:send, receiver, :==, arg))
476
+ elsif value(token) == '!~'
477
+ return s(:not, s(:send, receiver, :=~, arg))
478
+ end
479
+ end
480
+
481
+ s(:send, receiver, value(token).to_sym, arg)
482
+ end
483
+
484
+ def unary_op(token, receiver)
485
+ case value(token)
486
+ when '+', '-'
487
+ method = value(token) + '@'
488
+ else
489
+ method = value(token)
490
+ end
491
+
492
+ s(:send, receiver, method.to_sym)
493
+ end
494
+
495
+ def not_op(token, receiver=nil)
496
+ if @parser.version == 18
497
+ s(:not, receiver)
498
+ else
499
+ if receiver.nil?
500
+ s(:send, s(:nil), :'!')
501
+ else
502
+ s(:send, receiver, :'!')
503
+ end
504
+ end
505
+ end
506
+
507
+ #
508
+ # Control flow
509
+ #
510
+
511
+ # Logical operations: and, or
512
+
513
+ def logical_op(type, lhs, token, rhs)
514
+ s(type, lhs, rhs)
515
+ end
516
+
517
+ # Conditionals
518
+
519
+ def condition(cond_t, cond, then_t,
520
+ if_true, else_t, if_false, end_t)
521
+ s(:if, check_condition(cond), if_true, if_false)
522
+ end
523
+
524
+ def condition_mod(if_true, if_false, cond_t, cond)
525
+ s(:if, check_condition(cond), if_true, if_false)
526
+ end
527
+
528
+ def ternary(cond, question_t, if_true, colon_t, if_false)
529
+ s(:if, check_condition(cond), if_true, if_false)
530
+ end
531
+
532
+ # Case matching
533
+
534
+ def when(when_t, patterns, then_t, body)
535
+ s(:when, *(patterns << body))
536
+ end
537
+
538
+ def case(case_t, expr, body, end_t)
539
+ s(:case, expr, *body)
540
+ end
541
+
542
+ # Loops
543
+
544
+ def loop(loop_t, cond, do_t, body, end_t)
545
+ s(value(loop_t).to_sym, check_condition(cond), body)
546
+ end
547
+
548
+ def loop_mod(body, loop_t, cond)
549
+ s(value(loop_t).to_sym, check_condition(cond), body)
550
+ end
551
+
552
+ def for(for_t, iterator, in_t, iteratee,
553
+ do_t, body, end_t)
554
+ s(:for, iterator, iteratee, body)
555
+ end
556
+
557
+ # Exception handling
558
+
559
+ def begin(begin_t, body, end_t)
560
+ body
561
+ end
562
+
563
+ def rescue_body(rescue_t,
564
+ exc_list, assoc_t, exc_var,
565
+ then_t, compound_stmt)
566
+ s(:resbody, exc_list, exc_var, compound_stmt)
567
+ end
568
+
569
+ def begin_body(compound_stmt, rescue_bodies=[],
570
+ else_t=nil, else_=nil,
571
+ ensure_t=nil, ensure_=nil)
572
+ if rescue_bodies.any?
573
+ if else_t
574
+ compound_stmt = s(:rescue, compound_stmt,
575
+ *(rescue_bodies << else_))
576
+ else
577
+ compound_stmt = s(:rescue, compound_stmt,
578
+ *(rescue_bodies << nil))
579
+ end
580
+ end
581
+
582
+ if ensure_t
583
+ compound_stmt = s(:ensure, compound_stmt, ensure_)
584
+ end
585
+
586
+ compound_stmt
587
+ end
588
+
589
+ def compstmt(statements)
590
+ case
591
+ when statements.one?
592
+ statements.first
593
+ when statements.none?
594
+ s(:nil)
595
+ else
596
+ s(:begin, *statements)
597
+ end
598
+ end
599
+
600
+ # BEGIN, END
601
+
602
+ def preexe(preexe_t, lbrace_t, compstmt, rbrace_t)
603
+ s(:preexe, compstmt)
604
+ end
605
+
606
+ def postexe(postexe_t, lbrace_t, compstmt, rbrace_t)
607
+ s(:postexe, compstmt)
608
+ end
609
+
610
+ protected
611
+
612
+ def check_condition(cond)
613
+ if cond.type == :masgn
614
+ # TODO source maps
615
+ diagnostic :error, ERRORS[:masgn_as_condition],
616
+ nil #cond.src.expression
617
+ end
618
+
619
+ cond
620
+ end
621
+
622
+ def t(token, type, *args)
623
+ s(type, *(args << { :source_map => Source::Map.new(location(token)) }))
624
+ end
625
+
626
+ def value(token)
627
+ token[0]
628
+ end
629
+
630
+ def location(token)
631
+ token[1]
632
+ end
633
+
634
+ def s(type, *args)
635
+ if Hash === args.last
636
+ metadata = args.pop
637
+ else
638
+ metadata = {}
639
+ end
640
+
641
+ AST::Node.new(type, args, metadata)
642
+ end
643
+
644
+ def diagnostic(type, message, location, highlights=[])
645
+ @parser.diagnostics.process(
646
+ Diagnostic.new(type, message, location, highlights))
647
+
648
+ if type == :error
649
+ @parser.send :yyerror
650
+ end
651
+ end
652
+ end
653
+
654
+ end