手擼golang 行爲型設計模式 解釋器模式

手擼golang 行爲型設計模式 解釋器模式

緣起

最近複習設計模式
拜讀譚勇德的<<設計模式就該這樣學>>
本系列筆記擬採用golang練習之node

解釋器模式

解釋器模式(Interpreter Pattern)指給定一門語言,
定義它的文法的一種表示,
並定義一個解釋器
,該解釋器使用該表示來解釋語言中的句子。
解釋器模式是一種按照規定的文法(語法)進行解析的模式,
屬於行爲型設計模式。

(摘自 譚勇德 <<設計模式就該這樣學>>)

場景

  • 某業務系統, 隨數據量增長, 數據庫訪問壓力日漸增大
  • 業務team但願平臺team提供透明的緩存解決方案以緩解數據庫壓力
  • 平臺team經反覆討論研究, 決定採用解釋器模式, 直接攔截/解析/執行SQL(子集)語句, 提供透明化緩存服務

SQL(子集)文法

SQL:
  select + FIELD_LIST + from TABLE_NAME + (where BOOL_EXPRESSION)?
  
FIELD_LIST:
    *
  | COLUMN_LIST
  
COLUMN_LIST:
    COLUMN_NAME + (,COLUMN_NAME)*
  
COLUMN_NAME:
    IDENTIFIER
  
IDENTIFIER:
    [_a-zA-Z] + [_a-zA-Z0-9]*

TABLE_NAME: 
  IDENTIFIER
  
BOOL_EXPRESSION:
    STRING_FIELD = STRING_LITERAL
  | STRING_FIELD <> STRING_LITERAL
  | STRING_FIELD like STRING_LITERAL
  | STRING_FIELD not like STRING_LITERAL
  | INT_FIELD = INT_LITERAL
  | INT_FIELD <> INT_LITERAL
  | INT_FIELD > INT_LITERAL
  | INT_FIELD >= INT_LITERAL
  | INT_FIELD < INT_LITERAL
  | INT_FIELD <= INT_LITERAL
  | ( + BOOL_EXPRESSION + )
  | BOOL_EXPRESSION and BOOL_EXPRESSION
  | BOOL_EXPRESSION or BOOL_EXPRESSION
  
STRING_FIELD:
    IDENTIFIER
  
INT_FIELD:
    IDENTIFIER
  
STRING_LITERAL:
    \' + [^"]* + \'
  
INT_LIETRAL:
    [1-9] + [0-9]*

設計

  • IDatabase: 數據庫接口
  • IDataTable: 數據表接口
  • IDataRow: 數據行接口
  • IDataField: 數據字段接口
  • IRowFilter: 數據行過濾器接口
  • tEmptyRowFilter: 靜態爲true/false的行過濾器
  • tExpressionRowFilter: 基於布爾表達式的行過濾器
  • Tokens: SQL(子集)記號枚舉
  • Nodes: SQL(子集)語法樹節點枚舉
  • Chars: 詞法分析輔助類
  • ISQLParser: SQL(子集)詞法分析器接口
  • Lexer: 詞法分析實現
  • Parser: 語法分析實現, SQL(子集)解釋器的核心
  • tTokenQueue: 詞法節點隊列
  • tArrayStack: 基於數組實現的LIFO堆棧
  • IBoolExpression: 布爾表達式接口
  • tFieldExpression: 基於字段計算的布爾表達式
  • tLogicExpression: 基於關係(AND/OR)計算的布爾表達式
  • SaleOrder: 銷售訂單實體類
  • ISaleOrderService: 銷售訂單服務接口, 繼承自IDataTable接口
  • tMockDatabase: 虛擬的數據庫服務, 實現IDatabase接口
  • tMockSaleOrderService: 虛擬的銷售訂單服務, 實現ISaleOrderService接口

單元測試

interpreter_pattern_test.go, 模擬銷售訂單的保存與SQL查詢golang

package behavioral_patterns

import (
    "learning/gooop/behavioral_patterns/interpreter"
    "testing"
)

func Test_Interpreter(t *testing.T) {
    service := interpreter.MockSaleOrderService
    service.Save(interpreter.NewSaleOrder(1, "張三", "廣州", "電視", 10))
    service.Save(interpreter.NewSaleOrder(11, "張三三", "廣州", "電視", 11))
    service.Save(interpreter.NewSaleOrder(2, "李四", "深圳", "冰箱", 20))
    service.Save(interpreter.NewSaleOrder(22, "李四四", "深圳", "冰箱", 21))
    service.Save(interpreter.NewSaleOrder(3, "王五", "東莞", "空調", 30))
    service.Save(interpreter.NewSaleOrder(33, "王五五", "東莞", "空調", 31))


    db := interpreter.MockDatabase
    e,rows := db.Query("select * from sale_order where (city='廣州' or city='深圳') and quantity>10")
    if e != nil {
        t.Error(e)
    } else {
        for _,it := range rows {
            t.Logf("%s", it)
        }
    }
}

測試輸出

$ go test -v interpreter_pattern_test.go 
=== RUN   Test_Interpreter
    interpreter_pattern_test.go:24: id=2, customer=李四, city=深圳, product=冰箱, quantity=20
    interpreter_pattern_test.go:24: id=22, customer=李四四, city=深圳, product=冰箱, quantity=21
    interpreter_pattern_test.go:24: id=11, customer=張三三, city=廣州, product=電視, quantity=11
--- PASS: Test_Interpreter (0.00s)
PASS
ok      command-line-arguments  0.002s

IDatabase.go

數據庫接口sql

package interpreter

type IDatabase interface {
    Register(table IDataTable)
    Query(sql string) (error, []IDataRow)
}

IDataTable.go

數據表接口數據庫

package interpreter

type IDataTable interface {
    Name() string
    Filter(filter IRowFilter) []IDataRow
}

IDataRow.go

數據行接口express

package interpreter

type IDataRow interface {
    GetField(field string) (error,IDataField)
}

IDataField.go

數據字段接口設計模式

package interpreter

import (
    "errors"
)

type IDataField interface {
    Name() string
    DataType() DataTypes
    GetString() (error,string)
    GetInt() (error, int)
}


type DataTypes int
const StringDataType DataTypes = 1
const IntDataType DataTypes = 2


type tIntField struct {
    name string
    value int
}

func newIntField(name string, value int) *tIntField {
    return &tIntField{
        name,value,
    }
}

func (me *tIntField) Name() string {
    return me.name
}

func (me *tIntField) DataType() DataTypes {
    return IntDataType
}

func (me *tIntField) GetString() (error,string) {
    return errors.New("not implemented"), ""
}

func (me *tIntField) GetInt() (error,int) {
    return nil, me.value
}


type tStringField struct {
    name string
    value string
}

func newStringField(name string, value string) *tStringField {
    return &tStringField{
        name,value,
    }
}

func (me *tStringField) Name() string {
    return me.name
}

func (me *tStringField) DataType() DataTypes {
    return StringDataType
}

func (me *tStringField) GetString() (error,string) {
    return nil, me.value
}

func (me *tStringField) GetInt() (error,int) {
    return errors.New("not implemented"), 0
}

IRowFilter.go

數據行過濾器接口數組

package interpreter

type IRowFilter interface {
    Filter(row IDataRow) bool
}

tEmptyRowFilter.go

靜態爲true/false的行過濾器緩存

package interpreter

type tEmptyRowFilter struct {
    accept bool
}

func newEmptyRowFilter(accept bool) IRowFilter {
    return &tEmptyRowFilter{
        accept: accept,
    }
}

func (me *tEmptyRowFilter) Filter(row IDataRow) bool {
    return me.accept
}

tExpressionRowFilter.go

基於布爾表達式的行過濾器app

package interpreter


type tExpressionRowFilter struct {
    expression IBoolExpression
}

func newExpressionRowFilter(e IBoolExpression) IRowFilter {
    return &tExpressionRowFilter{
        expression: e,
    }
}

func (me *tExpressionRowFilter) Filter(row IDataRow) bool {
    return me.expression.Eval(row)
}

Tokens.go

SQL記號枚舉ide

package tokens

type Tokens string

const Select Tokens = "select"
const Star Tokens = "*"
const Comma Tokens = ","
const From Tokens = "from"
const Where Tokens = "where"
const Identifier Tokens = "identifier"
const LB Tokens = "("
const RB Tokens = ")"
const And Tokens = "and"
const OR Tokens = "or"
const Equal Tokens = "="
const NotEqual Tokens = "<>"
const Greater Tokens = ">"
const GreaterEqual Tokens = ">="
const Less Tokens = "<"
const LessEqual Tokens = "<="
const Like Tokens = "like"
const NotLike Tokens = "not like"
const StringLiteral Tokens = "string_literal"
const IntLiteral Tokens = "int_literal"

Nodes.go

SQL語法樹節點枚舉

package nodes

type Nodes int
const TokenNode Nodes = 1
const ExpressionNode Nodes = 2

Chars.go

詞法分析輔助類

package interpreter


type tChars struct {

}


func newCharsLib() *tChars {
    return &tChars{}
}

func (me *tChars) IsSpace(it rune) bool {
    switch it {
    case ' ':
        return true
    case '\t':
        return true
    case '\r':
        return true
    case '\n':
        return true
    }

    return false
}


func (me *tChars) Is09(it rune) bool {
    return it >= '0' && it <= '9'
}

func (me *tChars) Is19(it rune) bool {
    return it >= '1' && it <= '9'
}

func (me *tChars) IsLetter(it rune) bool {
    return (it >= 'a' && it <= 'z') || (it >= 'A' && it <= 'Z')
}

func (me *tChars) IsUnderscore(it rune) bool {
    return it == '_'
}

func (me *tChars) IsLB(it rune) bool {
    return it == '('
}

func (me *tChars) IsRB(it rune) bool {
    return it == ')'
}

func (me *tChars) IsChar(it rune, args... rune) bool {
    for _,v := range args {
        if v == it {
            return true
        }
    }
    return false
}

var chars = newCharsLib()

ISQLParser.go

SQL詞法分析器接口

package interpreter


type ISQLParser interface {
    Parse(sql string) (error,*ParseResult)
}


type ParseResult struct {
    Fields []string
    Table string
    RowFilter IRowFilter
}

func newParseResult() *ParseResult {
    return &ParseResult{
        make([]string, 0),
        "",
        nil,
    }
}

Lexer.go

SQL(子集)詞法分析實現

package interpreter

import (
    "errors"
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
)


type tLexer struct {
    chars []rune
    count int
    pos int
    tokens []*tTokenNode
}

func newLexer(sql string) *tLexer {
    chars := []rune(sql)
    return &tLexer{
        chars: chars,
        count: len(chars),
        pos: 0,
        tokens: make([]*tTokenNode, 0),
    }
}


func (me *tLexer) push(it *tTokenNode) {
    me.tokens = append(me.tokens, it)
}

func (me *tLexer) Parse() (error, []*tTokenNode) {
    fnMatchingWord := func(t tokens.Tokens) bool {
        pattern := string(t)
        from := me.pos
        if me.MatchingConst(pattern) && me.MatchingSpace() {
            me.push(newTokenNode(t, string(t), from, me.pos - 1))
            return true
        }

        return false
    }

    fnMatchingOP := func(t tokens.Tokens) bool {
        pattern := string(t)
        from := me.pos
        if me.MatchingConst(pattern) {
            me.push(newTokenNode(t, string(t), from, me.pos - 1))
            return true
        }

        return false
    }

    for {
        // eof
        if me.IsEof() {
            return nil, me.tokens
        }

        me.SkipSpace()
        from := me.pos

        // select, from, where
        if fnMatchingWord(tokens.Select) || fnMatchingWord(tokens.From) || fnMatchingWord(tokens.Where) {
            continue
        }

        // start,comma
        if fnMatchingWord(tokens.Star) || fnMatchingWord(tokens.Comma) {
            continue
        }

        // and, or
        if fnMatchingWord(tokens.And) {
            continue
        } else if fnMatchingWord(tokens.OR) {
            continue
        }

        // like, not like
        if fnMatchingWord(tokens.Like) {
            continue
        } else if fnMatchingWord(tokens.NotLike) {
            continue
        }

        // (, )
        if fnMatchingOP(tokens.LB) {
            continue
        } else if fnMatchingOP(tokens.RB) {
            continue
        }

        // =,<>,>,>=,<,<=
        if fnMatchingOP(tokens.Equal) {
            continue
        } else if fnMatchingOP(tokens.NotEqual) {
            continue
        } else if fnMatchingOP(tokens.GreaterEqual) {
            continue
        } else if fnMatchingOP(tokens.Greater) {
            continue
        } else if fnMatchingOP(tokens.LessEqual) {
            continue
        } else if fnMatchingOP(tokens.Less) {
            continue
        }

        // identifier
        b,v := me.MatchingIdentifier()
        if b {
            me.push(newTokenNode(tokens.Identifier, v, from, from + len(v)))
            continue
        }

        // string literal
        b,v = me.MatchingString()
        if b {
            me.push(newTokenNode(tokens.StringLiteral, v, from, from + len(v)))
            continue
        }

        // int literal
        b,v = me.MatchingInt()
        if b {
            me.push(newTokenNode(tokens.IntLiteral, v, from, from + len(v)))
            continue
        }

        // unknown
        return errors.New(fmt.Sprintf("unknown token at %v", from)), nil
    }
}


func (me *tLexer) IsEof() bool {
    return me.pos >= me.count
}

func (me *tLexer) SkipSpace() {
    for {
        if me.pos >= me.count {
            break
        }

        if chars.IsSpace(me.Char()) {
            me.pos++
        } else {
            break
        }
    }
}

func (me *tLexer) MatchingConst(s string) bool {
    pattern := []rune(s)
    for i,it := range pattern {
        n := me.pos + i
        if n >= me.count {
            return false
        }
        if me.chars[n] != it {
            return false
        }
    }

    me.pos += len(pattern)
    return true
}

func (me *tLexer) Char() rune {
    if me.pos >= me.count {
        return 0
    }

    return me.chars[me.pos]
}

func (me *tLexer) MatchingSpace() bool {
    if chars.IsSpace(me.Char()) {
        me.pos++
        return true
    }
    return false
}

func (me *tLexer) MatchingString() (bool,string) {
    mark := me.pos

    if me.Char() != '\'' {
        return false, ""
    }

    i := mark
    for {
        i++

        switch me.chars[i] {
        case '\'':
            me.pos = i + 1
            return true, string(me.chars[mark:me.pos])

        default:
            if i >= me.count {
                return false, ""
            }
        }
    }
}


func (me *tLexer) MatchingIdentifier() (bool, string) {
    mark := me.pos

    c := me.Char()
    if !(chars.IsUnderscore(c) || chars.IsLetter(c)) {
        return false, ""
    }

    i := mark
    for {
        i++
        if i > mark + 30 {
            return false,""
        }

        it := me.chars[i]
        if chars.IsLetter(it) || chars.Is09(it) || chars.IsUnderscore(it) {
            continue

        } else {
            me.pos = i
            return true, string(me.chars[mark:i])
        }
    }
}


func (me *tLexer) MatchingInt() (bool, string) {
    if me.Char() == '0' {
        me.pos++
        return true, "0"
    }

    mark := me.pos

    if !chars.Is19(me.Char()) {
        return false, ""
    }

    i := mark
    for {
        i++
        if i >= me.count {
            me.pos = me.count
            return true, string(me.chars[mark:])
        }

        if i > mark + 10 {
            return false, ""
        }

        it := me.chars[i]
        if chars.Is09(it) {
            continue
        }

        if chars.IsSpace(it) {
            me.pos = i - 1
            return true, string(me.chars[mark:i])

        } else {
            return false, ""
        }
    }
}

Parser.go

語法分析實現

package interpreter

import (
    "errors"
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
    "strconv"
)

type tSQLParser struct {
    result *ParseResult
}


func newSQLParser() ISQLParser {
    return &tSQLParser{
        newParseResult(),
    }
}

func (me *tSQLParser) Parse(sql string) (error,*ParseResult) {
    lexer := newLexer(sql)
    e, tks := lexer.Parse()
    if e != nil {
        return e, nil
    }

    queue := newTokenQueue(tks)

    // select (* | field-list)
    e = me.ParseSelectPart(queue)
    if e != nil {
        return e, nil
    }

    // from
    e = me.ParseFromPart(queue)
    if e != nil {
        return e, nil
    }

    // where + bool_expression
    e = me.ParseWherePart(queue)
    if e != nil {
        return e, nil
    }

    // eof
    if !queue.IsEmpty() {
        _,t := queue.Poll()
        return errors.New(fmt.Sprintf("expecting EOF at %v", t.from)), nil
    }

    return nil, me.result
}

func (me *tSQLParser) ParseSelectPart(queue *tTokenQueue) error {
    b,v := queue.Poll()
    if !b {
        return errors.New("expecting SELECT keyword")
    }

    if v.token != tokens.Select {
        return errors.New(fmt.Sprintf("expecting SELECT keyword but found '%s'", v.value))
    }

    fields := make([]string, 0)

    b,v = queue.Peek()
    if !b {
        return errors.New("unexpected EOF")
    }

    switch v.token {
    case tokens.Star:
        queue.Poll()
        fields = append(fields, v.value)
        break

    case tokens.Identifier:
        queue.Poll()
        fields = append(fields, v.value)
        break

    default:
        return errors.New(fmt.Sprintf("expecting column name but found '%s'", v.value))
    }

    for {
        b,v := queue.Peek()
        if !b {
            break
        }

        if v.token != tokens.Comma {
            break
        }
        queue.Poll()

        b,v = queue.Peek()
        if !b || v.token != tokens.Identifier {
            return errors.New(fmt.Sprintf("expecting column name but found '%s'", v.value))
        }
        queue.Poll()
        fields = append(fields, v.value)
    }

    if len(fields) > 0 {
        me.result.Fields = fields
        return nil
    }

    return errors.New("expecting column names")
}


func (me *tSQLParser) ParseFromPart(queue *tTokenQueue) error {
    b,v1 := queue.Poll()
    if !b {
        return errors.New("expecting 'from', but eof")
    }

    if v1.token != tokens.From {
        return errors.New(fmt.Sprintf("expecting 'from' at %v", v1.from))
    }

    b,v2 := queue.Poll()
    if !b {
        return errors.New("expecting table name, but eof")
    }
    if v2.token == tokens.Identifier {
        me.result.Table = v2.value
        return nil
    }

    return errors.New(fmt.Sprintf("expecting table name at %v", v2.from))
}


func (me *tSQLParser) ParseWherePart(queue *tTokenQueue) error {
    if queue.IsEmpty() {
        // no where clause
        me.result.RowFilter = newEmptyRowFilter(true)
        return nil
    }

    _,v1 := queue.Poll()
    if v1.token != tokens.Where {
        return errors.New(fmt.Sprintf("expecting 'where' keyword at %v", v1.from))
    }

    stack := newArrayStack()
    e, expression := me.ParseWhereExpression(queue, stack)
    if e != nil {
        return e
    }

    me.result.RowFilter = newExpressionRowFilter(expression)
    return nil
}


func (me *tSQLParser) ParseWhereExpression(queue *tTokenQueue, stack *tArrayStack) (error, IBoolExpression) {
    for {
        if queue.IsEmpty() {
            break
        }

        _,t := queue.Poll()
        switch t.token {
        case tokens.LB:
            stack.Push(t)
            break

        case tokens.Identifier:
            stack.Push(t)
            break

        case tokens.Equal:
            stack.Push(t)
            break

        case tokens.NotEqual:
            stack.Push(t)
            break

        case tokens.Like:
            stack.Push(t)
            break

        case tokens.NotLike:
            stack.Push(t)
            break

        case tokens.Greater:
            stack.Push(t)
            break

        case tokens.GreaterEqual:
            stack.Push(t)
            break

        case tokens.Less:
            stack.Push(t)
            break

        case tokens.LessEqual:
            stack.Push(t)
            break

        case tokens.And:
            stack.Push(t)
            break

        case tokens.OR:
            stack.Push(t)
            break

        case tokens.StringLiteral:
            // field op string
            b, v := stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting operator before %s", t.value)), nil
            }

            op := v.(*tTokenNode)
            if !me.TokenIn(op.token, tokens.Equal, tokens.NotEqual, tokens.Like, tokens.NotLike) {
                return errors.New(fmt.Sprintf("expecting string operator before %s", t.value)), nil
            }

            b,v = stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting column name before %s", op.from)), nil
            }
            field := v.(*tTokenNode)
            if field.token != tokens.Identifier {
                return errors.New(fmt.Sprintf("expecting column name at %v", field.from)), nil
            }

            exp := newStringFieldExpression(op.token, field.value, t.value)
            e := me.PushExpression(exp, stack)
            if e != nil {
                return e, nil
            }
            break


        case tokens.IntLiteral:
            // field op int
            b, v := stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting operator before %s", t.value)), nil
            }

            op := v.(*tTokenNode)
            if !me.TokenIn(op.token, tokens.Equal, tokens.NotEqual, tokens.Greater, tokens.GreaterEqual, tokens.Less, tokens.LessEqual) {
                return errors.New(fmt.Sprintf("expecting int operator before %s", t.value)), nil
            }

            b,v = stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expecting column name before %v", op.from)), nil
            }
            field := v.(*tTokenNode)
            if field.token != tokens.Identifier {
                return errors.New(fmt.Sprintf("expecting column name at %v", field.from)), nil
            }

            i,_ := strconv.Atoi(t.value)
            exp := newIntFieldExpression(op.token, field.value, i)
            e := me.PushExpression(exp, stack)
            if e != nil {
                return e, nil
            }
            break


        case tokens.RB:
            // )
            b,v := stack.Pop()
            if !b || !v.IsExpression() {
                return errors.New(fmt.Sprintf("expecting expression before %v", t.from)), nil
            }
            expression := v.(*tExpressionNode).Expression

            b,v = stack.Pop()
            if !b || !v.IsToken() {
                return errors.New(fmt.Sprintf("expected ( not found at %v", t.from)), nil
            }
            lb := v.(*tTokenNode)
            if lb.token != tokens.LB {
                return errors.New(fmt.Sprintf("expected ( not found at %v", t.from)), nil
            }

            e := me.PushExpression(expression, stack)
            if e != nil {
                return e, nil
            }
            break
        }
    }


    if stack.Size() != 1{
        return errors.New("invalid expression"), nil
    }
    ok, node := stack.Peek()
    if !ok || !node.IsExpression() {
        return errors.New("invalid expression"), nil
    }

    return nil,node.(*tExpressionNode).Expression
}


func (me *tSQLParser) TokenIn(t tokens.Tokens, args... tokens.Tokens) bool {
    for _,it := range args {
        if it == t {
            return true
        }
    }
    return false
}


func (me *tSQLParser) PushExpression(exp IBoolExpression, stack *tArrayStack) error {
    b,n := stack.Peek()
    if !b {
        stack.Push(newExpressionNode(exp))
        return nil
    }

    if !n.IsToken() {
        return errors.New("expecting and/or/(")
    }

    t := n.(*tTokenNode)
    if !me.TokenIn(t.token, tokens.And, tokens.OR, tokens.LB) {
        return errors.New("expecting and/or/(")
    }

    if me.TokenIn(t.token, tokens.And, tokens.OR) {
        stack.Pop()
        b,n = stack.Pop()
        if !b || !n.IsExpression(){
            return errors.New("expecting bool expression")
        }
        e := n.(*tExpressionNode)
        return me.PushExpression(newLogicNode(t.token, e.Expression, exp), stack)

    } else {
        stack.Push(newExpressionNode(exp))
        return nil
    }
}

tTokenQueue.go

詞法節點隊列

package interpreter

type tTokenQueue struct {
    items []*tTokenNode
    size int
    next int
}


func newTokenQueue(nodes []*tTokenNode) *tTokenQueue {
    return &tTokenQueue{
        nodes,
        len(nodes),
        0,
    }
}

func (me *tTokenQueue) Poll() (bool,*tTokenNode) {
    b,v := me.Peek()
    if b {
        me.next++
    }
    return b,v
}


func (me *tTokenQueue) Peek() (bool, *tTokenNode) {
    if me.next >= me.size {
        return false, nil
    }

    it := me.items[me.next]
    return true, it
}

func (me *tTokenQueue) IsEmpty() bool {
    return me.next >= me.size
}

tArrayStack.go

基於數組實現的LIFO堆棧

package interpreter

import "learning/gooop/behavioral_patterns/interpreter/tokens"

type tArrayStack struct {
    items []iStackNode
}


type iStackNode interface {
    IsToken() bool
    IsExpression() bool
}


func newArrayStack() *tArrayStack {
    return &tArrayStack{
        make([]iStackNode, 0),
    }
}

func (me *tArrayStack) Push(node iStackNode) {
    me.items = append(me.items, node)
}

func (me *tArrayStack) Size() int {
    return len(me.items)
}

func (me *tArrayStack) Peek() (bool,iStackNode) {
    if me.Size() <= 0 {
        return false, nil
    }
    return true, me.items[me.Size() - 1]
}

func (me *tArrayStack) Pop() (bool, iStackNode) {
    if me.Size() <= 0 {
        return false, nil
    }

    it := me.items[me.Size() - 1]
    me.items = me.items[:me.Size() - 1]
    return true, it
}

type tTokenNode struct {
    token tokens.Tokens
    value string
    from int
    to int
}

func newTokenNode(t tokens.Tokens, v string, from int, to int) *tTokenNode {
    return &tTokenNode{
        t,v, from, to,
    }
}


func (me *tTokenNode) IsToken() bool {
    return true
}

func (me *tTokenNode) IsExpression() bool {
    return false
}


type tExpressionNode struct {
    Expression IBoolExpression
}


func newExpressionNode(e IBoolExpression) *tExpressionNode {
    return &tExpressionNode{
        e,
    }
}


func (me *tExpressionNode) IsToken() bool {
    return false
}

func (me *tExpressionNode) IsExpression() bool {
    return true
}

IBoolExpression.go

布爾表達式接口

package interpreter

type IBoolExpression interface {
    Eval(row IDataRow) bool
}

tFieldExpression.go

基於字段計算的布爾表達式

package interpreter

import (
    "errors"
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
    "strings"
)

type tFieldExpression struct {
    op tokens.Tokens
    field string
    stringLiteral string
    intLiteral int
}


func newStringFieldExpression(op tokens.Tokens, field string, s string) *tFieldExpression {
    return &tFieldExpression{
        op, field, strings.Trim(s, "'"), 0,
    }
}

func newIntFieldExpression(op tokens.Tokens, field string, value int) *tFieldExpression {
    return &tFieldExpression{
        op, field, "", value,
    }
}

func (me *tFieldExpression) Eval(row IDataRow) bool {
    e, fld := row.GetField(me.field)
    if e != nil {
        panic(e)
    }

    switch fld.DataType() {
    case StringDataType:
        e,v := fld.GetString()
        if e != nil {
            panic(e)
        }
        return me.EvalString(v)

    case IntDataType:
        e,v := fld.GetInt()
        if e != nil {
            panic(e)
        }
        return me.EvalInt(v)

    default:
        panic(errors.New("unknown data type"))
    }
}

func (me *tFieldExpression) EvalString(value string) bool {
    switch me.op {
    case tokens.Equal:
        return value == me.stringLiteral

    case tokens.NotEqual:
        return value != me.stringLiteral

    case tokens.Like:
        fallthrough
    case tokens.NotLike:
        like := false
        p := strings.HasPrefix(value, "%")
        s := strings.HasPrefix(value, "%")

        if p && s {
            like = strings.Contains(value, me.stringLiteral)
        } else if p {
            like = strings.HasSuffix(value, me.stringLiteral)
        } else if s {
            like = strings.HasPrefix(value, me.stringLiteral)
        } else {
            like = value == me.stringLiteral
        }

        if me.op == tokens.Like {
            return like
        } else {
            return !like
        }
        break

    default:
        panic(errors.New(fmt.Sprintf("unsupported string operation: %s", me.op)))
    }

    return false
}


func (me *tFieldExpression) EvalInt(value int) bool {
    switch me.op {
    case tokens.Equal:
        return value == me.intLiteral

    case tokens.NotEqual:
        return value != me.intLiteral

    case tokens.Greater:
        return value > me.intLiteral

    case tokens.GreaterEqual:
        return value >= me.intLiteral

    case tokens.Less:
        return value < me.intLiteral

    case tokens.LessEqual:
        return value <= me.intLiteral

    default:
        panic(errors.New(fmt.Sprintf("unsupported int operation: %s", me.op)))
    }
}

tLogicExpression.go

基於關係(AND/OR)計算的布爾表達式

package interpreter

import (
    "fmt"
    "learning/gooop/behavioral_patterns/interpreter/tokens"
)

type tLogicExpression struct {
    op tokens.Tokens
    left IBoolExpression
    right IBoolExpression
}


func newLogicNode(op tokens.Tokens, left IBoolExpression, right IBoolExpression) *tLogicExpression {
    return &tLogicExpression{
        op, left, right,
    }
}


func (me *tLogicExpression) Eval(row IDataRow) bool {
    switch me.op {
    case tokens.And:
        return me.left.Eval(row) && me.right.Eval(row)

    case tokens.OR:
        return me.left.Eval(row) || me.right.Eval(row)

    default:
        panic(fmt.Sprintf("unsupported bool operation: %s", me.op))
    }
}

SaleOrder.go

銷售訂單實體類

package interpreter

import (
    "errors"
    "fmt"
    "strings"
)

type SaleOrder struct {
    id *tIntField
    customer *tStringField
    city *tStringField
    product *tStringField
    quantity *tIntField
}

func NewSaleOrder(id int, customer string, city string, product string, quantity int) *SaleOrder {
    return &SaleOrder{
        id: newIntField("id", id),
        customer: newStringField("customer", customer),
        city: newStringField("city", city),
        product: newStringField("product", product),
        quantity: newIntField("quantity", quantity),
    }
}


func (me *SaleOrder) ID() int {
    return me.id.value
}

func (me *SaleOrder) Customer() string {
    return me.customer.value
}

func (me *SaleOrder) City() string {
    return me.city.value
}

func (me *SaleOrder) Product() string {
    return me.product.value
}

func (me *SaleOrder) Quantity() int {
    return me.quantity.value
}

func (me *SaleOrder) GetField(field string) (error,IDataField) {
    if strings.EqualFold(field, "ID") {
        return nil, me.id
    }

    if strings.EqualFold(field, "Customer") {
        return nil, me.customer
    }

    if strings.EqualFold(field, "City") {
        return nil, me.city
    }

    if strings.EqualFold(field, "Product") {
        return nil, me.product
    }

    if strings.EqualFold(field, "Quantity") {
        return nil, me.quantity
    }

    return errors.New("no such field"), nil
}

func (me *SaleOrder) String() string {
    return fmt.Sprintf("id=%v, customer=%v, city=%v, product=%v, quantity=%v", me.ID(), me.Customer(), me.City(), me.Product(), me.Quantity())
}

ISaleOrderService.go

銷售訂單服務接口, 繼承自IDataTable接口

package interpreter

type ISaleOrderService interface {
    IDataTable
    Save(order *SaleOrder)
}

tMockDatabase.go

虛擬的數據庫服務, 實現IDatabase接口

package interpreter

import "errors"

type tMockDatabase struct {
    tables map[string]IDataTable
}

func newMockDatabase() IDatabase {
    return &tMockDatabase{
        tables: make(map[string]IDataTable, 0),
    }
}

func (me *tMockDatabase) Register(table IDataTable) {
    me.tables[table.Name()] = table
}

func (me *tMockDatabase) Query(sql string) (error, []IDataRow) {
    parser := newSQLParser()
    e, result := parser.Parse(sql)
    if e != nil {
        return e, nil
    }

    table, ok := me.tables[result.Table]
    if !ok {
        return errors.New("table not found"), nil
    }

    return nil, table.Filter(result.RowFilter)
}

var MockDatabase = newMockDatabase()

tMockSaleOrderService.go

虛擬的銷售訂單服務, 實現ISaleOrderService接口

package interpreter

type tMockSaleOrderService struct {
    items map[int]*SaleOrder
}

func (me *tMockSaleOrderService) Save(it *SaleOrder) {
    me.items[it.ID()] = it
}


func (me *tMockSaleOrderService) Name() string {
    return "sale_order"
}

func (me *tMockSaleOrderService) Filter(filter IRowFilter) []IDataRow {
    rows := make([]IDataRow, 0)
    for _,it := range me.items {
        if filter.Filter(it) {
            rows = append(rows, it)
        }
    }
    return rows
}

func newMockSaleOrderService() ISaleOrderService {
    it := &tMockSaleOrderService{
        items: make(map[int]*SaleOrder, 0),
    }
    MockDatabase.Register(it)
    return it
}

var MockSaleOrderService = newMockSaleOrderService()

解釋器模式小結

解釋器模式的優勢
(1)在解釋器模式中,因爲語法是由不少類表示的,當語法規則更改時,
    只需修改相應的非終結符表達式便可;
    當擴展語法時,只需添加相應的非終結符類便可。
(2)增長了新的解釋表達式的方式。
(3)解釋器模式對應的文法應當是比較簡單且易於實現的,過於複雜的語法並不適合使用解釋器模式。

解釋器模式的缺點
(1)解釋器模式的每一個語法都要產生一個非終結符表達式,當語法規則比較複雜時,
    就會產生大量解釋類,引發類膨脹,增長系統維護的難度。
(2)解釋器模式採用遞歸調用方法,
    每一個非終結符表達式都只關心與本身有關的表達式,
    每一個表達式都須要知道最終的結果,
    所以完整表達式的最終結果是經過從後往前遞歸調用的方式獲取的。
    當完整表達式層級較深時,解釋效率降低,
    且出錯時調試困難,由於遞歸迭代的層級太深。
    
(摘自 譚勇德 <<設計模式就該這樣學>>)

(end)

相關文章
相關標籤/搜索