published on in 编译

支持If语句

返回教程主页

上篇 使程序语言支持变量

下面我们来让程序语言支持If语句,这会使得bkcalclang能够处理更加复杂的问题。

这次的代码以上一篇《使程序语言支持变量》的代码为基础编写,如果发现不熟悉当下的内容可以回顾一下之前的篇章。

直接放出代码清单有点劝退,先进行内容讲解。

定义If节点的结构体

type If struct {
    condition Node
    then *Block
}

func NewIf(condition Node, then *Block) *If {
    return &If{condition: condition, then: then}
}

If结构当中condition存储判断表达式节点,then存储代码块,使用NewIf函数实例化。

定义If节点的运行方法

func (_if *If) Eval() float64 {
    condition := _if.condition.Eval()
    if condition != 0 {
        _if.then.Eval();
    }
    return 0.
}

如果Ifcondition运行后的值不为0则运行代码块then,否则不运行。

处理If语句的解析

    } else if token.Name == "IF" {
        lexer.NextToken()
        condition := parse_binary_add(lexer)
        if (condition == nil) {
            return nil
        }
        token = lexer.GetToken()
        if token.Name != "THEN" {
            return nil
        }
        then := parse(lexer)
        if then == nil {
            return nil
        }
        isBlockEnd = false
        return NewIf(condition, then)
    } else if token.Name == "END" {
        lexer.NextToken()
        isBlockEnd = true
        return nil
    }

我们在parse_statement函数中编写处理If语句的内容。

如果检测到token.NameIF则尝试进行If语法解析,先跳过当前token然后进行表达式解析,若解析成功则将其作为If结构的condition成员:

    } else if token.Name == "IF" {
        lexer.NextToken()
        condition := parse_binary_add(lexer)
        if (condition == nil) {
            return nil
        }

接下来取出下一个token判断如果为THEN就进行代码块解析,否则返回nil,在返回NewIf前,我们需要设定isBlockEndfalse,它是一个用于parse函数判断是否需要提前返回的全局变量:

        token = lexer.GetToken()
        if token.Name != "THEN" {
            return nil
        }
        then := parse(lexer)
        if then == nil {
            return nil
        }
        isBlockEnd = false
        return NewIf(condition, then)

我们需要在parse函数中利用isBlockEnd判断当前代码块是否结束:

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 {
        statement := parse_statement(lexer)
        if isBlockEnd {
            return block
        }

当遇到token.NameEND,我们需要设置isBlockEndtrue用以终结当前代码块。

    } else if token.Name == "END" {
        lexer.NextToken()
        isBlockEnd = true
        return nil
    }

定义词法解析器规则

lexer.AddRule("\\d+\\.?\\d*", "NUMBER")
lexer.AddRule("[\\p{L}\\d_]+", "NAME")
lexer.AddRule("\\+", "PLUS")
lexer.AddRule("-", "MINUS")
lexer.AddRule("\\*", "MUL")
lexer.AddRule("/", "DIV")
lexer.AddRule("\\(", "LPAR")
lexer.AddRule("\\)", "RPAR")
lexer.AddRule("=", "ASSIGN")
lexer.AddIgnores("[ \\f\\t]+")
lexer.AddIgnores("#[^\\r\\n]*")
lexer.AddReserve("set")
lexer.AddReserve("echo")
lexer.AddReserve("if")
lexer.AddReserve("then")
lexer.AddReserve("end")

这里我们需要添加ifthenend保留字。

使用一段测试脚本进行测试

测试内容:

set a = 2
if a then
    echo a
    set a = a - 1
    if a then
        echo a
        set a = a - 1
        if a then
            echo a
        end
    end
end

echo a

运行结果:

➜ go calc.go 
:= 2
:= 1
:= 0

代码清单

package main

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

var valueDict map[string]float64

var isBlockEnd bool = false

type Node interface {
    Eval() 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 _, statement := range block.statements {
        statement.Eval()
    }
}

type Number struct {
    value float64
}

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

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

type Name struct {
    name string
}

func NewName(token *BKLexer.Token) *Name {
    return &Name{name: token.Source}
}

func (name *Name) Eval() float64 {
    if value, found := valueDict[name.name]; found {
        return value;
    }
    return 0.
}

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) Eval() float64 {
    lhs, rhs := binaryOpt.lhs, binaryOpt.rhs
    switch binaryOpt.opt {
        case "+": return lhs.Eval() + rhs.Eval()
        case "-": return lhs.Eval() - rhs.Eval()
        case "*": return lhs.Eval() * rhs.Eval()
        case "/": return lhs.Eval() / rhs.Eval()
    }
    return 0
}

type Assign struct {
    name string
    value Node
}

func NewAssign(token *BKLexer.Token, value Node) *Assign {
    return &Assign{name: token.Source, value: value}
}

func (assign *Assign) Eval() float64 {
    value := assign.value.Eval()
    valueDict[assign.name] = value
    return value
}

type Echo struct {
    value Node
}

func NewEcho(value Node) *Echo {
    return &Echo{value: value}
}

func (echo *Echo) Eval() float64 {
    value := echo.value.Eval()
    fmt.Println(":=", value)
    return value
}

type If struct {
    condition Node
    then *Block
}

func NewIf(condition Node, then *Block) *If {
    return &If{condition: condition, then: then}
}

func (_if *If) Eval() float64 {
    condition := _if.condition.Eval()
    if condition != 0 {
        _if.then.Eval();
    }
    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 {
        statement := parse_statement(lexer)
        if isBlockEnd {
            return block
        }
        if statement == nil {
            return nil;
        }
        token = lexer.GetToken()
        if token.TType != BKLexer.TOKEN_TYPE_NEWLINE &&
           token.TType != BKLexer.TOKEN_TYPE_EOF {
            return nil;
        }
        block.AddStatement(statement)
        for token.TType == BKLexer.TOKEN_TYPE_NEWLINE {
            token = lexer.NextToken()
        }
    }
    return block
}

func parse_statement(lexer *BKLexer.Lexer) Node {
    token := lexer.GetToken()
    if token.Name == "SET" {
        name := lexer.NextToken()
        if name.Name != "NAME" {
            return nil
        }
        token = lexer.NextToken()
        if token.Name != "ASSIGN" {
            return nil
        }
        lexer.NextToken()
        value := parse_binary_add(lexer)
        if value == nil {
            return nil
        }
        return NewAssign(name, value)
    } else if token.Name == "ECHO" {
        lexer.NextToken()
        value := parse_binary_add(lexer)
        if (value == nil) {
            return nil
        }
        return NewEcho(value)
    } else if token.Name == "IF" {
        lexer.NextToken()
        condition := parse_binary_add(lexer)
        if (condition == nil) {
            return nil
        }
        token = lexer.GetToken()
        if token.Name != "THEN" {
            return nil
        }
        then := parse(lexer)
        if then == nil {
            return nil
        }
        isBlockEnd = false
        return NewIf(condition, then)
    } else if token.Name == "END" {
        lexer.NextToken()
        isBlockEnd = true
        return nil
    }
    return parse_binary_add(lexer)
}

func parse_binary_add(lexer *BKLexer.Lexer) Node {
    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 := factor(lexer)
    if lhs == nil {
        return nil
    }
    token := lexer.GetToken()
    for token.Source == "*" || token.Source == "/" {
        lexer.NextToken()
        rhs := factor(lexer)
        if rhs == nil {
            return nil
        }
        lhs = NewBinaryOpt(token, lhs, rhs)
        token = lexer.GetToken()
    }
    return lhs
}

func factor(lexer *BKLexer.Lexer) Node {
    token := lexer.GetToken()
    if token.Name == "LPAR" {
        lexer.NextToken()
        expr := parse_binary_add(lexer)
        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
    }
    if token.Name == "NAME" {
        name := NewName(token)
        lexer.NextToken()
        return name
    }
    return nil
}

func main() {
    lexer := BKLexer.NewLexer()
    lexer.AddRule("\\d+\\.?\\d*", "NUMBER")
    lexer.AddRule("[\\p{L}\\d_]+", "NAME")
    lexer.AddRule("\\+", "PLUS")
    lexer.AddRule("-", "MINUS")
    lexer.AddRule("\\*", "MUL")
    lexer.AddRule("/", "DIV")
    lexer.AddRule("\\(", "LPAR")
    lexer.AddRule("\\)", "RPAR")
    lexer.AddRule("=", "ASSIGN")
    lexer.AddIgnores("[ \\f\\t]+")
    lexer.AddIgnores("#[^\\r\\n]*")
    lexer.AddReserve("set")
    lexer.AddReserve("echo")
    lexer.AddReserve("if")
    lexer.AddReserve("then")
    lexer.AddReserve("end")

    bytes, err := ioutil.ReadFile("../test.txt")
    if err != nil {
        fmt.Println("read faild")
        return
    }
    code := string(bytes)
    lexer.Build(code)
    result := parse(lexer)
    if result == nil {
        fmt.Println("null result")
        return
    }
    valueDict = make(map[string]float64)
    result.Eval()
}

下篇 支持While语句