Merge: Calculator refactor & numerics proposition
authorJean Privat <jean@pryen.org>
Thu, 28 Aug 2014 05:15:41 +0000 (01:15 -0400)
committerJean Privat <jean@pryen.org>
Thu, 28 Aug 2014 05:15:41 +0000 (01:15 -0400)
* Modularize calculator.nit into 3 modules and move to a project structure.
* Rewrite the code behind the displayed string.
* Use numerics to switch nicely between `Float` and `Int`
* No more weird bug with the C button.

The Android UI will follow in another PR.

Pull-Request: #646
Reviewed-by: Jean Privat <jean@pryen.org>
Reviewed-by: Lucas Bajolet <r4pass@hotmail.com>
Reviewed-by: Alexandre Terrasa <alexandre@moz-code.org>

examples/calculator.nit [deleted file]
examples/calculator/Makefile [new file with mode: 0644]
examples/calculator/src/calculator_gtk.nit [new file with mode: 0644]
examples/calculator/src/calculator_logic.nit [new file with mode: 0644]
examples/calculator/src/calculator_test.nit [new file with mode: 0644]
lib/standard/numeric.nit [new file with mode: 0644]
lib/standard/standard.nit

diff --git a/examples/calculator.nit b/examples/calculator.nit
deleted file mode 100644 (file)
index 37792bd..0000000
+++ /dev/null
@@ -1,272 +0,0 @@
-# This file is part of NIT ( http://www.nitlanguage.org ).
-#
-# Copyright 2013 Alexis Laferrière <alexis.laf@xymus.net>
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-import gtk
-
-class CalculatorContext
-       var result : nullable Float = null
-
-       var last_op : nullable Char = null
-
-       var current : nullable Float = null
-       var after_point : nullable Int = null
-
-       fun push_op( op : Char )
-       do
-               apply_last_op_if_any
-               if op == 'C' then
-                       self.result = 0.0
-                       last_op = null
-               else
-                       last_op = op # store for next push_op
-               end
-
-               # prepare next current
-               after_point = null
-               current = null
-       end
-
-       fun push_digit( digit : Int )
-       do
-               var current = current
-               if current == null then current = 0.0
-
-               var after_point = after_point
-               if after_point == null then
-                       current = current * 10.0 + digit.to_f
-               else
-                       current = current + digit.to_f * 10.0.pow(after_point.to_f)
-                       self.after_point -= 1
-               end
-
-               self.current = current
-       end
-
-       fun switch_to_decimals
-       do
-               if self.current == null then current = 0.0
-               if after_point != null then return
-
-               after_point = -1
-       end
-
-       fun apply_last_op_if_any
-       do
-               var op = last_op
-
-               var result = result
-               if result == null then result = 0.0
-
-               var current = current
-               if current == null then current = 0.0
-
-               if op == null then
-                       result = current
-               else if op == '+' then
-                       result = result + current
-               else if op == '-' then
-                       result = result - current
-               else if op == '/' then
-                       result = result / current
-               else if op == '*' then
-                       result = result * current
-               end
-               self.result = result
-               self.current = null
-       end
-end
-
-class CalculatorGui
-       super GtkCallable
-
-       var win : GtkWindow is noinit
-       var container : GtkGrid is noinit
-
-       var lbl_disp : GtkLabel is noinit
-       var but_eq : GtkButton is noinit
-       var but_dot : GtkButton is noinit
-
-       var context = new CalculatorContext
-
-       redef fun signal( sender, user_data )
-       do
-               var after_point = context.after_point
-               if after_point == null then 
-                   after_point = 0
-               else
-                   after_point = (after_point.abs)
-               end
-               
-               if user_data isa Char then # is an operation
-                       var c = user_data
-                       if c == '.' then
-                               but_dot.sensitive= false
-                               context.switch_to_decimals
-                               lbl_disp.text = "{context.current.to_i}."
-                       else
-                               but_dot.sensitive= true
-                               context.push_op( c )
-                               
-                               var s = context.result.to_precision_native(6)
-                               var index : nullable Int = null
-                               for i in s.length.times do
-                                   var chiffre = s.chars[i]
-                                   if chiffre == '0' and index == null then
-                                       index = i
-                                   else if chiffre != '0' then
-                                       index = null
-                                   end
-                               end
-                               if index != null then
-                                       s = s.substring(0, index)
-                                       if s.chars[s.length-1] == ',' then s = s.substring(0, s.length-1)
-                               end
-                               lbl_disp.text = s
-                       end
-               else if user_data isa Int then # is a number
-                       var n = user_data
-                       context.push_digit( n )
-                       lbl_disp.text = context.current.to_precision_native(after_point)
-               end
-       end
-
-       init
-       do
-               init_gtk
-
-               win = new GtkWindow( 0 )
-
-               container = new GtkGrid(5,5,true)
-               win.add( container )
-
-               lbl_disp = new GtkLabel( "_" )
-               container.attach( lbl_disp, 0, 0, 5, 1 )
-
-               # digits
-               for n in [0..9] do
-                       var but = new GtkButton.with_label( n.to_s )
-                       but.request_size( 64, 64 )
-                       but.signal_connect( "clicked", self, n )
-                       if n == 0 then
-                               container.attach( but, 0, 4, 1, 1 )
-                       else container.attach( but, (n-1)%3, 3-(n-1)/3, 1, 1 )
-               end
-
-               # operators
-               var r = 1
-               for op in ['+', '-', '*', '/' ] do
-                       var but = new GtkButton.with_label( op.to_s )
-                       but.request_size( 64, 64 )
-                       but.signal_connect( "clicked", self, op )
-                       container.attach( but, 3, r, 1, 1 )
-                       r+=1
-               end
-
-               # =
-               but_eq = new GtkButton.with_label( "=" )
-               but_eq.request_size( 64, 64 )
-               but_eq.signal_connect( "clicked", self, '=' )
-               container.attach( but_eq, 4, 3, 1, 2 )
-
-               # .
-               but_dot = new GtkButton.with_label( "." )
-               but_dot.request_size( 64, 64 )
-               but_dot.signal_connect( "clicked", self, '.' )
-               container.attach( but_dot, 1, 4, 1, 1 )
-
-               #C
-               var but_c =  new GtkButton.with_label( "C" )
-               but_c.request_size( 64, 64 )
-               but_c.signal_connect("clicked", self, 'C')
-               container.attach( but_c, 2, 4, 1, 1 )
-
-               win.show_all
-       end
-end
-
-# context tests
-var context = new CalculatorContext
-context.push_digit( 1 )
-context.push_digit( 2 )
-context.push_op( '+' )
-context.push_digit( 3 )
-context.push_op( '*' )
-context.push_digit( 2 )
-context.push_op( '=' )
-var r = context.result.to_precision( 2 )
-assert r == "30.00" else print r
-
-context = new CalculatorContext
-context.push_digit( 1 )
-context.push_digit( 4 )
-context.switch_to_decimals
-context.push_digit( 1 )
-context.push_op( '*' )
-context.push_digit( 3 )
-context.push_op( '=' )
-r = context.result.to_precision( 2 )
-assert r == "42.30" else print r
-
-context.push_op( '+' )
-context.push_digit( 1 )
-context.push_digit( 1 )
-context.push_op( '=' )
-r = context.result.to_precision( 2 )
-assert r == "53.30" else print r
-
-context = new CalculatorContext
-context.push_digit( 4 )
-context.push_digit( 2 )
-context.switch_to_decimals
-context.push_digit( 3 )
-context.push_op( '/' )
-context.push_digit( 3 )
-context.push_op( '=' )
-r = context.result.to_precision( 2 )
-assert r == "14.10" else print r
-
-#test multiple decimals
-context = new CalculatorContext
-context.push_digit( 5 )
-context.push_digit( 0 )
-context.switch_to_decimals
-context.push_digit( 1 )
-context.push_digit( 2 )
-context.push_digit( 3 )
-context.push_op( '+' )
-context.push_digit( 1 )
-context.push_op( '=' )
-r = context.result.to_precision( 3 )
-assert r == "51.123" else print r
-
-#test 'C' button
-context = new CalculatorContext
-context.push_digit( 1 )
-context.push_digit( 0 )
-context.push_op( '+' )
-context.push_digit( 1 )
-context.push_digit( 0 )
-context.push_op( '=' )
-context.push_op( 'C' )
-r = context.result.to_precision( 1 )
-assert r == "0.0" else print r
-
-# graphical application
-
-if "NIT_TESTING".environ != "true" then
-       var app = new CalculatorGui
-       run_gtk
-end
diff --git a/examples/calculator/Makefile b/examples/calculator/Makefile
new file mode 100644 (file)
index 0000000..9782660
--- /dev/null
@@ -0,0 +1,3 @@
+all:
+       mkdir -p bin/
+       ../../bin/nitg --dir bin/ src/calculator_test.nit src/calculator_gtk.nit
diff --git a/examples/calculator/src/calculator_gtk.nit b/examples/calculator/src/calculator_gtk.nit
new file mode 100644 (file)
index 0000000..bd2bd11
--- /dev/null
@@ -0,0 +1,111 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013-2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# GTK calculator
+module calculator_gtk
+
+import calculator_logic
+
+import gtk
+
+class CalculatorGui
+       super GtkCallable
+
+       var win : GtkWindow is noinit
+       var container : GtkGrid is noinit
+
+       var lbl_disp : GtkLabel is noinit
+       var but_eq : GtkButton is noinit
+       var but_dot : GtkButton is noinit
+
+       var context = new CalculatorContext
+
+       redef fun signal(sender, op)
+       do
+               if op isa Char then # is an operation
+                       if op == '.' then
+                               but_dot.sensitive = false
+                               context.switch_to_decimals
+                       else
+                               but_dot.sensitive = true
+                               context.push_op op
+                       end
+               else if op isa Int then # is a number
+                       context.push_digit op
+               end
+
+               lbl_disp.text = context.display_text
+       end
+
+       init
+       do
+               init_gtk
+
+               win = new GtkWindow( 0 )
+
+               container = new GtkGrid(5,5,true)
+               win.add( container )
+
+               lbl_disp = new GtkLabel( "_" )
+               container.attach( lbl_disp, 0, 0, 5, 1 )
+
+               # digits
+               for n in [0..9] do
+                       var but = new GtkButton.with_label( n.to_s )
+                       but.request_size( 64, 64 )
+                       but.signal_connect( "clicked", self, n )
+                       if n == 0 then
+                               container.attach( but, 0, 4, 1, 1 )
+                       else container.attach( but, (n-1)%3, 3-(n-1)/3, 1, 1 )
+               end
+
+               # operators
+               var r = 1
+               for op in ['+', '-', '*', '/' ] do
+                       var but = new GtkButton.with_label( op.to_s )
+                       but.request_size( 64, 64 )
+                       but.signal_connect( "clicked", self, op )
+                       container.attach( but, 3, r, 1, 1 )
+                       r+=1
+               end
+
+               # =
+               but_eq = new GtkButton.with_label( "=" )
+               but_eq.request_size( 64, 64 )
+               but_eq.signal_connect( "clicked", self, '=' )
+               container.attach( but_eq, 4, 3, 1, 2 )
+
+               # .
+               but_dot = new GtkButton.with_label( "." )
+               but_dot.request_size( 64, 64 )
+               but_dot.signal_connect( "clicked", self, '.' )
+               container.attach( but_dot, 1, 4, 1, 1 )
+
+               #C
+               var but_c =  new GtkButton.with_label( "C" )
+               but_c.request_size( 64, 64 )
+               but_c.signal_connect("clicked", self, 'C')
+               container.attach( but_c, 2, 4, 1, 1 )
+
+               win.show_all
+       end
+end
+
+# graphical application
+if "NIT_TESTING".environ == "true" then exit 0
+
+var app = new CalculatorGui
+run_gtk
diff --git a/examples/calculator/src/calculator_logic.nit b/examples/calculator/src/calculator_logic.nit
new file mode 100644 (file)
index 0000000..c5fbaf7
--- /dev/null
@@ -0,0 +1,116 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013-2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Business logic of a calculator
+module calculator_logic
+
+class CalculatorContext
+       var result: nullable Numeric = null
+
+       var last_op: nullable Char = null
+
+       var current: nullable FlatBuffer = null
+       fun display_text: String
+       do
+               var result = result
+               var last_op = last_op
+               var current = current
+
+               var buf = new FlatBuffer
+
+               if result != null and (current == null or last_op != '=') then
+                       if last_op == '=' then buf.append "= "
+
+                       buf.append result.to_s
+                       buf.add ' '
+               end
+
+               if last_op != null and last_op != '=' then
+                       buf.add last_op
+                       buf.add ' '
+               end
+
+               if current != null then
+                       buf.append current.to_s
+                       buf.add ' '
+               end
+
+               return buf.to_s
+       end
+
+       fun push_op( op : Char )
+       do
+               apply_last_op_if_any
+               if op == 'C' then
+                       self.result = null
+                       last_op = null
+               else
+                       last_op = op # store for next push_op
+               end
+
+               # prepare next current
+               self.current = null
+       end
+
+       fun push_digit( digit : Int )
+       do
+               var current = current
+               if current == null then current = new FlatBuffer
+               current.add digit.to_s.chars.first
+               self.current = current
+
+               if last_op == '=' then
+                       self.result = null
+                       last_op = null
+               end
+       end
+
+       fun switch_to_decimals
+       do
+               var current = current
+               if current == null then current = new FlatBuffer.from("0")
+               if not current.chars.has('.') then current.add '.'
+               self.current = current
+       end
+
+       fun apply_last_op_if_any
+       do
+               var op = last_op
+
+               var result = result
+
+               var current = current
+               if current == null then current = new FlatBuffer
+
+               if op == null then
+                       result = current.to_n
+               else if op == '+' then
+                       result = result.add(current.to_n)
+               else if op == '-' then
+                       result = result.sub(current.to_n)
+               else if op == '/' then
+                       result = result.div(current.to_n)
+               else if op == '*' then
+                       result = result.mul(current.to_n)
+               end
+               self.result = result
+               self.current = null
+       end
+end
+
+redef universal Float
+       redef fun to_s do return to_precision(6)
+end
diff --git a/examples/calculator/src/calculator_test.nit b/examples/calculator/src/calculator_test.nit
new file mode 100644 (file)
index 0000000..6937751
--- /dev/null
@@ -0,0 +1,87 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013-2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Test the business logic
+module calculator_test
+
+import calculator_logic
+
+# context tests
+var context = new CalculatorContext
+context.push_digit( 1 )
+context.push_digit( 2 )
+context.push_op( '+' )
+context.push_digit( 3 )
+context.push_op( '*' )
+context.push_digit( 2 )
+context.push_op( '=' )
+var r = context.result
+assert r == "30.00" else print r or else "-"
+
+context = new CalculatorContext
+context.push_digit( 1 )
+context.push_digit( 4 )
+context.switch_to_decimals
+context.push_digit( 1 )
+context.push_op( '*' )
+context.push_digit( 3 )
+context.push_op( '=' )
+r = context.result
+assert r == "42.30" else print r or else "-"
+
+context.push_op( '+' )
+context.push_digit( 1 )
+context.push_digit( 1 )
+context.push_op( '=' )
+r = context.result
+assert r == "53.30" else print r or else "-"
+
+context = new CalculatorContext
+context.push_digit( 4 )
+context.push_digit( 2 )
+context.switch_to_decimals
+context.push_digit( 3 )
+context.push_op( '/' )
+context.push_digit( 3 )
+context.push_op( '=' )
+r = context.result
+assert r == "14.10" else print r or else "-"
+
+#test multiple decimals
+context = new CalculatorContext
+context.push_digit( 5 )
+context.push_digit( 0 )
+context.switch_to_decimals
+context.push_digit( 1 )
+context.push_digit( 2 )
+context.push_digit( 3 )
+context.push_op( '+' )
+context.push_digit( 1 )
+context.push_op( '=' )
+r = context.result
+assert r == "51.123" else print r or else "-"
+
+#test 'C' button
+context = new CalculatorContext
+context.push_digit( 1 )
+context.push_digit( 0 )
+context.push_op( '+' )
+context.push_digit( 1 )
+context.push_digit( 0 )
+context.push_op( '=' )
+context.push_op( 'C' )
+r = context.result
+assert r == "0.0" else print r or else "-"
diff --git a/lib/standard/numeric.nit b/lib/standard/numeric.nit
new file mode 100644 (file)
index 0000000..3452155
--- /dev/null
@@ -0,0 +1,127 @@
+# This file is part of NIT ( http://www.nitlanguage.org ).
+#
+# Copyright 2013-2014 Alexis Laferrière <alexis.laf@xymus.net>
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Advanced services for `Numeric` types
+module numeric
+
+import math
+import string
+
+redef class Text
+       # Get the numeric version of `self`
+       #
+       # require: `is_numeric`
+       #
+       # ~~~~
+       # assert "0".to_n == 0
+       # assert "0.0".to_n == 0.0
+       # assert ".12345".to_n == 0.12345
+       # assert "12345".to_n == 12345
+       # ~~~~
+       fun to_n: Numeric
+       do
+               if chars.has('.') then return to_f
+               return to_i
+       end
+end
+
+redef interface Numeric
+       # Universal `+` with any `Numeric`
+       #
+       # ~~~~
+       # assert 1.add(1) == 2
+       # assert 1.add(0.1) == 1.1
+       # assert 1.1.add(1.1) == 2.2
+       # assert 1.1.add(1) == 2.1
+       # ~~~~
+       fun add(other: Numeric): Numeric is abstract
+
+       # Universal `-` with any `Numeric`
+       #
+       # ~~~~
+       # assert 2.sub(1) == 1
+       # assert 1.sub(0.1) == 0.9
+       # assert 1.1.sub(0.1) == 1.0
+       # assert 2.1.sub(1) == 1.1
+       # ~~~~
+       fun sub(other: Numeric): Numeric is abstract
+
+       # Universal `/` with any `Numeric`
+       #
+       # ~~~~
+       # assert 8.div(2) == 4
+       # assert 4.div(0.5) == 8.0
+       # assert 1.1.div(0.1) == 11.0
+       # assert 2.2.div(2) == 1.1
+       # ~~~~
+       fun div(other: Numeric): Numeric is abstract
+
+       # Universal `*` with any `Numeric`
+       #
+       # ~~~~
+       # assert 2.mul(4) == 8
+       # assert 11.mul(0.1) == 1.1
+       # assert 11.1.mul(0.1) == 1.11
+       # assert 1.1.mul(4) == 4.4
+       # ~~~~
+       fun mul(other: Numeric): Numeric is abstract
+end
+
+redef universal Int
+       redef fun add(other)
+       do
+               if other isa Float then
+                       return to_f + other
+               else
+                       return self + other.as(Int)
+               end
+       end
+
+       redef fun sub(other)
+       do
+               if other isa Float then
+                       return to_f - other
+               else
+                       return self - other.as(Int)
+               end
+       end
+
+       redef fun mul(other)
+       do
+               if other isa Float then
+                       return to_f * other
+               else
+                       return self * other.as(Int)
+               end
+       end
+
+       redef fun div(other)
+       do
+               if other isa Float then
+                       return to_f / other
+               else if other isa Int then
+                       if other == 0 then return self.to_f / 0.0
+                       return self / other
+               else abort
+       end
+end
+
+redef universal Float
+       redef fun add(other) do return self + other.to_f
+       redef fun sub(other) do return self - other.to_f
+       redef fun div(other) do return self / other.to_f
+       redef fun mul(other) do return self * other.to_f
+end
index e1ba641..5becb03 100644 (file)
@@ -29,3 +29,4 @@ import kernel
 import gc
 import bitset
 import queue
+import numeric