Go 語言 示例: 編碼為S表達(dá)式

2023-03-14 17:00 更新

原文鏈接:https://gopl-zh.github.io/ch12/ch12-04.html


12.4. 示例: 編碼為S表達(dá)式

Display是一個用于顯示結(jié)構(gòu)化數(shù)據(jù)的調(diào)試工具,但是它并不能將任意的Go語言對象編碼為通用消息然后用于進(jìn)程間通信。

正如我們在4.5節(jié)中中看到的,Go語言的標(biāo)準(zhǔn)庫支持了包括JSON、XML和ASN.1等多種編碼格式。還有另一種依然被廣泛使用的格式是S表達(dá)式格式,采用Lisp語言的語法。但是和其他編碼格式不同的是,Go語言自帶的標(biāo)準(zhǔn)庫并不支持S表達(dá)式,主要是因?yàn)樗鼪]有一個公認(rèn)的標(biāo)準(zhǔn)規(guī)范。

在本節(jié)中,我們將定義一個包用于將任意的Go語言對象編碼為S表達(dá)式格式,它支持以下結(jié)構(gòu):

42          integer
"hello"     string(帶有Go風(fēng)格的引號)
foo         symbol(未用引號括起來的名字)
(1 2 3)     list  (括號包起來的0個或多個元素)

布爾型習(xí)慣上使用t符號表示true,空列表或nil符號表示false,但是為了簡單起見,我們暫時忽略布爾類型。同時忽略的還有chan管道和函數(shù),因?yàn)橥ㄟ^反射并無法知道它們的確切狀態(tài)。我們忽略的還有浮點(diǎn)數(shù)、復(fù)數(shù)和interface。支持它們是練習(xí)12.3的任務(wù)。

我們將Go語言的類型編碼為S表達(dá)式的方法如下。整數(shù)和字符串以顯而易見的方式編碼??罩稻幋a為nil符號。數(shù)組和slice被編碼為列表。

結(jié)構(gòu)體被編碼為成員對象的列表,每個成員對象對應(yīng)一個有兩個元素的子列表,子列表的第一個元素是成員的名字,第二個元素是成員的值。Map被編碼為鍵值對的列表。傳統(tǒng)上,S表達(dá)式使用點(diǎn)狀符號列表(key . value)結(jié)構(gòu)來表示key/value對,而不是用一個含雙元素的列表,不過為了簡單我們忽略了點(diǎn)狀符號列表。

編碼是由一個encode遞歸函數(shù)完成,如下所示。它的結(jié)構(gòu)本質(zhì)上和前面的Display函數(shù)類似:

gopl.io/ch12/sexpr

func encode(buf *bytes.Buffer, v reflect.Value) error {
    switch v.Kind() {
    case reflect.Invalid:
        buf.WriteString("nil")

    case reflect.Int, reflect.Int8, reflect.Int16,
        reflect.Int32, reflect.Int64:
        fmt.Fprintf(buf, "%d", v.Int())

    case reflect.Uint, reflect.Uint8, reflect.Uint16,
        reflect.Uint32, reflect.Uint64, reflect.Uintptr:
        fmt.Fprintf(buf, "%d", v.Uint())

    case reflect.String:
        fmt.Fprintf(buf, "%q", v.String())

    case reflect.Ptr:
        return encode(buf, v.Elem())

    case reflect.Array, reflect.Slice: // (value ...)
        buf.WriteByte('(')
        for i := 0; i < v.Len(); i++ {
            if i > 0 {
                buf.WriteByte(' ')
            }
            if err := encode(buf, v.Index(i)); err != nil {
                return err
            }
        }
        buf.WriteByte(')')

    case reflect.Struct: // ((name value) ...)
        buf.WriteByte('(')
        for i := 0; i < v.NumField(); i++ {
            if i > 0 {
                buf.WriteByte(' ')
            }
            fmt.Fprintf(buf, "(%s ", v.Type().Field(i).Name)
            if err := encode(buf, v.Field(i)); err != nil {
                return err
            }
            buf.WriteByte(')')
        }
        buf.WriteByte(')')

    case reflect.Map: // ((key value) ...)
        buf.WriteByte('(')
        for i, key := range v.MapKeys() {
            if i > 0 {
                buf.WriteByte(' ')
            }
            buf.WriteByte('(')
            if err := encode(buf, key); err != nil {
                return err
            }
            buf.WriteByte(' ')
            if err := encode(buf, v.MapIndex(key)); err != nil {
                return err
            }
            buf.WriteByte(')')
        }
        buf.WriteByte(')')

    default: // float, complex, bool, chan, func, interface
        return fmt.Errorf("unsupported type: %s", v.Type())
    }
    return nil
}

Marshal函數(shù)是對encode的包裝,以保持和encoding/...下其它包有著相似的API:

// Marshal encodes a Go value in S-expression form.
func Marshal(v interface{}) ([]byte, error) {
    var buf bytes.Buffer
    if err := encode(&buf, reflect.ValueOf(v)); err != nil {
        return nil, err
    }
    return buf.Bytes(), nil
}

下面是Marshal對12.3節(jié)的strangelove變量編碼后的結(jié)果:

((Title "Dr. Strangelove") (Subtitle "How I Learned to Stop Worrying and Lo
ve the Bomb") (Year 1964) (Actor (("Grp. Capt. Lionel Mandrake" "Peter Sell
ers") ("Pres. Merkin Muffley" "Peter Sellers") ("Gen. Buck Turgidson" "Geor
ge C. Scott") ("Brig. Gen. Jack D. Ripper" "Sterling Hayden") ("Maj. T.J. \
"King\" Kong" "Slim Pickens") ("Dr. Strangelove" "Peter Sellers"))) (Oscars
("Best Actor (Nomin.)" "Best Adapted Screenplay (Nomin.)" "Best Director (N
omin.)" "Best Picture (Nomin.)")) (Sequel nil))

整個輸出編碼為一行中以減少輸出的大小,但是也很難閱讀。下面是對S表達(dá)式手動格式化的結(jié)果。編寫一個S表達(dá)式的美化格式化函數(shù)將作為一個具有挑戰(zhàn)性的練習(xí)任務(wù);不過 http://gopl.io 也提供了一個簡單的版本。

((Title "Dr. Strangelove")
 (Subtitle "How I Learned to Stop Worrying and Love the Bomb")
 (Year 1964)
 (Actor (("Grp. Capt. Lionel Mandrake" "Peter Sellers")
         ("Pres. Merkin Muffley" "Peter Sellers")
         ("Gen. Buck Turgidson" "George C. Scott")
         ("Brig. Gen. Jack D. Ripper" "Sterling Hayden")
         ("Maj. T.J. \"King\" Kong" "Slim Pickens")
         ("Dr. Strangelove" "Peter Sellers")))
 (Oscars ("Best Actor (Nomin.)"
          "Best Adapted Screenplay (Nomin.)"
          "Best Director (Nomin.)"
          "Best Picture (Nomin.)"))
 (Sequel nil))

和fmt.Print、json.Marshal、Display函數(shù)類似,sexpr.Marshal函數(shù)處理帶環(huán)的數(shù)據(jù)結(jié)構(gòu)也會陷入死循環(huán)。

在12.6節(jié)中,我們將給出S表達(dá)式解碼器的實(shí)現(xiàn)步驟,但是在那之前,我們還需要先了解如何通過反射技術(shù)來更新程序的變量。

練習(xí) 12.3: 實(shí)現(xiàn)encode函數(shù)缺少的分支。將布爾類型編碼為t和nil,浮點(diǎn)數(shù)編碼為Go語言的格式,復(fù)數(shù)1+2i編碼為#C(1.0 2.0)格式。接口編碼為類型名和值對,例如("[]int" (1 2 3)),但是這個形式可能會造成歧義:reflect.Type.String方法對于不同的類型可能返回相同的結(jié)果。

練習(xí) 12.4: 修改encode函數(shù),以上面的格式化形式輸出S表達(dá)式。

練習(xí) 12.5: 修改encode函數(shù),用JSON格式代替S表達(dá)式格式。然后使用標(biāo)準(zhǔn)庫提供的json.Unmarshal解碼器來驗(yàn)證函數(shù)是正確的。

練習(xí) 12.6: 修改encode,作為一個優(yōu)化,忽略對是零值對象的編碼。

練習(xí) 12.7: 創(chuàng)建一個基于流式的API,用于S表達(dá)式的解碼,和json.Decoder(§4.5)函數(shù)功能類似。



以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號