module SY::ExpressibleInUnits

This mixin provides ability to respond to SY unit symbol methods.

Constants

COLLISION_WARNING
REDEFINE_WARNING
RecursionError

Public Class Methods

exponentiation_string(exp) click to toggle source

Return exponentiation string (suffix) or empty ς if not necessary.

# File lib/sy/expressible_in_units.rb, line 114
def exponentiation_string exp
  exp == 1 ? '' : " ** #{exp}"
end
find_unit(ς) click to toggle source

Find unit based on name / abbreviation.

# File lib/sy/expressible_in_units.rb, line 97
def find_unit ς
  known_units.find do |u|
    u.name.to_s.downcase == ς.downcase &&
      ( ς == ς.downcase || ς == ς.upcase ) ||
      u.short.to_s == ς
  end
end
included(receiver) click to toggle source

included hook of this module is set to perfom a casual check for blatant name collisions between SY::Unit-implied methods, and existing methods of the include receiver.

# File lib/sy/expressible_in_units.rb, line 49
def included receiver
  included_in << receiver # keep track of where the mixin has been included
  # Warn if the receiver has potentially colliding methods.
  inst_methods = receiver.instance_methods
  w = COLLISION_WARNING % ["%s", receiver]
  known_units.each do |unit|
    next unless unit.warns?
    name, short = unit.name, unit.abbreviation
    warn w % "name method ##{name}" if inst_methods.include? name
    warn w % "abbreviation method ##{short}" if inst_methods.include? short
  end
  # Warn if shadowing methods are defined on the receiver later.
  if receiver.is_a? Class
    receiver.extend ::SY::ExpressibleInUnits::DetectRedefine
  end
end
included_in() click to toggle source

Modules in which this mixin has been included.

# File lib/sy/expressible_in_units.rb, line 68
def included_in
  @included_in ||= []
end
known_units() click to toggle source

Currently defined unit instances, if any.

# File lib/sy/expressible_in_units.rb, line 83
def known_units
  begin
    unit_namespace.instances
  rescue NoMethodError; [] end
end
method_family() click to toggle source

All methods defined by this mixin.

# File lib/sy/expressible_in_units.rb, line 91
def method_family
  @method_family ||= []
end
prefix_method_string(prefix) click to toggle source

Return prefix method or empty string, if prefix method not necessary.

# File lib/sy/expressible_in_units.rb, line 107
def prefix_method_string prefix
  full_prefix = SY::PREFIX_TABLE.to_full( prefix )
  full_prefix == '' ? '' : ".#{full_prefix}"
end
unit_namespace() click to toggle source

Unit namespace.

# File lib/sy/expressible_in_units.rb, line 74
def unit_namespace
  begin
    SY::Unit
  rescue NameError # no SY::Unit defined yet
  end
end

Public Instance Methods

method_missing(ß, *args, &block) click to toggle source
Calls superclass method
# File lib/sy/expressible_in_units.rb, line 119
def method_missing ß, *args, &block
  return self if ß.to_s =~ /begin|end/ # 3rd party bug workaround
  super if ß.to_s =~ /to_.+/ # dissmiss :to_..., esp. :to_ary
  begin # prevent recurrent call of method_missing for the same symbol
    anti_recursion_exec token: ß, var: :@SY_Units_mmiss do
      prefixes, units, exps = parse_unit_symbol ß
      self.class.instance_variable_set "@no_collision", ß
      self.class.module_eval write_unit_method( ß, prefixes, units, exps )
      SY::ExpressibleInUnits.method_family << self.class.instance_method( ß )
    end
  rescue NameError => err
    super # give up
  rescue SY::ExpressibleInUnits::RecursionError
    super # give up
  else # actually invoke the method that we just defined
    send ß, *args, &block
  end
end
respond_to_missing?(ß, *args, &block) click to toggle source
# File lib/sy/expressible_in_units.rb, line 138
def respond_to_missing? ß, *args, &block
  # dismiss :to_... methods and /begin|end/ (3rd party bug workaround)
  return false if ß.to_s =~ /to_.+|begin|end/
  !! begin
    anti_recursion_exec token: ß, var: :@SY_Units_rmiss do
      parse_unit_symbol ß
    end
  rescue NameError, SY::ExpressibleInUnits::RecursionError
    false
  else
    true
  end
end

Private Instance Methods

anti_recursion_exec( token: nil, var: :@SY_anti_recursion_exec ) { || ... } click to toggle source

Takes a token as the first argument, a symbol of the instance variable to be used for storage of active tokens, grabs the token, executes the supplied block, and releases the token. The method guards against double execution for the same token, raising IllegalRecursionError in such case.

# File lib/sy/expressible_in_units.rb, line 207
def anti_recursion_exec( token: nil, var: :@SY_anti_recursion_exec )
  registry = self.class.instance_variable_get( var ) ||
    self.class.instance_variable_set( var, [] )
  raise RecursionError if registry.include? token
  begin
    registry << token
    yield if block_given?
  ensure
    registry.delete token
  end
end
parse_unit_symbol(ß) click to toggle source

Looking at the method symbol, delivered to method_missing, this method figures out which SY units it represents, along with prefixes and exponents.

# File lib/sy/expressible_in_units.rb, line 157
def parse_unit_symbol ß
  SY::Unit.parse_sps_using_all_prefixes( ß ) # rely on SY::Unit
end
write_unit_method(ß, prefixes, units, exponents) click to toggle source

Takes method symbol, and three more array arguments, representing prefixes, unit symbols and exponents. Generates an appropriate unit method as a string. Arrays must be of equal length. (Note: 'ß' is 'symbol', 'ς' is 'string')

# File lib/sy/expressible_in_units.rb, line 165
def write_unit_method ß, prefixes, units, exponents
  # Prepare prefix / unit / exponent triples for making factor strings:
  triples = [ prefixes, units, exponents ].transpose
  # A procedure for triple processing before use:
  process_triple = lambda do |pfx, unit_ς, exp|
    [ ::SY::ExpressibleInUnits.find_unit( unit_ς ).name.to_s.upcase, 
      ::SY::ExpressibleInUnits.prefix_method_string( pfx ),
      ::SY::ExpressibleInUnits.exponentiation_string( exp ) ]
  end
  # Method skeleton:
  if triples.size == 1 && triples.first[-1] == 1 then
    method_skeleton = "def #{ß}( exp=1 )\n" +
                      "  %s\n" +
                      "end"
    method_body = "if exp == 1 then\n" +
                  "  +( ::SY.Unit( :%s )%s ) * self\n" +
                  "else\n" +
                  "  +( ::SY.Unit( :%s )%s ) ** exp * self\n" +
                  "end"
    , pfxς, expς = process_triple.( *triples.shift )
    method_body %= [, pfxς] * 2
  else
    method_skeleton = "def #{ß}\n" +
                      "  %s\n" +
                      "end"
    factors = [ "+( ::SY.Unit( :%s )%s )%s * self" %
                process_triple.( *triples.shift ) ] +
      triples.map do |triple|
        "( ::SY.Unit( :%s )%s.relative ) )%s" % process_triple.( *triple )
      end
    # Multiply the factors toghether:
    method_body = factors.join( " * \n    " )
  end
  # Return the finished method string:
  return ( method_skeleton % method_body )
end