lib: intro the `standard::numeric` module
[nit.git] / examples / calculator.nit
1 # This file is part of NIT ( http://www.nitlanguage.org ).
2 #
3 # Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
4 #
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
8 #
9 # http://www.apache.org/licenses/LICENSE-2.0
10 #
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16
17 import gtk
18
19 class CalculatorContext
20 var result : nullable Float = null
21
22 var last_op : nullable Char = null
23
24 var current : nullable Float = null
25 var after_point : nullable Int = null
26
27 fun push_op( op : Char )
28 do
29 apply_last_op_if_any
30 if op == 'C' then
31 self.result = 0.0
32 last_op = null
33 else
34 last_op = op # store for next push_op
35 end
36
37 # prepare next current
38 after_point = null
39 current = null
40 end
41
42 fun push_digit( digit : Int )
43 do
44 var current = current
45 if current == null then current = 0.0
46
47 var after_point = after_point
48 if after_point == null then
49 current = current * 10.0 + digit.to_f
50 else
51 current = current + digit.to_f * 10.0.pow(after_point.to_f)
52 self.after_point -= 1
53 end
54
55 self.current = current
56 end
57
58 fun switch_to_decimals
59 do
60 if self.current == null then current = 0.0
61 if after_point != null then return
62
63 after_point = -1
64 end
65
66 fun apply_last_op_if_any
67 do
68 var op = last_op
69
70 var result = result
71 if result == null then result = 0.0
72
73 var current = current
74 if current == null then current = 0.0
75
76 if op == null then
77 result = current
78 else if op == '+' then
79 result = result + current
80 else if op == '-' then
81 result = result - current
82 else if op == '/' then
83 result = result / current
84 else if op == '*' then
85 result = result * current
86 end
87 self.result = result
88 self.current = null
89 end
90 end
91
92 class CalculatorGui
93 super GtkCallable
94
95 var win : GtkWindow is noinit
96 var container : GtkGrid is noinit
97
98 var lbl_disp : GtkLabel is noinit
99 var but_eq : GtkButton is noinit
100 var but_dot : GtkButton is noinit
101
102 var context = new CalculatorContext
103
104 redef fun signal( sender, user_data )
105 do
106 var after_point = context.after_point
107 if after_point == null then
108 after_point = 0
109 else
110 after_point = (after_point.abs)
111 end
112
113 if user_data isa Char then # is an operation
114 var c = user_data
115 if c == '.' then
116 but_dot.sensitive= false
117 context.switch_to_decimals
118 lbl_disp.text = "{context.current.to_i}."
119 else
120 but_dot.sensitive= true
121 context.push_op( c )
122
123 var s = context.result.to_precision_native(6)
124 var index : nullable Int = null
125 for i in s.length.times do
126 var chiffre = s.chars[i]
127 if chiffre == '0' and index == null then
128 index = i
129 else if chiffre != '0' then
130 index = null
131 end
132 end
133 if index != null then
134 s = s.substring(0, index)
135 if s.chars[s.length-1] == ',' then s = s.substring(0, s.length-1)
136 end
137 lbl_disp.text = s
138 end
139 else if user_data isa Int then # is a number
140 var n = user_data
141 context.push_digit( n )
142 lbl_disp.text = context.current.to_precision_native(after_point)
143 end
144 end
145
146 init
147 do
148 init_gtk
149
150 win = new GtkWindow( 0 )
151
152 container = new GtkGrid(5,5,true)
153 win.add( container )
154
155 lbl_disp = new GtkLabel( "_" )
156 container.attach( lbl_disp, 0, 0, 5, 1 )
157
158 # digits
159 for n in [0..9] do
160 var but = new GtkButton.with_label( n.to_s )
161 but.request_size( 64, 64 )
162 but.signal_connect( "clicked", self, n )
163 if n == 0 then
164 container.attach( but, 0, 4, 1, 1 )
165 else container.attach( but, (n-1)%3, 3-(n-1)/3, 1, 1 )
166 end
167
168 # operators
169 var r = 1
170 for op in ['+', '-', '*', '/' ] do
171 var but = new GtkButton.with_label( op.to_s )
172 but.request_size( 64, 64 )
173 but.signal_connect( "clicked", self, op )
174 container.attach( but, 3, r, 1, 1 )
175 r+=1
176 end
177
178 # =
179 but_eq = new GtkButton.with_label( "=" )
180 but_eq.request_size( 64, 64 )
181 but_eq.signal_connect( "clicked", self, '=' )
182 container.attach( but_eq, 4, 3, 1, 2 )
183
184 # .
185 but_dot = new GtkButton.with_label( "." )
186 but_dot.request_size( 64, 64 )
187 but_dot.signal_connect( "clicked", self, '.' )
188 container.attach( but_dot, 1, 4, 1, 1 )
189
190 #C
191 var but_c = new GtkButton.with_label( "C" )
192 but_c.request_size( 64, 64 )
193 but_c.signal_connect("clicked", self, 'C')
194 container.attach( but_c, 2, 4, 1, 1 )
195
196 win.show_all
197 end
198 end
199
200 # context tests
201 var context = new CalculatorContext
202 context.push_digit( 1 )
203 context.push_digit( 2 )
204 context.push_op( '+' )
205 context.push_digit( 3 )
206 context.push_op( '*' )
207 context.push_digit( 2 )
208 context.push_op( '=' )
209 var r = context.result.to_precision( 2 )
210 assert r == "30.00" else print r
211
212 context = new CalculatorContext
213 context.push_digit( 1 )
214 context.push_digit( 4 )
215 context.switch_to_decimals
216 context.push_digit( 1 )
217 context.push_op( '*' )
218 context.push_digit( 3 )
219 context.push_op( '=' )
220 r = context.result.to_precision( 2 )
221 assert r == "42.30" else print r
222
223 context.push_op( '+' )
224 context.push_digit( 1 )
225 context.push_digit( 1 )
226 context.push_op( '=' )
227 r = context.result.to_precision( 2 )
228 assert r == "53.30" else print r
229
230 context = new CalculatorContext
231 context.push_digit( 4 )
232 context.push_digit( 2 )
233 context.switch_to_decimals
234 context.push_digit( 3 )
235 context.push_op( '/' )
236 context.push_digit( 3 )
237 context.push_op( '=' )
238 r = context.result.to_precision( 2 )
239 assert r == "14.10" else print r
240
241 #test multiple decimals
242 context = new CalculatorContext
243 context.push_digit( 5 )
244 context.push_digit( 0 )
245 context.switch_to_decimals
246 context.push_digit( 1 )
247 context.push_digit( 2 )
248 context.push_digit( 3 )
249 context.push_op( '+' )
250 context.push_digit( 1 )
251 context.push_op( '=' )
252 r = context.result.to_precision( 3 )
253 assert r == "51.123" else print r
254
255 #test 'C' button
256 context = new CalculatorContext
257 context.push_digit( 1 )
258 context.push_digit( 0 )
259 context.push_op( '+' )
260 context.push_digit( 1 )
261 context.push_digit( 0 )
262 context.push_op( '=' )
263 context.push_op( 'C' )
264 r = context.result.to_precision( 1 )
265 assert r == "0.0" else print r
266
267 # graphical application
268
269 if "NIT_TESTING".environ != "true" then
270 var app = new CalculatorGui
271 run_gtk
272 end