Jun 10, 2023Kuina-chan


This is the tutorial 6 to learn the basic features of Kuin Programming Language for exe execution environment. This time, let's create a calculator application.

1Window Placement

1.1Window Placement



This time, as before, I will use the visual placement feature to create a window.
Click Add New File in the Edit menu, select Window, and add the file with the name "wnd_calc" (Figure 1-1).
Adding Window
Figure 1-1: Adding Window
Create a window using the same procedure as before (Figure 1-2).
Window Placement
Figure 1-2: Window Placement
One Edit control and 16 Btn controls are placed and positioned as shown in Table 1-1.
Table 1-1: Position Of Controls
Control Name Position
wndMain 0 × 0 - 195 × 220
edit1 10 × 10 - 175 × 19
btn1 10 × 35 - 40 × 40
btn2 55 × 35 - 40 × 40
btn3 100 × 35 - 40 × 40
btn4 145 × 35 - 40 × 40
btn5 10 × 80 - 40 × 40
btn6 55 × 80 - 40 × 40
btn7 100 × 80 - 40 × 40
btn8 145 × 80 - 40 × 40
btn9 10 × 125 - 40 × 40
btn10 55 × 125 - 40 × 40
btn11 100 × 125 - 40 × 40
btn12 145 × 125 - 40 × 40
btn13 10 × 170 - 40 × 40
btn14 55 × 170 - 40 × 40
btn15 100 × 170 - 40 × 40
btn16 145 × 170 - 40 × 40
There are many controls, but they are regular. You can place them quickly by placing only one row of Btn and then copy and paste it four times to arrange them in four lines.
Once you have placed them, select Btn one by one, press the "Edit Properties..." button, and set the text item to the text shown in Table 1-2.
Table 1-2: Button Text
Control Name Value Of text
btn1 7
btn2 8
btn3 9
btn4 /
btn5 4
btn6 5
btn7 6
btn8 *
btn9 1
btn10 2
btn11 3
btn12 -
btn13 0
btn14 .
btn15 =
btn16 +
That's it, the look is ready. Press "Show Code" at the top of the Kuin editor to see the automatically generated program. The functions show and close are generated. So, select \main from the list of files on the right of the Kuin editor and write a program to display the window you just created (Figure 1-3).
  1. func main()
  2.   do \wnd_calc@show()
  3.   while(wnd@act())
  4.   end while
  5.   do \wnd_calc@close()
  6. end func
Figure 1-3: calc1.kn
In lines 2 and 5, the automatically generated "\wnd_calc@show()" and "\wnd_calc@close()" are called. When you run the program, a window with the appearance of a calculator will appear (Figure 1-4).
Execution Result (1)
Figure 1-4: Execution Result (1)

1.2Button Press Event



For now, nothing will happen if you press the button while the program is running, so let's make sure that when the button is pressed, text will be entered into the Edit control.
Select \wnd_calc from the list of files on the right of the Kuin editor and press "Show Designer" to return to the placement screen. Select all but btn15 and edit1 from the list of controls on the left of the Kuin editor (Figure 1-5).
Selecting Controls
Figure 1-5: Selecting Controls
Holding down the Shift key while clicking allows you to select them all at once, and holding down the Ctrl key while clicking allows you to toggle the selection state one by one. You can use these to select quickly.
While in that state, press "Edit Properties..." and write "\main@btnOnPush" in the onPush field. In the onPush field, you can set the function to be called when the buttons are pressed, and in this case, the btnOnPush function in the \main file will be called.
Once that is set, select \main from the file list and add the program shown in Figure 1-6.
  1. func main()
  2.   do \wnd_calc@show()
  3.   while(wnd@act())
  4.   end while
  5.   do \wnd_calc@close()
  6. end func
  7.  
  8. +func btnOnPush(btn: wnd@WndBase)
  9.   do \wnd_calc@edit1.setText(\wnd_calc@edit1.getText() ~ (btn $ wnd@Btn).getText())
  10. end func
Figure 1-6: calc2.kn
When the buttons are pressed, the btnOnPush function in lines 8 to 10 will be called. We need to write a "+" at the beginning of the btnOnPush function line so that it can be called from other source files.
The argument btn of the btnOnPush function contains the instance of the button that was pressed, and the text set in the btn is added to the text of edit1 in line 9.
When you run the program, you can enter a mathematical expression by pressing the button, as shown in Figure 1-7.
Execution Result (2)
Figure 1-7: Execution Result (2)

2Calculations In Calculator

2.1Reverse Polish Notation



From here I will start to implement the calculation process when the "=" button is pressed.
Select \wnd_calc from the file list, select btn15, press "Edit Properties...", and write "\main@btnEqualOnPush" in the onPush field.
Select \main from the file list and add Figure 2-1, which is a bit long.
  1. const excptCalcErr: int :: 0x0001
  2.  
  3. func main()
  4.   do \wnd_calc@show()
  5.   while(wnd@act())
  6.   end while
  7.   do \wnd_calc@close()
  8. end func
  9.  
  10. +func btnOnPush(btn: wnd@WndBase)
  11.   do \wnd_calc@edit1.setText(\wnd_calc@edit1.getText() ~ (btn $ wnd@Btn).getText())
  12. end func
  13.  
  14. +func btnEqualOnPush(btn: wnd@WndBase)
  15.   try
  16.     var expression: [][]char :: @parse(\wnd_calc@edit1.getText())
  17.     do \wnd_calc@edit1.setText(@calc(expression))
  18.   catch
  19.     do \wnd_calc@edit1.setText("Error")
  20.   end try
  21. end func
  22.  
  23. func parse(expression: []char): [][]char
  24.   var output: list<[]char> :: #list<[]char>
  25.   var tmp: stack<char> :: #stack<char>
  26.   var numBuf: []char :: ""
  27.   for i(0, ^expression - 1)
  28.     var c: char :: expression[i]
  29.     if('0' <= c & c <= '9' | c = '.')
  30.       do numBuf :~ c.toStr()
  31.     else
  32.       if(numBuf <> "")
  33.         do output.add(numBuf)
  34.         do numBuf :: ""
  35.       end if
  36.       while(^tmp > 0 & priority(tmp.peek()) >= priority(c))
  37.         do output.add(tmp.get().toStr())
  38.       end while
  39.       do tmp.add(c)
  40.     end if
  41.   end for
  42.   if(numBuf <> "")
  43.     do output.add(numBuf)
  44.     do numBuf :: ""
  45.   end if
  46.   while(^tmp > 0)
  47.     do output.add(tmp.get().toStr())
  48.   end while
  49.   ret output.toArray()
  50.  
  51.   func priority(c: char): int
  52.     switch(c)
  53.     case '+', '-'
  54.       ret 10
  55.     case '*', '/'
  56.       ret 20
  57.     default
  58.       throw @excptCalcErr
  59.     end switch
  60.   end func
  61. end func
  62.  
  63. func calc(expression: [][]char): []char
  64.   ret expression.join(" ")
  65. end func
Figure 2-1: calc3.kn
The btnEqualOnPush function from line 14 to 21 is the process when the "=" button is pressed.
The try, catch, and end try in lines 15 to 20 are the syntax for handling exceptions (errors). In the try statement, the steps from try to catch are processed in order, and if an exception occurs in the middle, the steps from catch to end try are executed. If no exception is raised, the process from catch to end try will be skipped.
In lines 16 and 17 within try, the expressions are processed by @parse and @calc and displayed in the Edit control. I will use the @parse function to parse the expression and the @calc function to do the actual calculation. If an exception occurs during the process, "Error" will be displayed at line 19.
Lines 23 to 61 are the contents of @parse, which parses the expression, as we will see in detail later. In line 63-65 @calc, I will implement the calculation of the parsed expression, but for now it is simply a space-separated string concatenated by the join method.
By the way, in the 1st line, the code 0x0001 of the exception to be raised is defined as an alias with the name excptCalcErr. You can write 0x0001 directly, but since it makes the program harder to read, it is convenient to define an alias with a const statement like this.

2.2Analyzing Expression



I will now explain the contents of the @parse function.
For example, in the case of the expression "2-3*4*5", you have to calculate "3*4" first because there is a rule that multiplication should be calculated first before addition. This is complicated, so the program converts expressions into a notation called Reverse Polish Notation, which takes into account the order of the calculations.
If we write "2-3*4*5" in reverse Polish notation, we get "2 3 4 * 5 * -" (Figure 2-2).
Reverse Polish Notation
Figure 2-2: Reverse Polish Notation
In other words, if you create a tree structure that represents the order of computation, and then follow the tree structure to pick up nodes as it folds back, you get Reverse Polish Notation. Reverse Polish Notation is useful because you can get the correct result by reading from the beginning and calculating each time an operator such as "+" or "*" appears.
In this case, we are using a method called the shunting-yard algorithm as a way to convert from mathematical expressions to Reverse Polish Notation. The shunting-yard algorithm is a method of accumulating operators on a stack and removing them from the stack when operators with lower priority than them appear.
Now I will post the contents of the @parse function again (Figure 2-3).
  1. func parse(expression: []char): [][]char
  2.   var output: list<[]char> :: #list<[]char>
  3.   var tmp: stack<char> :: #stack<char>
  4.   var numBuf: []char :: ""
  5.   for i(0, ^expression - 1)
  6.     var c: char :: expression[i]
  7.     if('0' <= c & c <= '9' | c = '.')
  8.       do numBuf :~ c.toStr()
  9.     else
  10.       if(numBuf <> "")
  11.         do output.add(numBuf)
  12.         do numBuf :: ""
  13.       end if
  14.       while(^tmp > 0 & priority(tmp.peek()) >= priority(c))
  15.         do output.add(tmp.get().toStr())
  16.       end while
  17.       do tmp.add(c)
  18.     end if
  19.   end for
  20.   if(numBuf <> "")
  21.     do output.add(numBuf)
  22.     do numBuf :: ""
  23.   end if
  24.   while(^tmp > 0)
  25.     do output.add(tmp.get().toStr())
  26.   end while
  27.   ret output.toArray()
  28.  
  29.   func priority(c: char): int
  30.     switch(c)
  31.     case '+', '-'
  32.       ret 10
  33.     case '*', '/'
  34.       ret 20
  35.     default
  36.       throw @excptCalcErr
  37.     end switch
  38.   end func
  39. end func
Figure 2-3: calc4.kn
In the 2nd and 3rd lines, a list output for output and a stack tmp to temporarily store the operators are created. The numBuf in the 4th line is a buffer to store numerical values.
In lines 5 through 19, the expression is parsed one character at a time. In the case of numbers, for example, to prevent the number "12" from being split into "1" and "2", it is stored once in numBuf and then output on lines 11 and 21. In the case of operators, the priority is calculated by the priority function on line 29, and when an operator with a lower priority than that in the stack appears, it is removed from the stack and output on lines 15 and 25. When the processing is finished, line 27 converts the output list to an array and returns it.
If you run it and type "2-3*4*5" and press the "=" button, the program will output "2 3 4 * 5 * -" which is converted to Reverse Polish Notation (Figure 2-4).
Execution Result (3)
Figure 2-4: Execution Result (3)

2.3Calculating Expression



Finally, let's let the program calculate the resulting expression in Reverse Polish Notation. Rewrite the contents of the @calc function as shown in Figure 2-5.
  1. func calc(expression: [][]char): []char
  2.   var nums: stack<num@BigFloat> :: #stack<num@BigFloat>
  3.   for i(0, ^expression - 1)
  4.     switch(expression[i])
  5.     case "+"
  6.       var a: num@BigFloat :: nums.get()
  7.       var b: num@BigFloat :: nums.get()
  8.       do b.add(a)
  9.       do nums.add(b)
  10.     case "-"
  11.       var a: num@BigFloat :: nums.get()
  12.       var b: num@BigFloat :: nums.get()
  13.       do b.sub(a)
  14.       do nums.add(b)
  15.     case "*"
  16.       var a: num@BigFloat :: nums.get()
  17.       var b: num@BigFloat :: nums.get()
  18.       do b.mul(a)
  19.       do nums.add(b)
  20.     case "/"
  21.       var a: num@BigFloat :: nums.get()
  22.       var b: num@BigFloat :: nums.get()
  23.       do b.div(a)
  24.       do nums.add(b)
  25.     default
  26.       var num: num@BigFloat :: num@makeBigFloatFromStr(expression[i])
  27.       do nums.add(num)
  28.     end switch
  29.   end for
  30.   if(^nums <> 1)
  31.     throw @excptCalcErr
  32.   end if
  33.   ret nums.get().toStr()
  34. end func
Figure 2-5: calc5.kn
You can also use the float type for calculations, but since a calculator with high calculation accuracy is preferable, let's use the num@BigFloat type here, which can handle 100 decimal digits.
In line 2, the program creates a stack nums that contains the values of the num@BigFloat type, and in lines 3 through 30, it interprets the expression in Reverse Polish Notation in turn.
When a number comes in, the program will add it to the stack of nums as shown in lines 25 to 27. If "+", "-", "*", or "/" comes in, it will take two values from nums, perform each operation, and return the result to nums, as shown in lines 5 to 24. After repeating this, the result of the calculation will be left in the nums at the end.
When the process is finished, line 33 returns the results of the remaining calculations in nums. If you run it and type "2-3*4*5" and press "=", you will get the correct result of "-58" (Figure 2-6).
Execution Result (4)
Figure 2-6: Execution Result (4)
That's it, our calculator application is complete. Next time, I will make a 3D game.
1686383164enf