* 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>
+++ /dev/null
-# 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
--- /dev/null
+all:
+ mkdir -p bin/
+ ../../bin/nitg --dir bin/ src/calculator_test.nit src/calculator_gtk.nit
--- /dev/null
+# 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
--- /dev/null
+# 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
--- /dev/null
+# 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 "-"
--- /dev/null
+# 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
import gc
import bitset
import queue
+import numeric