parser 0.9.alpha1 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
[![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
|
-
|
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
|