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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 0ae148c9d3c9c69aeceffad4bc1f16ea21e5514d
4
- data.tar.gz: 209e7af5273d7b6925a60f6238ed2fed4f5460ca
2
+ SHA256:
3
+ metadata.gz: 8a24b1dd911192fab7c86796633229aedf885cbb061e9b51e339779bb3f7f134
4
+ data.tar.gz: 0d5cb6a79465f230eb42fea6982cd0e95b2f34fdf401f94de9f19e0ea22b91a0
5
5
  SHA512:
6
- metadata.gz: 9dd6ec7445cc90d6a346bb1d1ba248731c514a7981c9885ba1c845347bd71fa47812519e3c5588e8b467a81243f809f60a2d6b877f6bb1cf9041ac4cb8347f39
7
- data.tar.gz: c07d353edea1eb702c78b1dc2482d35edd0d76994c6ead7cc1035ae60569974af975fa855d809647a169718ac44bf7eabe74614067c90390dcb9afd3a69b6fa8
6
+ metadata.gz: ddf6b453e0724ae2f72097102cd4384ede0349af09243a2feb6e3b6999179c8e04555998462faa094d44fcd7bc5de2930929da4a646793edda585580e5f45ee6
7
+ data.tar.gz: 7bb0cb4121647825249942bbc990b29c0ddb7ad3c785ecf194c791a4946397790512b9825d7d07f74305373639224102e8b73a076178b55775f83f8b5f888285
data/.gitignore CHANGED
@@ -9,4 +9,3 @@
9
9
  /tmp/
10
10
 
11
11
  /.iseq/
12
- /lib/vernacular/parser.rb
@@ -1,18 +1,9 @@
1
1
  AllCops:
2
2
  DisplayCopNames: true
3
3
  DisplayStyleGuide: true
4
- TargetRubyVersion: 2.3
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
@@ -1,6 +1,4 @@
1
1
  language: ruby
2
- rvm: 2.4.1
2
+ rvm: 2.5.0
3
3
  cache: bundler
4
- script:
5
- - bundle exec rake
6
- - bundle exec rubocop
4
+ script: bundle exec rake && bundle exec rubocop
data/Gemfile CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  source 'https://rubygems.org'
2
4
 
3
5
  gemspec
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2017 Kevin Deisz
3
+ Copyright (c) 2018 Kevin Deisz
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
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::Modifiers::RegexModifier.new(pattern) do |match|
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
- ### `Modifiers::RegexModifier`
47
+ ### `RegexModifier`
48
48
 
49
- Regex modifiers are by far the simpler of the two to configure. They take the same arguments as `String#gsub`. Either configure them with a string, as in:
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::Modifiers::RegexModifier.new(/~u\((.+?)\)/, 'URI.parse("\1")')
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::Modifiers::RegexModifier.new(pattern) do |match|
58
+ Vernacular::RegexModifier.new(pattern) do |match|
59
59
  eval(match[3..-2])
60
60
  end
61
61
  ```
62
62
 
63
- ### `Modifiers::ASTModifier`
63
+ ### `ASTModifier`
64
64
 
65
- 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:
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
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'bundler/gem_tasks'
2
4
  require 'rake/testtask'
3
5
 
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
2
3
 
3
4
  require 'bundler/setup'
4
5
  require 'vernacular'
@@ -1,14 +1,14 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'date'
2
4
  require 'digest'
3
- require 'parser'
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) if filepath != PARSER_PATH
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' % c.ord }
75
+ source_path.gsub(/[^A-Za-z0-9\._-]/) { |c| format('%02x', c.ord) }
82
76
  .gsub('.rb', '.yarb')
83
77
  end
84
78
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Vernacular
2
4
  # Builds a hash out of the given modifiers that represents that current state
3
5
  # of configuration. This ensures that if the configuration of `Vernacular`
@@ -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'.freeze
9
+ FORMAT = '%FT%T%:z'
8
10
 
9
11
  def initialize
10
12
  super(/~d\((.+?)\)/) do |match|
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Vernacular
2
4
  module Modifiers
3
5
  # Extends Ruby syntax to allow number sigils, or ~n(...). The expression
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Vernacular
2
4
  module Modifiers
3
5
  # Extends Ruby syntax to allow URI sigils, or ~u(...). The expression
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Vernacular
2
4
  # Represents a modification to Ruby source that should be injected into the
3
5
  # require process that modifies the source via a regex pattern.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Vernacular
2
4
  # Represents a file that contains Ruby source code that can be read from and
3
5
  # compiled down to instruction sequences.
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Vernacular
2
- VERSION = '0.1.2'.freeze
4
+ VERSION = '1.0.0'
3
5
  end
@@ -1,4 +1,4 @@
1
- # coding: utf-8
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.add_dependency 'parser', '~> 2.4'
25
- spec.add_dependency 'racc', '~> 1.4'
26
-
27
- spec.add_development_dependency 'bundler', '~> 1.15'
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.1.2
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: 2017-09-14 00:00:00.000000000 Z
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.15'
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.15'
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.10'
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.10'
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.0'
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.0'
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.49'
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.49'
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.6.11
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