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.
- checksums.yaml +4 -4
- data/.travis.yml +4 -3
- data/AST_FORMAT.md +1338 -0
- data/README.md +58 -3
- data/Rakefile +32 -12
- data/bin/benchmark +47 -0
- data/bin/explain-parse +14 -0
- data/bin/parse +6 -0
- data/lib/parser.rb +84 -0
- data/lib/parser/all.rb +2 -0
- data/lib/parser/ast/node.rb +11 -0
- data/lib/parser/ast/processor.rb +8 -0
- data/lib/parser/base.rb +116 -0
- data/lib/parser/builders/default.rb +654 -0
- data/lib/parser/compatibility/ruby1_8.rb +13 -0
- data/lib/parser/diagnostic.rb +44 -0
- data/lib/parser/diagnostic/engine.rb +44 -0
- data/lib/parser/lexer.rl +335 -245
- data/lib/parser/lexer/explanation.rb +37 -0
- data/lib/parser/{lexer_literal.rb → lexer/literal.rb} +22 -12
- data/lib/parser/lexer/stack_state.rb +38 -0
- data/lib/parser/ruby18.y +1957 -0
- data/lib/parser/ruby19.y +2154 -0
- data/lib/parser/source/buffer.rb +78 -0
- data/lib/parser/source/map.rb +20 -0
- data/lib/parser/source/map/operator.rb +15 -0
- data/lib/parser/source/map/variable_assignment.rb +15 -0
- data/lib/parser/source/range.rb +66 -0
- data/lib/parser/static_environment.rb +12 -6
- data/parser.gemspec +23 -13
- data/test/helper.rb +45 -0
- data/test/parse_helper.rb +204 -0
- data/test/racc_coverage_helper.rb +130 -0
- data/test/test_diagnostic.rb +47 -0
- data/test/test_diagnostic_engine.rb +58 -0
- data/test/test_lexer.rb +601 -357
- data/test/test_lexer_stack_state.rb +69 -0
- data/test/test_parse_helper.rb +74 -0
- data/test/test_parser.rb +3654 -0
- data/test/test_source_buffer.rb +80 -0
- data/test/test_source_range.rb +51 -0
- data/test/test_static_environment.rb +1 -4
- metadata +137 -12
data/README.md
CHANGED
@@ -2,8 +2,9 @@
|
|
2
2
|
|
3
3
|
[](https://travis-ci.org/whitequark/parser)
|
4
4
|
[](https://codeclimate.com/github/whitequark/parser)
|
5
|
+
[](https://coveralls.io/r/whitequark/parser)
|
5
6
|
|
6
|
-
|
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
|
-
|
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
|
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
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/clean'
|
2
4
|
|
3
5
|
task :default => [:generate, :test]
|
4
6
|
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
+
|
data/bin/benchmark
ADDED
@@ -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)
|
data/bin/explain-parse
ADDED
@@ -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)
|
data/bin/parse
ADDED
data/lib/parser.rb
CHANGED
@@ -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
|
data/lib/parser/all.rb
ADDED
data/lib/parser/base.rb
ADDED
@@ -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
|