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).

Create a window using the same procedure as before (Figure 1-2).

One Edit control and 16 Btn controls are placed and positioned as shown in Table 1-1.
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.
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).
- func main()
- do \wnd_calc@show()
- while(wnd@act())
- end while
- do \wnd_calc@close()
- end func
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).

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).

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.
- func main()
- do \wnd_calc@show()
- while(wnd@act())
- end while
- do \wnd_calc@close()
- end func
-
- +func btnOnPush(btn: wnd@WndBase)
- do \wnd_calc@edit1.setText(\wnd_calc@edit1.getText() ~ (btn $ wnd@Btn).getText())
- end func
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.

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.
- const excptCalcErr: int :: 0x0001
-
- func main()
- do \wnd_calc@show()
- while(wnd@act())
- end while
- do \wnd_calc@close()
- end func
-
- +func btnOnPush(btn: wnd@WndBase)
- do \wnd_calc@edit1.setText(\wnd_calc@edit1.getText() ~ (btn $ wnd@Btn).getText())
- end func
-
- +func btnEqualOnPush(btn: wnd@WndBase)
- try
- var expression: [][]char :: @parse(\wnd_calc@edit1.getText())
- do \wnd_calc@edit1.setText(@calc(expression))
- catch
- do \wnd_calc@edit1.setText("Error")
- end try
- end func
-
- func parse(expression: []char): [][]char
- var output: list<[]char> :: #list<[]char>
- var tmp: stack<char> :: #stack<char>
- var numBuf: []char :: ""
- for i(0, ^expression - 1)
- var c: char :: expression[i]
- if('0' <= c & c <= '9' | c = '.')
- do numBuf :~ c.toStr()
- else
- if(numBuf <> "")
- do output.add(numBuf)
- do numBuf :: ""
- end if
- while(^tmp > 0 & priority(tmp.peek()) >= priority(c))
- do output.add(tmp.get().toStr())
- end while
- do tmp.add(c)
- end if
- end for
- if(numBuf <> "")
- do output.add(numBuf)
- do numBuf :: ""
- end if
- while(^tmp > 0)
- do output.add(tmp.get().toStr())
- end while
- ret output.toArray()
-
- func priority(c: char): int
- switch(c)
- case '+', '-'
- ret 10
- case '*', '/'
- ret 20
- default
- throw @excptCalcErr
- end switch
- end func
- end func
-
- func calc(expression: [][]char): []char
- ret expression.join(" ")
- end func
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).

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).
- func parse(expression: []char): [][]char
- var output: list<[]char> :: #list<[]char>
- var tmp: stack<char> :: #stack<char>
- var numBuf: []char :: ""
- for i(0, ^expression - 1)
- var c: char :: expression[i]
- if('0' <= c & c <= '9' | c = '.')
- do numBuf :~ c.toStr()
- else
- if(numBuf <> "")
- do output.add(numBuf)
- do numBuf :: ""
- end if
- while(^tmp > 0 & priority(tmp.peek()) >= priority(c))
- do output.add(tmp.get().toStr())
- end while
- do tmp.add(c)
- end if
- end for
- if(numBuf <> "")
- do output.add(numBuf)
- do numBuf :: ""
- end if
- while(^tmp > 0)
- do output.add(tmp.get().toStr())
- end while
- ret output.toArray()
-
- func priority(c: char): int
- switch(c)
- case '+', '-'
- ret 10
- case '*', '/'
- ret 20
- default
- throw @excptCalcErr
- end switch
- end func
- end func
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).

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.
- func calc(expression: [][]char): []char
- var nums: stack<num@BigFloat> :: #stack<num@BigFloat>
- for i(0, ^expression - 1)
- switch(expression[i])
- case "+"
- var a: num@BigFloat :: nums.get()
- var b: num@BigFloat :: nums.get()
- do b.add(a)
- do nums.add(b)
- case "-"
- var a: num@BigFloat :: nums.get()
- var b: num@BigFloat :: nums.get()
- do b.sub(a)
- do nums.add(b)
- case "*"
- var a: num@BigFloat :: nums.get()
- var b: num@BigFloat :: nums.get()
- do b.mul(a)
- do nums.add(b)
- case "/"
- var a: num@BigFloat :: nums.get()
- var b: num@BigFloat :: nums.get()
- do b.div(a)
- do nums.add(b)
- default
- var num: num@BigFloat :: num@makeBigFloatFromStr(expression[i])
- do nums.add(num)
- end switch
- end for
- if(^nums <> 1)
- throw @excptCalcErr
- end if
- ret nums.get().toStr()
- end func
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).

That's it, our calculator application is complete. Next time, I will make a 3D game.