# Make calculator support statement block

Time：2021-4-21

Now let’s add the function of statement block to the calculator program, so that the program can do batch operations,
Similar to code blocks in programming languages.

This time the code is the same as the previous oneRecursive downward algorithm for Calc
If you are not familiar with the current content, you can review the previous chapter.

#### Code list [go language as an example]

``````package main

import (
"fmt"
"strconv"
"io/ioutil"
"./bklexer"
)

type Node interface {
GetValue() float64
}

type Block struct {
statements []Node
}

func NewBlock() *Block {
return &Block{}
}

func (block *Block) AddStatement(statement Node) {
block.statements = append(block.statements, statement)
}

func (block *Block) Eval() {
for i, statement := range block.statements {
fmt.Printf("out[%d] = %f\n", i, statement.GetValue())
}
}

type Number struct {
value float64
}

func NewNumber(token *BKLexer.Token) *Number {
value, _ := strconv.ParseFloat(token.Source, 64)
return &Number{value: value}
}

func (number *Number) GetValue() float64 {
return number.value
}

type BinaryOpt struct {
opt string
lhs Node
rhs Node
}

func NewBinaryOpt(token *BKLexer.Token, lhs Node, rhs Node) *BinaryOpt {
return &BinaryOpt{opt: token.Source, lhs: lhs, rhs: rhs}
}

func (binaryOpt *BinaryOpt) GetValue() float64 {
lhs, rhs := binaryOpt.lhs, binaryOpt.rhs
switch binaryOpt.opt {
case "+": return lhs.GetValue() + rhs.GetValue()
case "-": return lhs.GetValue() - rhs.GetValue()
case "*": return lhs.GetValue() * rhs.GetValue()
case "/": return lhs.GetValue() / rhs.GetValue()
}
return 0
}

func parse(lexer *BKLexer.Lexer) *Block {
block := NewBlock()
token := lexer.NextToken()
for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
token = lexer.NextToken()
}
for token.TType != BKLexer.TOKEN_TYPE_EOF {
if statement == nil {
return nil;
}
token = lexer.GetToken()
if token.TType != BKLexer.TOKEN_TYPE_NEWLINE &&
token.TType != BKLexer.TOKEN_TYPE_EOF {
return nil;
}
for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
token = lexer.NextToken()
}
}
return block
}

lhs := parse_binary_mul(lexer)
if lhs == nil {
return nil
}
token := lexer.GetToken()
for token.Source == "+" || token.Source == "-" {
lexer.NextToken()
rhs := parse_binary_mul(lexer)
if rhs == nil {
return nil
}
lhs = NewBinaryOpt(token, lhs, rhs)
token = lexer.GetToken()
}
return lhs
}

func parse_binary_mul(lexer *BKLexer.Lexer) Node {
lhs := parse_number(lexer)
if lhs == nil {
return nil
}
token := lexer.GetToken()
for token.Source == "*" || token.Source == "/" {
lexer.NextToken()
rhs := parse_number(lexer)
if rhs == nil {
return nil
}
lhs = NewBinaryOpt(token, lhs, rhs)
token = lexer.GetToken()
}
return lhs
}

func parse_number(lexer *BKLexer.Lexer) Node {
token := lexer.GetToken()
if token.Name == "LPAR" {
lexer.NextToken()
if expr == nil {
return nil
}
token := lexer.GetToken()
if token.Name != "RPAR" {
return nil
}
lexer.NextToken()
return expr
}
if token.Name == "NUMBER" {
number := NewNumber(token)
lexer.NextToken()
return number
}
return nil
}

func main() {
fmt.Println("Hello My Calc.")

lexer := BKLexer.NewLexer()

if err != nil {
return
}
code := string(bytes)
fmt.Println(code)
lexer.Build(code)
result := parse(lexer)
if result == nil {
fmt.Println("null result")
return
}
result.Eval()
}``````

#### Bring in the packages you need to use

``````import (
"fmt"
"strconv"
"io/ioutil"
"./bklexer"
)``````
• `fmt`Printout
• `strconv`String conversion
• `io/ioutil`read file
• `./bklexer`For lexical analysis

#### Define statement block structure

``````type Block struct {
statements []Node
}

func NewBlock() *Block {
return &Block{}
}``````

`Block`Members of the structure`statements`For storing each statement, we use`NewBlock`Method instance this structure.

#### Add a member method to the statement block structure

``````func (block *Block) AddStatement(statement Node) {
block.statements = append(block.statements, statement)
}

func (block *Block) Eval() {
for i, statement := range block.statements {
fmt.Printf("out[%d] = %f\n", i, statement.GetValue())
}
}``````

`AddStatement`Used to insert a new statement,`Eval`Used for batch calculation results and printing.

#### Define the syntax interpreter entry function

``````func parse(lexer *BKLexer.Lexer) *Block {
block := NewBlock()
token := lexer.NextToken()
for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
token = lexer.NextToken()
}
for token.TType != BKLexer.TOKEN_TYPE_EOF {
if statement == nil {
return nil;
}
token = lexer.GetToken()
if token.TType != BKLexer.TOKEN_TYPE_NEWLINE &&
token.TType != BKLexer.TOKEN_TYPE_EOF {
return nil;
}
for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
token = lexer.NextToken()
}
}
return block
}``````

`parse`In this paper, we first give an example`block`Structure object, and then start parsing`Token`
Use the method of loop analysis to get each one in order`statement`And insert`block`Finally, block is returned.

It should be noted that the end of each statement is either a newline or the end, otherwise it will be regarded as a parsing error and returned`nil`:

``````        token = lexer.GetToken()
if token.TType != BKLexer.TOKEN_TYPE_NEWLINE &&
token.TType != BKLexer.TOKEN_TYPE_EOF {
return nil;
}``````

After parsing a statement, we should immediately pass the following newline character, which can make us ignore blank lines in batch calculation

``````        for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
token = lexer.NextToken()
}``````

#### There are several changes to be noted

``````func parse_number(lexer *BKLexer.Lexer) Node {
token := lexer.GetToken()

......``````

stay`parse_number`Function,
`token := lexer.NextToken()`Become`token := lexer.GetToken()`
Therefore, we need to make sure that all parsing functions need to be initiatively invoked in the current function when they call each other.`NextToken()`method.

For example:

``````......
func parse(lexer *BKLexer.Lexer) *Block {
......
token := lexer.NextToken()
for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
token = lexer.NextToken()
}
for token.TType != BKLexer.TOKEN_TYPE_EOF {
......
......
for token.Source == "+" || token.Source == "-" {
lexer.NextToken()
rhs := parse_binary_mul(lexer)
......``````

#### Define lexical parser rules

``````lexer := BKLexer.NewLexer()

It should be noted that there are new rules for setting invalid characters`lexer.AddIgnores("#[^\\r\\n]*")`
This enables the program to support Python’s annotation syntax.

#### Read the file for analysis and calculation

``````bytes, err := ioutil.ReadFile("../test.txt")
if err != nil {
return
}
code := string(bytes)
fmt.Println(code)
lexer.Build(code)
result := parse(lexer)
if result == nil {
fmt.Println("null result")
return
}
result.Eval()``````

#### Use a test script to test

Test content:

``````1 + 2 # plus
3 - 4

# here is a comment

5 * 6 # mul
7 / 8

1 + (2 - 3) * 4 / 5 # composite``````

Results of operation:

``````➜ go run calc.go
Hello My Calc.
1 + 2 # plus
3 - 4

# here is a comment

5 * 6 # mul
7 / 8

1 + (2 - 3) * 4 / 5 # composite

out[0] = 3.000000
out[1] = -1.000000
out[2] = 30.000000
out[3] = 0.875000
out[4] = 0.200000``````

Part twoMaking programming languages support variablesWelcome to pay attention.