From 10cb56adf5f339cf9fe57acf54fc416a39392384 Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Mon, 20 Apr 2015 20:44:25 -0400 Subject: [PATCH] started writing an emitter --- lib/emit.go | 110 +++++++++++++++++++++++++++++++++++++++++++++++ lib/emit_test.go | 39 +++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 lib/emit.go create mode 100644 lib/emit_test.go diff --git a/lib/emit.go b/lib/emit.go new file mode 100644 index 0000000..8d63f40 --- /dev/null +++ b/lib/emit.go @@ -0,0 +1,110 @@ +package moon + +import ( + "bytes" + "math" + "reflect" + "strconv" +) + +func Encode(v interface{}) ([]byte, error) { + e := &encoder{} + if err := e.encode(v); err != nil { + return nil, err + } + return e.Bytes(), nil +} + +type encoder struct { + bytes.Buffer + scratch [64]byte +} + +func (e *encoder) encode(v interface{}) error { + e.encodeValue(reflect.ValueOf(v)) + return nil +} + +func (e *encoder) encodeValue(v reflect.Value) { + fn := valueEncoder(v) + fn(e, v) +} + +func valueEncoder(v reflect.Value) encodeFn { + if !v.IsValid() { + return encodeNull + } + return typeEncoder(v.Type()) +} + +func typeEncoder(t reflect.Type) encodeFn { + switch t.Kind() { + case reflect.Bool: + return encodeBool + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return encodeInt + case reflect.Float32: + return encodeFloat32 + case reflect.Float64: + return encodeFloat64 + case reflect.String: + return encodeString + default: + panic("I don't know what to do here") + } +} + +type encodeFn func(e *encoder, v reflect.Value) + +func encodeBool(e *encoder, v reflect.Value) { + if v.Bool() { + e.WriteString("true") + } else { + e.WriteString("false") + } +} + +func encodeInt(e *encoder, v reflect.Value) { + b := strconv.AppendInt(e.scratch[:0], v.Int(), 10) + e.Write(b) +} + +func encodeNull(e *encoder, v reflect.Value) { + e.WriteString("null") +} + +func encodeFloat(bits int) encodeFn { + return func(e *encoder, v reflect.Value) { + f := v.Float() + if math.IsInf(f, 0) || math.IsNaN(f) { + panic("that value is bad") // TODO: not this + } + b := strconv.AppendFloat(e.scratch[:0], f, 'f', 1, bits) + e.Write(b) + } +} + +var ( + encodeFloat32 = encodeFloat(32) + encodeFloat64 = encodeFloat(64) +) + +// this is a really over-simplified string emitter. I highly doubt it can stay +// like this. +func encodeString(e *encoder, v reflect.Value) { + s := v.String() + e.WriteByte('"') + for _, r := range s { + switch r { + case '\\': + e.WriteByte('\\') + e.WriteByte('\\') + case '"': + e.WriteByte('\\') + e.WriteByte('"') + default: + e.WriteRune(r) + } + } + e.WriteByte('"') +} diff --git a/lib/emit_test.go b/lib/emit_test.go new file mode 100644 index 0000000..2d29c54 --- /dev/null +++ b/lib/emit_test.go @@ -0,0 +1,39 @@ +package moon + +import ( + "testing" +) + +var valueTests = []struct { + in interface{} + out string +}{ + {nil, "null"}, + {true, "true"}, + {false, "false"}, + {0, "0"}, + {1, "1"}, + {12345, "12345"}, + {.1, "0.1"}, + {1.0, "1.0"}, + {1.0e9, "1000000000.0"}, + // this is kinda gross, but it's the only way I've figured out how to + // prevent 1.0 printing out as 1 and thus having its type changed from + // float to int. Sometimes having obnoxious string representations is + // better than something having things change type. + {"a string", `"a string"`}, + {`it's got "quotes"`, `"it's got \"quotes\""`}, +} + +func TestWriteValues(t *testing.T) { + for _, test := range valueTests { + out, err := Encode(test.in) + if err != nil { + t.Error(err) + continue + } + if string(out) != test.out { + t.Errorf("expected '%s', saw '%s'", test.out, string(out)) + } + } +}