numru-units 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/numbernode.rb ADDED
@@ -0,0 +1,53 @@
1
+ class NumberNode < TerminalNode
2
+
3
+ def initialize(arg)
4
+ raise TypeError unless Numeric === arg
5
+ @a = arg
6
+ end
7
+
8
+ UNITY = NumberNode.new(1)
9
+ ZERO = NumberNode.new(0)
10
+
11
+ def to_s
12
+ if @a == @a.to_i
13
+ sprintf("%d",@a)
14
+ else
15
+ String(@a)
16
+ end
17
+ end
18
+
19
+ attr_reader :a
20
+
21
+ alias :value :a
22
+ alias :factor :a
23
+
24
+ def == (other)
25
+ case other
26
+ when NumberNode
27
+ @a == other.a
28
+ else
29
+ false
30
+ end
31
+ end
32
+
33
+ def add_eval(another)
34
+ raise TypeError unless NumberNode === another
35
+ NumberNode.new(@a + another.value)
36
+ end
37
+
38
+ def mul_eval(another)
39
+ case another
40
+ when NumberNode then NumberNode.new(@a * another.a)
41
+ when PowNode
42
+ raise TypeError unless NumberNode === another.lhs
43
+ raise TypeError unless NumberNode === another.rhs
44
+ NumberNode.new(@a * Units::pow_f(another.lhs.value, another.rhs.value))
45
+ else raise TypeError
46
+ end
47
+ end
48
+
49
+ def name; "1"; end
50
+
51
+ def power; UNITY; end
52
+
53
+ end
data/src/pownode.rb ADDED
@@ -0,0 +1,98 @@
1
+ class PowNode < ContainerNode
2
+
3
+ include BinaryNode
4
+
5
+ def initialize(lhs, rhs)
6
+ @lhs, @rhs = lhs, rhs
7
+ raise TypeError unless NumberNode === @rhs
8
+ end
9
+
10
+ def to_s
11
+ lhs = @lhs.to_s
12
+ case lhs
13
+ when /\d$/, /[\d\.]/
14
+ lhs = "(#{lhs})"
15
+ end
16
+ rhs = @rhs.to_s
17
+ if rhs == '1'
18
+ lhs
19
+ else
20
+ rhs = "^(#{rhs})" if (/\./ =~ rhs)
21
+ lhs + rhs
22
+ end
23
+ end
24
+
25
+ attr_reader :lhs, :rhs
26
+ alias :power :rhs
27
+
28
+ def pow_eval(other)
29
+ case other
30
+ when NumberNode
31
+ PowNode.new(@lhs, @rhs.mul_eval(other))
32
+ else
33
+ super(other)
34
+ end
35
+ end
36
+
37
+ def flatten2
38
+ x = @lhs.flatten2
39
+ case x
40
+ when NumberNode
41
+ a = @lhs.pow_eval(@rhs)
42
+ when TerminalNode
43
+ a = self
44
+ when PowNode
45
+ a = PowNode.new(x.lhs, x.rhs.mul_eval(@rhs))
46
+ when MulNode, MultiNode
47
+ a = MultiNode.new()
48
+ for gc in x
49
+ a.append(gc.pow_eval(@rhs))
50
+ end
51
+ else
52
+ raise "internal error"
53
+ end
54
+ return a
55
+ end
56
+
57
+ def name
58
+ case @lhs
59
+ when NumberNode, NameNode
60
+ @lhs.name
61
+ else
62
+ raise "internal error"
63
+ end
64
+ end
65
+
66
+ def value
67
+ case @lhs
68
+ when NumberNode
69
+ Units::pow_f(@lhs.value, @rhs.value)
70
+ else
71
+ raise(format('%s#value: internal error', self.class.to_s))
72
+ end
73
+ end
74
+
75
+ def mul_eval(another)
76
+ raise "internal error (#{name}, #{another.name})" if name != another.name
77
+ case @lhs
78
+ when NumberNode
79
+ NumberNode.new(Units::pow_f(@lhs.value, @rhs.value) * another.value)
80
+ else
81
+ self.class.new(@lhs, @rhs.add_eval(another.power))
82
+ end
83
+ end
84
+
85
+ def sort
86
+ case @lhs
87
+ when NumberNode
88
+ NumberNode.new(Units::pow_f(@lhs.value, @rhs.value))
89
+ else
90
+ self
91
+ end
92
+ end
93
+
94
+ def factor
95
+ Units::pow_f(@lhs.factor, @rhs.value)
96
+ end
97
+
98
+ end
data/src/rules.rb ADDED
@@ -0,0 +1,65 @@
1
+ class Units
2
+
3
+ token INT ERR SHIFT SPACE MULTIPLY DIVIDE EXPONENT REAL NAME DATE TIME ZONE
4
+ options no_result_var
5
+
6
+ rule
7
+
8
+ unit_spec:
9
+ /* { yyaccept; } */ /* <-- to acccept empty unit_spec */
10
+ | origin_exp { yyaccept; }
11
+ | error { yyerrok }
12
+ ;
13
+
14
+ origin_exp:
15
+ unit_exp
16
+ | unit_exp SHIFT value_exp { val[0].shift(val[2]) }
17
+ | unit_exp SHIFT timestamp { val[0].shift(val[2]) }
18
+ ;
19
+
20
+ unit_exp:
21
+ power_exp
22
+ | number_exp
23
+ | unit_exp power_exp { val[0].mul(val[1]) }
24
+ | unit_exp MULTIPLY power_exp { val[0].mul(val[2]) }
25
+ | unit_exp DIVIDE power_exp { val[0].divide(val[2]) }
26
+ | unit_exp MULTIPLY number_exp { val[0].mul(val[2]) }
27
+ | unit_exp DIVIDE number_exp { val[0].divide(val[2]) }
28
+ ;
29
+
30
+ power_exp:
31
+ NAME { NameNode.new(val[0]) }
32
+ | power_exp number_exp { val[0].pow(val[1]) }
33
+ | power_exp EXPONENT value_exp { val[0].pow(val[2]) }
34
+ | '(' origin_exp ')' { val[1] }
35
+ ;
36
+
37
+ value_exp:
38
+ number_exp
39
+ | '(' value_exp ')' { val[1] }
40
+ ;
41
+
42
+ number_exp:
43
+ INT { NumberNode.new(val[0]) }
44
+ | REAL { NumberNode.new(val[0]) }
45
+ ;
46
+
47
+ timestamp:
48
+ time_exp
49
+ | '(' timestamp ')' { val[1] }
50
+ ;
51
+
52
+ time_exp:
53
+ DATE { TimeNode.new(val[0], 0.0, 0) }
54
+ | DATE TIME { TimeNode.new(val[0], val[1], 0) }
55
+ | DATE TIME ZONE { TimeNode.new(val[0], val[1], val[2]) }
56
+ ;
57
+
58
+ end
59
+
60
+ ---- header
61
+
62
+ require 'date'
63
+
64
+ ---- inner
65
+
data/src/shiftnode.rb ADDED
@@ -0,0 +1,63 @@
1
+ class ShiftNode < ContainerNode
2
+
3
+ include BinaryNode
4
+
5
+ def initialize(lhs, rhs)
6
+ @lhs, @rhs = lhs, rhs
7
+ end
8
+
9
+ attr_reader :lhs, :rhs
10
+ alias :ref :rhs
11
+
12
+ def to_s
13
+ "(#{@lhs.to_s} @ #{@rhs.to_s})"
14
+ end
15
+
16
+ def trim2; @lhs; end
17
+ def trim
18
+ self.class.new(@lhs.trim, @rhs.trim2)
19
+ end
20
+
21
+ def flatten2; @lhs; end
22
+ def flatten
23
+ lf = @lhs.flatten
24
+ case lf
25
+ when ShiftNode
26
+ rf = lf.rhs.add_eval(@rhs)
27
+ self.class.new(lf.lhs, rf)
28
+ else
29
+ self.class.new(lf, @rhs.flatten)
30
+ end
31
+ end
32
+
33
+ def sort
34
+ self.class.new(@lhs.sort, @rhs.sort)
35
+ end
36
+
37
+ def ref
38
+ case @lhs
39
+ when ShiftNode
40
+ @lhs.ref.add_eval(@rhs)
41
+ else
42
+ @rhs
43
+ end
44
+ end
45
+
46
+ def deref
47
+ case @lhs
48
+ when ShiftNode
49
+ @lhs.deref
50
+ else
51
+ @lhs
52
+ end
53
+ end
54
+
55
+ def name
56
+ @lhs.name
57
+ end
58
+
59
+ def factor
60
+ @lhs.factor
61
+ end
62
+
63
+ end
data/src/test.rb ADDED
@@ -0,0 +1,122 @@
1
+ require 'units' # Use require "numru/units" after installation.
2
+ include NumRu
3
+
4
+ def assert(test, seikai)
5
+ raise "#{test.inspect} != #{seikai.inspect}" if test != seikai
6
+ puts "ok #{seikai.inspect}"
7
+ end
8
+
9
+ puts "=== reduce1 ==="
10
+
11
+ assert Units.new('').reduce1.to_s, ""
12
+ assert Units.new('m').reduce1.to_s, "m"
13
+ assert Units.new('3').reduce1.to_s, "3"
14
+ assert Units.new('3.14').reduce1.to_s, "3.14"
15
+ assert Units.new('m2').reduce1.to_s, "m2"
16
+ assert Units.new('m.s').reduce1.to_s, "m.s"
17
+ assert Units.new('m/s').reduce1.to_s, "m.s-1"
18
+ assert Units.new('kg.m/s2').reduce1.to_s, "kg.m.(s2)-1"
19
+ assert Units.new('s @ 2003-11-29').reduce1.to_s,
20
+ "(s @ 2003-11-29T00:00:00.00 +00:00)"
21
+ assert Units.new('s @ 2003-11-29T11:24').reduce1.to_s,
22
+ "(s @ 2003-11-29T11:24:00.00 +00:00)"
23
+ assert Units.new('s @ 2003-11-29T11:24:11 -09:00').reduce1.to_s,
24
+ "(s @ 2003-11-29T11:24:11.00 -09:00)"
25
+
26
+ assert Units.new('100').reduce1.to_s, "100"
27
+ assert Units.new('(10)^2').reduce1.to_s, "(10)2"
28
+ assert Units.new('(10)^2/100').reduce1.to_s, "(10)2.(100)-1"
29
+
30
+ puts "=== reduce2 ==="
31
+
32
+ assert Units.new('s @ 2003-11-29').reduce2.to_s,
33
+ "(s @ 2003-11-29T00:00:00.00 +00:00)"
34
+ assert Units.new('m/(s @ 2003-11-29)').reduce2.to_s, "m.s-1"
35
+ assert Units.new('m/((K @ 273.15) (s from 2003-11-29))').reduce2.to_s, "m.(K.s)-1"
36
+
37
+ assert Units.new('(10)^2/100').reduce2.to_s, "(10)2.(100)-1"
38
+
39
+ puts "=== reduce3 ==="
40
+
41
+ assert Units::MultiNode.new(Units::NameNode.new('a'), \
42
+ Units::NumberNode.new(1), \
43
+ Units::NameNode.new('b')).to_s, 'a.1 b'
44
+
45
+ assert Units.new('kg').reduce3.inspect, "Units[Name[kg]]"
46
+ assert Units.new('kg.m').reduce3.inspect, "Units[Multi[Name[kg], Name[m]]]"
47
+ assert Units.new('kg.m.s').reduce3.inspect,
48
+ "Units[Multi[Name[kg], Name[m], Name[s]]]"
49
+
50
+ assert Units.new('(m.s)^2').reduce3.inspect,
51
+ "Units[Multi[Pow[Name[m], Number[2]], Pow[Name[s], Number[2]]]]"
52
+ assert Units.new('K @ 273.15').reduce3.inspect,
53
+ "Units[Shift[Name[K], Number[273.15]]]"
54
+ assert Units.new('((a.b)^2)^2').reduce3.inspect,
55
+ "Units[Multi[Pow[Name[a], Number[4]], Pow[Name[b], Number[4]]]]"
56
+ assert Units.new('((a.b)^2 c4 d)^2').reduce3.inspect,
57
+ "Units[Multi[Pow[Name[a], Number[4]], Pow[Name[b], Number[4]], Pow[Name[c], Number[8]], Pow[Name[d], Number[2]]]]"
58
+ assert Units.new('((a.b)^2 c4 d)^2').reduce3.to_s,
59
+ "a4 b4 c8 d2"
60
+ assert Units.new('((a.b)^2 a4 b)^2').reduce3.to_s,
61
+ "a4 b4 a8 b2"
62
+
63
+ assert Units.new('s @ 2003-11-29').reduce3.to_s,
64
+ "(s @ 2003-11-29T00:00:00.00 +00:00)"
65
+ assert Units.new('m/(s @ 2003-11-29)').reduce3.to_s, "m.s-1"
66
+ assert Units.new('m/((K @ 273.15) (s from 2003-11-29))').reduce3.to_s, "m.K-1 s-1"
67
+
68
+ assert Units.new('(10)^2/100').reduce3.to_s, "(10)2.(100)-1"
69
+
70
+ puts "=== reduce4 ==="
71
+
72
+ assert Units.new('((a.b)^2 a4 b @ now)^2 @ 273.15').reduce4.to_s,
73
+ "(a12 b6 @ 273.15)"
74
+
75
+ assert Units.new('km2').reduce4.to_s, "km2"
76
+ assert Units.new('hours.hour').reduce4.to_s, "hour2"
77
+ assert Units.new('(10)^2').reduce4.to_s, "100"
78
+ assert Units.new('100/10').reduce4.to_s, "10.0"
79
+ assert Units.new('(10)^2/100').reduce4.to_s, "1.0"
80
+
81
+ puts "=== reduce5 ==="
82
+
83
+ assert Units.new('km2').reduce5.to_s, "1000000 m2"
84
+ assert Units.new('(10)^2/100').reduce5.to_s, "1.0"
85
+
86
+ assert Units.new('hPa').reduce5.to_s, "100 kg.m-1 s-2"
87
+ assert Units.new('mb').reduce5.to_s, "100.0 kg.m-1 s-2"
88
+
89
+ assert Units.new('hPa/mb').reduce5.to_s, "1.0"
90
+
91
+ assert Units.new('(K @ 273.15)@ 10').reduce5.to_s, "(K @ 283.15)"
92
+
93
+ puts "=== APPLICATIONS ==="
94
+
95
+ assert Units.new('km @ 2').convert(3, Units.new('m @ 100')), 4900
96
+ assert Units.new('degree_F').convert(32, Units.new('K')).to_s, "273.15"
97
+
98
+ u1 = Units.new('m/s')
99
+ u2 = Units.new('mm/s')
100
+ assert((u1/u2).to_s, "m.mm-1")
101
+ assert((u1*u2).to_s, "m.mm.s-2")
102
+
103
+ u1 = Units.new('years since 1999-01-01 00:00').reduce4
104
+ u2 = Units.new('hours since 2001-01-01 00:00').reduce4
105
+ assert u1.convert(3, u2), 24 * 365
106
+ u3 = Units.new('months since 2001-01-01 00:00').reduce4
107
+ assert u1.convert(3, u3), 12.0
108
+
109
+ Units.reduce_level = 3
110
+ assert((Units.new('hours') ** 2).to_s, "hours2")
111
+ Units.reduce_level = 4
112
+ assert((Units.new('hours') ** 2).to_s, "hour2")
113
+ Units.reduce_level = 5
114
+ assert((Units.new('hours') ** 2).to_s, "12960000 s2")
115
+
116
+ assert(Units.new('day') =~ Units.new('s since 2002-01-01'), true)
117
+ assert(Units.new('m') =~ Units.new('1'), false)
118
+
119
+ un1 = Units['day since 2000-01-01']
120
+ un2 = Units['s since 2000-01-01']
121
+ assert(un1.convert(0, un2), 0.0)
122
+ assert(un1.convert(1, un2), 86400.0)
data/src/timenode.rb ADDED
@@ -0,0 +1,136 @@
1
+ class XDate
2
+
3
+ def initialize(year, month, day)
4
+ @year, @month, @day = year.to_i, month.to_i, day.to_i
5
+ end
6
+
7
+ attr_reader :year, :month, :day
8
+
9
+ def to_s
10
+ format('%04d-%02d-%02d', @year, @month, @day)
11
+ end
12
+
13
+ alias :inspect :to_s
14
+
15
+ def to_time
16
+ Time.gm(@year, @month, @day)
17
+ end
18
+
19
+ def to_date
20
+ Date.new(@year, @month, @day)
21
+ end
22
+
23
+ def -(other)
24
+ case other
25
+ when XDate
26
+ (to_date - other.to_date)
27
+ when Time
28
+ to_time - other
29
+ when Date
30
+ (to_date - other.to_date)
31
+ else
32
+ to_date - other
33
+ end
34
+ end
35
+
36
+ def +(other)
37
+ t = to_date + other
38
+ self.class.new(t.year, t.month, t.mday)
39
+ end
40
+
41
+ end
42
+
43
+ class TimeNode < TerminalNode
44
+
45
+ def initialize(date, time, zone)
46
+ @date, @time, @zone = date, time, zone
47
+ if :now === @date then
48
+ now = Time.now.utc
49
+ @date = XDate.new(now.year, now.month, now.day)
50
+ @time = ((now.hour * 60 + now.min) * 60 + Float(now.sec))
51
+ else
52
+ qdays = (@time / 86400).floor
53
+ if not qdays.zero?
54
+ @date += qdays
55
+ @time -= (qdays * 86400)
56
+ end
57
+ end
58
+ raise TypeError unless XDate === @date
59
+ @time = 0.0 unless @time
60
+ raise TypeError unless Float === @time
61
+ @zone = 0 unless @zone
62
+ raise TypeError unless Integer === @zone
63
+ end
64
+
65
+ attr_reader :date, :time, :zone
66
+
67
+ def to_s
68
+ hr = @time.floor / 3600
69
+ mi = (@time.floor / 60) % 60
70
+ sc = @time % 60
71
+ tzm = @zone.abs
72
+ tzh = tzm / 60
73
+ tzm %= 60
74
+ tzh = -tzh if @zone < 0
75
+ format("%sT%02d:%02d:%05.2f %+03d:%02d", \
76
+ @date.to_s, hr, mi, sc, tzh, tzm)
77
+ end
78
+
79
+ def self::pentad(d)
80
+ (d > 25) ? 5 : ((d - 1) / 5)
81
+ end
82
+
83
+ def add_time(increment)
84
+ inc = increment.reduce5
85
+ case inc.name
86
+ when 's'
87
+ t2 = @time + inc.factor
88
+ d2 = @date + (t2 / 86400)
89
+ t2 = t2 % 86400
90
+ self.class.new(d2, t2, @zone)
91
+ when 'pentad'
92
+ ifac = Integer(inc.factor)
93
+ ipen = ifac % 6
94
+ imon = ifac / 6
95
+ spen = self.class.pentad(@date.day)
96
+ smon = @date.month + imon + spen / 6
97
+ spen = spen % 6
98
+ sday = spen * 5 + (@date.day - 1) % 5 + 1
99
+ syear = @date.year + (smon - 1) / 12
100
+ smon = (smon - 1) % 12 + 1
101
+ sdate = XDate.new(syear, smon, sday)
102
+ self.class.new(sdate, @time, @zone)
103
+ else
104
+ raise "bad time unit '#{inc.name}'"
105
+ end
106
+ end
107
+
108
+ def utcsod
109
+ @time - @zone * 60
110
+ end
111
+
112
+ def div_time(units)
113
+ base = units.ref
114
+ inc = units.deref.reduce5
115
+ begin
116
+ incname = inc.name
117
+ rescue Exception
118
+ incname = "(undefined)"
119
+ end
120
+ case incname
121
+ when 's'
122
+ dif = (@date - base.date) * 86400 + (utcsod - base.utcsod)
123
+ dif / inc.factor
124
+ when 'pentad'
125
+ dif = (@date.year - base.date.year) * 72
126
+ dif += (@date.month - base.date.month) * 6
127
+ dif += self.class.pentad(@date.day)
128
+ dif -= self.class.pentad(base.date.day)
129
+ dif = Float(dif) if dif % inc.factor != 0
130
+ dif / inc.factor
131
+ else
132
+ raise "bad time unit '#{incname}'"
133
+ end
134
+ end
135
+
136
+ end