mirror of
https://github.com/golang/go.git
synced 2024-10-01 23:37:14 +00:00
add JSON library
R=r DELTA=1127 (1127 added, 0 deleted, 0 changed) OCL=20975 CL=20983
This commit is contained in:
parent
fa945d5bf8
commit
793a6effcf
@ -12,6 +12,7 @@ DIRS=\
|
|||||||
hash\
|
hash\
|
||||||
http\
|
http\
|
||||||
io\
|
io\
|
||||||
|
json\
|
||||||
math\
|
math\
|
||||||
net\
|
net\
|
||||||
os\
|
os\
|
||||||
@ -94,6 +95,8 @@ fmt.dirinstall: io.dirinstall reflect.dirinstall strconv.dirinstall
|
|||||||
hash.dirinstall: os.dirinstall
|
hash.dirinstall: os.dirinstall
|
||||||
http.dirinstall: bufio.install io.dirinstall net.dirinstall os.dirinstall strings.install
|
http.dirinstall: bufio.install io.dirinstall net.dirinstall os.dirinstall strings.install
|
||||||
io.dirinstall: os.dirinstall syscall.dirinstall
|
io.dirinstall: os.dirinstall syscall.dirinstall
|
||||||
|
json.dirinstall: container/array.dirinstall fmt.dirinstall io.dirinstall math.dirinstall \
|
||||||
|
strconv.dirinstall strings.install utf8.install
|
||||||
net.dirinstall: fmt.dirinstall once.install os.dirinstall strconv.dirinstall
|
net.dirinstall: fmt.dirinstall once.install os.dirinstall strconv.dirinstall
|
||||||
os.dirinstall: syscall.dirinstall
|
os.dirinstall: syscall.dirinstall
|
||||||
regexp.dirinstall: os.dirinstall
|
regexp.dirinstall: os.dirinstall
|
||||||
|
64
src/lib/json/Makefile
Normal file
64
src/lib/json/Makefile
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
# Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
# Use of this source code is governed by a BSD-style
|
||||||
|
# license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
# DO NOT EDIT. Automatically generated by gobuild.
|
||||||
|
# gobuild -m >Makefile
|
||||||
|
O=6
|
||||||
|
GC=$(O)g
|
||||||
|
CC=$(O)c -w
|
||||||
|
AS=$(O)a
|
||||||
|
AR=$(O)ar
|
||||||
|
|
||||||
|
default: packages
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f *.$O *.a $O.out
|
||||||
|
|
||||||
|
test: packages
|
||||||
|
gotest
|
||||||
|
|
||||||
|
coverage: packages
|
||||||
|
gotest
|
||||||
|
6cov -g `pwd` | grep -v '_test\.go:'
|
||||||
|
|
||||||
|
%.$O: %.go
|
||||||
|
$(GC) $*.go
|
||||||
|
|
||||||
|
%.$O: %.c
|
||||||
|
$(CC) $*.c
|
||||||
|
|
||||||
|
%.$O: %.s
|
||||||
|
$(AS) $*.s
|
||||||
|
|
||||||
|
O1=\
|
||||||
|
parse.$O\
|
||||||
|
|
||||||
|
O2=\
|
||||||
|
generic.$O\
|
||||||
|
struct.$O\
|
||||||
|
|
||||||
|
json.a: a1 a2
|
||||||
|
|
||||||
|
a1: $(O1)
|
||||||
|
$(AR) grc json.a parse.$O
|
||||||
|
rm -f $(O1)
|
||||||
|
|
||||||
|
a2: $(O2)
|
||||||
|
$(AR) grc json.a generic.$O struct.$O
|
||||||
|
rm -f $(O2)
|
||||||
|
|
||||||
|
newpkg: clean
|
||||||
|
$(AR) grc json.a
|
||||||
|
|
||||||
|
$(O1): newpkg
|
||||||
|
$(O2): a1
|
||||||
|
|
||||||
|
nuke: clean
|
||||||
|
rm -f $(GOROOT)/pkg/json.a
|
||||||
|
|
||||||
|
packages: json.a
|
||||||
|
|
||||||
|
install: packages
|
||||||
|
cp json.a $(GOROOT)/pkg/json.a
|
||||||
|
|
303
src/lib/json/generic.go
Normal file
303
src/lib/json/generic.go
Normal file
@ -0,0 +1,303 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Generic JSON representation.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"array";
|
||||||
|
"fmt";
|
||||||
|
"math";
|
||||||
|
"json";
|
||||||
|
"strconv";
|
||||||
|
"strings";
|
||||||
|
)
|
||||||
|
|
||||||
|
export const (
|
||||||
|
StringKind = iota;
|
||||||
|
NumberKind;
|
||||||
|
MapKind; // JSON term is "Object", but in Go, it's a map
|
||||||
|
ArrayKind;
|
||||||
|
BoolKind;
|
||||||
|
NullKind;
|
||||||
|
)
|
||||||
|
|
||||||
|
export type Json interface {
|
||||||
|
Kind() int;
|
||||||
|
String() string;
|
||||||
|
Number() float64;
|
||||||
|
Bool() bool;
|
||||||
|
Get(s string) Json;
|
||||||
|
Elem(i int) Json;
|
||||||
|
Len() int;
|
||||||
|
}
|
||||||
|
|
||||||
|
export func JsonToString(j Json) string {
|
||||||
|
if j == nil {
|
||||||
|
return "null"
|
||||||
|
}
|
||||||
|
if j.Kind() == StringKind {
|
||||||
|
return Quote(j.String())
|
||||||
|
}
|
||||||
|
return j.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
type Null struct { }
|
||||||
|
export var null Json = &Null{}
|
||||||
|
func (*Null) Kind() int { return NullKind }
|
||||||
|
func (*Null) String() string { return "null" }
|
||||||
|
func (*Null) Number() float64 { return 0 }
|
||||||
|
func (*Null) Bool() bool { return false }
|
||||||
|
func (*Null) Get(s string) Json { return null }
|
||||||
|
func (*Null) Elem(int) Json { return null }
|
||||||
|
func (*Null) Len() int { return 0 }
|
||||||
|
|
||||||
|
type String struct { s string; Null }
|
||||||
|
func (j *String) Kind() int { return StringKind }
|
||||||
|
func (j *String) String() string { return j.s }
|
||||||
|
|
||||||
|
type Number struct { f float64; Null }
|
||||||
|
func (j *Number) Kind() int { return NumberKind }
|
||||||
|
func (j *Number) Number() float64 { return j.f }
|
||||||
|
func (j *Number) String() string {
|
||||||
|
if math.Floor(j.f) == j.f {
|
||||||
|
return fmt.sprintf("%.0f", j.f);
|
||||||
|
}
|
||||||
|
return fmt.sprintf("%g", j.f);
|
||||||
|
}
|
||||||
|
|
||||||
|
type Array struct { a *array.Array; Null }
|
||||||
|
func (j *Array) Kind() int { return ArrayKind }
|
||||||
|
func (j *Array) Len() int { return j.a.Len() }
|
||||||
|
func (j *Array) Elem(i int) Json {
|
||||||
|
if i < 0 || i >= j.a.Len() {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return j.a.At(i)
|
||||||
|
}
|
||||||
|
func (j *Array) String() string {
|
||||||
|
s := "[";
|
||||||
|
for i := 0; i < j.a.Len(); i++ {
|
||||||
|
if i > 0 {
|
||||||
|
s += ",";
|
||||||
|
}
|
||||||
|
s += JsonToString(j.a.At(i).(Json));
|
||||||
|
}
|
||||||
|
s += "]";
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Bool struct { b bool; Null }
|
||||||
|
func (j *Bool) Kind() int { return BoolKind }
|
||||||
|
func (j *Bool) Bool() bool { return j.b }
|
||||||
|
func (j *Bool) String() string {
|
||||||
|
if j.b {
|
||||||
|
return "true"
|
||||||
|
}
|
||||||
|
return "false"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Map struct { m *map[string]Json; Null }
|
||||||
|
func (j *Map) Kind() int { return MapKind }
|
||||||
|
func (j *Map) Get(s string) Json {
|
||||||
|
if j.m == nil {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
v, ok := j.m[s];
|
||||||
|
if !ok {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
func (j *Map) String() string {
|
||||||
|
s := "{";
|
||||||
|
first := true;
|
||||||
|
for k,v range j.m {
|
||||||
|
if first {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
s += ",";
|
||||||
|
}
|
||||||
|
s += Quote(k);
|
||||||
|
s += ":";
|
||||||
|
s += JsonToString(v);
|
||||||
|
}
|
||||||
|
s += "}";
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
|
||||||
|
export func Walk(j Json, path string) Json {
|
||||||
|
for len(path) > 0 {
|
||||||
|
var elem string;
|
||||||
|
if i := strings.index(path, '/'); i >= 0 {
|
||||||
|
elem = path[0:i];
|
||||||
|
path = path[i+1:len(path)];
|
||||||
|
} else {
|
||||||
|
elem = path;
|
||||||
|
path = "";
|
||||||
|
}
|
||||||
|
switch j.Kind() {
|
||||||
|
case ArrayKind:
|
||||||
|
indx, err := strconv.atoi(elem);
|
||||||
|
if err != nil {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
j = j.Elem(indx);
|
||||||
|
case MapKind:
|
||||||
|
j = j.Get(elem);
|
||||||
|
default:
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return j
|
||||||
|
}
|
||||||
|
|
||||||
|
export func Equal(a, b Json) bool {
|
||||||
|
switch {
|
||||||
|
case a == nil && b == nil:
|
||||||
|
return true;
|
||||||
|
case a == nil || b == nil:
|
||||||
|
return false;
|
||||||
|
case a.Kind() != b.Kind():
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch a.Kind() {
|
||||||
|
case NullKind:
|
||||||
|
return true;
|
||||||
|
case StringKind:
|
||||||
|
return a.String() == b.String();
|
||||||
|
case NumberKind:
|
||||||
|
return a.Number() == b.Number();
|
||||||
|
case BoolKind:
|
||||||
|
return a.Bool() == b.Bool();
|
||||||
|
case ArrayKind:
|
||||||
|
if a.Len() != b.Len() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for i := 0; i < a.Len(); i++ {
|
||||||
|
if !Equal(a.Elem(i), b.Elem(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MapKind:
|
||||||
|
m := a.(*Map).m;
|
||||||
|
if len(m) != len(b.(*Map).m) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for k,v range m {
|
||||||
|
if !Equal(v, b.Get(k)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// invalid kind
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parse builder for Json objects.
|
||||||
|
|
||||||
|
type JsonBuilder struct {
|
||||||
|
// either writing to *ptr
|
||||||
|
ptr *Json;
|
||||||
|
|
||||||
|
// or to a[i] (can't set ptr = &a[i])
|
||||||
|
a *array.Array;
|
||||||
|
i int;
|
||||||
|
|
||||||
|
// or to m[k] (can't set ptr = &m[k])
|
||||||
|
m *map[string] Json;
|
||||||
|
k string;
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Put(j Json) {
|
||||||
|
switch {
|
||||||
|
case b.ptr != nil:
|
||||||
|
*b.ptr = j;
|
||||||
|
case b.a != nil:
|
||||||
|
b.a.Set(b.i, j);
|
||||||
|
case b.m != nil:
|
||||||
|
b.m[b.k] = j;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Get() Json {
|
||||||
|
switch {
|
||||||
|
case b.ptr != nil:
|
||||||
|
return *b.ptr;
|
||||||
|
case b.a != nil:
|
||||||
|
return b.a.At(b.i);
|
||||||
|
case b.m != nil:
|
||||||
|
return b.m[b.k];
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Float64(f float64) {
|
||||||
|
b.Put(&Number{f, Null{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Int64(i int64) {
|
||||||
|
b.Float64(float64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Uint64(i uint64) {
|
||||||
|
b.Float64(float64(i))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Bool(tf bool) {
|
||||||
|
b.Put(&Bool{tf, Null{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Null() {
|
||||||
|
b.Put(null)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) String(s string) {
|
||||||
|
b.Put(&String{s, Null{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Array() {
|
||||||
|
b.Put(&Array{array.New(0), Null{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Map() {
|
||||||
|
b.Put(&Map{new(map[string]Json), Null{}})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Elem(i int) Builder {
|
||||||
|
bb := new(JsonBuilder);
|
||||||
|
bb.a = b.Get().(*Array).a;
|
||||||
|
bb.i = i;
|
||||||
|
for i >= bb.a.Len() {
|
||||||
|
bb.a.Push(null)
|
||||||
|
}
|
||||||
|
return bb
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *JsonBuilder) Key(k string) Builder {
|
||||||
|
bb := new(JsonBuilder);
|
||||||
|
bb.m = b.Get().(*Map).m;
|
||||||
|
bb.k = k;
|
||||||
|
bb.m[k] = null;
|
||||||
|
return bb
|
||||||
|
}
|
||||||
|
|
||||||
|
export func StringToJson(s string) (json Json, ok bool, errtok string) {
|
||||||
|
var errindx int;
|
||||||
|
var j Json;
|
||||||
|
b := new(JsonBuilder);
|
||||||
|
b.ptr = &j;
|
||||||
|
ok, errindx, errtok = Parse(s, b);
|
||||||
|
if !ok {
|
||||||
|
return nil, false, errtok
|
||||||
|
}
|
||||||
|
return j, true, ""
|
||||||
|
}
|
72
src/lib/json/generic_test.go
Normal file
72
src/lib/json/generic_test.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"json";
|
||||||
|
"testing";
|
||||||
|
)
|
||||||
|
|
||||||
|
var jsontests = []string {
|
||||||
|
`null`,
|
||||||
|
`true`,
|
||||||
|
`false`,
|
||||||
|
`"abc"`,
|
||||||
|
`123`,
|
||||||
|
`0.1`,
|
||||||
|
`1e-10`,
|
||||||
|
`[]`,
|
||||||
|
`[1,2,3,4]`,
|
||||||
|
`[1,2,"abc",null,true,false]`,
|
||||||
|
`{}`,
|
||||||
|
`{"a":1}`,
|
||||||
|
}
|
||||||
|
|
||||||
|
export func TestJson(t *testing.T) {
|
||||||
|
for i := 0; i < len(jsontests); i++ {
|
||||||
|
val, ok, errtok := StringToJson(jsontests[i]);
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("StringToJson(%#q) => error near %v", jsontests[i], errtok);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
str := JsonToString(val);
|
||||||
|
if str != jsontests[i] {
|
||||||
|
t.Errorf("JsonToString(StringToJson(%#q)) = %#q", jsontests[i], str);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export func TestJsonMap(t *testing.T) {
|
||||||
|
values := new(map[string]Json);
|
||||||
|
mapstr := "{";
|
||||||
|
for i := 0; i < len(jsontests); i++ {
|
||||||
|
val, ok, errtok := StringToJson(jsontests[i]);
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("StringToJson(%#q) => error near %v", jsontests[i], errtok);
|
||||||
|
}
|
||||||
|
if i > 0 {
|
||||||
|
mapstr += ",";
|
||||||
|
}
|
||||||
|
values[jsontests[i]] = val;
|
||||||
|
mapstr += Quote(jsontests[i]);
|
||||||
|
mapstr += ":";
|
||||||
|
mapstr += JsonToString(val);
|
||||||
|
}
|
||||||
|
mapstr += "}";
|
||||||
|
|
||||||
|
mapv, ok, errtok := StringToJson(mapstr);
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("StringToJson(%#q) => error near %v", mapstr, errtok);
|
||||||
|
}
|
||||||
|
if mapv == nil {
|
||||||
|
t.Fatalf("StringToJson(%#q) => nil, %v, %v", mapstr, ok, errtok);
|
||||||
|
}
|
||||||
|
for k,v range values {
|
||||||
|
if v1 := mapv.Get(k); !Equal(v1, v) {
|
||||||
|
t.Errorf("MapTest: Walk(%#q) => %v, want %v", k, v1, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
400
src/lib/json/parse.go
Normal file
400
src/lib/json/parse.go
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// JSON (JavaScript Object Notation) parser.
|
||||||
|
// See http://www.json.org/
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"array";
|
||||||
|
"fmt";
|
||||||
|
"io";
|
||||||
|
"math";
|
||||||
|
"strconv";
|
||||||
|
"strings";
|
||||||
|
"utf8";
|
||||||
|
)
|
||||||
|
|
||||||
|
// Strings
|
||||||
|
//
|
||||||
|
// Double quoted with escapes: \" \\ \/ \b \f \n \r \t \uXXXX.
|
||||||
|
// No literal control characters, supposedly.
|
||||||
|
// Have also seen \' and embedded newlines.
|
||||||
|
|
||||||
|
func UnHex(p string, r, l int) (v int, ok bool) {
|
||||||
|
v = 0;
|
||||||
|
for i := r; i < l; i++ {
|
||||||
|
if i >= len(p) {
|
||||||
|
return 0, false
|
||||||
|
}
|
||||||
|
v *= 16;
|
||||||
|
switch {
|
||||||
|
case '0' <= p[i] && p[i] <= '9':
|
||||||
|
v += int(p[i] - '0');
|
||||||
|
case 'a' <= p[i] && p[i] <= 'f':
|
||||||
|
v += int(p[i] - 'a' + 10);
|
||||||
|
case 'A' <= p[i] && p[i] <= 'F':
|
||||||
|
v += int(p[i] - 'A' + 10);
|
||||||
|
default:
|
||||||
|
return 0, false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return v, true;
|
||||||
|
}
|
||||||
|
|
||||||
|
export func Unquote(s string) (t string, ok bool) {
|
||||||
|
if len(s) < 2 || s[0] != '"' || s[len(s)-1] != '"' {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := new([]byte, len(s));
|
||||||
|
w := 0;
|
||||||
|
for r := 1; r < len(s)-1; {
|
||||||
|
switch {
|
||||||
|
case s[r] == '\\':
|
||||||
|
r++;
|
||||||
|
if r >= len(s)-1 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch s[r] {
|
||||||
|
default:
|
||||||
|
return;
|
||||||
|
case '"', '\\', '/', '\'':
|
||||||
|
b[w] = s[r];
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
case 'b':
|
||||||
|
b[w] = '\b';
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
case 'f':
|
||||||
|
b[w] = '\f';
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
case 'n':
|
||||||
|
b[w] = '\n';
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
case 'r':
|
||||||
|
b[w] = '\r';
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
case 't':
|
||||||
|
b[w] = '\t';
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
case 'u':
|
||||||
|
r++;
|
||||||
|
rune, ok := UnHex(s, r, 4);
|
||||||
|
if !ok {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r += 4;
|
||||||
|
w += utf8.EncodeRune(rune, b[w:len(b)]);
|
||||||
|
}
|
||||||
|
// Control characters are invalid, but we've seen raw \n.
|
||||||
|
case s[r] < ' ' && s[r] != '\n':
|
||||||
|
if s[r] == '\n' {
|
||||||
|
b[w] = '\n';
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
// ASCII
|
||||||
|
case s[r] < utf8.RuneSelf:
|
||||||
|
b[w] = s[r];
|
||||||
|
r++;
|
||||||
|
w++;
|
||||||
|
// Coerce to well-formed UTF-8.
|
||||||
|
default:
|
||||||
|
rune, size := utf8.DecodeRuneInString(s, r);
|
||||||
|
r += size;
|
||||||
|
w += utf8.EncodeRune(rune, b[w:len(b)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return string(b[0:w]), true
|
||||||
|
}
|
||||||
|
|
||||||
|
export func Quote(s string) string {
|
||||||
|
chr := new([]byte, utf8.UTFMax);
|
||||||
|
chr0 := chr[0:1];
|
||||||
|
b := new(io.ByteBuffer);
|
||||||
|
chr[0] = '"';
|
||||||
|
b.Write(chr0);
|
||||||
|
for i := 0; i < len(s); i++ {
|
||||||
|
switch {
|
||||||
|
case s[i]=='"' || s[i]=='\\':
|
||||||
|
chr[0] = '\\';
|
||||||
|
chr[1] = s[i];
|
||||||
|
b.Write(chr[0:2]);
|
||||||
|
|
||||||
|
case s[i] == '\b':
|
||||||
|
chr[0] = '\\';
|
||||||
|
chr[1] = 'b';
|
||||||
|
b.Write(chr[0:2]);
|
||||||
|
|
||||||
|
case s[i] == '\f':
|
||||||
|
chr[0] = '\\';
|
||||||
|
chr[1] = 'f';
|
||||||
|
b.Write(chr[0:2]);
|
||||||
|
|
||||||
|
case s[i] == '\n':
|
||||||
|
chr[0] = '\\';
|
||||||
|
chr[1] = 'n';
|
||||||
|
b.Write(chr[0:2]);
|
||||||
|
|
||||||
|
case s[i] == '\r':
|
||||||
|
chr[0] = '\\';
|
||||||
|
chr[1] = 'r';
|
||||||
|
b.Write(chr[0:2]);
|
||||||
|
|
||||||
|
case s[i] == '\t':
|
||||||
|
chr[0] = '\\';
|
||||||
|
chr[1] = 't';
|
||||||
|
b.Write(chr[0:2]);
|
||||||
|
|
||||||
|
case 0x20 <= s[i] && s[i] < utf8.RuneSelf:
|
||||||
|
chr[0] = s[i];
|
||||||
|
b.Write(chr0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
chr[0] = '"';
|
||||||
|
b.Write(chr0);
|
||||||
|
return string(b.Data());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Lexer
|
||||||
|
|
||||||
|
type Lexer struct {
|
||||||
|
s string;
|
||||||
|
i int;
|
||||||
|
kind int;
|
||||||
|
token string;
|
||||||
|
}
|
||||||
|
|
||||||
|
func Punct(c byte) bool {
|
||||||
|
return c=='"' || c=='[' || c==']' || c==':' || c=='{' || c=='}' || c==','
|
||||||
|
}
|
||||||
|
|
||||||
|
func White(c byte) bool {
|
||||||
|
return c==' ' || c=='\t' || c=='\n' || c=='\v'
|
||||||
|
}
|
||||||
|
|
||||||
|
func SkipWhite(p string, i int) int {
|
||||||
|
for i < len(p) && White(p[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func SkipToken(p string, i int) int {
|
||||||
|
for i < len(p) && !Punct(p[i]) && !White(p[i]) {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
|
||||||
|
func SkipString(p string, i int) int {
|
||||||
|
for i++; i < len(p) && p[i] != '"'; i++ {
|
||||||
|
if p[i] == '\\' {
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if i >= len(p) {
|
||||||
|
return i
|
||||||
|
}
|
||||||
|
return i+1
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *Lexer) Next() {
|
||||||
|
i, s := t.i, t.s;
|
||||||
|
i = SkipWhite(s, i);
|
||||||
|
if i >= len(s) {
|
||||||
|
t.kind = 0;
|
||||||
|
t.token = "";
|
||||||
|
t.i = len(s);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
c := s[i];
|
||||||
|
switch {
|
||||||
|
case c == '-' || '0' <= c && c <= '9':
|
||||||
|
j := SkipToken(s, i);
|
||||||
|
t.kind = '1';
|
||||||
|
t.token = s[i:j];
|
||||||
|
i = j;
|
||||||
|
|
||||||
|
case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
|
||||||
|
j := SkipToken(s, i);
|
||||||
|
t.kind = 'a';
|
||||||
|
t.token = s[i:j];
|
||||||
|
i = j;
|
||||||
|
|
||||||
|
case c == '"':
|
||||||
|
j := SkipString(s, i);
|
||||||
|
t.kind = '"';
|
||||||
|
t.token = s[i:j];
|
||||||
|
i = j;
|
||||||
|
|
||||||
|
case c == '[', c == ']', c == ':', c == '{', c == '}', c == ',':
|
||||||
|
t.kind = int(c);
|
||||||
|
t.token = s[i:i+1];
|
||||||
|
i++;
|
||||||
|
|
||||||
|
default:
|
||||||
|
t.kind = '?';
|
||||||
|
t.token = s[i:i+1];
|
||||||
|
}
|
||||||
|
|
||||||
|
t.i = i;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// Parser
|
||||||
|
//
|
||||||
|
// Implements parsing but not the actions. Those are
|
||||||
|
// carried out by the implementation of the Builder interface.
|
||||||
|
// A Builder represents the object being created.
|
||||||
|
// Calling a method like Int64(i) sets that object to i.
|
||||||
|
// Calling a method like Elem(i) or Key(s) creates a
|
||||||
|
// new builder for a subpiece of the object (logically,
|
||||||
|
// an array element or a map key).
|
||||||
|
//
|
||||||
|
// There are two Builders, in other files.
|
||||||
|
// The JsonBuilder builds a generic Json structure
|
||||||
|
// in which maps are maps.
|
||||||
|
// The StructBuilder copies data into a possibly
|
||||||
|
// nested data structure, using the "map keys"
|
||||||
|
// as struct field names.
|
||||||
|
|
||||||
|
type Value interface {}
|
||||||
|
|
||||||
|
export type Builder interface {
|
||||||
|
// Set value
|
||||||
|
Int64(i int64);
|
||||||
|
Uint64(i uint64);
|
||||||
|
Float64(f float64);
|
||||||
|
String(s string);
|
||||||
|
Bool(b bool);
|
||||||
|
Null();
|
||||||
|
Array();
|
||||||
|
Map();
|
||||||
|
|
||||||
|
// Create sub-Builders
|
||||||
|
Elem(i int) Builder;
|
||||||
|
Key(s string) Builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
func ParseValue(lex *Lexer, build Builder) bool {
|
||||||
|
ok := false;
|
||||||
|
Switch:
|
||||||
|
switch lex.kind {
|
||||||
|
case 0:
|
||||||
|
break;
|
||||||
|
case '1':
|
||||||
|
// If the number is exactly an integer, use that.
|
||||||
|
if i, err := strconv.atoi64(lex.token); err == nil {
|
||||||
|
build.Int64(i);
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
else if i, err := strconv.atoui64(lex.token); err == nil {
|
||||||
|
build.Uint64(i);
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
// Fall back to floating point.
|
||||||
|
else if f, err := strconv.atof64(lex.token); err == nil {
|
||||||
|
build.Float64(f);
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case 'a':
|
||||||
|
switch lex.token {
|
||||||
|
case "true":
|
||||||
|
build.Bool(true);
|
||||||
|
ok = true;
|
||||||
|
case "false":
|
||||||
|
build.Bool(false);
|
||||||
|
ok = true;
|
||||||
|
case "null":
|
||||||
|
build.Null();
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '"':
|
||||||
|
if str, ok1 := Unquote(lex.token); ok1 {
|
||||||
|
build.String(str);
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
case '[':
|
||||||
|
// array
|
||||||
|
build.Array();
|
||||||
|
lex.Next();
|
||||||
|
n := 0;
|
||||||
|
for lex.kind != ']' {
|
||||||
|
if n > 0 {
|
||||||
|
if lex.kind != ',' {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
lex.Next();
|
||||||
|
}
|
||||||
|
if !ParseValue(lex, build.Elem(n)) {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
|
||||||
|
case '{':
|
||||||
|
// map
|
||||||
|
lex.Next();
|
||||||
|
build.Map();
|
||||||
|
n := 0;
|
||||||
|
for lex.kind != '}' {
|
||||||
|
if n > 0 {
|
||||||
|
if lex.kind != ',' {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
lex.Next();
|
||||||
|
}
|
||||||
|
if lex.kind != '"' {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
key, ok := Unquote(lex.token);
|
||||||
|
if !ok {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
lex.Next();
|
||||||
|
if lex.kind != ':' {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
lex.Next();
|
||||||
|
if !ParseValue(lex, build.Key(key)) {
|
||||||
|
break Switch;
|
||||||
|
}
|
||||||
|
n++;
|
||||||
|
}
|
||||||
|
ok = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ok {
|
||||||
|
lex.Next();
|
||||||
|
}
|
||||||
|
return ok;
|
||||||
|
}
|
||||||
|
|
||||||
|
export func Parse(s string, build Builder) (ok bool, errindx int, errtok string) {
|
||||||
|
lex := new(Lexer);
|
||||||
|
lex.s = s;
|
||||||
|
lex.Next();
|
||||||
|
if ParseValue(lex, build) {
|
||||||
|
if lex.kind == 0 { // EOF
|
||||||
|
return true, 0, ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false, lex.i, lex.token
|
||||||
|
}
|
||||||
|
|
214
src/lib/json/struct.go
Normal file
214
src/lib/json/struct.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
// Marshalling and unmarshalling of
|
||||||
|
// JSON data into Go structs using reflection.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"json";
|
||||||
|
"reflect";
|
||||||
|
)
|
||||||
|
|
||||||
|
type StructBuilder struct {
|
||||||
|
val reflect.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
var nobuilder *StructBuilder
|
||||||
|
|
||||||
|
func SetFloat(v reflect.Value, f float64) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.FloatKind:
|
||||||
|
v.(reflect.FloatValue).Set(float(f));
|
||||||
|
case reflect.Float32Kind:
|
||||||
|
v.(reflect.Float32Value).Set(float32(f));
|
||||||
|
case reflect.Float64Kind:
|
||||||
|
v.(reflect.Float64Value).Set(float64(f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func SetInt(v reflect.Value, i int64) {
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.IntKind:
|
||||||
|
v.(reflect.IntValue).Set(int(i));
|
||||||
|
case reflect.Int8Kind:
|
||||||
|
v.(reflect.Int8Value).Set(int8(i));
|
||||||
|
case reflect.Int16Kind:
|
||||||
|
v.(reflect.Int16Value).Set(int16(i));
|
||||||
|
case reflect.Int32Kind:
|
||||||
|
v.(reflect.Int32Value).Set(int32(i));
|
||||||
|
case reflect.Int64Kind:
|
||||||
|
v.(reflect.Int64Value).Set(int64(i));
|
||||||
|
case reflect.UintKind:
|
||||||
|
v.(reflect.UintValue).Set(uint(i));
|
||||||
|
case reflect.Uint8Kind:
|
||||||
|
v.(reflect.Uint8Value).Set(uint8(i));
|
||||||
|
case reflect.Uint16Kind:
|
||||||
|
v.(reflect.Uint16Value).Set(uint16(i));
|
||||||
|
case reflect.Uint32Kind:
|
||||||
|
v.(reflect.Uint32Value).Set(uint32(i));
|
||||||
|
case reflect.Uint64Kind:
|
||||||
|
v.(reflect.Uint64Value).Set(uint64(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Int64(i int64) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v := b.val;
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.FloatKind, reflect.Float32Kind, reflect.Float64Kind:
|
||||||
|
SetFloat(v, float64(i));
|
||||||
|
default:
|
||||||
|
SetInt(v, i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Uint64(i uint64) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v := b.val;
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.FloatKind, reflect.Float32Kind, reflect.Float64Kind:
|
||||||
|
SetFloat(v, float64(i));
|
||||||
|
default:
|
||||||
|
SetInt(v, int64(i));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Float64(f float64) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
v := b.val;
|
||||||
|
switch v.Kind() {
|
||||||
|
case reflect.FloatKind, reflect.Float32Kind, reflect.Float64Kind:
|
||||||
|
SetFloat(v, f);
|
||||||
|
default:
|
||||||
|
SetInt(v, int64(f));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Null() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) String(s string) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := b.val; v.Kind() == reflect.StringKind {
|
||||||
|
v.(reflect.StringValue).Set(s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Bool(tf bool) {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := b.val; v.Kind() == reflect.BoolKind {
|
||||||
|
v.(reflect.BoolValue).Set(tf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Array() {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := b.val; v.Kind() == reflect.PtrKind {
|
||||||
|
pv := v.(reflect.PtrValue);
|
||||||
|
psubtype := pv.Type().(reflect.PtrType).Sub();
|
||||||
|
if pv.Get() == nil && psubtype.Kind() == reflect.ArrayKind {
|
||||||
|
av := reflect.NewOpenArrayValue(psubtype, 0, 8);
|
||||||
|
pv.SetSub(av);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Elem(i int) Builder {
|
||||||
|
if b == nil || i < 0 {
|
||||||
|
return nobuilder
|
||||||
|
}
|
||||||
|
v := b.val;
|
||||||
|
if v.Kind() == reflect.PtrKind {
|
||||||
|
// If we have a pointer to an array, allocate or grow
|
||||||
|
// the array as necessary. Then set v to the array itself.
|
||||||
|
pv := v.(reflect.PtrValue);
|
||||||
|
psub := pv.Sub();
|
||||||
|
if psub.Kind() == reflect.ArrayKind {
|
||||||
|
av := psub.(reflect.ArrayValue);
|
||||||
|
if i > av.Cap() {
|
||||||
|
n := av.Cap();
|
||||||
|
if n < 8 {
|
||||||
|
n = 8
|
||||||
|
}
|
||||||
|
for n <= i {
|
||||||
|
n *= 2
|
||||||
|
}
|
||||||
|
av1 := reflect.NewOpenArrayValue(av.Type(), av.Len(), n);
|
||||||
|
reflect.CopyArray(av1, av, av.Len());
|
||||||
|
pv.SetSub(av1);
|
||||||
|
av = av1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
v = psub;
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.ArrayKind {
|
||||||
|
// Array was grown above, or is fixed size.
|
||||||
|
av := v.(reflect.ArrayValue);
|
||||||
|
if av.Len() <= i && i < av.Cap() {
|
||||||
|
av.SetLen(i+1);
|
||||||
|
}
|
||||||
|
if i < av.Len() {
|
||||||
|
return &StructBuilder{ av.Elem(i) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nobuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Map() {
|
||||||
|
if b == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if v := b.val; v.Kind() == reflect.PtrKind {
|
||||||
|
pv := v.(reflect.PtrValue);
|
||||||
|
if pv.Get() == nil {
|
||||||
|
pv.SetSub(reflect.NewInitValue(pv.Type().(reflect.PtrType).Sub()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *StructBuilder) Key(k string) Builder {
|
||||||
|
if b == nil {
|
||||||
|
return nobuilder
|
||||||
|
}
|
||||||
|
v := b.val;
|
||||||
|
if v.Kind() == reflect.PtrKind {
|
||||||
|
v = v.(reflect.PtrValue).Sub();
|
||||||
|
}
|
||||||
|
if v.Kind() == reflect.StructKind {
|
||||||
|
sv := v.(reflect.StructValue);
|
||||||
|
t := v.Type().(reflect.StructType);
|
||||||
|
for i := 0; i < t.Len(); i++ {
|
||||||
|
name, typ, tag, off := t.Field(i);
|
||||||
|
if k == name {
|
||||||
|
return &StructBuilder{ sv.Field(i) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nobuilder
|
||||||
|
}
|
||||||
|
|
||||||
|
export func Unmarshal(s string, val interface{}) (ok bool, errtok string) {
|
||||||
|
var errindx int;
|
||||||
|
var val1 interface{};
|
||||||
|
b := &StructBuilder{ reflect.NewValue(val) };
|
||||||
|
ok, errindx, errtok = Parse(s, b);
|
||||||
|
if !ok {
|
||||||
|
return false, errtok
|
||||||
|
}
|
||||||
|
return true, ""
|
||||||
|
}
|
82
src/lib/json/struct_test.go
Normal file
82
src/lib/json/struct_test.go
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
// Copyright 2009 The Go Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package json
|
||||||
|
|
||||||
|
import (
|
||||||
|
"json";
|
||||||
|
"testing";
|
||||||
|
)
|
||||||
|
|
||||||
|
type MyStruct struct {
|
||||||
|
t bool;
|
||||||
|
f bool;
|
||||||
|
s string;
|
||||||
|
i8 int8;
|
||||||
|
i16 int16;
|
||||||
|
i32 int32;
|
||||||
|
i64 int64;
|
||||||
|
u8 uint8;
|
||||||
|
u16 uint16;
|
||||||
|
u32 uint32;
|
||||||
|
u64 uint64;
|
||||||
|
i int;
|
||||||
|
u uint;
|
||||||
|
fl float;
|
||||||
|
fl32 float32;
|
||||||
|
fl64 float64;
|
||||||
|
a *[]string;
|
||||||
|
my *MyStruct;
|
||||||
|
};
|
||||||
|
|
||||||
|
const Encoded =
|
||||||
|
`{"t":true,"f":false,"s":"abc","i8":1,"i16":2,"i32":3,"i64":4,`
|
||||||
|
` "u8":5,"u16":6,"u32":7,"u64":8,`
|
||||||
|
` "i":-9,"u":10,"bogusfield":"should be ignored",`
|
||||||
|
` "fl":11.5,"fl32":12.25,"fl64":13.75,`
|
||||||
|
` "a":["x","y","z"],"my":{"s":"subguy"}}`;
|
||||||
|
|
||||||
|
|
||||||
|
func Check(t *testing.T, ok bool, name string, v interface{}) {
|
||||||
|
if !ok {
|
||||||
|
t.Errorf("%s = %v (BAD)", name, v);
|
||||||
|
} else {
|
||||||
|
t.Logf("%s = %v (good)", name, v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export func TestUnmarshal(t *testing.T) {
|
||||||
|
var m MyStruct;
|
||||||
|
m.f = true;
|
||||||
|
ok, errtok := Unmarshal(Encoded, &m);
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("Unmarshal failed near %s", errtok);
|
||||||
|
}
|
||||||
|
Check(t, m.t==true, "t", m.t);
|
||||||
|
Check(t, m.f==false, "f", m.f);
|
||||||
|
Check(t, m.s=="abc", "s", m.s);
|
||||||
|
Check(t, m.i8==1, "i8", m.i8);
|
||||||
|
Check(t, m.i16==2, "i16", m.i16);
|
||||||
|
Check(t, m.i32==3, "i32", m.i32);
|
||||||
|
Check(t, m.i64==4, "i64", m.i64);
|
||||||
|
Check(t, m.u8==5, "u8", m.u8);
|
||||||
|
Check(t, m.u16==6, "u16", m.u16);
|
||||||
|
Check(t, m.u32==7, "u32", m.u32);
|
||||||
|
Check(t, m.u64==8, "u64", m.u64);
|
||||||
|
Check(t, m.i==-9, "i", m.i);
|
||||||
|
Check(t, m.u==10, "u", m.u);
|
||||||
|
Check(t, m.fl==11.5, "fl", m.fl);
|
||||||
|
Check(t, m.fl32==12.25, "fl32", m.fl32);
|
||||||
|
Check(t, m.fl64==13.75, "fl64", m.fl64);
|
||||||
|
Check(t, m.a!=nil, "a", m.a);
|
||||||
|
if m.a != nil {
|
||||||
|
Check(t, m.a[0]=="x", "a[0]", m.a[0]);
|
||||||
|
Check(t, m.a[1]=="y", "a[1]", m.a[1]);
|
||||||
|
Check(t, m.a[2]=="z", "a[2]", m.a[2]);
|
||||||
|
}
|
||||||
|
Check(t, m.my!=nil, "my", m.my);
|
||||||
|
if m.my != nil {
|
||||||
|
Check(t, m.my.s=="subguy", "my.s", m.my.s);
|
||||||
|
}
|
||||||
|
}
|
@ -26,6 +26,7 @@ maketest() {
|
|||||||
maketest \
|
maketest \
|
||||||
lib/fmt\
|
lib/fmt\
|
||||||
lib/hash\
|
lib/hash\
|
||||||
|
lib/json\
|
||||||
lib/math\
|
lib/math\
|
||||||
lib/reflect\
|
lib/reflect\
|
||||||
lib/regexp\
|
lib/regexp\
|
||||||
|
Loading…
Reference in New Issue
Block a user