From c6f0f1bbdc87cbdaa0456e6de99d0a93a6fba610 Mon Sep 17 00:00:00 2001 From: chai2010 Date: Fri, 1 Jan 2016 11:45:19 +0800 Subject: [PATCH] ch12-04 done Fixes #113 --- ch12/ch12-04.md | 154 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 153 insertions(+), 1 deletion(-) diff --git a/ch12/ch12-04.md b/ch12/ch12-04.md index 5bb26e2..fb57747 100644 --- a/ch12/ch12-04.md +++ b/ch12/ch12-04.md @@ -1,3 +1,155 @@ ## 12.4. 示例: 編碼S表達式 -TODO +Display是一個用於顯示結構化數據的調試工具,但是它併不能將任意的Go語言對象編碼爲通用消息然後用於進程間通信。 + +正如我們在4.5節中中看到的,Go語言的標準庫支持了包括JSON、XML和ASN.1等多種編碼格式。還有另一種依然被廣泛使用的格式是S表達式格式,采用類似Lisp語言的語法。但是和其他編碼格式不同的是,Go語言自帶的標準庫併不支持S表達式,主要是因爲它沒有一個公認的標準規范。 + +在本節中,我們將定義一個包用於將Go語言的對象編碼爲S表達式格式,它支持以下結構: + +``` +42 integer +"hello" string (with Go-style quotation) +foo symbol (an unquoted name) +(1 2 3) list (zero or more items enclosed in parentheses) +``` + +布爾型習慣上使用t符號表示true,空列表或nil符號表示false,但是爲了簡單起見,我們暫時忽略布爾類型。同時忽略的還有chan管道和函數,因爲通過反射併無法知道它們的確切狀態。我們忽略的還浮點數、複數和interface。支持它們是練習12.3的任務。 + +我們將Go語言的類型編碼爲S表達式的方法如下。整數和字符串以自然的方式編碼。Nil值編碼爲nil符號。數組和slice被編碼爲一個列表。 + +結構體被編碼爲成員對象的列表,每個成員對象對應一個個僅有兩個元素的子列表,其中子列表的第一個元素是成員的名字,子列表的第二個元素是成員的值。Map被編碼爲鍵值對的列表。傳統上,S表達式使用點狀符號列表(key . value)結構來表示key/value對,而不是用一個含雙元素的列表,不過爲了簡單我們忽略了點狀符號列表。 + +編碼是由一個encode遞歸函數完成,如下所示。它的結構本質上和前面的Display函數類似: + +```Go +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函數是對encode的保證,以保持和encoding/...下其它包有着相似的API: + +```Go +// 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節的strangelove變量編碼後的結果: + +``` +((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表達式格式化的約定。編寫一個S表達式的格式化函數將作爲一個具有挑戰性的練習任務;不過 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函數類似,sexpr.Marshal函數處理帶環的數據結構也會陷入死循環。 + +在12.6節中,我們將給出S表達式解碼器的實現步驟,但是在那之前,我們還需要先了解如果通過反射技術來更新程序的變量。 + +**練習 12.3:** 實現encode函數缺少的分支。將布爾類型編碼爲t和nil,浮點數編碼爲Go語言的格式,複數1+2i編碼爲#C(1.0 2.0)格式。接口編碼爲類型名和值對,例如("[]int" (1 2 3)),但是這個形式可能會造成歧義:reflect.Type.String方法對於不同的類型可能返迴相同的結果。 + +**練習 12.4:** 脩改encode函數,以上面的格式化形式輸出S表達式。 + +**練習 12.5:** 脩改encode函數,用JSON格式代替S表達式格式。然後使用標準庫提供的json.Unmarshal解碼器來驗證函數是正確的。 + +**練習 12.6:** 脩改encode,作爲一個優化,忽略對是零值對象的編碼。 + +**練習 12.7:** 創建一個基於流式的API,用於S表達式的解碼,和json.Decoder(§4.5)函數功能類似。 + +