vernacular 0.1.2 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +1 -10
- data/.travis.yml +2 -4
- data/Gemfile +2 -0
- data/LICENSE +1 -1
- data/README.md +8 -57
- data/Rakefile +2 -0
- data/bin/console +1 -0
- data/lib/vernacular.rb +6 -12
- data/lib/vernacular/configuration_hash.rb +2 -0
- data/lib/vernacular/modifiers/date_sigil.rb +3 -1
- data/lib/vernacular/modifiers/number_sigil.rb +2 -0
- data/lib/vernacular/modifiers/uri_sigil.rb +2 -0
- data/lib/vernacular/regex_modifier.rb +2 -0
- data/lib/vernacular/source_file.rb +2 -0
- data/lib/vernacular/version.rb +3 -1
- data/vernacular.gemspec +5 -8
- metadata +11 -43
- data/lib/vernacular/ast_modifier.rb +0 -60
- data/lib/vernacular/ast_parser.rb +0 -95
- data/lib/vernacular/modifiers/typed_method_args.rb +0 -64
- data/lib/vernacular/modifiers/typed_method_returns.rb +0 -64
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 8a24b1dd911192fab7c86796633229aedf885cbb061e9b51e339779bb3f7f134
|
4
|
+
data.tar.gz: 0d5cb6a79465f230eb42fea6982cd0e95b2f34fdf401f94de9f19e0ea22b91a0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: ddf6b453e0724ae2f72097102cd4384ede0349af09243a2feb6e3b6999179c8e04555998462faa094d44fcd7bc5de2930929da4a646793edda585580e5f45ee6
|
7
|
+
data.tar.gz: 7bb0cb4121647825249942bbc990b29c0ddb7ad3c785ecf194c791a4946397790512b9825d7d07f74305373639224102e8b73a076178b55775f83f8b5f888285
|
data/.gitignore
CHANGED
data/.rubocop.yml
CHANGED
@@ -1,18 +1,9 @@
|
|
1
1
|
AllCops:
|
2
2
|
DisplayCopNames: true
|
3
3
|
DisplayStyleGuide: true
|
4
|
-
TargetRubyVersion: 2.
|
4
|
+
TargetRubyVersion: 2.5
|
5
5
|
Exclude:
|
6
|
-
- 'lib/vernacular/parser.rb'
|
7
6
|
- 'test/date_sigil_test.rb'
|
8
7
|
- 'test/number_sigil_test.rb'
|
9
|
-
- 'test/type_safe_method_args_test.rb'
|
10
|
-
- 'test/type_safe_method_returns_test.rb'
|
11
8
|
- 'test/uri_sigil_test.rb'
|
12
9
|
- 'vendor/**/*'
|
13
|
-
|
14
|
-
Style/FormatString:
|
15
|
-
EnforcedStyle: percent
|
16
|
-
|
17
|
-
Style/FrozenStringLiteralComment:
|
18
|
-
Enabled: false
|
data/.travis.yml
CHANGED
data/Gemfile
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
@@ -31,7 +31,7 @@ For example,
|
|
31
31
|
Vernacular.configure do |config|
|
32
32
|
pattern = /~n\(([\d\s+-\/*\(\)]+?)\)/
|
33
33
|
modifier =
|
34
|
-
Vernacular::
|
34
|
+
Vernacular::RegexModifier.new(pattern) do |match|
|
35
35
|
eval(match[3..-2])
|
36
36
|
end
|
37
37
|
config.add(modifier)
|
@@ -42,76 +42,27 @@ will extend Ruby syntax to allow `~n(...)` symbols which will evaluate the inter
|
|
42
42
|
|
43
43
|
### `Modifiers`
|
44
44
|
|
45
|
-
Modifiers allow you to modify the source of the Ruby code before it is compiled by injecting themselves into the require chain through `RubyVM::InstructionSequence::load_iseq`.
|
45
|
+
Modifiers allow you to modify the source of the Ruby code before it is compiled by injecting themselves into the require chain through `RubyVM::InstructionSequence::load_iseq`. They can be any of the preconfigured modifiers built into `Vernacular`, or they can just be a plain ruby class that responds to the method `modify(source)` where `source` is a string of code. The method should returned the modified source.
|
46
46
|
|
47
|
-
### `
|
47
|
+
### `RegexModifier`
|
48
48
|
|
49
|
-
Regex modifiers
|
49
|
+
Regex modifiers take the same arguments as `String#gsub`. Either configure them with a string, as in:
|
50
50
|
|
51
51
|
```ruby
|
52
|
-
Vernacular::
|
52
|
+
Vernacular::RegexModifier.new(/~u\((.+?)\)/, 'URI.parse("\1")')
|
53
53
|
```
|
54
54
|
|
55
55
|
or configure them using a block, as in:
|
56
56
|
|
57
57
|
```ruby
|
58
|
-
Vernacular::
|
58
|
+
Vernacular::RegexModifier.new(pattern) do |match|
|
59
59
|
eval(match[3..-2])
|
60
60
|
end
|
61
61
|
```
|
62
62
|
|
63
|
-
### `
|
63
|
+
### `ASTModifier`
|
64
64
|
|
65
|
-
|
66
|
-
|
67
|
-
```ruby
|
68
|
-
Vernacular::Modifiers::ASTModifier.new do |modifier|
|
69
|
-
# Extend the parser to support and equal sign and a class path following the
|
70
|
-
# declaration of a functions arguments to represent its return type.
|
71
|
-
modifier.extend_parser(:f_arglist, 'f_arglist tEQL cpath', <<~PARSE)
|
72
|
-
result = @builder.type_check_arglist(*val)
|
73
|
-
PARSE
|
74
|
-
|
75
|
-
# Extend the builder by adding a `type_check_arglist` function that will build
|
76
|
-
# a new node type and place it at the end of the argument list.
|
77
|
-
modifier.extend_builder(:type_check_arglist) do |arglist, equal, cpath|
|
78
|
-
arglist << n(:type_check_arglist, [equal, cpath], nil)
|
79
|
-
end
|
80
|
-
|
81
|
-
# Extend the rewriter by adding an `on_def` callback, which will be called
|
82
|
-
# whenever a `def` node is added to the AST. Then, loop through and find any
|
83
|
-
# `type_check_arglist` nodes, and remove them. Finally, insert the
|
84
|
-
# appropriate raises around the execution of the function to mirror the type
|
85
|
-
# checking.
|
86
|
-
modifier.build_rewriter do
|
87
|
-
def on_def(node)
|
88
|
-
type_check_node = node.children[1].children.last
|
89
|
-
return super if !type_check_node || type_check_node.type != :type_check_arglist
|
90
|
-
|
91
|
-
remove(type_check_node.children[0][1])
|
92
|
-
remove(type_check_node.children[1].loc.expression)
|
93
|
-
type = build_constant(type_check_node.children[1])
|
94
|
-
|
95
|
-
@source_rewriter.transaction do
|
96
|
-
insert_before(node.children[2].loc.expression, "result = begin\n")
|
97
|
-
insert_after(node.children[2].loc.expression,
|
98
|
-
"\nend\nraise \"Invalid return value, expected #{type}, " <<
|
99
|
-
"got \#{result.class.name}\" unless result.is_a?(#{type})\nresult")
|
100
|
-
end
|
101
|
-
|
102
|
-
super
|
103
|
-
end
|
104
|
-
|
105
|
-
private
|
106
|
-
|
107
|
-
def build_constant(node, suffix = nil)
|
108
|
-
child_node, name = node.children
|
109
|
-
new_name = suffix ? "#{name}::#{suffix}" : name
|
110
|
-
child_node ? build_constant(child_node, new_name) : new_name
|
111
|
-
end
|
112
|
-
end
|
113
|
-
end
|
114
|
-
```
|
65
|
+
For access to and documentation on the `ASTModifier`, check out the [`vernacular-ast`](https://github.com/kddeisz/vernacular-ast) gem.
|
115
66
|
|
116
67
|
## Development
|
117
68
|
|
data/Rakefile
CHANGED
data/bin/console
CHANGED
data/lib/vernacular.rb
CHANGED
@@ -1,14 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'date'
|
2
4
|
require 'digest'
|
3
|
-
require '
|
4
|
-
require 'tempfile'
|
5
|
+
require 'fileutils'
|
5
6
|
require 'uri'
|
6
7
|
|
7
|
-
require 'vernacular/ast_modifier'
|
8
|
-
require 'vernacular/ast_parser'
|
9
8
|
require 'vernacular/configuration_hash'
|
10
9
|
require 'vernacular/regex_modifier'
|
11
10
|
require 'vernacular/source_file'
|
11
|
+
require 'vernacular/version'
|
12
12
|
|
13
13
|
Dir[File.expand_path('vernacular/modifiers/*', __dir__)].each do |file|
|
14
14
|
require file
|
@@ -16,13 +16,11 @@ end
|
|
16
16
|
|
17
17
|
# Allows extending ruby's syntax and compilation process
|
18
18
|
module Vernacular
|
19
|
-
PARSER_PATH = File.expand_path('vernacular/parser.rb', __dir__).freeze
|
20
|
-
|
21
19
|
# Module that gets included into `RubyVM::InstructionSequence` in order to
|
22
20
|
# hook into the require process.
|
23
21
|
module InstructionSequenceMixin
|
24
22
|
def load_iseq(filepath)
|
25
|
-
::Vernacular::SourceFile.load_iseq(filepath)
|
23
|
+
::Vernacular::SourceFile.load_iseq(filepath)
|
26
24
|
end
|
27
25
|
end
|
28
26
|
|
@@ -30,10 +28,6 @@ module Vernacular
|
|
30
28
|
# hook into the bootsnap compilation process.
|
31
29
|
module BootsnapMixin
|
32
30
|
def input_to_storage(contents, filepath)
|
33
|
-
if filepath == PARSER_PATH
|
34
|
-
raise ::Bootsnap::CompileCache::Uncompilable, "can't compile parser"
|
35
|
-
end
|
36
|
-
|
37
31
|
contents = ::Vernacular.modify(contents)
|
38
32
|
iseq = RubyVM::InstructionSequence.compile(contents, filepath, filepath)
|
39
33
|
iseq.to_binary
|
@@ -78,7 +72,7 @@ module Vernacular
|
|
78
72
|
end
|
79
73
|
|
80
74
|
def iseq_path_for(source_path)
|
81
|
-
source_path.gsub(/[^A-Za-z0-9\._-]/) { |c| '%02x'
|
75
|
+
source_path.gsub(/[^A-Za-z0-9\._-]/) { |c| format('%02x', c.ord) }
|
82
76
|
.gsub('.rb', '.yarb')
|
83
77
|
end
|
84
78
|
|
@@ -1,10 +1,12 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Vernacular
|
2
4
|
module Modifiers
|
3
5
|
# Extends Ruby syntax to allow date sigils, or ~d(...). The date inside is
|
4
6
|
# parsed and as an added benefit if it is a set value it is replaced with
|
5
7
|
# the more efficient `strptime`.
|
6
8
|
class DateSigil < RegexModifier
|
7
|
-
FORMAT = '%FT%T%:z'
|
9
|
+
FORMAT = '%FT%T%:z'
|
8
10
|
|
9
11
|
def initialize
|
10
12
|
super(/~d\((.+?)\)/) do |match|
|
data/lib/vernacular/version.rb
CHANGED
data/vernacular.gemspec
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
#
|
1
|
+
# frozen_string_literal: true
|
2
2
|
|
3
3
|
lib = File.expand_path('../lib', __FILE__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
@@ -21,11 +21,8 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
22
|
spec.require_paths = ['lib']
|
23
23
|
|
24
|
-
spec.
|
25
|
-
spec.
|
26
|
-
|
27
|
-
spec.add_development_dependency '
|
28
|
-
spec.add_development_dependency 'minitest', '~> 5.10'
|
29
|
-
spec.add_development_dependency 'rake', '~> 12.0'
|
30
|
-
spec.add_development_dependency 'rubocop', '~> 0.49'
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.16'
|
25
|
+
spec.add_development_dependency 'minitest', '~> 5.11'
|
26
|
+
spec.add_development_dependency 'rake', '~> 12.3'
|
27
|
+
spec.add_development_dependency 'rubocop', '~> 0.52'
|
31
28
|
end
|
metadata
CHANGED
@@ -1,99 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: vernacular
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 1.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Kevin Deisz
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2018-01-30 00:00:00.000000000 Z
|
12
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
13
|
- !ruby/object:Gem::Dependency
|
42
14
|
name: bundler
|
43
15
|
requirement: !ruby/object:Gem::Requirement
|
44
16
|
requirements:
|
45
17
|
- - "~>"
|
46
18
|
- !ruby/object:Gem::Version
|
47
|
-
version: '1.
|
19
|
+
version: '1.16'
|
48
20
|
type: :development
|
49
21
|
prerelease: false
|
50
22
|
version_requirements: !ruby/object:Gem::Requirement
|
51
23
|
requirements:
|
52
24
|
- - "~>"
|
53
25
|
- !ruby/object:Gem::Version
|
54
|
-
version: '1.
|
26
|
+
version: '1.16'
|
55
27
|
- !ruby/object:Gem::Dependency
|
56
28
|
name: minitest
|
57
29
|
requirement: !ruby/object:Gem::Requirement
|
58
30
|
requirements:
|
59
31
|
- - "~>"
|
60
32
|
- !ruby/object:Gem::Version
|
61
|
-
version: '5.
|
33
|
+
version: '5.11'
|
62
34
|
type: :development
|
63
35
|
prerelease: false
|
64
36
|
version_requirements: !ruby/object:Gem::Requirement
|
65
37
|
requirements:
|
66
38
|
- - "~>"
|
67
39
|
- !ruby/object:Gem::Version
|
68
|
-
version: '5.
|
40
|
+
version: '5.11'
|
69
41
|
- !ruby/object:Gem::Dependency
|
70
42
|
name: rake
|
71
43
|
requirement: !ruby/object:Gem::Requirement
|
72
44
|
requirements:
|
73
45
|
- - "~>"
|
74
46
|
- !ruby/object:Gem::Version
|
75
|
-
version: '12.
|
47
|
+
version: '12.3'
|
76
48
|
type: :development
|
77
49
|
prerelease: false
|
78
50
|
version_requirements: !ruby/object:Gem::Requirement
|
79
51
|
requirements:
|
80
52
|
- - "~>"
|
81
53
|
- !ruby/object:Gem::Version
|
82
|
-
version: '12.
|
54
|
+
version: '12.3'
|
83
55
|
- !ruby/object:Gem::Dependency
|
84
56
|
name: rubocop
|
85
57
|
requirement: !ruby/object:Gem::Requirement
|
86
58
|
requirements:
|
87
59
|
- - "~>"
|
88
60
|
- !ruby/object:Gem::Version
|
89
|
-
version: '0.
|
61
|
+
version: '0.52'
|
90
62
|
type: :development
|
91
63
|
prerelease: false
|
92
64
|
version_requirements: !ruby/object:Gem::Requirement
|
93
65
|
requirements:
|
94
66
|
- - "~>"
|
95
67
|
- !ruby/object:Gem::Version
|
96
|
-
version: '0.
|
68
|
+
version: '0.52'
|
97
69
|
description:
|
98
70
|
email:
|
99
71
|
- kevin.deisz@gmail.com
|
@@ -111,13 +83,9 @@ files:
|
|
111
83
|
- bin/console
|
112
84
|
- bin/setup
|
113
85
|
- lib/vernacular.rb
|
114
|
-
- lib/vernacular/ast_modifier.rb
|
115
|
-
- lib/vernacular/ast_parser.rb
|
116
86
|
- lib/vernacular/configuration_hash.rb
|
117
87
|
- lib/vernacular/modifiers/date_sigil.rb
|
118
88
|
- lib/vernacular/modifiers/number_sigil.rb
|
119
|
-
- lib/vernacular/modifiers/typed_method_args.rb
|
120
|
-
- lib/vernacular/modifiers/typed_method_returns.rb
|
121
89
|
- lib/vernacular/modifiers/uri_sigil.rb
|
122
90
|
- lib/vernacular/regex_modifier.rb
|
123
91
|
- lib/vernacular/source_file.rb
|
@@ -143,7 +111,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
143
111
|
version: '0'
|
144
112
|
requirements: []
|
145
113
|
rubyforge_project:
|
146
|
-
rubygems_version: 2.
|
114
|
+
rubygems_version: 2.7.4
|
147
115
|
signing_key:
|
148
116
|
specification_version: 4
|
149
117
|
summary: Allows extending ruby's syntax and compilation process
|
@@ -1,60 +0,0 @@
|
|
1
|
-
module Vernacular
|
2
|
-
# Represents a modification that will be performed against the AST between the
|
3
|
-
# time that the source code is read and the time that it is compiled down to
|
4
|
-
# YARV instruction sequences.
|
5
|
-
class ASTModifier
|
6
|
-
BuilderExtension =
|
7
|
-
Struct.new(:method, :block) do
|
8
|
-
def components
|
9
|
-
[method, block.source_location]
|
10
|
-
end
|
11
|
-
end
|
12
|
-
attr_reader :builder_extensions
|
13
|
-
|
14
|
-
ParserExtension =
|
15
|
-
Struct.new(:symbol, :pattern, :code) do
|
16
|
-
def components
|
17
|
-
[symbol, pattern, code]
|
18
|
-
end
|
19
|
-
end
|
20
|
-
attr_reader :parser_extensions
|
21
|
-
|
22
|
-
attr_reader :rewriter_block
|
23
|
-
|
24
|
-
def initialize
|
25
|
-
@builder_extensions = []
|
26
|
-
@parser_extensions = []
|
27
|
-
yield self if block_given?
|
28
|
-
end
|
29
|
-
|
30
|
-
def build_rewriter(&block)
|
31
|
-
@rewriter_block = block
|
32
|
-
end
|
33
|
-
|
34
|
-
def extend_builder(method, &block)
|
35
|
-
builder_extensions << BuilderExtension.new(method, block)
|
36
|
-
end
|
37
|
-
|
38
|
-
def extend_parser(symbol, pattern, code)
|
39
|
-
parser_extensions << ParserExtension.new(symbol, pattern, code)
|
40
|
-
end
|
41
|
-
|
42
|
-
def modify(source)
|
43
|
-
raise 'You must first configure a rewriter!' unless rewriter_block
|
44
|
-
|
45
|
-
rewriter = Class.new(Parser::Rewriter, &rewriter_block).new
|
46
|
-
rewriter.instance_variable_set(:@parser, ASTParser.parser)
|
47
|
-
|
48
|
-
buffer = Parser::Source::Buffer.new('<dynamic>')
|
49
|
-
buffer.source = source
|
50
|
-
|
51
|
-
ast = ASTParser.parse(source)
|
52
|
-
rewriter.rewrite(buffer, ast)
|
53
|
-
end
|
54
|
-
|
55
|
-
def components
|
56
|
-
(builder_extensions + parser_extensions).flat_map(&:components) +
|
57
|
-
rewriter_block.source_location
|
58
|
-
end
|
59
|
-
end
|
60
|
-
end
|
@@ -1,95 +0,0 @@
|
|
1
|
-
module Vernacular
|
2
|
-
# Handles monkeying around with the `parser` gem to get it to handle the
|
3
|
-
# various modifications that users can configure `Vernacular` to perform.
|
4
|
-
class ASTParser
|
5
|
-
def parser
|
6
|
-
source = parser_source
|
7
|
-
|
8
|
-
ast_modifiers.each do |modifier|
|
9
|
-
modifier.parser_extensions.each do |parser_extension|
|
10
|
-
source = extend_parser(source, parser_extension)
|
11
|
-
end
|
12
|
-
end
|
13
|
-
|
14
|
-
write_parser(source)
|
15
|
-
load PARSER_PATH
|
16
|
-
Parser::Vernacular.new(builder)
|
17
|
-
end
|
18
|
-
|
19
|
-
class << self
|
20
|
-
def parse(string)
|
21
|
-
parser.reset
|
22
|
-
buffer = Parser::Base.send(:setup_source_buffer, '(string)', 1, string,
|
23
|
-
@parser.default_encoding)
|
24
|
-
parser.parse(buffer)
|
25
|
-
end
|
26
|
-
|
27
|
-
def parser
|
28
|
-
@parser ||= new.parser
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
private
|
33
|
-
|
34
|
-
def ast_modifiers
|
35
|
-
Vernacular.modifiers.grep(ASTModifier)
|
36
|
-
end
|
37
|
-
|
38
|
-
def builder
|
39
|
-
modifiers = ast_modifiers
|
40
|
-
|
41
|
-
Class.new(Parser::Builders::Default) do
|
42
|
-
modifiers.each do |modifier|
|
43
|
-
modifier.builder_extensions.each do |builder_extension|
|
44
|
-
define_method(builder_extension.method, &builder_extension.block)
|
45
|
-
end
|
46
|
-
end
|
47
|
-
end.new
|
48
|
-
end
|
49
|
-
|
50
|
-
def compile_parser(filepath)
|
51
|
-
exec_path = Gem.activate_bin_path('racc', 'racc', [])
|
52
|
-
`#{exec_path} --superclass=Parser::Base -o #{PARSER_PATH} #{filepath}`
|
53
|
-
File.write(
|
54
|
-
PARSER_PATH,
|
55
|
-
File.read(PARSER_PATH).gsub("Ruby#{parser_version}", 'Vernacular')
|
56
|
-
)
|
57
|
-
end
|
58
|
-
|
59
|
-
# rubocop:disable Metrics/MethodLength
|
60
|
-
def extend_parser(source, parser_extension)
|
61
|
-
needle = "#{parser_extension.symbol}:"
|
62
|
-
pattern = /\A\s+#{needle}/
|
63
|
-
|
64
|
-
source.split("\n").each_with_object([]) do |line, edited|
|
65
|
-
if line =~ pattern
|
66
|
-
lhs, rhs = line.split(needle)
|
67
|
-
edited << "#{lhs}#{needle} #{parser_extension.pattern}\n" \
|
68
|
-
"{\n#{parser_extension.code}\n}\n#{lhs}|#{rhs}"
|
69
|
-
else
|
70
|
-
edited << line
|
71
|
-
end
|
72
|
-
end.join("\n")
|
73
|
-
end
|
74
|
-
# rubocop:enable Metrics/MethodLength
|
75
|
-
|
76
|
-
def parser_source
|
77
|
-
filepath, = Parser.method(:check_for_encoding_support).source_location
|
78
|
-
grammar_filepath = "../../lib/parser/ruby#{parser_version}.y"
|
79
|
-
File.read(File.expand_path(grammar_filepath, filepath))
|
80
|
-
end
|
81
|
-
|
82
|
-
def parser_version
|
83
|
-
@parser_version ||= RUBY_VERSION.gsub(/\A(\d)\.(\d).+/, '\1\2')
|
84
|
-
end
|
85
|
-
|
86
|
-
def write_parser(source)
|
87
|
-
file = Tempfile.new(['parser-', '.y'])
|
88
|
-
file.write(source)
|
89
|
-
compile_parser(file.path)
|
90
|
-
ensure
|
91
|
-
file.close
|
92
|
-
file.unlink
|
93
|
-
end
|
94
|
-
end
|
95
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
module Vernacular
|
2
|
-
module Modifiers
|
3
|
-
# Extends Ruby syntax to allow typed method argument declarations, as in:
|
4
|
-
# def my_method(argument_a : Integer, argument_b : String); end
|
5
|
-
class TypedMethodArgs < ASTModifier
|
6
|
-
def initialize
|
7
|
-
super
|
8
|
-
|
9
|
-
extend_parser(:f_arg, 'f_arg tCOLON cpath', <<~PARSE)
|
10
|
-
result = @builder.type_check_arg(*val)
|
11
|
-
PARSE
|
12
|
-
|
13
|
-
extend_builder(:type_check_arg) do |args, colon, cpath|
|
14
|
-
location = args[0].loc.with_operator(loc(colon))
|
15
|
-
.with_expression(join_exprs(args[0], cpath))
|
16
|
-
[n(:type_check_arg, [args, cpath], location)]
|
17
|
-
end
|
18
|
-
|
19
|
-
build_rewriter { include TypedMethodArgsRewriter }
|
20
|
-
end
|
21
|
-
|
22
|
-
# Methods to be included in the rewriter in order to handle
|
23
|
-
# `type_check_arg` nodes.
|
24
|
-
module TypedMethodArgsRewriter
|
25
|
-
# Triggered whenever a `:def` node is added to the AST. Finds any
|
26
|
-
# `type_check_arg` nodes, replaces them with normal `:arg` nodes, and
|
27
|
-
# adds in the represented type check to the beginning of the method.
|
28
|
-
def on_def(node)
|
29
|
-
type_checks = build_type_checks(node.children[1].children)
|
30
|
-
if type_checks.any?
|
31
|
-
insert_before(node.children[2].loc.expression, type_checks.join)
|
32
|
-
end
|
33
|
-
|
34
|
-
super
|
35
|
-
end
|
36
|
-
|
37
|
-
private
|
38
|
-
|
39
|
-
def build_constant(node, suffix = nil)
|
40
|
-
child_node, name = node.children
|
41
|
-
new_name = suffix ? "#{name}::#{suffix}" : name
|
42
|
-
child_node ? build_constant(child_node, new_name) : new_name
|
43
|
-
end
|
44
|
-
|
45
|
-
def build_type_checks(arg_list_node)
|
46
|
-
arg_list_node.each_with_object([]) do |arg, type_checks|
|
47
|
-
next unless arg.type == :type_check_arg
|
48
|
-
|
49
|
-
type_checks << type_check(arg)
|
50
|
-
remove(arg.loc.operator)
|
51
|
-
remove(arg.children[1].loc.expression)
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
55
|
-
def type_check(arg_node)
|
56
|
-
arg_name = arg_node.children[0][0].children[0]
|
57
|
-
type = build_constant(arg_node.children[1])
|
58
|
-
"raise ArgumentError, \"Invalid type, expected #{type}, got " \
|
59
|
-
"\#{#{arg_name}.class.name}\" unless #{arg_name}.is_a?(#{type});"
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|
@@ -1,64 +0,0 @@
|
|
1
|
-
module Vernacular
|
2
|
-
module Modifiers
|
3
|
-
# Extends Ruby syntax to allow typed method return declarations, as in:
|
4
|
-
# def my_method(argument_a, argument_b) = return_type; end
|
5
|
-
class TypedMethodReturns < ASTModifier
|
6
|
-
def initialize
|
7
|
-
super
|
8
|
-
|
9
|
-
extend_parser(:f_arglist, 'f_arglist tEQL cpath', <<~PARSE)
|
10
|
-
result = @builder.type_check_arglist(*val)
|
11
|
-
PARSE
|
12
|
-
|
13
|
-
extend_builder(:type_check_arglist) do |arglist, equal, cpath|
|
14
|
-
arglist << n(:type_check_arglist, [equal, cpath], nil)
|
15
|
-
end
|
16
|
-
|
17
|
-
build_rewriter { include TypedMethodReturnsRewriter }
|
18
|
-
end
|
19
|
-
|
20
|
-
# Methods to be included in the rewriter in order to handle
|
21
|
-
# `type_check_arglist` nodes.
|
22
|
-
module TypedMethodReturnsRewriter
|
23
|
-
def on_def(method_node)
|
24
|
-
type_check_node = type_check_node_from(method_node)
|
25
|
-
return super unless type_check_node
|
26
|
-
|
27
|
-
type_node = type_check_node.children[1]
|
28
|
-
remove(type_check_node.children[0][1])
|
29
|
-
remove(type_node.loc.expression)
|
30
|
-
type_check_method(method_node, type_node)
|
31
|
-
|
32
|
-
super
|
33
|
-
end
|
34
|
-
|
35
|
-
private
|
36
|
-
|
37
|
-
def build_constant(node, suffix = nil)
|
38
|
-
child_node, name = node.children
|
39
|
-
new_name = suffix ? "#{name}::#{suffix}" : name
|
40
|
-
child_node ? build_constant(child_node, new_name) : new_name
|
41
|
-
end
|
42
|
-
|
43
|
-
def type_check_node_from(method_node)
|
44
|
-
type_check_node = method_node.children[1].children.last
|
45
|
-
return if !type_check_node ||
|
46
|
-
type_check_node.type != :type_check_arglist
|
47
|
-
type_check_node
|
48
|
-
end
|
49
|
-
|
50
|
-
def type_check_method(method_node, type_node)
|
51
|
-
expression = method_node.children[2].loc.expression
|
52
|
-
type = build_constant(type_node)
|
53
|
-
|
54
|
-
@source_rewriter.transaction do
|
55
|
-
insert_before(expression, "result = begin\n")
|
56
|
-
insert_after(expression, "\nend\nraise \"Invalid return value, " \
|
57
|
-
"expected #{type}, got \#{result.class.name}\" unless " \
|
58
|
-
"result.is_a?(#{type})\nresult")
|
59
|
-
end
|
60
|
-
end
|
61
|
-
end
|
62
|
-
end
|
63
|
-
end
|
64
|
-
end
|