images/blog-posts

支持While语句

返回教程主页

上篇 支持If语句

下面我们来让程序语言支持While语句,这会使得bkcalclang能够解决一些依赖循环结构处理的问题。

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

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

定义While节点的结构体

type While struct {
    condition Node
    then *Block
}

func NewWhile(condition Node, then *Block) *While {
    return &While{condition: condition, then: then}
}

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

定义While节点的运行方法

func (while *While) Eval() float64 {
    for while.condition.Eval() != 0 {
        while.then.Eval();
    }
    return 0.
}

在循环结构中如果Whilecondition运行后的值不为0则始终运行代码块then,否则中断循环并返回0。

处理While语句的解析

    } else if token.Name == "WHILE" {
        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 NewWhile(condition, then)

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

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

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

接下来取出下一个token判断如果为THEN就进行代码块解析,否则返回nil,在返回NewWhile前,我们需要设定isBlockEndfalse:

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

定义词法解析器规则

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("while")
lexer.AddReserve("then")
lexer.AddReserve("end")

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

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

测试内容【斐波那契数列】:

set n = 15
set a = 0
set b = 1

while n then
    set n = n - 1
    set b = a + b
    set a = b - a
    echo a
end

运行结果:

➜ go calc.go 

:= 1
:= 1
:= 2
:= 3
:= 5
:= 8
:= 13
:= 21
:= 34
:= 55
:= 89
:= 144
:= 233
:= 377
:= 610

代码清单

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

type While struct {
    condition Node
    then *Block
}

func NewWhile(condition Node, then *Block) *While {
    return &While{condition: condition, then: then}
}

func (while *While) Eval() float64 {
    for while.condition.Eval() != 0 {
        while.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 == "WHILE" {
        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 NewWhile(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("while")
    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()
}

SUBSCRIBE


🔒 No spam. Unsubscribe any time.

About kk

kk

Vincenzo Antedoro is an engineer who helps those who want to invest in renewables. For the rest he enjoys teaching with the method of learning by doing..

» More about kk