Vernacular
Allows extending ruby's syntax and compilation process.
Installation
Add this line to your application's Gemfile:
gem 'vernacular'
And then execute:
$ bundle
Or install it yourself as:
$ gem install vernacular
Usage
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.
For example,
Vernacular.configure do |config|
pattern = /~n\(([\d\s+-\/*\(\)]+?)\)/
modifier =
Vernacular::Modifiers::RegexModifier.new(pattern) do |match|
eval(match[3..-2])
end
config.add(modifier)
end
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.
Modifiers
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
.
Modifiers::RegexModifier
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:
Vernacular::Modifiers::RegexModifier.new(/~u\((.+?)\)/, 'URI.parse("\1")')
or configure them using a block, as in:
Vernacular::Modifiers::RegexModifier.new(pattern) do |match|
eval(match[3..-2])
end
Modifiers::ASTModifier
AST modifiers are somewhat more difficult to configure. A basic knowledge of the 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:
Vernacular::Modifiers::ASTModifier.new do |modifier|
# Extend the parser to support and equal sign and a class path following the
# declaration of a functions arguments to represent its return type.
modifier.extend_parser(:f_arglist, 'f_arglist tEQL cpath', <<~PARSE)
result = @builder.type_check_arglist(*val)
PARSE
# Extend the builder by adding a `type_check_arglist` function that will build
# a new node type and place it at the end of the argument list.
modifier.extend_builder(:type_check_arglist) do |arglist, equal, cpath|
arglist << n(:type_check_arglist, [equal, cpath], nil)
end
# Extend the rewriter by adding an `on_def` callback, which will be called
# whenever a `def` node is added to the AST. Then, loop through and find any
# `type_check_arglist` nodes, and remove them. Finally, insert the
# appropriate raises around the execution of the function to mirror the type
# checking.
modifier.build_rewriter do
def on_def(node)
type_check_node = node.children[1].children.last
return super if !type_check_node || type_check_node.type != :type_check_arglist
remove(type_check_node.children[0][1])
remove(type_check_node.children[1].loc.expression)
type = build_constant(type_check_node.children[1])
@source_rewriter.transaction do
insert_before(node.children[2].loc.expression, "result = begin\n")
insert_after(node.children[2].loc.expression,
"\nend\nraise \"Invalid return value, expected #{type}, " <<
"got \#{result.class.name}\" unless result.is_a?(#{type})\nresult")
end
super
end
private
def build_constant(node, suffix = nil)
child_node, name = node.children
new_name = suffix ? "#{name}::#{suffix}" : name
child_node ? build_constant(child_node, new_name) : new_name
end
end
end
Development
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.
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.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/kddeisz/vernacular.
License
The gem is available as open source under the terms of the MIT License.