parser 0.9.alpha1 → 0.9.0

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