vernacular 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 552dab9651e6e6fb7732220f14ab9c1efd5b92a0
4
+ data.tar.gz: 60ec95cc675a2e7f68d5e2e06d7cfffa0ffa9c40
5
+ SHA512:
6
+ metadata.gz: 17f64c2e0948369a0004e058a0fb6f131f832f8a43f9ff601a34ba74f6855dd63b93ef6da3a711170c21d485e598763e303bbeb929596e44e0e5cbc831b5d21b
7
+ data.tar.gz: f7d4ad0f7ee1b41f97e2d50da16f6fdd1abdcde2f3ba354c5423052d912398f8a391b5b48c25ca5f1fbbd1dd1ae03aaef50d2400b73c7a453ae3ad7f6c7e3ca3
@@ -0,0 +1,12 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+
11
+ /.iseq/
12
+ /lib/vernacular/parser.rb
@@ -0,0 +1,18 @@
1
+ AllCops:
2
+ DisplayCopNames: true
3
+ DisplayStyleGuide: true
4
+ TargetRubyVersion: 2.3
5
+ Exclude:
6
+ - 'lib/vernacular/parser.rb'
7
+ - 'test/date_sigil_test.rb'
8
+ - 'test/number_sigil_test.rb'
9
+ - 'test/type_safe_method_args_test.rb'
10
+ - 'test/type_safe_method_returns_test.rb'
11
+ - 'test/uri_sigil_test.rb'
12
+ - 'vendor/**/*'
13
+
14
+ Style/FormatString:
15
+ EnforcedStyle: percent
16
+
17
+ Style/FrozenStringLiteralComment:
18
+ Enabled: false
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm: 2.4.1
3
+ cache: bundler
4
+ script:
5
+ - bundle exec rake
6
+ - bundle exec rubocop
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Kevin Deisz
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
@@ -0,0 +1,127 @@
1
+ # Vernacular
2
+
3
+ [![Build Status](https://travis-ci.org/kddeisz/vernacular.svg?branch=master)](https://travis-ci.org/kddeisz/vernacular)
4
+
5
+ Allows extending ruby's syntax and compilation process.
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'vernacular'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install vernacular
22
+
23
+ ## Usage
24
+
25
+ At the very beginning of your script or application, require `vernacular`. Then, configure your list of modifiers so that `vernacular` knows how to modify your code before it is compiled.
26
+
27
+ For example,
28
+
29
+ ```ruby
30
+ Vernacular.configure do |config|
31
+ pattern = /~n\(([\d\s+-\/*\(\)]+?)\)/
32
+ modifier =
33
+ Vernacular::Modifiers::RegexModifier.new(pattern) do |match|
34
+ eval(match[3..-2])
35
+ end
36
+ config.add(modifier)
37
+ end
38
+ ```
39
+
40
+ will extend Ruby syntax to allow `~n(...)` symbols which will evaluate the interior expression as one number. This reduces the number of objects and instructions allocated for a given segment of Ruby, which can improve performance and memory.
41
+
42
+ ### `Modifiers`
43
+
44
+ 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
+
46
+ ### `Modifiers::RegexModifier`
47
+
48
+ 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
+
50
+ ```ruby
51
+ Vernacular::Modifiers::RegexModifier.new(/~u\((.+?)\)/, 'URI.parse("\1")')
52
+ ```
53
+
54
+ or configure them using a block, as in:
55
+
56
+ ```ruby
57
+ Vernacular::Modifiers::RegexModifier.new(pattern) do |match|
58
+ eval(match[3..-2])
59
+ end
60
+ ```
61
+
62
+ ### `Modifiers::ASTModifier`
63
+
64
+ 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:
65
+
66
+ ```ruby
67
+ Vernacular::Modifiers::ASTModifier.new do |modifier|
68
+ # Extend the parser to support and equal sign and a class path following the
69
+ # declaration of a functions arguments to represent its return type.
70
+ modifier.extend_parser(:f_arglist, 'f_arglist tEQL cpath', <<~PARSE)
71
+ result = @builder.type_check_arglist(*val)
72
+ PARSE
73
+
74
+ # Extend the builder by adding a `type_check_arglist` function that will build
75
+ # a new node type and place it at the end of the argument list.
76
+ modifier.extend_builder(:type_check_arglist) do |arglist, equal, cpath|
77
+ arglist << n(:type_check_arglist, [equal, cpath], nil)
78
+ end
79
+
80
+ # Extend the rewriter by adding an `on_def` callback, which will be called
81
+ # whenever a `def` node is added to the AST. Then, loop through and find any
82
+ # `type_check_arglist` nodes, and remove them. Finally, insert the
83
+ # appropriate raises around the execution of the function to mirror the type
84
+ # checking.
85
+ modifier.build_rewriter do
86
+ def on_def(node)
87
+ type_check_node = node.children[1].children.last
88
+ return super if !type_check_node || type_check_node.type != :type_check_arglist
89
+
90
+ remove(type_check_node.children[0][1])
91
+ remove(type_check_node.children[1].loc.expression)
92
+ type = build_constant(type_check_node.children[1])
93
+
94
+ @source_rewriter.transaction do
95
+ insert_before(node.children[2].loc.expression, "result = begin\n")
96
+ insert_after(node.children[2].loc.expression,
97
+ "\nend\nraise \"Invalid return value, expected #{type}, " <<
98
+ "got \#{result.class.name}\" unless result.is_a?(#{type})\nresult")
99
+ end
100
+
101
+ super
102
+ end
103
+
104
+ private
105
+
106
+ def build_constant(node, suffix = nil)
107
+ child_node, name = node.children
108
+ new_name = suffix ? "#{name}::#{suffix}" : name
109
+ child_node ? build_constant(child_node, new_name) : new_name
110
+ end
111
+ end
112
+ end
113
+ ```
114
+
115
+ ## Development
116
+
117
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
118
+
119
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
120
+
121
+ ## Contributing
122
+
123
+ Bug reports and pull requests are welcome on GitHub at https://github.com/kddeisz/vernacular.
124
+
125
+ ## License
126
+
127
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -0,0 +1,19 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ filepath = File.expand_path('test/test_loader.rb', __dir__)
6
+ t.ruby_opts << "-r #{filepath}"
7
+ t.warning = false
8
+
9
+ t.libs << 'test'
10
+ t.libs << 'lib'
11
+ t.test_files = FileList['test/**/*_test.rb']
12
+ end
13
+
14
+ desc 'Clear out compiled files'
15
+ task :clear do
16
+ `rm -r .iseq`
17
+ end
18
+
19
+ task default: :test
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'bundler/setup'
4
+ require 'vernacular'
5
+
6
+ require 'irb'
7
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,62 @@
1
+ require 'date'
2
+ require 'digest'
3
+ require 'parser'
4
+ require 'tempfile'
5
+ require 'uri'
6
+
7
+ require 'vernacular/ast_modifier'
8
+ require 'vernacular/ast_parser'
9
+ require 'vernacular/configuration_hash'
10
+ require 'vernacular/regex_modifier'
11
+ require 'vernacular/source_file'
12
+
13
+ Dir[File.expand_path('vernacular/modifiers/*', __dir__)].each do |file|
14
+ require file
15
+ end
16
+
17
+ # Allows extending ruby's syntax and compilation process
18
+ module Vernacular
19
+ # Module that gets included into `RubyVM::InstructionSequence` in order to
20
+ # hook into the require process.
21
+ module InstructionSequenceMixin
22
+ PARSER_PATH = File.expand_path('vernacular/parser.rb', __dir__).freeze
23
+
24
+ def load_iseq(filepath)
25
+ ::Vernacular::SourceFile.load_iseq(filepath) if filepath != PARSER_PATH
26
+ end
27
+ end
28
+
29
+ class << self
30
+ attr_reader :iseq_dir, :modifiers
31
+
32
+ def add(modifier)
33
+ modifiers << modifier
34
+ end
35
+
36
+ def clear
37
+ Dir.glob(File.join(iseq_dir, '**/*.yarb')) { |path| File.delete(path) }
38
+ end
39
+
40
+ def configure
41
+ return if @configured
42
+
43
+ @modifiers = []
44
+ yield self
45
+
46
+ hash = ConfigurationHash.new(modifiers).hash
47
+ @iseq_dir = File.expand_path(File.join('../.iseq', hash), __dir__)
48
+ FileUtils.mkdir_p(iseq_dir) unless File.directory?(iseq_dir)
49
+
50
+ class << RubyVM::InstructionSequence
51
+ prepend ::Vernacular::InstructionSequenceMixin
52
+ end
53
+
54
+ @configured = true
55
+ end
56
+
57
+ def iseq_path_for(source_path)
58
+ source_path.gsub(/[^A-Za-z0-9\._-]/) { |c| '%02x' % c.ord }
59
+ .gsub('.rb', '.yarb')
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,60 @@
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
@@ -0,0 +1,88 @@
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 'vernacular/parser.rb'
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
+ output = File.expand_path('../parser.rb', __FILE__)
52
+ exec_path = Gem.activate_bin_path('racc', 'racc', [])
53
+ `#{exec_path} --superclass=Parser::Base -o #{output} #{filepath}`
54
+ File.write(output, File.read(output).gsub('Ruby24', 'Vernacular'))
55
+ end
56
+
57
+ # rubocop:disable Metrics/MethodLength
58
+ def extend_parser(source, parser_extension)
59
+ needle = "#{parser_extension.symbol}:"
60
+ pattern = /\A\s+#{needle}/
61
+
62
+ source.split("\n").each_with_object([]) do |line, edited|
63
+ if line =~ pattern
64
+ lhs, rhs = line.split(needle)
65
+ edited << "#{lhs}#{needle} #{parser_extension.pattern}\n" \
66
+ "{\n#{parser_extension.code}\n}\n#{lhs}|#{rhs}"
67
+ else
68
+ edited << line
69
+ end
70
+ end.join("\n")
71
+ end
72
+ # rubocop:enable Metrics/MethodLength
73
+
74
+ def parser_source
75
+ filepath, = Parser.method(:check_for_encoding_support).source_location
76
+ File.read(File.expand_path('../../lib/parser/ruby24.y', filepath))
77
+ end
78
+
79
+ def write_parser(source)
80
+ file = Tempfile.new(['parser-', '.y'])
81
+ file.write(source)
82
+ compile_parser(file.path)
83
+ ensure
84
+ file.close
85
+ file.unlink
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,20 @@
1
+ module Vernacular
2
+ # Builds a hash out of the given modifiers that represents that current state
3
+ # of configuration. This ensures that if the configuration of `Vernacular`
4
+ # changes between runs it doesn't pick up the old compiled files.
5
+ class ConfigurationHash
6
+ attr_reader :modifiers
7
+
8
+ def initialize(modifiers = [])
9
+ @modifiers = modifiers
10
+ end
11
+
12
+ def hash
13
+ digest = Digest::MD5.new
14
+ modifiers.each do |modifier|
15
+ digest << modifier.components.inspect
16
+ end
17
+ digest.to_s
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,22 @@
1
+ module Vernacular
2
+ module Modifiers
3
+ # Extends Ruby syntax to allow date sigils, or ~d(...). The date inside is
4
+ # parsed and as an added benefit if it is a set value it is replaced with
5
+ # the more efficient `strptime`.
6
+ class DateSigil < RegexModifier
7
+ FORMAT = '%FT%T%:z'.freeze
8
+
9
+ def initialize
10
+ super(/~d\((.+?)\)/) do |match|
11
+ content = match[3..-2]
12
+ begin
13
+ date = Date.parse(content)
14
+ "Date.strptime('#{date.strftime(FORMAT)}', '#{FORMAT}')"
15
+ rescue ArgumentError
16
+ "Date.parse(#{content})"
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,13 @@
1
+ module Vernacular
2
+ module Modifiers
3
+ # Extends Ruby syntax to allow number sigils, or ~n(...). The expression
4
+ # inside is parsed and evaluated, and is replaced by the result.
5
+ class NumberSigil < RegexModifier
6
+ def initialize
7
+ super(%r{~n\(([\d\s+-/*\(\)]+?)\)}) do |match|
8
+ eval(match[3..-2]) # rubocop:disable Security/Eval
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,64 @@
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
@@ -0,0 +1,64 @@
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
@@ -0,0 +1,11 @@
1
+ module Vernacular
2
+ module Modifiers
3
+ # Extends Ruby syntax to allow URI sigils, or ~u(...). The expression
4
+ # inside contains a valid URL.
5
+ class URISigil < RegexModifier
6
+ def initialize
7
+ super(/~u\((.+?)\)/, 'URI.parse("\1")')
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,25 @@
1
+ module Vernacular
2
+ # Represents a modification to Ruby source that should be injected into the
3
+ # require process that modifies the source via a regex pattern.
4
+ class RegexModifier
5
+ attr_reader :pattern, :replacement, :block
6
+
7
+ def initialize(pattern, replacement = nil, &block)
8
+ @pattern = pattern
9
+ @replacement = replacement
10
+ @block = block
11
+ end
12
+
13
+ def modify(source)
14
+ if replacement
15
+ source.gsub(pattern, replacement)
16
+ else
17
+ source.gsub(pattern, &block)
18
+ end
19
+ end
20
+
21
+ def components
22
+ [pattern, replacement] + (block ? block.source_location : [])
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ module Vernacular
2
+ # Represents a file that contains Ruby source code that can be read from and
3
+ # compiled down to instruction sequences.
4
+ class SourceFile
5
+ attr_reader :source_path, :iseq_path
6
+
7
+ def initialize(source_path, iseq_path)
8
+ @source_path = source_path
9
+ @iseq_path = iseq_path
10
+ end
11
+
12
+ def dump
13
+ source = File.read(source_path)
14
+ Vernacular.modifiers.each do |modifier|
15
+ source = modifier.modify(source)
16
+ end
17
+
18
+ iseq = RubyVM::InstructionSequence.compile(source)
19
+ digest = ::Digest::MD5.file(source_path).digest
20
+ File.binwrite(iseq_path, iseq.to_binary("MD5:#{digest}"))
21
+ iseq
22
+ rescue SyntaxError, RuntimeError
23
+ nil
24
+ end
25
+
26
+ def load
27
+ if File.exist?(iseq_path) &&
28
+ (File.mtime(source_path) <= File.mtime(iseq_path))
29
+ RubyVM::InstructionSequence.load_from_binary(File.binread(iseq_path))
30
+ else
31
+ dump
32
+ end
33
+ end
34
+
35
+ def self.load_iseq(source_path)
36
+ new(
37
+ source_path,
38
+ File.join(Vernacular.iseq_dir, Vernacular.iseq_path_for(source_path))
39
+ ).load
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,3 @@
1
+ module Vernacular
2
+ VERSION = '0.0.1'.freeze
3
+ end
@@ -0,0 +1,31 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'vernacular/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = 'vernacular'
9
+ spec.version = Vernacular::VERSION
10
+ spec.authors = ['Kevin Deisz']
11
+ spec.email = ['kevin.deisz@gmail.com']
12
+
13
+ spec.summary = "Allows extending ruby's syntax and compilation process"
14
+ spec.homepage = 'https://github.com/kddeisz/vernacular'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+ spec.bindir = 'exe'
21
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
+ spec.require_paths = ['lib']
23
+
24
+ spec.add_dependency 'parser', '~> 2.4'
25
+ spec.add_dependency 'racc', '~> 1.4'
26
+
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'
31
+ end
metadata ADDED
@@ -0,0 +1,150 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: vernacular
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kevin Deisz
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-09-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: parser
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.4'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: racc
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.4'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.4'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.15'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.15'
55
+ - !ruby/object:Gem::Dependency
56
+ name: minitest
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '5.10'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '5.10'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '12.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '12.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rubocop
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '0.49'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '0.49'
97
+ description:
98
+ email:
99
+ - kevin.deisz@gmail.com
100
+ executables: []
101
+ extensions: []
102
+ extra_rdoc_files: []
103
+ files:
104
+ - ".gitignore"
105
+ - ".rubocop.yml"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE
109
+ - README.md
110
+ - Rakefile
111
+ - bin/console
112
+ - bin/setup
113
+ - lib/vernacular.rb
114
+ - lib/vernacular/ast_modifier.rb
115
+ - lib/vernacular/ast_parser.rb
116
+ - lib/vernacular/configuration_hash.rb
117
+ - lib/vernacular/modifiers/date_sigil.rb
118
+ - lib/vernacular/modifiers/number_sigil.rb
119
+ - lib/vernacular/modifiers/typed_method_args.rb
120
+ - lib/vernacular/modifiers/typed_method_returns.rb
121
+ - lib/vernacular/modifiers/uri_sigil.rb
122
+ - lib/vernacular/regex_modifier.rb
123
+ - lib/vernacular/source_file.rb
124
+ - lib/vernacular/version.rb
125
+ - vernacular.gemspec
126
+ homepage: https://github.com/kddeisz/vernacular
127
+ licenses:
128
+ - MIT
129
+ metadata: {}
130
+ post_install_message:
131
+ rdoc_options: []
132
+ require_paths:
133
+ - lib
134
+ required_ruby_version: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ required_rubygems_version: !ruby/object:Gem::Requirement
140
+ requirements:
141
+ - - ">="
142
+ - !ruby/object:Gem::Version
143
+ version: '0'
144
+ requirements: []
145
+ rubyforge_project:
146
+ rubygems_version: 2.6.13
147
+ signing_key:
148
+ specification_version: 4
149
+ summary: Allows extending ruby's syntax and compilation process
150
+ test_files: []