Class: Reality::Measure::Unit

Inherits:
Object
  • Object
show all
Defined in:
lib/reality/measure/unit.rb

Constant Summary collapse

UNIT_REGEX =

FIXME: there are many non-ASCII units, especially in money

/[a-zA-Z\$]+/
POWER_REGEX =
/[²³]|\^(\d+)/
OP_REGEX =
/[\/*·]/

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(*components) ⇒ Unit

Returns a new instance of Unit.



53
54
55
56
57
58
# File 'lib/reality/measure/unit.rb', line 53

def initialize(*components)
  @components = components.
    group_by{|sig, pow| sig}.
    map{|sig, cmps| [sig, cmps.map(&:last).inject(:+)]}.
    reject{|sig, pow| pow.zero?}
end

Class Attribute Details

.unicodeObject

Returns the value of attribute unicode.



9
10
11
# File 'lib/reality/measure/unit.rb', line 9

def unicode
  @unicode
end

Instance Attribute Details

#componentsObject (readonly)

Returns the value of attribute components.



51
52
53
# File 'lib/reality/measure/unit.rb', line 51

def components
  @components
end

Class Method Details

.parse(str) ⇒ Object



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
# File 'lib/reality/measure/unit.rb', line 15

def parse(str)
  return str if str.kind_of?(Unit)
  
  scanner = StringScanner.new(str)
  denom = false
  units = []
  
  loop do
    # (variable [power] operator) ....
    unit = scanner.scan(UNIT_REGEX) or fail("Variable expected at #{scanner.rest}")
    pow = scanner.scan(POWER_REGEX)
    units << [unit, parse_pow(pow, denom)]
    break if scanner.eos?

    op = scanner.scan(OP_REGEX) or fail("Operator expected at #{scanner.rest}")
    if op == '/'
      denom and fail("Second division at #{scanner.rest}")
      denom = true
    end
  end
  new(*units)
end

.parse_pow(p, denom) ⇒ Object



38
39
40
41
42
43
44
45
46
47
48
# File 'lib/reality/measure/unit.rb', line 38

def parse_pow(p, denom)
  res = case p
  when nil then 1
  when '²' then 2
  when '³' then 3
  when /^\^(\d+)$/ then $1.to_i
  else fail(ArgumentError, "Can't parse power #{p}")
  end

  denom ? -res : res
end

Instance Method Details

#*(other) ⇒ Object



72
73
74
75
76
77
# File 'lib/reality/measure/unit.rb', line 72

def *(other)
  other.class == self.class or
    fail(TypeError, "Can't multiply #{self.class} by #{other.class}")

  self.class.new(*components, *other.components)
end

#-@Object



68
69
70
# File 'lib/reality/measure/unit.rb', line 68

def -@
  self.class.new(*components.map{|sig, pow| [sig, -pow]})
end

#/(other) ⇒ Object



79
80
81
82
83
84
# File 'lib/reality/measure/unit.rb', line 79

def /(other)
  other.class == self.class or
    fail(TypeError, "Can't divide #{self.class} by #{other.class}")

  self * -other
end

#==(other) ⇒ Object



60
61
62
# File 'lib/reality/measure/unit.rb', line 60

def ==(other)
  other.class == self.class && other.components == self.components
end

#scalar?Boolean

Returns:

  • (Boolean)


64
65
66
# File 'lib/reality/measure/unit.rb', line 64

def scalar?
  components.empty?
end

#to_sObject



86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/reality/measure/unit.rb', line 86

def to_s
  num, denom = components.partition{|sig, pow| pow > 0}
  numerator = num.map{|sig, pow| "#{sig}#{power(pow)}"}.join(mul)
  denominator = denom.map{|sig, pow| "#{sig}#{power(pow)}"}.join(mul)
  case
  when numerator.empty?
    [1, denominator].join('/')
  when denominator.empty?
    numerator
  else
    [numerator, denominator].join('/')
  end
end