vernacular-ast 0.0.1
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 +7 -0
- data/.gitignore +10 -0
- data/.rubocop.yml +9 -0
- data/.travis.yml +4 -0
- data/Gemfile +5 -0
- data/Gemfile.lock +43 -0
- data/LICENSE +21 -0
- data/README.md +93 -0
- data/Rakefile +16 -0
- data/bin/console +8 -0
- data/bin/setup +6 -0
- data/lib/vernacular/ast.rb +39 -0
- data/lib/vernacular/ast/ast_parser.rb +97 -0
- data/lib/vernacular/ast/modifier.rb +66 -0
- data/lib/vernacular/ast/modifiers/typed_method_args.rb +72 -0
- data/lib/vernacular/ast/modifiers/typed_method_returns.rb +72 -0
- data/lib/vernacular/ast/version.rb +7 -0
- data/vernacular-ast.gemspec +32 -0
- metadata +159 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 9cbcc5e2850b12300e6162f2f9f3f3e8cb28daf3387b3199765584a14c7ca460
|
4
|
+
data.tar.gz: 4df105ca7065b14e281a1d97011d8ea805bf6cd5edb454de8b534d267dab2be1
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f98b176fa74fe14985d5102cd32f88bc2b7d54c79de71c439c37952df8cbffa4557daa6b3df3e19e3f4979825ac28c70b7d4016a5296a59b2498f9799f0a647e
|
7
|
+
data.tar.gz: 6826f32766b8877adbb5599b537ac48e8c508a40ee6dfb6004f1bbc87796af16568f4f92c4541eb3585b7d9972f89755ea9eda6070d186b09e9452e4a3216714
|
data/.gitignore
ADDED
data/.rubocop.yml
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
vernacular-ast (0.0.1)
|
5
|
+
parser (~> 2.4)
|
6
|
+
racc (~> 1.4)
|
7
|
+
vernacular (~> 1.0)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
remote: https://rubygems.org/
|
11
|
+
specs:
|
12
|
+
ast (2.4.0)
|
13
|
+
minitest (5.11.3)
|
14
|
+
parallel (1.12.1)
|
15
|
+
parser (2.4.0.2)
|
16
|
+
ast (~> 2.3)
|
17
|
+
powerpack (0.1.1)
|
18
|
+
racc (1.4.14)
|
19
|
+
rainbow (3.0.0)
|
20
|
+
rake (12.3.0)
|
21
|
+
rubocop (0.52.1)
|
22
|
+
parallel (~> 1.10)
|
23
|
+
parser (>= 2.4.0.2, < 3.0)
|
24
|
+
powerpack (~> 0.1)
|
25
|
+
rainbow (>= 2.2.2, < 4.0)
|
26
|
+
ruby-progressbar (~> 1.7)
|
27
|
+
unicode-display_width (~> 1.0, >= 1.0.1)
|
28
|
+
ruby-progressbar (1.9.0)
|
29
|
+
unicode-display_width (1.3.0)
|
30
|
+
vernacular (1.0.0)
|
31
|
+
|
32
|
+
PLATFORMS
|
33
|
+
ruby
|
34
|
+
|
35
|
+
DEPENDENCIES
|
36
|
+
bundler (~> 1.16)
|
37
|
+
minitest (~> 5.11)
|
38
|
+
rake (~> 12.3)
|
39
|
+
rubocop (~> 0.52)
|
40
|
+
vernacular-ast!
|
41
|
+
|
42
|
+
BUNDLED WITH
|
43
|
+
1.16.1
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2018 Kevin Deisz
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,93 @@
|
|
1
|
+
# Vernacular::AST
|
2
|
+
|
3
|
+
[](https://travis-ci.org/kddeisz/vernacular-ast)
|
4
|
+
[](https://rubygems.org/gems/vernacular-ast)
|
5
|
+
|
6
|
+
Extends `Vernacular` to support rewriting the AST.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add this line to your application's Gemfile:
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'vernacular-ast'
|
14
|
+
```
|
15
|
+
|
16
|
+
And then execute:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
Or install it yourself as:
|
21
|
+
|
22
|
+
$ gem install vernacular-ast
|
23
|
+
|
24
|
+
## Usage
|
25
|
+
|
26
|
+
For general usage information, see the [`README`](https://github.com/kddeisz/vernacular) for the `Vernacular` gem.
|
27
|
+
|
28
|
+
### `Modifiers::ASTModifier`
|
29
|
+
|
30
|
+
AST modifiers are somewhat more difficult to configure. A basic knowledge of the [`parser`](https://github.com/whitequark/parser) gem is required. First, extend the `Parser` to understand the additional syntax that you're trying to add. Second, extend the `Builder` with information about how to build s-expressions with your extra information. Finally, extend the `Rewriter` with code that will modify your extended AST by rewriting into a valid Ruby AST. An example is below:
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Vernacular::ASTModifier.new do |modifier|
|
34
|
+
# Extend the parser to support and equal sign and a class path following the
|
35
|
+
# declaration of a functions arguments to represent its return type.
|
36
|
+
modifier.extend_parser(:f_arglist, 'f_arglist tEQL cpath', <<~PARSE)
|
37
|
+
result = @builder.type_check_arglist(*val)
|
38
|
+
PARSE
|
39
|
+
|
40
|
+
# Extend the builder by adding a `type_check_arglist` function that will build
|
41
|
+
# a new node type and place it at the end of the argument list.
|
42
|
+
modifier.extend_builder(:type_check_arglist) do |arglist, equal, cpath|
|
43
|
+
arglist << n(:type_check_arglist, [equal, cpath], nil)
|
44
|
+
end
|
45
|
+
|
46
|
+
# Extend the rewriter by adding an `on_def` callback, which will be called
|
47
|
+
# whenever a `def` node is added to the AST. Then, loop through and find any
|
48
|
+
# `type_check_arglist` nodes, and remove them. Finally, insert the
|
49
|
+
# appropriate raises around the execution of the function to mirror the type
|
50
|
+
# checking.
|
51
|
+
modifier.build_rewriter do
|
52
|
+
def on_def(node)
|
53
|
+
type_check_node = node.children[1].children.last
|
54
|
+
return super if !type_check_node || type_check_node.type != :type_check_arglist
|
55
|
+
|
56
|
+
remove(type_check_node.children[0][1])
|
57
|
+
remove(type_check_node.children[1].loc.expression)
|
58
|
+
type = build_constant(type_check_node.children[1])
|
59
|
+
|
60
|
+
@source_rewriter.transaction do
|
61
|
+
insert_before(node.children[2].loc.expression, "result = begin\n")
|
62
|
+
insert_after(node.children[2].loc.expression,
|
63
|
+
"\nend\nraise \"Invalid return value, expected #{type}, " <<
|
64
|
+
"got \#{result.class.name}\" unless result.is_a?(#{type})\nresult")
|
65
|
+
end
|
66
|
+
|
67
|
+
super
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def build_constant(node, suffix = nil)
|
73
|
+
child_node, name = node.children
|
74
|
+
new_name = suffix ? "#{name}::#{suffix}" : name
|
75
|
+
child_node ? build_constant(child_node, new_name) : new_name
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
## Development
|
82
|
+
|
83
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
84
|
+
|
85
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
86
|
+
|
87
|
+
## Contributing
|
88
|
+
|
89
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/kddeisz/vernacular-ast.
|
90
|
+
|
91
|
+
## License
|
92
|
+
|
93
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/testtask'
|
5
|
+
|
6
|
+
Rake::TestTask.new(:test) do |t|
|
7
|
+
filepath = File.expand_path('test/test_loader.rb', __dir__)
|
8
|
+
t.ruby_opts << "-r #{filepath}"
|
9
|
+
t.warning = false
|
10
|
+
|
11
|
+
t.libs << 'test'
|
12
|
+
t.libs << 'lib'
|
13
|
+
t.test_files = FileList['test/**/*_test.rb']
|
14
|
+
end
|
15
|
+
|
16
|
+
task default: :test
|
data/bin/console
ADDED
data/bin/setup
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'parser'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'vernacular'
|
6
|
+
|
7
|
+
require 'vernacular/ast/ast_parser'
|
8
|
+
require 'vernacular/ast/modifier'
|
9
|
+
require 'vernacular/ast/version'
|
10
|
+
|
11
|
+
require 'vernacular/ast/modifiers/typed_method_args'
|
12
|
+
require 'vernacular/ast/modifiers/typed_method_returns'
|
13
|
+
|
14
|
+
module Vernacular
|
15
|
+
# Extends Vernacular to support rewriting the AST
|
16
|
+
module AST
|
17
|
+
PARSER_PATH = File.expand_path('ast/parser.rb', __dir__).freeze
|
18
|
+
|
19
|
+
Vernacular::InstructionSequenceMixin.prepend(
|
20
|
+
Module.new do
|
21
|
+
def load_iseq(filepath)
|
22
|
+
super unless filepath == PARSER_PATH
|
23
|
+
end
|
24
|
+
end
|
25
|
+
)
|
26
|
+
|
27
|
+
Vernacular::BootsnapMixin.prepend(
|
28
|
+
Module.new do
|
29
|
+
def input_to_storage(_, filepath)
|
30
|
+
if filepath == PARSER_PATH
|
31
|
+
raise ::Bootsnap::CompileCache::Uncompilable, "can't compile parser"
|
32
|
+
end
|
33
|
+
|
34
|
+
super
|
35
|
+
end
|
36
|
+
end
|
37
|
+
)
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vernacular
|
4
|
+
module AST
|
5
|
+
# Handles monkeying around with the `parser` gem to get it to handle the
|
6
|
+
# various modifications that users can configure `Vernacular` to perform.
|
7
|
+
class ASTParser
|
8
|
+
def parser
|
9
|
+
source = parser_source
|
10
|
+
|
11
|
+
ast_modifiers.each do |modifier|
|
12
|
+
modifier.parser_extensions.each do |parser_extension|
|
13
|
+
source = extend_parser(source, parser_extension)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
write_parser(source)
|
18
|
+
load 'vernacular/ast/parser.rb'
|
19
|
+
Parser::Vernacular.new(builder)
|
20
|
+
end
|
21
|
+
|
22
|
+
class << self
|
23
|
+
def parse(string)
|
24
|
+
parser.reset
|
25
|
+
buffer = Parser::Base.send(:setup_source_buffer, '(string)', 1,
|
26
|
+
string, @parser.default_encoding)
|
27
|
+
parser.parse(buffer)
|
28
|
+
end
|
29
|
+
|
30
|
+
def parser
|
31
|
+
@parser ||= new.parser
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def ast_modifiers
|
38
|
+
Vernacular.modifiers.grep(ASTModifier)
|
39
|
+
end
|
40
|
+
|
41
|
+
def builder
|
42
|
+
modifiers = ast_modifiers
|
43
|
+
|
44
|
+
Class.new(Parser::Builders::Default) do
|
45
|
+
modifiers.each do |modifier|
|
46
|
+
modifier.builder_extensions.each do |builder_extension|
|
47
|
+
define_method(builder_extension.method, &builder_extension.block)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end.new
|
51
|
+
end
|
52
|
+
|
53
|
+
def compile_parser(filepath)
|
54
|
+
output = PARSER_PATH
|
55
|
+
exec_path = Gem.activate_bin_path('racc', 'racc', [])
|
56
|
+
`#{exec_path} --superclass=Parser::Base -o #{output} #{filepath}`
|
57
|
+
File.write(output, File.read(output).gsub('Ruby25', 'Vernacular'))
|
58
|
+
end
|
59
|
+
|
60
|
+
# rubocop:disable Metrics/MethodLength
|
61
|
+
def extend_parser(source, parser_extension)
|
62
|
+
needle = "#{parser_extension.symbol}:"
|
63
|
+
pattern = /\A\s+#{needle}/
|
64
|
+
|
65
|
+
source.split("\n").each_with_object([]) do |line, edited|
|
66
|
+
if line.match?(pattern)
|
67
|
+
lhs, rhs = line.split(needle)
|
68
|
+
edited << "#{lhs}#{needle} #{parser_extension.pattern}\n" \
|
69
|
+
"{\n#{parser_extension.code}\n}\n#{lhs}|#{rhs}"
|
70
|
+
else
|
71
|
+
edited << line
|
72
|
+
end
|
73
|
+
end.join("\n")
|
74
|
+
end
|
75
|
+
# rubocop:enable Metrics/MethodLength
|
76
|
+
|
77
|
+
def parser_source
|
78
|
+
filepath, = Parser.method(:check_for_encoding_support).source_location
|
79
|
+
grammar_filepath = "../../lib/parser/ruby#{parser_version}.y"
|
80
|
+
File.read(File.expand_path(grammar_filepath, filepath))
|
81
|
+
end
|
82
|
+
|
83
|
+
def parser_version
|
84
|
+
@parser_version ||= RUBY_VERSION.gsub(/\A(\d)\.(\d).+/, '\1\2')
|
85
|
+
end
|
86
|
+
|
87
|
+
def write_parser(source)
|
88
|
+
file = Tempfile.new(['parser-', '.y'])
|
89
|
+
file.write(source)
|
90
|
+
compile_parser(file.path)
|
91
|
+
ensure
|
92
|
+
file.close
|
93
|
+
file.unlink
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vernacular
|
4
|
+
module AST
|
5
|
+
# Represents a modification that will be performed against the AST between
|
6
|
+
# the time that the source code is read and the time that it is compiled
|
7
|
+
# down to YARV instruction sequences.
|
8
|
+
class Modifier
|
9
|
+
BuilderExtension =
|
10
|
+
Struct.new(:method, :block) do
|
11
|
+
def components
|
12
|
+
[method, block.source_location]
|
13
|
+
end
|
14
|
+
end
|
15
|
+
attr_reader :builder_extensions
|
16
|
+
|
17
|
+
ParserExtension =
|
18
|
+
Struct.new(:symbol, :pattern, :code) do
|
19
|
+
def components
|
20
|
+
[symbol, pattern, code]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
attr_reader :parser_extensions
|
24
|
+
|
25
|
+
attr_reader :rewriter_block
|
26
|
+
|
27
|
+
def initialize
|
28
|
+
@builder_extensions = []
|
29
|
+
@parser_extensions = []
|
30
|
+
yield self if block_given?
|
31
|
+
end
|
32
|
+
|
33
|
+
def build_rewriter(&block)
|
34
|
+
@rewriter_block = block
|
35
|
+
end
|
36
|
+
|
37
|
+
def extend_builder(method, &block)
|
38
|
+
builder_extensions << BuilderExtension.new(method, block)
|
39
|
+
end
|
40
|
+
|
41
|
+
def extend_parser(symbol, pattern, code)
|
42
|
+
parser_extensions << ParserExtension.new(symbol, pattern, code)
|
43
|
+
end
|
44
|
+
|
45
|
+
def modify(source)
|
46
|
+
raise 'You must first configure a rewriter!' unless rewriter_block
|
47
|
+
|
48
|
+
rewriter = Class.new(Parser::Rewriter, &rewriter_block).new
|
49
|
+
rewriter.instance_variable_set(:@parser, ASTParser.parser)
|
50
|
+
|
51
|
+
buffer = Parser::Source::Buffer.new('<dynamic>')
|
52
|
+
buffer.source = source
|
53
|
+
|
54
|
+
ast = ASTParser.parse(source)
|
55
|
+
rewriter.rewrite(buffer, ast)
|
56
|
+
end
|
57
|
+
|
58
|
+
def components
|
59
|
+
(builder_extensions + parser_extensions).flat_map(&:components) +
|
60
|
+
rewriter_block.source_location
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
Vernacular::ASTModifier = Vernacular::AST::Modifier
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vernacular
|
4
|
+
module AST
|
5
|
+
module Modifiers
|
6
|
+
# Extends Ruby syntax to allow typed method argument declarations, as in:
|
7
|
+
# def my_method(argument_a : Integer, argument_b : String); end
|
8
|
+
class TypedMethodArgs < ASTModifier
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
|
12
|
+
extend_parser(:f_arg, 'f_arg tCOLON cpath', <<~PARSE)
|
13
|
+
result = @builder.type_check_arg(*val)
|
14
|
+
PARSE
|
15
|
+
|
16
|
+
extend_builder(:type_check_arg) do |args, colon, cpath|
|
17
|
+
location = args[0].loc.with_operator(loc(colon))
|
18
|
+
.with_expression(join_exprs(args[0], cpath))
|
19
|
+
[n(:type_check_arg, [args, cpath], location)]
|
20
|
+
end
|
21
|
+
|
22
|
+
build_rewriter { include TypedMethodArgsRewriter }
|
23
|
+
end
|
24
|
+
|
25
|
+
# Methods to be included in the rewriter in order to handle
|
26
|
+
# `type_check_arg` nodes.
|
27
|
+
module TypedMethodArgsRewriter
|
28
|
+
# Triggered whenever a `:def` node is added to the AST. Finds any
|
29
|
+
# `type_check_arg` nodes, replaces them with normal `:arg` nodes, and
|
30
|
+
# adds in the represented type check to the beginning of the method.
|
31
|
+
def on_def(node)
|
32
|
+
type_checks = build_type_checks(node.children[1].children)
|
33
|
+
if type_checks.any?
|
34
|
+
insert_before(node.children[2].loc.expression, type_checks.join)
|
35
|
+
end
|
36
|
+
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def build_constant(node, suffix = nil)
|
43
|
+
child_node, name = node.children
|
44
|
+
new_name = suffix ? "#{name}::#{suffix}" : name
|
45
|
+
child_node ? build_constant(child_node, new_name) : new_name
|
46
|
+
end
|
47
|
+
|
48
|
+
def build_type_checks(arg_list_node)
|
49
|
+
arg_list_node.each_with_object([]) do |arg, type_checks|
|
50
|
+
next unless arg.type == :type_check_arg
|
51
|
+
|
52
|
+
type_checks << type_check(arg)
|
53
|
+
remove(arg.loc.operator)
|
54
|
+
remove(arg.children[1].loc.expression)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def type_check(arg_node)
|
59
|
+
arg_name = arg_node.children[0][0].children[0]
|
60
|
+
type = build_constant(arg_node.children[1])
|
61
|
+
"raise ArgumentError, \"Invalid type, expected #{type}, got " \
|
62
|
+
"\#{#{arg_name}.class.name}\" unless #{arg_name}.is_a?(#{type});"
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# For legacy support
|
71
|
+
Vernacular::Modifiers::TypedMethodArgs =
|
72
|
+
Vernacular::AST::Modifiers::TypedMethodArgs
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Vernacular
|
4
|
+
module AST
|
5
|
+
module Modifiers
|
6
|
+
# Extends Ruby syntax to allow typed method return declarations, as in:
|
7
|
+
# def my_method(argument_a, argument_b) = return_type; end
|
8
|
+
class TypedMethodReturns < ASTModifier
|
9
|
+
def initialize
|
10
|
+
super
|
11
|
+
|
12
|
+
extend_parser(:f_arglist, 'f_arglist tEQL cpath', <<~PARSE)
|
13
|
+
result = @builder.type_check_arglist(*val)
|
14
|
+
PARSE
|
15
|
+
|
16
|
+
extend_builder(:type_check_arglist) do |arglist, equal, cpath|
|
17
|
+
arglist << n(:type_check_arglist, [equal, cpath], nil)
|
18
|
+
end
|
19
|
+
|
20
|
+
build_rewriter { include TypedMethodReturnsRewriter }
|
21
|
+
end
|
22
|
+
|
23
|
+
# Methods to be included in the rewriter in order to handle
|
24
|
+
# `type_check_arglist` nodes.
|
25
|
+
module TypedMethodReturnsRewriter
|
26
|
+
def on_def(method_node)
|
27
|
+
type_check_node = type_check_node_from(method_node)
|
28
|
+
return super unless type_check_node
|
29
|
+
|
30
|
+
type_node = type_check_node.children[1]
|
31
|
+
remove(type_check_node.children[0][1])
|
32
|
+
remove(type_node.loc.expression)
|
33
|
+
type_check_method(method_node, type_node)
|
34
|
+
|
35
|
+
super
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def build_constant(node, suffix = nil)
|
41
|
+
child_node, name = node.children
|
42
|
+
new_name = suffix ? "#{name}::#{suffix}" : name
|
43
|
+
child_node ? build_constant(child_node, new_name) : new_name
|
44
|
+
end
|
45
|
+
|
46
|
+
def type_check_node_from(method_node)
|
47
|
+
type_check_node = method_node.children[1].children.last
|
48
|
+
return if !type_check_node ||
|
49
|
+
type_check_node.type != :type_check_arglist
|
50
|
+
type_check_node
|
51
|
+
end
|
52
|
+
|
53
|
+
def type_check_method(method_node, type_node)
|
54
|
+
expression = method_node.children[2].loc.expression
|
55
|
+
type = build_constant(type_node)
|
56
|
+
|
57
|
+
@source_rewriter.transaction do
|
58
|
+
insert_before(expression, "result = begin\n")
|
59
|
+
insert_after(expression, "\nend\nraise \"Invalid return value, " \
|
60
|
+
"expected #{type}, got \#{result.class.name}\" unless " \
|
61
|
+
"result.is_a?(#{type})\nresult")
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# For legacy support
|
71
|
+
Vernacular::Modifiers::TypedMethodReturns =
|
72
|
+
Vernacular::AST::Modifiers::TypedMethodReturns
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib', __FILE__)
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
+
require 'vernacular/ast/version'
|
6
|
+
|
7
|
+
Gem::Specification.new do |spec|
|
8
|
+
spec.name = 'vernacular-ast'
|
9
|
+
spec.version = Vernacular::AST::VERSION
|
10
|
+
spec.authors = ['Kevin Deisz']
|
11
|
+
spec.email = ['kevin.deisz@gmail.com']
|
12
|
+
|
13
|
+
spec.summary = 'Extends Vernacular to support rewriting the AST'
|
14
|
+
spec.homepage = 'https://github.com/kddeisz/vernacular-ast'
|
15
|
+
spec.license = 'MIT'
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = 'exe'
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ['lib']
|
23
|
+
|
24
|
+
spec.add_dependency 'parser', '~> 2.4'
|
25
|
+
spec.add_dependency 'racc', '~> 1.4'
|
26
|
+
spec.add_dependency 'vernacular', '~> 1.0'
|
27
|
+
|
28
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
29
|
+
spec.add_development_dependency 'minitest', '~> 5.11'
|
30
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
31
|
+
spec.add_development_dependency 'rubocop', '~> 0.52'
|
32
|
+
end
|
metadata
ADDED
@@ -0,0 +1,159 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: vernacular-ast
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Kevin Deisz
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-01-30 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: parser
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '2.4'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '2.4'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: racc
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.4'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.4'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: vernacular
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: bundler
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1.16'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1.16'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: minitest
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '5.11'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '5.11'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rake
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '12.3'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '12.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '0.52'
|
104
|
+
type: :development
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0.52'
|
111
|
+
description:
|
112
|
+
email:
|
113
|
+
- kevin.deisz@gmail.com
|
114
|
+
executables: []
|
115
|
+
extensions: []
|
116
|
+
extra_rdoc_files: []
|
117
|
+
files:
|
118
|
+
- ".gitignore"
|
119
|
+
- ".rubocop.yml"
|
120
|
+
- ".travis.yml"
|
121
|
+
- Gemfile
|
122
|
+
- Gemfile.lock
|
123
|
+
- LICENSE
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- bin/console
|
127
|
+
- bin/setup
|
128
|
+
- lib/vernacular/ast.rb
|
129
|
+
- lib/vernacular/ast/ast_parser.rb
|
130
|
+
- lib/vernacular/ast/modifier.rb
|
131
|
+
- lib/vernacular/ast/modifiers/typed_method_args.rb
|
132
|
+
- lib/vernacular/ast/modifiers/typed_method_returns.rb
|
133
|
+
- lib/vernacular/ast/version.rb
|
134
|
+
- vernacular-ast.gemspec
|
135
|
+
homepage: https://github.com/kddeisz/vernacular-ast
|
136
|
+
licenses:
|
137
|
+
- MIT
|
138
|
+
metadata: {}
|
139
|
+
post_install_message:
|
140
|
+
rdoc_options: []
|
141
|
+
require_paths:
|
142
|
+
- lib
|
143
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
144
|
+
requirements:
|
145
|
+
- - ">="
|
146
|
+
- !ruby/object:Gem::Version
|
147
|
+
version: '0'
|
148
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
149
|
+
requirements:
|
150
|
+
- - ">="
|
151
|
+
- !ruby/object:Gem::Version
|
152
|
+
version: '0'
|
153
|
+
requirements: []
|
154
|
+
rubyforge_project:
|
155
|
+
rubygems_version: 2.7.4
|
156
|
+
signing_key:
|
157
|
+
specification_version: 4
|
158
|
+
summary: Extends Vernacular to support rewriting the AST
|
159
|
+
test_files: []
|