gopl-zh.github.com/ch12/ch12-04.md
chai2010 c6f0f1bbdc ch12-04 done
Fixes #113
2016-01-01 11:45:19 +08:00

156 lines
6.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 12.4. 示例: 編碼S表達式
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)函數功能類似。