vernacular 0.1.2 → 1.0.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 +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
|