mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-11-16 19:33:47 +00:00
8fda418f3a
Change-Id: I7b6938936231fd722814984678ffa30402539fd9
153 lines
6.4 KiB
Markdown
153 lines
6.4 KiB
Markdown
## 12.4. 示例: 编码为S表达式
|
||
|
||
Display是一个用于显示结构化数据的调试工具,但是它并不能将任意的Go语言对象编码为通用消息然后用于进程间通信。
|
||
|
||
正如我们在4.5节中中看到的,Go语言的标准库支持了包括JSON、XML和ASN.1等多种编码格式。还有另一种依然被广泛使用的格式是S表达式格式,采用Lisp语言的语法。但是和其他编码格式不同的是,Go语言自带的标准库并不支持S表达式,主要是因为它没有一个公认的标准规范。
|
||
|
||
在本节中,我们将定义一个包用于将任意的Go语言对象编码为S表达式格式,它支持以下结构:
|
||
|
||
```
|
||
42 integer
|
||
"hello" string (带有Go风格的引号)
|
||
foo symbol (未用引号括起来的名字)
|
||
(1 2 3) list (括号包起来的0个或多个元素)
|
||
```
|
||
|
||
布尔型习惯上使用t符号表示true,空列表或nil符号表示false,但是为了简单起见,我们暂时忽略布尔类型。同时忽略的还有chan管道和函数,因为通过反射并无法知道它们的确切状态。我们忽略的还有浮点数、复数和interface。支持它们是练习12.3的任务。
|
||
|
||
我们将Go语言的类型编码为S表达式的方法如下。整数和字符串以显而易见的方式编码。空值编码为nil符号。数组和slice被编码为列表。
|
||
|
||
结构体被编码为成员对象的列表,每个成员对象对应一个有两个元素的子列表,子列表的第一个元素是成员的名字,第二个元素是成员的值。Map被编码为键值对的列表。传统上,S表达式使用点状符号列表(key . value)结构来表示key/value对,而不是用一个含双元素的列表,不过为了简单我们忽略了点状符号列表。
|
||
|
||
编码是由一个encode递归函数完成,如下所示。它的结构本质上和前面的Display函数类似:
|
||
|
||
<u><i>gopl.io/ch12/sexpr</i></u>
|
||
```Go
|
||
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)函数功能类似。
|