module SY::ExpressibleInUnits
This mixin provides ability to respond to SY
unit symbol methods.
Constants
- COLLISION_WARNING
- REDEFINE_WARNING
- RecursionError
Public Class Methods
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 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 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
Modules in which this mixin has been included.
# File lib/sy/expressible_in_units.rb, line 68 def included_in @included_in ||= [] end
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
All methods defined by this mixin.
# File lib/sy/expressible_in_units.rb, line 91 def method_family @method_family ||= [] end
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.
# 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
# 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
# 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
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
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
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" uς, pfxς, expς = process_triple.( *triples.shift ) method_body %= [uς, 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