Hugo站点渲染之 - FSM - Finite State Machine 状态机

293 阅读2分钟

我正在参加「掘金·启航计划」

在上一章Action Block词法解析器中,我们看到了Hugo解析模板的详细步骤:

image.png

思路很清晰,当正在解析的字符符合某项要求时,交给具体的对象进行处理。 像Text, LeftDelim等。 这就让要我们想到了有限状态机FSM - Finite State Machine。 实际上Hugo也是用state来命名这些处理函数的。

我们顺着这个视角来看看样例中,生成的tokens之间的关系:

image.png

从层级关系来看,可以清晰的看到text和action是一个层,在action中的元素是另一层。 除了action之外,都会识别token并提交token。 而action的主要作用是识别下一个token,并转交给具体的处理函数进行处理。

这样一来,我们发现,每个处理函数,对应着不同的分析状态。 其中有的状态会提交token,有的状态只负责识别下一个可能的token。 从状态切换的视角来看,很像是FSM - Finite State Machine的工作模式。

image.png

从上面的公式中可以看出。 输入信息是当前状态加上事件。 输出信息是处理动作加上新的状态。

当前状态对应的是相应的处理函数,而事件则是识别到的字符信息。 与状态对应的数据就是位置信息和行信息。 处理动作就是函数中所做出的下一个状态决定。 同时也生成了新的数据,像新的位置信息和新地行信息。

我们结合FSM的概念再梳理一下可能的实现方法:

image.png

初始状态就是Text,初始数据就是样例模板片断。 触发动作后,识别出下一个状态是Left Delim,同时数据更新为action block相应的信息。 而触发事件就是当有请求想要获取下一个token - nextToken的时候。 这样我们就可以依据FSM的理念,同样实现一个action block模板的词法分析器了。

动手实践 - Show Me the Code of FSM #

package main

import (
	"errors"
	"fmt"
	"github.com/sunwei/gobyexample/modules/fsm"
)

func main() {
	// initial fsm with init state and data
	f := fsm.New(firstState, &data{
		err: nil,
		raw: "first",
	})
	// add state with handler
	f.Add(firstState,
		func(event fsm.Event) (fsm.State, fsm.Data) {
			if event.Type() == fsm.Action {
				fmt.Println(event.Data().Raw())
			}
			return secondState, &data{
				err: nil,
				raw: "second",
			}
		})
	f.Add(secondState,
		func(event fsm.Event) (fsm.State, fsm.Data) {
			if event.Type() == fsm.Action {
				fmt.Println(event.Data().Raw())
			}
			return lastState, &data{
				err: errors.New("something wrong"),
				raw: "last",
			}
		})
	// error occurs
	f.Add(lastState,
		func(event fsm.Event) (fsm.State, fsm.Data) {
			if e := event.Data().Error(); e != nil {
				fmt.Println(e)
				return errorState, nil
			}
			// if there is no error
			// quite with eof state
			return eofState, &data{
				err: nil,
				raw: "",
			}
		})

	for {
		// send message to notify fsm start the processing
		e := f.Process("continue")
		// quit with error
		if e != nil {
			fmt.Println("break because of error")
			break
		}
		// quite for eof state
		if f.State() == eofState {
			fmt.Println("eof")
			break
		}
	}
}

const (
	firstState  = "first"
	secondState = "second"
	lastState   = "last"
	errorState  = "error"
	eofState    = "eof"
)

type data struct {
	err error
	raw any
}

func (d *data) Error() error {
	return d.err
}
func (d *data) Raw() any {
	return d.raw
}

样例输出:

# FSM example
first
second
break because of error

Program exited.