class Unit::System

Constants

DEC
OPERATOR
OPERATOR_TOKENS
REAL
SI
SYMBOL
TOKENIZER
VALUE_TOKENS

Attributes

factor[R]
factor_symbol[R]
factor_value[R]
name[R]
unit[R]
unit_symbol[R]

Public Class Methods

new(name) { |self| ... } click to toggle source
# File lib/unit/system.rb, line 7
def initialize(name)
  @name = name
  @unit = {}
  @unit_symbol = {}

  # one is internal trivial factor
  @factor = {one: {symbol: 'one', value: 1} }
  @factor_symbol = {'one' => :one}
  @factor_value = {1 => :one}

  @loaded_systems = []
  @loaded_filenames = []

  yield(self) if block_given?
end

Public Instance Methods

load(input) click to toggle source
# File lib/unit/system.rb, line 23
def load(input)
  case input
  when Hash
    data = input
  when IO
    data = YAML.load(input.read)
  when String
    if File.exist?(input)
      return if @loaded_filenames.include?(input)
      data = YAML.load_file(input)
      @loaded_filenames << input
    else
      load(input.to_sym)
      return
    end
  when Symbol
    return if @loaded_systems.include?(input)
    data = YAML.load_file(File.join(File.dirname(__FILE__), 'systems', "#{input}.yml"))
    @loaded_systems << input
  end

  load_factors(data['factors']) if data['factors']
  load_units(data['units']) if data['units']

  @unit.each {|name, unit| validate_unit(unit[:def]) }

  true
end
parse_unit(expr) click to toggle source
# File lib/unit/system.rb, line 62
def parse_unit(expr)
  stack, result, implicit_mul = [], [], false
  expr.to_s.scan(TOKENIZER).each do |tok|
    if tok == '('
      stack << '('
      implicit_mul = false
    elsif tok == ')'
      compute(result, stack.pop) while !stack.empty? && stack.last != '('
      raise(SyntaxError, 'Unexpected token )') if stack.empty?
      stack.pop
      implicit_mul = true
    elsif OPERATOR.key?(tok)
      compute(result, stack.pop) while !stack.empty? && stack.last != '(' && OPERATOR[stack.last][1] >= OPERATOR[tok][1]
      stack << OPERATOR[tok][0]
      implicit_mul = false
    else
      val = case tok
            when REAL   then [[:one, tok.to_f, 1]]
            when DEC    then [[:one, tok.to_i, 1]]
            when SYMBOL then symbol_to_unit(tok)
            end
      stack << '*' if implicit_mul
      implicit_mul = true
      result << val
    end
  end
  compute(result, stack.pop) while !stack.empty?
  result.last
end
validate_unit(units) click to toggle source
# File lib/unit/system.rb, line 52
def validate_unit(units)
  units.each do |factor, unit, exp|
    #raise TypeError, 'Factor must be symbol' if !(Symbol === factor)
    #raise TypeError, 'Unit must be symbol' if !(Numeric === unit || Symbol === unit)
    #raise TypeError, 'Exponent must be numeric' if !(Numeric === exp)
    raise TypeError, "Undefined factor #{factor}" if !@factor[factor]
    raise TypeError, "Undefined unit #{unit}" if !(Numeric === unit || @unit[unit])
  end
end

Private Instance Methods

compute(result, op) click to toggle source
# File lib/unit/system.rb, line 119
def compute(result, op)
  b = result.pop
  a = result.pop || raise(SyntaxError, "Unexpected token #{op}")
  result << case op
            when '*' then a + b
            when '/' then a + Unit.power_unit(b, -1)
            when '^' then Unit.power_unit(a, b[0][1])
            else raise SyntaxError, "Unexpected token #{op}"
            end
end
load_factors(factors) click to toggle source
# File lib/unit/system.rb, line 130
def load_factors(factors)
  factors.each do |name, factor|
    name = name.to_sym
    symbols = [factor['sym'] || []].flatten
    base, exp = factor["def"].to_s.split("^").map { |value| Integer(value) }
    exp ||= 1
    raise "Invalid definition for factor #{name}" unless base
    value = base ** exp
    $stderr.puts "Factor #{name} already defined" if @factor[name]
    @factor[name] = { symbol: symbols.first, value: value }
    symbols.each do |sym|
      $stderr.puts "Factor symbol #{sym} for #{name} already defined" if @factor_symbol[name]
      @factor_symbol[sym] = name
    end
    @factor_symbol[name.to_s] = @factor_value[value] = name
  end
end
load_units(units) click to toggle source
# File lib/unit/system.rb, line 148
def load_units(units)
  units.each do |name, unit|
    name = name.to_sym
    symbols = [unit['sym'] || []].flatten
    $stderr.puts "Unit #{name} already defined" if @unit[name]
    @unit[name] = { symbol: symbols.first, def: parse_unit(unit['def'])  }
    symbols.each do |sym|
      $stderr.puts "Unit symbol #{sym} for #{name} already defined" if @unit_symbol[name]
      @unit_symbol[sym] = name
    end
    @unit_symbol[name.to_s] = name
  end
end
lookup_symbol(symbol) click to toggle source
# File lib/unit/system.rb, line 102
def lookup_symbol(symbol)
  if unit_symbol[symbol]
    [[:one, unit_symbol[symbol], 1]]
  else
    found = factor_symbol.keys.find do |sym|
      symbol[0..sym.size-1] == sym && unit_symbol[symbol[sym.size..-1]]
    end
    [[factor_symbol[found], unit_symbol[symbol[found.size..-1]], 1]] if found
  end
end
symbol_to_unit(symbol) click to toggle source
# File lib/unit/system.rb, line 113
def symbol_to_unit(symbol)
  lookup_symbol(symbol) ||
    (symbol[-1..-1] == 's' ? lookup_symbol(symbol[0..-2]) : nil) || # Try english plural
    [[:one, symbol.to_sym, 1]]
end