Skip to content

Commit

Permalink
[GR-29472] Add a simple sinatra benchmark
Browse files Browse the repository at this point in the history
PullRequest: truffleruby/2511
  • Loading branch information
eregon committed Mar 23, 2021
2 parents 6407ccd + 1e90fbb commit 2855d7c
Show file tree
Hide file tree
Showing 176 changed files with 19,091 additions and 0 deletions.
16 changes: 16 additions & 0 deletions bench/sinatra/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Based on https://github.com/benchmark-driver/sinatra/blob/master/driver.yml

Gems were vendored using:
```
bundle install --standalone
```

with this Gemfile:
```
source 'https://rubygems.org'
gem 'sinatra'
```

Then moving `bundle/ruby/version/gems` to just `bundle/gems`,
and keeping only `lib`, `bin` and licenses.
16 changes: 16 additions & 0 deletions bench/sinatra/bundle/bundler/setup.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
path = File.expand_path('..', __FILE__)
kernel = (class << ::Kernel; self; end)
[kernel, ::Kernel].each do |k|
if k.private_method_defined?(:gem_original_require)
private_require = k.private_method_defined?(:require)
k.send(:remove_method, :require)
k.send(:define_method, :require, k.instance_method(:gem_original_require))
k.send(:private, :require) if private_require
end
end
$:.unshift File.expand_path("#{path}/../gems/ruby2_keywords-0.0.4/lib")
$:.unshift File.expand_path("#{path}/../gems/mustermann-1.1.1/lib")
$:.unshift File.expand_path("#{path}/../gems/rack-2.2.3/lib")
$:.unshift File.expand_path("#{path}/../gems/rack-protection-2.1.0/lib")
$:.unshift File.expand_path("#{path}/../gems/tilt-2.0.10/lib")
$:.unshift File.expand_path("#{path}/../gems/sinatra-2.1.0/lib")
23 changes: 23 additions & 0 deletions bench/sinatra/bundle/gems/mustermann-1.1.1/LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
Copyright (c) 2013-2017 Konstantin Haase
Copyright (c) 2016-2017 Zachary Scott

Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:

The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
134 changes: 134 additions & 0 deletions bench/sinatra/bundle/gems/mustermann-1.1.1/lib/mustermann.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
# frozen_string_literal: true
require 'mustermann/pattern'
require 'mustermann/composite'
require 'mustermann/concat'
require 'thread'

# Namespace and main entry point for the Mustermann library.
#
# Under normal circumstances the only external API entry point you should be using is {Mustermann.new}.
module Mustermann
# Type to use if no type is given.
# @api private
DEFAULT_TYPE = :sinatra

# Creates a new pattern based on input.
#
# * From {Mustermann::Pattern}: returns given pattern.
# * From String: creates a pattern from the string, depending on type option (defaults to {Mustermann::Sinatra})
# * From Regexp: creates a {Mustermann::Regular} pattern.
# * From Symbol: creates a {Mustermann::Sinatra} pattern with a single named capture named after the input.
# * From an Array or multiple inputs: creates a new pattern from each element, combines them to a {Mustermann::Composite}.
# * From anything else: Will try to call to_pattern on it or raise a TypeError.
#
# Note that if the input is a {Mustermann::Pattern}, Regexp or Symbol, the type option is ignored and if to_pattern is
# called on the object, the type will be handed on but might be ignored by the input object.
#
# If you want to enforce the pattern type, you should create them via their expected class.
#
# @example creating patterns
# require 'mustermann'
#
# Mustermann.new("/:name") # => #<Mustermann::Sinatra:"/example">
# Mustermann.new("/{name}", type: :template) # => #<Mustermann::Template:"/{name}">
# Mustermann.new(/.*/) # => #<Mustermann::Regular:".*">
# Mustermann.new(:name, capture: :word) # => #<Mustermann::Sinatra:":name">
# Mustermann.new("/", "/*.jpg", type: :shell) # => #<Mustermann::Composite:(shell:"/" | shell:"/*.jpg")>
#
# @example using custom #to_pattern
# require 'mustermann'
#
# class MyObject
# def to_pattern(**options)
# Mustermann.new("/:name", **options)
# end
# end
#
# Mustermann.new(MyObject.new, type: :rails) # => #<Mustermann::Rails:"/:name">
#
# @example enforcing type
# require 'mustermann/sinatra'
#
# Mustermann::Sinatra.new("/:name")
#
# @param [String, Pattern, Regexp, Symbol, #to_pattern, Array<String, Pattern, Regexp, Symbol, #to_pattern>]
# input The representation of the pattern
# @param [Hash] options The options hash
# @return [Mustermann::Pattern] pattern corresponding to string.
# @raise (see [])
# @raise (see Mustermann::Pattern.new)
# @raise [TypeError] if the passed object cannot be converted to a pattern
# @see file:README.md#Types_and_Options "Types and Options" in the README
def self.new(*input, type: DEFAULT_TYPE, operator: :|, **options)
type ||= DEFAULT_TYPE
input = input.first if input.size < 2
case input
when Pattern then input
when Regexp then self[:regexp].new(input, **options)
when String then self[type].new(input, **options)
when Symbol then self[:sinatra].new(input.inspect, **options)
when Array then input.map { |i| new(i, type: type, **options) }.inject(operator)
else
pattern = input.to_pattern(type: type, **options) if input.respond_to? :to_pattern
raise TypeError, "#{input.class} can't be coerced into Mustermann::Pattern" if pattern.nil?
pattern
end
end

@mutex ||= Mutex.new
@types ||= {}

# Maps a type to its factory.
#
# @example
# Mustermann[:sinatra] # => Mustermann::Sinatra
#
# @param [Symbol] name a pattern type identifier
# @raise [ArgumentError] if the type is not supported
# @return [Class, #new] pattern factory
def self.[](name)
return name if name.respond_to? :new
@types.fetch(normalized = normalized_type(name)) do
@mutex.synchronize do
error = try_require "mustermann/#{normalized}"
@types.fetch(normalized) { raise ArgumentError, "unsupported type %p#{" (#{error.message})" if error}" % name }
end
end
end

# @return [LoadError, nil]
# @!visibility private
def self.try_require(path)
require(path)
nil
rescue LoadError => error
raise(error) unless error.path == path
error
end

# @!visibility private
def self.register(name, type)
@types[normalized_type(name)] = type
end

# @!visibility private
def self.normalized_type(type)
type.to_s.gsub('-', '_').downcase
end

# @!visibility private
def self.extend_object(object)
return super unless defined? ::Sinatra::Base and object.is_a? Class and object < ::Sinatra::Base
require 'mustermann/extension'
object.register Extension
end
end

# :nocov:
begin
require 'mustermann/visualizer' if defined?(Pry) or defined?(IRB)
rescue LoadError => error
raise error unless error.path == 'mustermann/visualizer'
$stderr.puts(error.message) if caller_locations[1].absolute_path =~ %r{/lib/pry/|/irb/|^\((?:irb|pry)\)$}
end
# :nocov:
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# frozen_string_literal: true
require 'mustermann/ast/translator'

module Mustermann
module AST
# Make sure #start and #stop is set on every node and within its parents #start and #stop.
# @!visibility private
class Boundaries < Translator
# @return [Mustermann::AST::Node] the ast passed as first argument
# @!visibility private
def self.set_boundaries(ast, string: nil, start: 0, stop: string.length)
new.translate(ast, start, stop)
ast
end

translate(:node) do |start, stop|
t.set_boundaries(node, start, stop)
t(payload, node.start, node.stop)
end

translate(:with_look_ahead) do |start, stop|
t.set_boundaries(node, start, stop)
t(head, node.start, node.stop)
t(payload, node.start, node.stop)
end

translate(Array) do |start, stop|
each do |subnode|
t(subnode, start, stop)
start = subnode.stop
end
end

translate(Object) { |*| node }

# Checks that a node is within the given boundaries.
# @!visibility private
def set_boundaries(node, start, stop)
node.start = start if node.start.nil? or node.start < start
node.stop = node.start + node.min_size if node.stop.nil? or node.stop < node.start
node.stop = stop if node.stop > stop
end
end
end
end
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
# frozen_string_literal: true
require 'mustermann/ast/translator'

module Mustermann
# @see Mustermann::AST::Pattern
module AST
# Regexp compilation logic.
# @!visibility private
class Compiler < Translator
raises CompileError

# Trivial compilations
translate(Array) { |**o| map { |e| t(e, **o) }.join }
translate(:node) { |**o| t(payload, **o) }
translate(:separator) { |**o| Regexp.escape(payload) }
translate(:optional) { |**o| "(?:%s)?" % t(payload, **o) }
translate(:char) { |**o| t.encoded(payload, **o) }

translate :union do |**options|
"(?:%s)" % payload.map { |e| "(?:%s)" % t(e, **options) }.join(?|)
end

translate :expression do |greedy: true, **options|
t(payload, allow_reserved: operator.allow_reserved, greedy: greedy && !operator.allow_reserved,
parametric: operator.parametric, separator: operator.separator, **options)
end

translate :with_look_ahead do |**options|
lookahead = each_leaf.inject("") do |ahead, element|
ahead + t(element, skip_optional: true, lookahead: ahead, greedy: false, no_captures: true, **options).to_s
end
lookahead << (at_end ? '$' : '/')
t(head, lookahead: lookahead, **options) + t(payload, **options)
end

# Capture compilation is complex. :(
# @!visibility private
class Capture < NodeTranslator
register :capture

# @!visibility private
def translate(**options)
return pattern(**options) if options[:no_captures]
"(?<#{name}>#{translate(no_captures: true, **options)})"
end

# @return [String] regexp without the named capture
# @!visibility private
def pattern(capture: nil, **options)
case capture
when Symbol then from_symbol(capture, **options)
when Array then from_array(capture, **options)
when Hash then from_hash(capture, **options)
when String then from_string(capture, **options)
when nil then from_nil(**options)
else capture
end
end

private
def qualified(string, greedy: true, **options) "#{string}#{qualifier || "+#{?? unless greedy}"}" end
def with_lookahead(string, lookahead: nil, **options) lookahead ? "(?:(?!#{lookahead})#{string})" : string end
def from_hash(hash, **options) pattern(capture: hash[name.to_sym], **options) end
def from_array(array, **options) Regexp.union(*array.map { |e| pattern(capture: e, **options) }) end
def from_symbol(symbol, **options) qualified(with_lookahead("[[:#{symbol}:]]", **options), **options) end
def from_string(string, **options) Regexp.new(string.chars.map { |c| t.encoded(c, **options) }.join) end
def from_nil(**options) qualified(with_lookahead(default(**options), **options), **options) end
def default(**options) constraint || "[^/\\?#]" end
end

# @!visibility private
class Splat < Capture
register :splat, :named_splat
# splats are always non-greedy
# @!visibility private
def pattern(**options)
constraint || ".*?"
end
end

# @!visibility private
class Variable < Capture
register :variable

# @!visibility private
def translate(**options)
return super(**options) if explode or not options[:parametric]
# Remove this line after fixing broken compatibility between 2.1 and 2.2
options.delete(:parametric) if options.has_key?(:parametric)
parametric super(parametric: false, **options)
end

# @!visibility private
def pattern(parametric: false, separator: nil, **options)
register_param(parametric: parametric, separator: separator, **options)
pattern = super(**options)
pattern = parametric(pattern) if parametric
pattern = "#{pattern}(?:#{Regexp.escape(separator)}#{pattern})*" if explode and separator
pattern
end

# @!visibility private
def parametric(string)
"#{Regexp.escape(name)}(?:=#{string})?"
end

# @!visibility private
def qualified(string, **options)
prefix ? "#{string}{1,#{prefix}}" : super(string, **options)
end

# @!visibility private
def default(allow_reserved: false, **options)
allow_reserved ? '[\w\-\.~%\:/\?#\[\]@\!\$\&\'\(\)\*\+,;=]' : '[\w\-\.~%]'
end

# @!visibility private
def register_param(parametric: false, split_params: nil, separator: nil, **options)
return unless explode and split_params
split_params[name] = { separator: separator, parametric: parametric }
end
end

# @return [String] Regular expression for matching the given character in all representations
# @!visibility private
def encoded(char, uri_decode: true, space_matches_plus: true, **options)
return Regexp.escape(char) unless uri_decode
encoded = escape(char, escape: /./)
list = [escape(char), encoded.downcase, encoded.upcase].uniq.map { |c| Regexp.escape(c) }
if char == " "
list << encoded('+') if space_matches_plus
list << " "
end
"(?:%s)" % list.join("|")
end

# Compiles an AST to a regular expression.
# @param [Mustermann::AST::Node] ast the tree
# @return [Regexp] corresponding regular expression.
#
# @!visibility private
def self.compile(ast, **options)
new.compile(ast, **options)
end

# Compiles an AST to a regular expression.
# @param [Mustermann::AST::Node] ast the tree
# @return [Regexp] corresponding regular expression.
#
# @!visibility private
def compile(ast, except: nil, **options)
except &&= "(?!#{translate(except, no_captures: true, **options)}\\Z)"
Regexp.new("#{except}#{translate(ast, **options)}")
end
end

private_constant :Compiler
end
end
Loading

0 comments on commit 2855d7c

Please sign in to comment.