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 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