Repository: amzn/ion-go
Pull request #158 (2020-09-12 08:06:31)

Update Reader Value APIs to symbol token abstraction
Issue #, if available:
#151  

Description of changes:
-Update Reader API to return SymbolToken abstraction instead of string representation of Symbols. 

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.




Patch:
diff --git a/cmd/ion-go/eventwriter.go b/cmd/ion-go/eventwriter.go
index a242e79..9bc6c05 100644
--- a/cmd/ion-go/eventwriter.go
+++ b/cmd/ion-go/eventwriter.go
@@ -29,7 +29,7 @@ type eventwriter struct {
 
 	depth       int
 	fieldname   *string
-	annotations []string
+	annotations []ion.SymbolToken
 	inStruct    map[int]bool
 }
 
@@ -58,12 +58,12 @@ func (e *eventwriter) FieldName(val string) error {
 	return nil
 }
 
-func (e *eventwriter) Annotation(val string) error {
+func (e *eventwriter) Annotation(val ion.SymbolToken) error {
 	e.annotations = append(e.annotations, val)
 	return nil
 }
 
-func (e *eventwriter) Annotations(values ...string) error {
+func (e *eventwriter) Annotations(values ...ion.SymbolToken) error {
 	e.annotations = append(e.annotations, values...)
 	return nil
 }
@@ -284,13 +284,13 @@ func (e *eventwriter) write(ev event) error {
 	e.annotations = nil
 
 	if name != nil {
-		ev.FieldName = &token{Text: *name}
+		ev.FieldName = &ion.SymbolToken{Text: name}
 	}
 
 	if len(annos) > 0 {
-		asyms := make([]token, len(annos))
+		asyms := make([]ion.SymbolToken, len(annos))
 		for i, a := range annos {
-			asyms[i] = token{Text: a}
+			asyms[i] = a
 		}
 		ev.Annotations = asyms
 	}
diff --git a/cmd/ion-go/nopwriter.go b/cmd/ion-go/nopwriter.go
index 50ba4cf..2eea05b 100644
--- a/cmd/ion-go/nopwriter.go
+++ b/cmd/ion-go/nopwriter.go
@@ -36,11 +36,11 @@ func (nopwriter) FieldNameSymbol(val ion.SymbolToken) error {
 	return nil
 }
 
-func (nopwriter) Annotation(string) error {
+func (nopwriter) Annotation(ion.SymbolToken) error {
 	return nil
 }
 
-func (nopwriter) Annotations(...string) error {
+func (nopwriter) Annotations(...ion.SymbolToken) error {
 	return nil
 }
 
diff --git a/cmd/ion-go/process.go b/cmd/ion-go/process.go
index 4450007..88d1af6 100644
--- a/cmd/ion-go/process.go
+++ b/cmd/ion-go/process.go
@@ -216,14 +216,20 @@ func (p *processor) process(in ion.Reader) error {
 
 	for in.Next() {
 		p.idx++
-		name := in.FieldName()
-		if name != nil {
-			if err = p.out.FieldName(*name); err != nil {
+		name, e := in.FieldName()
+		if e != nil {
+			return p.error(read, err)
+		}
+		if name != nil && name.Text != nil {
+			if err = p.out.FieldName(*name.Text); err != nil {
 				return p.error(write, err)
 			}
 		}
 
-		annos := in.Annotations()
+		annos, err := in.Annotations()
+		if err != nil {
+			return p.error(read, err)
+		}
 		if len(annos) > 0 {
 			if err = p.out.Annotations(annos...); err != nil {
 				return p.error(write, err)
diff --git a/cmd/ion-go/schema.go b/cmd/ion-go/schema.go
index 15b79cb..47083d8 100644
--- a/cmd/ion-go/schema.go
+++ b/cmd/ion-go/schema.go
@@ -22,17 +22,6 @@ import (
 	"github.com/amzn/ion-go/ion"
 )
 
-type importlocation struct {
-	ImportName string `ion:"import_name"`
-	Location   int    `ion:"location"`
-}
-
-// token describes an Ion symbol token.
-type token struct {
-	Text           string          `ion:"text,omitempty"`
-	ImportLocation *importlocation `ion:"import_location,omitempty"`
-}
-
 type importdescriptor struct {
 	ImportName string `ion:"import_name"`
 	Version    int    `ion:"version"`
@@ -80,8 +69,8 @@ func (i iontype) MarshalIon(w ion.Writer) error {
 type event struct {
 	EventType   eventtype          `ion:"event_type"`
 	IonType     iontype            `ion:"ion_type,omitempty"`
-	FieldName   *token             `ion:"field_name,omitempty"`
-	Annotations []token            `ion:"annotations,omitempty"`
+	FieldName   *ion.SymbolToken   `ion:"field_name,omitempty"`
+	Annotations []ion.SymbolToken  `ion:"annotations,omitempty"`
 	ValueText   string             `ion:"value_text,omitempty"`
 	ValueBinary []int              `ion:"value_binary,omitempty"`
 	Imports     []importdescriptor `ion:"imports,omitempty"`
diff --git a/ion/binaryreader.go b/ion/binaryreader.go
index 0847aa4..ce457cf 100644
--- a/ion/binaryreader.go
+++ b/ion/binaryreader.go
@@ -267,7 +267,7 @@ func (r *binaryReader) readFieldName() error {
 		return err
 	}
 
-	r.fieldNameSymbol = &st
+	r.fieldName = &st
 	return nil
 }
 
diff --git a/ion/consts.go b/ion/consts.go
index 9ccae99..3b17500 100644
--- a/ion/consts.go
+++ b/ion/consts.go
@@ -66,3 +66,4 @@ var hexChars = []byte{
 var timestampType = reflect.TypeOf(Timestamp{})
 var decimalType = reflect.TypeOf(Decimal{})
 var bigIntType = reflect.TypeOf(big.Int{})
+var symbolType = reflect.TypeOf(SymbolToken{})
diff --git a/ion/marshal.go b/ion/marshal.go
index df0b953..9931ba1 100644
--- a/ion/marshal.go
+++ b/ion/marshal.go
@@ -436,10 +436,10 @@ func (m *Encoder) encodeWithAnnotation(v reflect.Value, fields []field) error {
 			if err != nil {
 				return err
 			}
-			listOfAnnotations, ok := annotations.Interface().([]string)
+			listOfAnnotations, ok := annotations.Interface().([]SymbolToken)
 			if !ok {
 				return fmt.Errorf("ion: '%v' is provided for annotations, "+
-					"it must be of type []string", annotations.Kind())
+					"it must be of type []SymbolToken", annotations.Kind())
 			}
 			err = m.w.Annotations(listOfAnnotations...)
 			if err != nil {
diff --git a/ion/reader.go b/ion/reader.go
index ef4adad..6f43584 100644
--- a/ion/reader.go
+++ b/ion/reader.go
@@ -90,13 +90,9 @@ type Reader interface {
 	// even if the Type is not NullType (for example, null.struct has type Struct).
 	IsNull() bool
 
-	// FieldName returns the field name associated with the current value. It returns
-	// nil if there is no current value or the current value has no field name.
-	FieldName() *string
-
-	// Annotations returns the set of annotations associated with the current value.
+	// Annotations returns the annotations associated with the current value as a list of SymbolTokens.
 	// It returns nil if there is no current value or the current value has no annotations.
-	Annotations() []string
+	Annotations() ([]SymbolToken, error)
 
 	// StepIn steps in to the current value if it is a container. It returns an error if there
 	// is no current value or if the value is not a container. On success, the Reader is
@@ -150,8 +146,8 @@ type Reader interface {
 	// nil if the value is null. It returns an error if the current value is not an Ion timestamp.
 	TimestampValue() (*Timestamp, error)
 
-	// StringValue returns the current value as a string (if that makes sense). It returns nil if
-	// the value is null. It returns an error if the current value is not an Ion symbol or an Ion string.
+	// StringValue returns the current value as a string (if that makes sense). Returns `nil` for Ion null string.
+	// It returns an error if the current value is not an Ion string.
 	StringValue() (*string, error)
 
 	// ByteValue returns the current value as a byte slice (if that makes sense). It returns
@@ -161,9 +157,10 @@ type Reader interface {
 	// IsInStruct indicates if the reader is currently positioned in a struct.
 	IsInStruct() bool
 
-	// FieldNameSymbol returns the field name associated with the current value as a SymbolToken.
-	// It returns an error if the current value has no field name.
-	FieldNameSymbol() (*SymbolToken, error)
+	// FieldName returns the field name associated with the current value as a SymbolToken. It returns
+	// nil if there is no current value or the current value has no field name. It returns an error if
+	// the `SymbolToken` SID is not found in the symbol table.
+	FieldName() (*SymbolToken, error)
 
 	// SymbolValue returns the SymbolToken associated with the current value. It returns an
 	// error if the current value is not an Ion symbol.
@@ -209,11 +206,11 @@ type reader struct {
 	eof bool
 	err error
 
-	lst             SymbolTable
-	fieldNameSymbol *SymbolToken
-	annotations     []SymbolToken
-	valueType       Type
-	value           interface{}
+	lst         SymbolTable
+	fieldName   *SymbolToken
+	annotations []SymbolToken
+	valueType   Type
+	value       interface{}
 }
 
 // Err returns the current error.
@@ -231,31 +228,13 @@ func (r *reader) IsNull() bool {
 	return r.valueType != NoType && r.value == nil
 }
 
-// FieldName returns the current value's field name.
-func (r *reader) FieldName() *string {
-	if r.fieldNameSymbol != nil {
-		return r.fieldNameSymbol.Text
-	}
-	return nil
-}
-
 // Annotations returns the current value's annotations.
-func (r *reader) Annotations() []string {
-	var annotations []string
-	for _, an := range r.annotations {
-		if an.Text != nil {
-			sid, systemSymbolName := getSystemSymbolMapping(r.SymbolTable(), *an.Text)
-			if sid != SymbolIDUnknown {
-				annotations = append(annotations, systemSymbolName)
-			} else {
-				annotations = append(annotations, *an.Text)
-			}
-		} else {
-			annotations = append(annotations, "")
-		}
+func (r *reader) Annotations() ([]SymbolToken, error) {
+	if r.err != nil {
+		return nil, r.err
 	}
 
-	return annotations
+	return r.annotations, nil
 }
 
 // BoolValue returns the current value as a bool.
@@ -420,8 +399,8 @@ func (r *reader) ByteValue() ([]byte, error) {
 
 // Clear clears the current value from the reader.
 func (r *reader) clear() {
-	if r.fieldNameSymbol != nil {
-		r.fieldNameSymbol.Text = nil
+	if r.fieldName != nil {
+		r.fieldName.Text = nil
 	}
 	r.annotations = nil
 	r.valueType = NoType
@@ -439,21 +418,16 @@ func (r *reader) StringValue() (*string, error) {
 		return nil, r.err
 	}
 
-	if r.valueType != StringType && r.valueType != SymbolType {
-		return nil, &UsageError{"Reader.StringValue", "value is not a string or symbol"}
+	if r.valueType != StringType {
+		return nil, &UsageError{"Reader.StringValue", "value is not a string"}
 	}
 
 	if r.value == nil {
 		return nil, nil
 	}
 
-	// Check if value is symbol or string.
-	st, ok := r.value.(*SymbolToken)
-	if !ok {
-		val := r.value.(string)
-		return &val, nil
-	}
-	return st.Text, nil
+	val := r.value.(string)
+	return &val, nil
 }
 
 // SymbolValue returns the current value as a symbol token.
@@ -466,16 +440,20 @@ func (r *reader) SymbolValue() (*SymbolToken, error) {
 		return nil, &UsageError{"Reader.SymbolValue", "value is not a symbol"}
 	}
 
+	if r.value == nil {
+		return nil, nil
+	}
+
 	return r.value.(*SymbolToken), nil
 }
 
-// FieldNameSymbol returns the current field name as a symbol token.
-func (r *reader) FieldNameSymbol() (*SymbolToken, error) {
+// FieldName returns the current field name as a symbol token.
+func (r *reader) FieldName() (*SymbolToken, error) {
 	if r.err != nil {
 		return nil, r.err
 	}
 
-	return r.fieldNameSymbol, nil
+	return r.fieldName, nil
 }
 
 // SymbolTable returns the current symbol table.
diff --git a/ion/readlocalsymboltable.go b/ion/readlocalsymboltable.go
index bd4ba20..ed60b47 100644
--- a/ion/readlocalsymboltable.go
+++ b/ion/readlocalsymboltable.go
@@ -16,10 +16,15 @@ func readLocalSymbolTable(r Reader, cat Catalog) (SymbolTable, error) {
 
 	for r.Next() {
 		var err error
-		if r.FieldName() == nil {
+		fieldName, err := r.FieldName()
+		if err != nil {
+			return nil, err
+		}
+		if fieldName == nil || fieldName.Text == nil {
 			return nil, fmt.Errorf("ion: field name is nil")
 		}
-		switch *r.FieldName() {
+
+		switch *fieldName.Text {
 		case "symbols":
 			if foundLocals {
 				return nil, fmt.Errorf("ion: multiple symbol fields found within a single local symbol table")
@@ -102,7 +107,15 @@ func readImport(r Reader, cat Catalog) (SharedSymbolTable, error) {
 
 	for r.Next() {
 		var err error
-		switch *r.FieldName() {
+		fieldName, err := r.FieldName()
+		if err != nil {
+			return nil, err
+		}
+		if fieldName == nil || fieldName.Text == nil {
+			return nil, fmt.Errorf("ion: field name is nil")
+		}
+
+		switch *fieldName.Text {
 		case "name":
 			if r.Type() == StringType {
 				var val *string
diff --git a/ion/symboltable.go b/ion/symboltable.go
index 140c3e6..0d57717 100644
--- a/ion/symboltable.go
+++ b/ion/symboltable.go
@@ -156,7 +156,8 @@ func (s *sst) FindByID(id uint64) (string, bool) {
 }
 
 func (s *sst) WriteTo(w Writer) error {
-	if err := w.Annotation("$ion_shared_symbol_table"); err != nil {
+	ionSharedSymbolTableText := "$ion_shared_symbol_table"
+	if err := w.Annotation(SymbolToken{Text: &ionSharedSymbolTableText, LocalSID: 9}); err != nil {
 		return err
 	}
 	if err := w.BeginStruct(); err != nil {
@@ -275,9 +276,12 @@ func (s *bogusSST) WriteTo(w Writer) error {
 }
 
 func (s *bogusSST) String() string {
+	ionSharedSymbolTableText := "$ion_shared_symbol_table"
+	bogusText := "bogus"
+
 	buf := strings.Builder{}
 	w := NewTextWriter(&buf)
-	w.Annotations("$ion_shared_symbol_table", "bogus")
+	w.Annotations(SymbolToken{Text: &ionSharedSymbolTableText, LocalSID: 9}, SymbolToken{Text: &bogusText, LocalSID: SymbolIDUnknown})
 	w.BeginStruct()
 
 	w.FieldName("name")
@@ -405,7 +409,8 @@ func (t *lst) WriteTo(w Writer) error {
 		return nil
 	}
 
-	if err := w.Annotation("$ion_symbol_table"); err != nil {
+	ionSymbolTableText := "$ion_symbol_table"
+	if err := w.Annotation(SymbolToken{Text: &ionSymbolTableText, LocalSID: 3}); err != nil {
 		return err
 	}
 	if err := w.BeginStruct(); err != nil {
diff --git a/ion/textreader.go b/ion/textreader.go
index 7a5bfc6..02b7a46 100644
--- a/ion/textreader.go
+++ b/ion/textreader.go
@@ -183,13 +183,13 @@ func (t *textReader) nextBeforeFieldName() (bool, error) {
 		}
 
 		if tok == tokenSymbolQuoted {
-			t.fieldNameSymbol = &SymbolToken{Text: &val, LocalSID: SymbolIDUnknown}
+			t.fieldName = &SymbolToken{Text: &val, LocalSID: SymbolIDUnknown}
 		} else {
 			st, err := NewSymbolToken(t.SymbolTable(), val)
 			if err != nil {
 				return false, err
 			}
-			t.fieldNameSymbol = &st
+			t.fieldName = &st
 		}
 
 		// Skip over the following colon.
@@ -255,6 +255,10 @@ func (t *textReader) nextBeforeTypeAnnotations() (bool, error) {
 				return false, err
 			}
 
+			if tok == tokenSymbolQuoted {
+				token = SymbolToken{Text: &val, LocalSID: SymbolIDUnknown}
+			}
+
 			t.annotations = append(t.annotations, token)
 			return false, nil
 		}
@@ -428,7 +432,7 @@ func (t *textReader) onSymbol(val string, tok token, ws bool) error {
 	valueType := SymbolType
 	var value interface{} = val
 
-	if tok == tokenSymbol || tok == tokenSymbolOperator {
+	if tok == tokenSymbol || tok == tokenSymbolOperator || tok == tokenDot {
 		switch val {
 		case "null":
 			vt, err := t.onNull(ws)
diff --git a/ion/unmarshal.go b/ion/unmarshal.go
index 0fd5a68..1375b6f 100644
--- a/ion/unmarshal.go
+++ b/ion/unmarshal.go
@@ -165,9 +165,12 @@ func (d *Decoder) decode() (interface{}, error) {
 	case TimestampType:
 		return d.r.TimestampValue()
 
-	case StringType, SymbolType:
+	case StringType:
 		return d.r.StringValue()
 
+	case SymbolType:
+		return d.r.SymbolValue()
+
 	case BlobType, ClobType:
 		return d.r.ByteValue()
 
@@ -211,8 +214,12 @@ func (d *Decoder) decodeMap() (map[string]interface{}, error) {
 	result := map[string]interface{}{}
 
 	for d.r.Next() {
-		if d.r.FieldName() != nil {
-			name := d.r.FieldName()
+		fieldName, err := d.r.FieldName()
+		if err != nil {
+			return nil, err
+		}
+		if fieldName != nil && fieldName.Text != nil {
+			name := fieldName.Text
 			value, err := d.decode()
 			if err != nil {
 				return nil, err
@@ -304,9 +311,12 @@ func (d *Decoder) decodeTo(v reflect.Value) error {
 	case TimestampType:
 		return d.decodeTimestampTo(v)
 
-	case StringType, SymbolType:
+	case StringType:
 		return d.decodeStringTo(v)
 
+	case SymbolType:
+		return d.decodeSymbolTo(v)
+
 	case BlobType, ClobType:
 		return d.decodeLobTo(v)
 
@@ -489,6 +499,35 @@ func (d *Decoder) decodeTimestampTo(v reflect.Value) error {
 	return fmt.Errorf("ion: cannot decode timestamp to %v", v.Type().String())
 }
 
+func (d *Decoder) decodeSymbolTo(v reflect.Value) error {
+	val, err := d.r.SymbolValue()
+	if err != nil {
+		return err
+	}
+
+	switch v.Kind() {
+	case reflect.String:
+		if val != nil {
+			v.SetString(*val.Text)
+		}
+		return nil
+
+	case reflect.Struct:
+		if v.Type() == symbolType {
+			v.Set(reflect.ValueOf(val))
+			return d.attachAnnotations(v)
+		}
+		return d.decodeToStructWithAnnotation(v, symbolType.Kind())
+
+	case reflect.Interface:
+		if v.NumMethod() == 0 {
+			v.Set(reflect.ValueOf(val))
+			return nil
+		}
+	}
+	return fmt.Errorf("ion: cannot decode symbol to %v", v.Type().String())
+}
+
 func (d *Decoder) decodeStringTo(v reflect.Value) error {
 	val, err := d.r.StringValue()
 	if err != nil {
@@ -583,16 +622,21 @@ func (d *Decoder) decodeStructToStruct(v reflect.Value) error {
 	}
 
 	for d.r.Next() {
-		name := d.r.FieldName()
-		field := findField(fields, *name)
-		if field != nil {
-			subv, err := findSubvalue(v, field)
-			if err != nil {
-				return err
-			}
+		fieldName, err := d.r.FieldName()
+		if err != nil {
+			return err
+		}
+		if fieldName != nil && fieldName.Text != nil {
+			field := findField(fields, *fieldName.Text)
+			if field != nil {
+				subv, err := findSubvalue(v, field)
+				if err != nil {
+					return err
+				}
 
-			if err := d.decodeTo(subv); err != nil {
-				return err
+				if err := d.decodeTo(subv); err != nil {
+					return err
+				}
 			}
 		}
 	}
@@ -649,21 +693,27 @@ func (d *Decoder) decodeStructToMap(v reflect.Value) error {
 	}
 
 	for d.r.Next() {
-		name := d.r.FieldName()
-		if err := d.decodeTo(subv); err != nil {
+		fieldName, err := d.r.FieldName()
+		if err != nil {
 			return err
 		}
 
-		var kv reflect.Value
-		switch t.Key().Kind() {
-		case reflect.String:
-			kv = reflect.ValueOf(*name)
-		default:
-			panic(fmt.Sprintf("the key for map to hold field name must be of type string. Found: %v", t.Key().Kind().String()))
-		}
+		if fieldName != nil && fieldName.Text != nil {
+			if err := d.decodeTo(subv); err != nil {
+				return err
+			}
 
-		if kv.IsValid() {
-			v.SetMapIndex(kv, subv)
+			var kv reflect.Value
+			switch t.Key().Kind() {
+			case reflect.String:
+				kv = reflect.ValueOf(*fieldName.Text)
+			default:
+				panic(fmt.Sprintf("the key for map to hold field name must be of type string. Found: %v", t.Key().Kind().String()))
+			}
+
+			if kv.IsValid() {
+				v.SetMapIndex(kv, subv)
+			}
 		}
 	}
 
@@ -820,7 +870,11 @@ func (d *Decoder) attachAnnotations(v reflect.Value) error {
 			if err != nil {
 				return err
 			}
-			annotations := d.r.Annotations()
+
+			annotations, err := d.r.Annotations()
+			if err != nil {
+				return err
+			}
 			subValue.Set(reflect.ValueOf(annotations))
 			break
 		}
diff --git a/ion/writer.go b/ion/writer.go
index cd03610..da817f0 100644
--- a/ion/writer.go
+++ b/ion/writer.go
@@ -73,10 +73,10 @@ type Writer interface {
 	FieldNameSymbol(val SymbolToken) error
 
 	// Annotation adds a single annotation to the next value written.
-	Annotation(val string) error
+	Annotation(val SymbolToken) error
 
 	// Annotations adds multiple annotations to the next value written.
-	Annotations(values ...string) error
+	Annotations(values ...SymbolToken) error
 
 	// WriteNull writes an untyped null value.
 	WriteNull() error
@@ -186,35 +186,23 @@ func (w *writer) FieldNameSymbol(val SymbolToken) error {
 }
 
 // Annotation adds an annotation to the next value written.
-func (w *writer) Annotation(val string) error {
+func (w *writer) Annotation(val SymbolToken) error {
 	if w.err != nil {
 		return w.err
 	}
 
-	var token SymbolToken
-	token, w.err = NewSymbolToken(V1SystemSymbolTable, val)
-	if w.err != nil {
-		return w.err
-	}
-
-	w.annotations = append(w.annotations, token)
+	w.annotations = append(w.annotations, val)
 
 	return nil
 }
 
 // Annotations adds one or more annotations to the next value written.
-func (w *writer) Annotations(values ...string) error {
-	if w.err != nil {
-		return w.err
-	}
-
-	var tokens []SymbolToken
-	tokens, w.err = NewSymbolTokens(V1SystemSymbolTable, values)
+func (w *writer) Annotations(values ...SymbolToken) error {
 	if w.err != nil {
 		return w.err
 	}
 
-	w.annotations = append(w.annotations, tokens...)
+	w.annotations = append(w.annotations, values...)
 
 	return nil
 }


Test patch:
diff --git a/ion/binaryreader_test.go b/ion/binaryreader_test.go
index 038cbde..36135e9 100644
--- a/ion/binaryreader_test.go
+++ b/ion/binaryreader_test.go
@@ -63,7 +63,7 @@ func TestReadNullLST(t *testing.T) {
 		0x71, 0x09,
 	}
 	r := NewReaderBytes(ion)
-	_symbol(t, r, newString("$ion_shared_symbol_table"), SymbolToken{Text: newString("$ion_shared_symbol_table"), LocalSID: 9})
+	_symbol(t, r, SymbolToken{Text: newString("$ion_shared_symbol_table"), LocalSID: 9})
 	_eof(t, r)
 }
 
@@ -74,7 +74,7 @@ func TestReadEmptyLST(t *testing.T) {
 		0x71, 0x09,
 	}
 	r := NewReaderBytes(ion)
-	_symbol(t, r, newString("$ion_shared_symbol_table"), SymbolToken{Text: newString("$ion_shared_symbol_table"), LocalSID: 9})
+	_symbol(t, r, SymbolToken{Text: newString("$ion_shared_symbol_table"), LocalSID: 9})
 	_eof(t, r)
 }
 
@@ -160,17 +160,14 @@ func TestReadBinaryStructs(t *testing.T) {
 	_struct(t, r, func(t *testing.T, r Reader) {
 		_eof(t, r)
 	})
-	_structAF(t, r, nil, []string{"foo"}, func(t *testing.T, r Reader) {
-		name := "name"
-		_structAF(t, r, &name, []string{"bar"}, func(t *testing.T, r Reader) {
+	_structAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}}, func(t *testing.T, r Reader) {
+		_structAF(t, r, &SymbolToken{Text: newString("name"), LocalSID: 4}, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, func(t *testing.T, r Reader) {
 			_eof(t, r)
 		})
-		maxID := "max_id"
-		_intAF(t, r, &maxID, nil, 0)
+		_intAF(t, r, &SymbolToken{Text: newString("max_id"), LocalSID: 8}, nil, 0)
 	})
-	emptyString := ""
-	_structAF(t, r, &emptyString, nil, func(t *testing.T, r Reader) {
-		_intAF(t, r, &emptyString, nil, 15)
+	_structAF(t, r, nil, nil, func(t *testing.T, r Reader) {
+		_intAF(t, r, &SymbolToken{Text: newString(""), LocalSID: SymbolIDUnknown}, nil, 15)
 	})
 	_eof(t, r)
 }
@@ -280,11 +277,22 @@ func TestReadBinaryFieldNames(t *testing.T) {
 	})
 	r.Next()
 	r.StepIn()
-	_nextF(t, r, nil, SymbolToken{Text: nil, LocalSID: 0}, false, false)
-	_nextF(t, r, newString("$ion"), SymbolToken{Text: newString("$ion"), LocalSID: 1}, false, false)
-	_nextF(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: 110}, false, false)
-	_nextF(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: 111}, false, false)
-	_nextF(t, r, nil, SymbolToken{}, true, true)
+	_nextF(t, r, &SymbolToken{Text: nil, LocalSID: 0}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("$ion"), LocalSID: 1}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: 110}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("bar"), LocalSID: 111}, false, false)
+	_nextF(t, r, &SymbolToken{}, true, true)
+}
+
+func TestReadBinaryNullFieldName(t *testing.T) {
+	r := readBinary([]byte{
+		0xDE, 0x8F, // {
+		0x7F, 0x21, 0x01, // null.symbol: 1
+		// }
+	})
+	r.Next()
+	r.StepIn()
+	_nextF(t, r, &SymbolToken{}, true, true)
 }
 
 func TestReadBinarySymbols(t *testing.T) {
@@ -293,13 +301,31 @@ func TestReadBinarySymbols(t *testing.T) {
 		0x71, 0x01, // $ion
 		0x71, 0x6E, // foo
 		0x71, 0x6F, // bar
+		0x7F,       // null.symbol
 		0x71, 0x71, // $113
 	})
-	_symbolAF(t, r, nil, nil, nil, SymbolToken{Text: nil, LocalSID: 0}, false, false, false)
-	_symbol(t, r, newString("$ion"), SymbolToken{Text: newString("$ion"), LocalSID: 1})
-	_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: 110})
-	_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: 111})
-	_symbolAF(t, r, nil, nil, nil, SymbolToken{}, true, true, true)
+	_symbolAF(t, r, nil, nil, &SymbolToken{Text: nil, LocalSID: 0}, false, false)
+	_symbol(t, r, SymbolToken{Text: newString("$ion"), LocalSID: 1})
+	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: 110})
+	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: 111})
+	_symbolAF(t, r, nil, nil, nil, false, false)
+	_symbolAF(t, r, nil, nil, &SymbolToken{}, true, true)
+}
+
+func TestReadBinaryAnnotations(t *testing.T) {
+	r := readBinary([]byte{
+		0xE3, 0x81, 0x80, 0x0F, // $0::null
+		0xE3, 0x81, 0x81, 0x0F, // $ion::null
+		0xE3, 0x81, 0xEE, 0x0F, // foo::null
+		0xE3, 0x81, 0xEF, 0x0F, // bar::null
+		0xE3, 0x81, 0xF1, 0x0F, // $113::null
+	})
+
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: nil, LocalSID: 0}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("$ion"), LocalSID: 1}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: 110}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: 111}}, false, false)
+	_nextA(t, r, nil, true, true)
 }
 
 func TestReadBinaryTimestamps(t *testing.T) {
@@ -402,13 +428,13 @@ func TestReadMultipleLSTs(t *testing.T) {
 		0x71, 0x6F, // $111
 	})
 
-	_symbolAF(t, r, nil, nil, nil, SymbolToken{Text: nil, LocalSID: 11}, false, false, false)
-	_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: 111})
-	_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: 11})
-	_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: 11})
-	_symbol(t, r, newString("baz"), SymbolToken{Text: newString("baz"), LocalSID: 12})
-	_symbol(t, r, newString("baz"), SymbolToken{Text: newString("baz"), LocalSID: 12})
-	_symbolAF(t, r, nil, nil, nil, SymbolToken{}, true, true, true)
+	_symbolAF(t, r, nil, nil, &SymbolToken{Text: nil, LocalSID: 11}, false, false)
+	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: 111})
+	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: 11})
+	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: 11})
+	_symbol(t, r, SymbolToken{Text: newString("baz"), LocalSID: 12})
+	_symbol(t, r, SymbolToken{Text: newString("baz"), LocalSID: 12})
+	_symbolAF(t, r, nil, nil, &SymbolToken{}, true, true)
 }
 
 func TestReadBinaryInts(t *testing.T) {
@@ -464,8 +490,8 @@ func TestReadBinaryNulls(t *testing.T) {
 	})
 
 	_null(t, r, NullType)
-	_nullAF(t, r, NullType, nil, []string{"$ion"})
-	_nullAF(t, r, NullType, nil, []string{"foo", "bar"})
+	_nullAF(t, r, NullType, nil, []SymbolToken{SymbolToken{Text: newString("$ion"), LocalSID: 1}})
+	_nullAF(t, r, NullType, nil, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}})
 	_eof(t, r)
 }
 
diff --git a/ion/binarywriter_test.go b/ion/binarywriter_test.go
index d0c3da8..d482de1 100644
--- a/ion/binarywriter_test.go
+++ b/ion/binarywriter_test.go
@@ -38,11 +38,11 @@ func TestWriteBinaryStruct(t *testing.T) {
 		w.BeginStruct()
 		w.EndStruct()
 
-		w.Annotation("foo")
+		w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 		w.BeginStruct()
 		{
 			w.FieldName("name")
-			w.Annotation("bar")
+			w.Annotation(SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
 			w.WriteNull()
 
 			w.FieldName("max_id")
@@ -64,10 +64,10 @@ func TestWriteBinarySexp(t *testing.T) {
 		w.BeginSexp()
 		w.EndSexp()
 
-		w.Annotation("foo")
+		w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 		w.BeginSexp()
 		{
-			w.Annotation("bar")
+			w.Annotation(SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
 			w.WriteNull()
 
 			w.WriteInt(0)
@@ -88,10 +88,10 @@ func TestWriteBinaryList(t *testing.T) {
 		w.BeginList()
 		w.EndList()
 
-		w.Annotation("foo")
+		w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 		w.BeginList()
 		{
-			w.Annotation("bar")
+			w.Annotation(SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
 			w.WriteNull()
 
 			w.WriteInt(0)
@@ -289,7 +289,7 @@ func TestWriteBinaryBoolAnnotated(t *testing.T) {
 	}
 
 	testBinaryWriter(t, eval, func(w Writer) {
-		w.Annotations("name", "version")
+		w.Annotations(SymbolToken{Text: newString("name"), LocalSID: 4}, SymbolToken{Text: newString("version"), LocalSID: 5})
 		w.WriteBool(false)
 	})
 }
diff --git a/ion/catalog_test.go b/ion/catalog_test.go
index e31e706..2122eab 100644
--- a/ion/catalog_test.go
+++ b/ion/catalog_test.go
@@ -39,7 +39,7 @@ func TestCatalog(t *testing.T) {
 	out := NewBinaryWriter(&buf, sst)
 
 	for i := 0; i < 10; i++ {
-		out.Annotation("item")
+		out.Annotation(SymbolToken{Text: newString("item"), LocalSID: SymbolIDUnknown})
 		MarshalTo(out, &Item{
 			ID:          i,
 			Name:        fmt.Sprintf("Item %v", i),
diff --git a/ion/cmp_test.go b/ion/cmp_test.go
index bdc6305..84bd9de 100644
--- a/ion/cmp_test.go
+++ b/ion/cmp_test.go
@@ -29,6 +29,7 @@ type ionEqual interface {
 type ionFloat struct{ float64 }
 type ionDecimal struct{ *Decimal }
 type ionTimestamp struct{ Timestamp }
+type ionSymbol struct{ *SymbolToken }
 
 func (thisFloat ionFloat) eq(other ionEqual) bool {
 	return cmp.Equal(thisFloat.float64, other.(ionFloat).float64, cmpopts.EquateNaNs())
@@ -51,8 +52,32 @@ func (thisTimestamp ionTimestamp) eq(other ionEqual) bool {
 	return false
 }
 
-func cmpAnnotations(thisAnnotations, otherAnnotations []string) bool {
-	return reflect.DeepEqual(thisAnnotations, otherAnnotations)
+func (thisSymbol ionSymbol) eq(other ionEqual) bool {
+	if val, ok := other.(ionSymbol); ok {
+		return thisSymbol.SymbolToken.Equal(val.SymbolToken)
+	}
+	return false
+}
+
+func cmpAnnotations(thisAnnotations, otherAnnotations []SymbolToken) bool {
+	if len(thisAnnotations) == 0 && len(otherAnnotations) == 0 {
+		return true
+	}
+
+	if len(thisAnnotations) != len(otherAnnotations) {
+		return false
+	}
+
+	res := false
+	for idx, this := range thisAnnotations {
+		other := otherAnnotations[idx]
+		res = this.Equal(&other)
+
+		if !res {
+			return false
+		}
+	}
+	return res
 }
 
 func cmpFloats(thisValue, otherValue interface{}) bool {
@@ -103,6 +128,16 @@ func cmpTimestamps(thisValue, otherValue interface{}) bool {
 	}
 }
 
+func cmpSymbols(thisValue, otherValue interface{}) bool {
+	val1 := thisValue.(*SymbolToken)
+	val2 := otherValue.(*SymbolToken)
+
+	if val1 == nil || val2 == nil {
+		return val1 == nil && val2 == nil
+	}
+	return val1.Equal(val2)
+}
+
 func cmpValueSlices(thisValues, otherValues []interface{}) bool {
 	if len(thisValues) == 0 && len(otherValues) == 0 {
 		return true
@@ -202,7 +237,7 @@ func containersEquality(this, other interface{}) bool {
 	default:
 		otherItem := other.(ionItem)
 		thisItem := this.(ionItem)
-		if thisItem.fieldName == otherItem.fieldName && thisItem.equal(otherItem) {
+		if thisItem.fieldName.Equal(&otherItem.fieldName) && thisItem.equal(otherItem) {
 			return true
 		}
 	}
diff --git a/ion/integration_test.go b/ion/integration_test.go
index 5a878cc..583e043 100644
--- a/ion/integration_test.go
+++ b/ion/integration_test.go
@@ -35,9 +35,9 @@ type testingFunc func(t *testing.T, path string)
 
 type ionItem struct {
 	ionType     Type
-	annotations []string
+	annotations []SymbolToken
 	value       []interface{}
-	fieldName   string
+	fieldName   SymbolToken
 }
 
 func (i *ionItem) equal(o ionItem) bool {
@@ -59,6 +59,8 @@ func (i *ionItem) equal(o ionItem) bool {
 		return cmpValueSlices(i.value, o.value)
 	case StructType:
 		return cmpStruct(i.value, o.value)
+	case SymbolType:
+		return cmpSymbols(i.value[0], o.value[0])
 	default:
 		return reflect.DeepEqual(i.value, o.value)
 	}
@@ -304,7 +306,11 @@ func testEquivalency(t *testing.T, fp string, eq bool) {
 	r := NewReader(file)
 	topLevelCounter := 0
 	for r.Next() {
-		embDoc := isEmbeddedDoc(r.Annotations())
+		annotations, err := r.Annotations()
+		if err != nil {
+			t.Fatal(err)
+		}
+		embDoc := isEmbeddedDoc(annotations)
 		ionType := r.Type()
 		switch ionType {
 		case StructType, ListType, SexpType:
@@ -358,11 +364,9 @@ func handleEmbeddedDoc(t *testing.T, r Reader) [][]ionItem {
 	return values
 }
 
-func isEmbeddedDoc(an []string) bool {
-	for _, a := range an {
-		if a == "embedded_documents" {
-			return true
-		}
+func isEmbeddedDoc(an []SymbolToken) bool {
+	if len(an) >= 1 && an[0].Text != nil && *an[0].Text == "embedded_documents" {
+		return true
 	}
 	return false
 }
@@ -436,7 +440,7 @@ func isInSkipList(skipList []string, fn string) bool {
 // Read all the values in the reader and write them in the writer
 func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 	for reader.Next() {
-		fns, err := reader.FieldNameSymbol()
+		fns, err := reader.FieldName()
 		if err == nil && reader.IsInStruct() && fns != nil {
 			err = writer.FieldNameSymbol(*fns)
 			if err != nil {
@@ -444,7 +448,11 @@ func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 			}
 		}
 
-		an := reader.Annotations()
+		an, err := reader.Annotations()
+		if err != nil {
+			t.Fatal(err)
+		}
+
 		if len(an) > 0 {
 			err := writer.Annotations(an...)
 			if err != nil {
@@ -582,15 +590,17 @@ func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 			}
 
 		case SymbolType:
-			val, err := reader.StringValue()
+			val, err := reader.SymbolValue()
 			if err != nil {
 				t.Errorf("Something went wrong while reading a Symbol value: " + err.Error())
 			}
 
 			if val != nil {
-				err = writer.WriteSymbol(*val)
-				if err != nil {
-					t.Errorf("Something went wrong while writing a Symbol value: " + err.Error())
+				if val.Text != nil {
+					err = writer.WriteSymbol(*val.Text)
+					if err != nil {
+						t.Errorf("Something went wrong while writing a Symbol value: " + err.Error())
+					}
 				}
 			}
 
@@ -691,12 +701,19 @@ func writeFromReaderToWriter(t *testing.T, reader Reader, writer Writer) {
 func readCurrentValue(t *testing.T, reader Reader) ionItem {
 	var ionItem ionItem
 
-	an := reader.Annotations()
+	an, err := reader.Annotations()
+	if err != nil {
+		t.Errorf("Something went wrong when reading annotations. " + err.Error())
+	}
+
 	if len(an) > 0 {
 		ionItem.annotations = an
 	}
 
-	fn := reader.FieldName()
+	fn, err := reader.FieldName()
+	if err != nil {
+		t.Errorf("Something went wrong when reading field name. " + err.Error())
+	}
 	if fn != nil {
 		ionItem.fieldName = *fn
 	}
@@ -768,7 +785,7 @@ func readCurrentValue(t *testing.T, reader Reader) ionItem {
 		ionItem.ionType = TimestampType
 
 	case SymbolType:
-		val, err := reader.StringValue()
+		val, err := reader.SymbolValue()
 		if err != nil {
 			t.Errorf("Something went wrong when reading Symbol value. " + err.Error())
 		}
diff --git a/ion/marshal_test.go b/ion/marshal_test.go
index 7c4d059..be10adc 100644
--- a/ion/marshal_test.go
+++ b/ion/marshal_test.go
@@ -338,11 +338,11 @@ func TestMarshalValuesWithAnnotation(t *testing.T) {
 
 	type foo struct {
 		Value   interface{}
-		AnyName []string `ion:",annotations"`
+		AnyName []SymbolToken `ion:",annotations"`
 	}
 
 	buildValue := func(val interface{}) foo {
-		return foo{val, []string{"symbols or string", "annotations"}}
+		return foo{val, []SymbolToken{SymbolToken{Text: newString("symbols or string"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("annotations"), LocalSID: SymbolIDUnknown}}}
 	}
 
 	test(buildValue(nil), "null", "'symbols or string'::annotations::null")
diff --git a/ion/symboltable_test.go b/ion/symboltable_test.go
index 78c59d7..5d25454 100644
--- a/ion/symboltable_test.go
+++ b/ion/symboltable_test.go
@@ -220,3 +220,7 @@ func testString(t *testing.T, st SymbolTable, expected string) {
 		}
 	})
 }
+
+func newString(value string) *string {
+	return &value
+}
diff --git a/ion/symboltoken_test.go b/ion/symboltoken_test.go
index bccbe52..b3941bc 100644
--- a/ion/symboltoken_test.go
+++ b/ion/symboltoken_test.go
@@ -22,10 +22,6 @@ import (
 	"github.com/google/go-cmp/cmp"
 )
 
-func newString(value string) *string {
-	return &value
-}
-
 var symbolTokenEqualsTestData = []struct {
 	text1   *string
 	sid1    int64
diff --git a/ion/textreader_test.go b/ion/textreader_test.go
index f227a66..f0c1e91 100644
--- a/ion/textreader_test.go
+++ b/ion/textreader_test.go
@@ -35,17 +35,49 @@ func TestReadTextSymbols(t *testing.T) {
 				foo
 				bar
 				$ion
+				null.symbol
 				$11`
 
 	r := NewReaderString(ionText)
-	_symbolAF(t, r, nil, nil, nil, SymbolToken{Text: nil, LocalSID: 0}, false, false, false)
-	_symbol(t, r, newString("$4"), SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown})
-	_symbol(t, r, newString("name"), SymbolToken{Text: newString("name"), LocalSID: 4})
-	_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: 10})
-	_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: 10})
-	_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-	_symbol(t, r, newString("$ion"), SymbolToken{Text: newString("$ion"), LocalSID: 1})
-	_symbolAF(t, r, nil, nil, nil, SymbolToken{}, true, true, true)
+	_symbolAF(t, r, nil, nil, &SymbolToken{Text: nil, LocalSID: 0}, false, false)
+	_symbol(t, r, SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, SymbolToken{Text: newString("name"), LocalSID: 4})
+	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: 10})
+	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: 10})
+	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, SymbolToken{Text: newString("$ion"), LocalSID: 1})
+	_symbolAF(t, r, nil, nil, nil, false, false)
+	_symbolAF(t, r, nil, nil, &SymbolToken{}, true, true)
+}
+
+func TestReadTextAnnotations(t *testing.T) {
+	ionText := `$ion_symbol_table::
+				{
+					symbols:[ "foo" ]
+				}
+				[
+					$0::1,
+					'$4'::1,
+					$4::1,
+					$10::1,
+					foo::1,
+					bar::1,
+					$ion::1,
+					$11::1
+				]`
+
+	r := NewReaderString(ionText)
+	r.Next()
+	r.StepIn()
+
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: nil, LocalSID: 0}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("name"), LocalSID: 4}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: 10}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: 10}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, false, false)
+	_nextA(t, r, []SymbolToken{SymbolToken{Text: newString("$ion"), LocalSID: 1}}, false, false)
+	_nextA(t, r, nil, true, true)
 }
 
 func TestReadTextFieldNames(t *testing.T) {
@@ -61,6 +93,7 @@ func TestReadTextFieldNames(t *testing.T) {
 					foo:1,
 					bar:1,
 					$ion:1,
+					null.symbol:1
 					$11:1
 				}`
 
@@ -68,16 +101,30 @@ func TestReadTextFieldNames(t *testing.T) {
 	r.Next()
 	err := r.StepIn()
 	if err != nil {
-		t.Errorf("stepin returned err: %v", err.Error())
+		t.Errorf("stepIn returned err: %v", err.Error())
+	}
+	_nextF(t, r, &SymbolToken{Text: nil, LocalSID: 0}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("name"), LocalSID: 4}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: 10}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: 10}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}, false, false)
+	_nextF(t, r, &SymbolToken{Text: newString("$ion"), LocalSID: 1}, false, false)
+	_nextF(t, r, &SymbolToken{}, true, true)
+}
+
+func TestReadTextNullFieldName(t *testing.T) {
+	ionText := `{
+					null.symbol:1
+				}`
+
+	r := NewReaderString(ionText)
+	r.Next()
+	err := r.StepIn()
+	if err != nil {
+		t.Errorf("stepIn returned err: %v", err.Error())
 	}
-	_nextF(t, r, nil, SymbolToken{Text: nil, LocalSID: 0}, false, false)
-	_nextF(t, r, newString("$4"), SymbolToken{Text: newString("$4"), LocalSID: SymbolIDUnknown}, false, false)
-	_nextF(t, r, newString("name"), SymbolToken{Text: newString("name"), LocalSID: 4}, false, false)
-	_nextF(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: 10}, false, false)
-	_nextF(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: 10}, false, false)
-	_nextF(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}, false, false)
-	_nextF(t, r, newString("$ion"), SymbolToken{Text: newString("$ion"), LocalSID: 1}, false, false)
-	_nextF(t, r, nil, SymbolToken{}, true, true)
+	_nextF(t, r, &SymbolToken{}, true, true)
 }
 
 func TestIgnoreValues(t *testing.T) {
@@ -87,7 +134,7 @@ func TestIgnoreValues(t *testing.T) {
 	_next(t, r, StructType)
 	_next(t, r, ListType)
 
-	_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 	_eof(t, r)
 }
 
@@ -110,13 +157,13 @@ func TestReadSexps(t *testing.T) {
 	})
 
 	test("(foo)", func(t *testing.T, r Reader) {
-		_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 	})
 
 	test("(foo bar baz :: boop)", func(t *testing.T, r Reader) {
-		_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
-		_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-		_symbolAF(t, r, nil, []string{"baz"}, newString("boop"), SymbolToken{Text: newString("boop"), LocalSID: SymbolIDUnknown}, false, false, false)
+		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
+		_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("boop"), LocalSID: SymbolIDUnknown}, false, false)
 	})
 }
 
@@ -134,20 +181,17 @@ func TestStructs(t *testing.T) {
 	})
 
 	test("{foo : bar :: baz}", func(t *testing.T, r Reader) {
-		foo := "foo"
-		_symbolAF(t, r, &foo, []string{"bar"}, newString("baz"), SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, false, false, false)
+		_symbolAF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, false, false)
 	})
 
 	test("{foo: a, bar: b, baz: c}", func(t *testing.T, r Reader) {
-		foo, bar, baz := "foo", "bar", "baz"
-		_symbolAF(t, r, &foo, nil, newString("a"), SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, false, false, false)
-		_symbolAF(t, r, &bar, nil, newString("b"), SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}, false, false, false)
-		_symbolAF(t, r, &baz, nil, newString("c"), SymbolToken{Text: newString("c"), LocalSID: SymbolIDUnknown}, false, false, false)
+		_symbolAF(t, r, &SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbolAF(t, r, &SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}, false, false)
+		_symbolAF(t, r, &SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("c"), LocalSID: SymbolIDUnknown}, false, false)
 	})
 
 	test("{\"\": a}", func(t *testing.T, r Reader) {
-		emptyString := ""
-		_symbolAF(t, r, &emptyString, nil, newString("a"), SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, false, false, false)
+		_symbolAF(t, r, &SymbolToken{Text: newString(""), LocalSID: SymbolIDUnknown}, nil, &SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, false, false)
 	})
 }
 
@@ -167,7 +211,7 @@ func TestNullStructs(t *testing.T) {
 	r := NewReaderString("null.struct 'null'::{foo:bar}")
 
 	_null(t, r, StructType)
-	_nextAF(t, r, StructType, nil, []string{"null"})
+	_nextAF(t, r, StructType, nil, []SymbolToken{SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown}})
 	_eof(t, r)
 }
 
@@ -185,14 +229,14 @@ func TestLists(t *testing.T) {
 	})
 
 	test("[foo]", func(t *testing.T, r Reader) {
-		_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 		_eof(t, r)
 	})
 
 	test("[foo, bar, baz::boop]", func(t *testing.T, r Reader) {
-		_symbol(t, r, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
-		_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-		_symbolAF(t, r, nil, []string{"baz"}, newString("boop"), SymbolToken{Text: newString("boop"), LocalSID: SymbolIDUnknown}, false, false, false)
+		_symbol(t, r, SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
+		_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("boop"), LocalSID: SymbolIDUnknown}, false, false)
 		_eof(t, r)
 	})
 }
@@ -265,7 +309,7 @@ func TestBlobs(t *testing.T) {
 }
 
 func TestTimestamps(t *testing.T) {
-	testA := func(str string, etas []string, eval Timestamp) {
+	testA := func(str string, etas []SymbolToken, eval Timestamp) {
 		t.Run(str, func(t *testing.T) {
 			r := NewReaderString(str)
 			_nextAF(t, r, TimestampType, nil, etas)
@@ -302,11 +346,11 @@ func TestTimestamps(t *testing.T) {
 	et2 := time.Date(2001, time.January, 1, 0, 0, 0, 1, time.UTC)
 	test("2001-01-01T00:00:00.000000000999Z", NewTimestampWithFractionalSeconds(et2, TimestampPrecisionNanosecond, TimezoneUTC, 12))
 
-	testA("foo::'bar'::2001-01-01T00:00:00.000Z", []string{"foo", "bar"}, NewTimestampWithFractionalSeconds(et, TimestampPrecisionNanosecond, TimezoneUTC, 3))
+	testA("foo::'bar'::2001-01-01T00:00:00.000Z", []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, NewTimestampWithFractionalSeconds(et, TimestampPrecisionNanosecond, TimezoneUTC, 3))
 }
 
 func TestDecimals(t *testing.T) {
-	testA := func(str string, etas []string, eval string) {
+	testA := func(str string, etas []SymbolToken, eval string) {
 		t.Run(str, func(t *testing.T) {
 			ee := MustParseDecimal(eval)
 
@@ -336,11 +380,11 @@ func TestDecimals(t *testing.T) {
 	test("123d+2", "12300")
 	test("123d-2", "1.23")
 
-	testA("  foo :: 'bar' :: 123.  ", []string{"foo", "bar"}, "123")
+	testA("  foo :: 'bar' :: 123.  ", []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, "123")
 }
 
 func TestFloats(t *testing.T) {
-	testA := func(str string, etas []string, eval float64) {
+	testA := func(str string, etas []SymbolToken, eval float64) {
 		t.Run(str, func(t *testing.T) {
 			r := NewReaderString(str)
 			_floatAF(t, r, nil, etas, eval)
@@ -358,7 +402,7 @@ func TestFloats(t *testing.T) {
 	test("+inf", math.Inf(1))
 	test("-inf", math.Inf(-1))
 
-	testA("foo::'bar'::1e100", []string{"foo", "bar"}, 1e100)
+	testA("foo::'bar'::1e100", []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}, 1e100)
 }
 
 func TestInts(t *testing.T) {
@@ -436,9 +480,9 @@ func TestInts(t *testing.T) {
 func TestStrings(t *testing.T) {
 	r := NewReaderString(`foo::"bar" "baz" 'a'::'b'::'''beep''' '''boop''' null.string`)
 
-	_stringAF(t, r, nil, []string{"foo"}, newString("bar"))
+	_stringAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}}, newString("bar"))
 	_string(t, r, newString("baz"))
-	_stringAF(t, r, nil, []string{"a", "b"}, newString("beepboop"))
+	_stringAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}}, newString("beepboop"))
 	_null(t, r, StringType)
 
 	_eof(t, r)
@@ -447,9 +491,9 @@ func TestStrings(t *testing.T) {
 func TestSymbols(t *testing.T) {
 	r := NewReaderString("'null'::foo bar a::b::'baz' null.symbol")
 
-	_symbolAF(t, r, nil, []string{"null"}, newString("foo"), SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, false, false, false)
-	_symbol(t, r, newString("bar"), SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
-	_symbolAF(t, r, nil, []string{"a", "b"}, newString("baz"), SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, false, false, false)
+	_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}, false, false)
+	_symbol(t, r, SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown})
+	_symbolAF(t, r, nil, []SymbolToken{SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown}}, &SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}, false, false)
 	_null(t, r, SymbolType)
 
 	_eof(t, r)
@@ -471,12 +515,12 @@ func TestOperators(t *testing.T) {
 	r := NewReaderString("(a*(b+c))")
 
 	_sexp(t, r, func(t *testing.T, r Reader) {
-		_symbol(t, r, newString("a"), SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
-		_symbol(t, r, newString("*"), SymbolToken{Text: newString("*"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
+		_symbol(t, r, SymbolToken{Text: newString("*"), LocalSID: SymbolIDUnknown})
 		_sexp(t, r, func(t *testing.T, r Reader) {
-			_symbol(t, r, newString("b"), SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown})
-			_symbol(t, r, newString("+"), SymbolToken{Text: newString("+"), LocalSID: SymbolIDUnknown})
-			_symbol(t, r, newString("c"), SymbolToken{Text: newString("c"), LocalSID: SymbolIDUnknown})
+			_symbol(t, r, SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown})
+			_symbol(t, r, SymbolToken{Text: newString("+"), LocalSID: SymbolIDUnknown})
+			_symbol(t, r, SymbolToken{Text: newString("c"), LocalSID: SymbolIDUnknown})
 			_eof(t, r)
 		})
 		_eof(t, r)
@@ -486,7 +530,7 @@ func TestOperators(t *testing.T) {
 func TestTopLevelOperators(t *testing.T) {
 	r := NewReaderString("a + b")
 
-	_symbol(t, r, newString("a"), SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
+	_symbol(t, r, SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
 
 	if r.Next() {
 		t.Errorf("next returned true")
@@ -543,7 +587,7 @@ func _sexp(t *testing.T, r Reader, f containerhandler) {
 	_sexpAF(t, r, nil, nil, f)
 }
 
-func _sexpAF(t *testing.T, r Reader, efn *string, etas []string, f containerhandler) {
+func _sexpAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, f containerhandler) {
 	_containerAF(t, r, SexpType, efn, etas, f)
 }
 
@@ -551,7 +595,7 @@ func _struct(t *testing.T, r Reader, f containerhandler) {
 	_structAF(t, r, nil, nil, f)
 }
 
-func _structAF(t *testing.T, r Reader, efn *string, etas []string, f containerhandler) {
+func _structAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, f containerhandler) {
 	_containerAF(t, r, StructType, efn, etas, f)
 }
 
@@ -559,11 +603,11 @@ func _list(t *testing.T, r Reader, f containerhandler) {
 	_listAF(t, r, nil, nil, f)
 }
 
-func _listAF(t *testing.T, r Reader, efn *string, etas []string, f containerhandler) {
+func _listAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, f containerhandler) {
 	_containerAF(t, r, ListType, efn, etas, f)
 }
 
-func _containerAF(t *testing.T, r Reader, et Type, efn *string, etas []string, f containerhandler) {
+func _containerAF(t *testing.T, r Reader, et Type, efn *SymbolToken, etas []SymbolToken, f containerhandler) {
 	_nextAF(t, r, et, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.%v", et, et)
@@ -584,7 +628,7 @@ func _int(t *testing.T, r Reader, eval int) {
 	_intAF(t, r, nil, nil, eval)
 }
 
-func _intAF(t *testing.T, r Reader, efn *string, etas []string, eval int) {
+func _intAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval int) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -611,7 +655,7 @@ func _int64(t *testing.T, r Reader, eval int64) {
 	_int64AF(t, r, nil, nil, eval)
 }
 
-func _int64AF(t *testing.T, r Reader, efn *string, etas []string, eval int64) {
+func _int64AF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval int64) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -638,7 +682,7 @@ func _uint(t *testing.T, r Reader, eval uint64) {
 	_uintAF(t, r, nil, nil, eval)
 }
 
-func _uintAF(t *testing.T, r Reader, efn *string, etas []string, eval uint64) {
+func _uintAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval uint64) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -665,7 +709,7 @@ func _bigInt(t *testing.T, r Reader, eval *big.Int) {
 	_bigIntAF(t, r, nil, nil, eval)
 }
 
-func _bigIntAF(t *testing.T, r Reader, efn *string, etas []string, eval *big.Int) {
+func _bigIntAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval *big.Int) {
 	_nextAF(t, r, IntType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.int", eval)
@@ -692,7 +736,7 @@ func _float(t *testing.T, r Reader, eval float64) {
 	_floatAF(t, r, nil, nil, eval)
 }
 
-func _floatAF(t *testing.T, r Reader, efn *string, etas []string, eval float64) {
+func _floatAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval float64) {
 	_nextAF(t, r, FloatType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.float", eval)
@@ -716,7 +760,7 @@ func _decimal(t *testing.T, r Reader, eval *Decimal) {
 	_decimalAF(t, r, nil, nil, eval)
 }
 
-func _decimalAF(t *testing.T, r Reader, efn *string, etas []string, eval *Decimal) {
+func _decimalAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval *Decimal) {
 	_nextAF(t, r, DecimalType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.decimal", eval)
@@ -736,7 +780,7 @@ func _timestamp(t *testing.T, r Reader, eval Timestamp) {
 	_timestampAF(t, r, nil, nil, eval)
 }
 
-func _timestampAF(t *testing.T, r Reader, efn *string, etas []string, eval Timestamp) {
+func _timestampAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval Timestamp) {
 	_nextAF(t, r, TimestampType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.timestamp", eval)
@@ -756,7 +800,7 @@ func _string(t *testing.T, r Reader, eval *string) {
 	_stringAF(t, r, nil, nil, eval)
 }
 
-func _stringAF(t *testing.T, r Reader, efn *string, etas []string, eval *string) {
+func _stringAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval *string) {
 	_nextAF(t, r, StringType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.string", eval)
@@ -777,40 +821,23 @@ func _stringAF(t *testing.T, r Reader, efn *string, etas []string, eval *string)
 
 // _symbolAF calls reader.next and asserts the expected symbol value. This function also asserts the value has neither
 // annotation or field name.
-func _symbol(t *testing.T, r Reader, eval *string, evalst SymbolToken) {
-	_symbolAF(t, r, nil, nil, eval, evalst, false, false, false)
+func _symbol(t *testing.T, r Reader, evalst SymbolToken) {
+	_symbolAF(t, r, nil, nil, &evalst, false, false)
 }
 
 // _symbolAF calls reader.next and asserts the expected symbol value, annotation, and field name.
-func _symbolAF(t *testing.T, r Reader, efn *string, etas []string, eval *string, evalSt SymbolToken, isStringValueError bool, isSymbolValueError bool, isNextError bool) {
+func _symbolAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, evalSt *SymbolToken, isSymbolValueError bool, isNextError bool) {
 	if !isNextError {
 		_nextAF(t, r, SymbolType, efn, etas)
 	} else {
 		r.Next()
 	}
 
-	if r.IsNull() {
-		t.Fatalf("expected %v, got null.symbol", eval)
-	}
-
-	val, err := r.StringValue()
-	if isStringValueError {
-		if err == nil {
-			t.Fatal("stringvalue did not return an error")
-		}
-	} else {
-		if err != nil {
-			t.Fatal(err)
-		}
-		if eval == nil {
-			if eval != val {
-				t.Errorf("expected %v, got %v", eval, val)
-			}
-		} else if *val != *eval {
-			t.Errorf("expected %v, got %v", *eval, *val)
+	if evalSt != nil {
+		if r.IsNull() {
+			t.Fatalf("expected %v, got null.symbol", evalSt.Text)
 		}
 	}
-
 	symbolVal, err := r.SymbolValue()
 	if isSymbolValueError {
 		if err == nil {
@@ -820,8 +847,14 @@ func _symbolAF(t *testing.T, r Reader, efn *string, etas []string, eval *string,
 		if err != nil {
 			t.Fatal(err)
 		}
-		if !symbolVal.Equal(&evalSt) {
-			t.Errorf("expected %v, got %v", &evalSt, symbolVal)
+		if evalSt != nil {
+			if !symbolVal.Equal(evalSt) {
+				t.Errorf("expected %v, got %v", &evalSt, symbolVal)
+			}
+		} else {
+			if symbolVal != nil {
+				t.Errorf("expected %v, got %v", evalSt, symbolVal)
+			}
 		}
 	}
 }
@@ -830,7 +863,7 @@ func _bool(t *testing.T, r Reader, eval bool) {
 	_boolAF(t, r, nil, nil, eval)
 }
 
-func _boolAF(t *testing.T, r Reader, efn *string, etas []string, eval bool) {
+func _boolAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval bool) {
 	_nextAF(t, r, BoolType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.bool", eval)
@@ -849,7 +882,7 @@ func _clob(t *testing.T, r Reader, eval []byte) {
 	_clobAF(t, r, nil, nil, eval)
 }
 
-func _clobAF(t *testing.T, r Reader, efn *string, etas []string, eval []byte) {
+func _clobAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval []byte) {
 	_nextAF(t, r, ClobType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.clob", eval)
@@ -868,7 +901,7 @@ func _blob(t *testing.T, r Reader, eval []byte) {
 	_blobAF(t, r, nil, nil, eval)
 }
 
-func _blobAF(t *testing.T, r Reader, efn *string, etas []string, eval []byte) {
+func _blobAF(t *testing.T, r Reader, efn *SymbolToken, etas []SymbolToken, eval []byte) {
 	_nextAF(t, r, BlobType, efn, etas)
 	if r.IsNull() {
 		t.Fatalf("expected %v, got null.blob", eval)
@@ -887,7 +920,7 @@ func _null(t *testing.T, r Reader, et Type) {
 	_nullAF(t, r, et, nil, nil)
 }
 
-func _nullAF(t *testing.T, r Reader, et Type, efn *string, etas []string) {
+func _nullAF(t *testing.T, r Reader, et Type, efn *SymbolToken, etas []SymbolToken) {
 	_nextAF(t, r, et, efn, etas)
 	if !r.IsNull() {
 		t.Error("isnull returned false")
@@ -898,7 +931,7 @@ func _next(t *testing.T, r Reader, et Type) {
 	_nextAF(t, r, et, nil, nil)
 }
 
-func _nextAF(t *testing.T, r Reader, et Type, efn *string, etas []string) {
+func _nextAF(t *testing.T, r Reader, et Type, efn *SymbolToken, etas []SymbolToken) {
 	if !r.Next() {
 		t.Fatal(r.Err())
 	}
@@ -906,47 +939,78 @@ func _nextAF(t *testing.T, r Reader, et Type, efn *string, etas []string) {
 		t.Fatalf("expected %v, got %v", et, r.Type())
 	}
 
-	fn := r.FieldName()
-	if efn != nil && fn != nil && *efn != *fn {
+	fn, err := r.FieldName()
+	if err != nil {
+		t.Errorf("fieldname returned error: %v", err.Error())
+	}
+
+	if efn != nil && fn != nil && !efn.Equal(fn) {
 		t.Errorf("expected fieldname=%v, got %v", *efn, *fn)
 	}
-	if !_strequals(etas, r.Annotations()) {
-		t.Errorf("expected type annotations=%v, got %v", etas, r.Annotations())
+
+	annotations, err := r.Annotations()
+	if err != nil {
+		t.Errorf("annotations returned error: %v", err.Error())
+
+	}
+
+	if !_symbolTokenEquals(etas, annotations) {
+		t.Errorf("expected type annotations=%v, got %v", etas, annotations)
 	}
 }
 
-func _nextF(t *testing.T, r Reader, efn *string, efns SymbolToken, isFieldNameSymbolError bool, isNextError bool) {
+func _nextA(t *testing.T, r Reader, etas []SymbolToken, isAnnotationError bool, isNextError bool) {
 	if !r.Next() && !isNextError {
 		t.Fatal(r.Err())
 	}
 
-	fn := r.FieldName()
-	if efn != nil && fn != nil && *efn != *fn {
-		t.Errorf("expected fieldname=%v, got %v", *efn, *fn)
+	annotations, err := r.Annotations()
+
+	if isAnnotationError {
+		if err == nil {
+			t.Fatal("fieldName did not return an error")
+		}
+	} else {
+		if !_symbolTokenEquals(etas, annotations) {
+			t.Errorf("expected type annotations=%v, got %v", etas, annotations)
+		}
 	}
+}
 
-	fns, err := r.FieldNameSymbol()
-	if isFieldNameSymbolError {
+func _nextF(t *testing.T, r Reader, efns *SymbolToken, isFieldNameError bool, isNextError bool) {
+	if !r.Next() && !isNextError {
+		t.Fatal(r.Err())
+	}
+
+	fn, err := r.FieldName()
+
+	if isFieldNameError {
 		if err == nil {
-			t.Fatal("stringvalue did not return an error")
+			t.Fatal("fieldName did not return an error")
 		}
 	} else {
 		if err != nil {
-			t.Errorf("fieldnamesymbol returned an error: %v", err.Error())
+			t.Errorf("fieldname returned an error: %v", err.Error())
 		}
-		if !efns.Equal(fns) {
-			t.Errorf("expected fieldnamesymbol=%v, got %v", efns, fns)
+		if efns != nil {
+			if !efns.Equal(fn) {
+				t.Errorf("expected fieldnamesymbol=%v, got %v", efns, fn)
+			}
+		} else {
+			if fn != nil {
+				t.Errorf("expected fieldnamesymbol=%v, got %v", efns, fn)
+			}
 		}
 	}
 }
 
-func _strequals(a, b []string) bool {
+func _symbolTokenEquals(a, b []SymbolToken) bool {
 	if len(a) != len(b) {
 		return false
 	}
 
 	for i := 0; i < len(a); i++ {
-		if a[i] != b[i] {
+		if !a[i].Equal(&b[i]) {
 			return false
 		}
 	}
diff --git a/ion/textwriter_test.go b/ion/textwriter_test.go
index 9b121c6..b644806 100644
--- a/ion/textwriter_test.go
+++ b/ion/textwriter_test.go
@@ -49,9 +49,9 @@ func TestWriteTextEmptyStruct(t *testing.T) {
 
 func TestWriteTextAnnotatedStruct(t *testing.T) {
 	testTextWriter(t, "foo::$bar::'.baz'::{}", func(w Writer) {
-		w.Annotation("foo")
-		w.Annotation("$bar")
-		w.Annotation(".baz")
+		w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
+		w.Annotation(SymbolToken{Text: newString("$bar"), LocalSID: SymbolIDUnknown})
+		w.Annotation(SymbolToken{Text: newString(".baz"), LocalSID: SymbolIDUnknown})
 		w.BeginStruct()
 		err := w.EndStruct()
 
@@ -66,7 +66,7 @@ func TestWriteTextNestedStruct(t *testing.T) {
 		w.BeginStruct()
 
 		w.FieldName("foo")
-		w.Annotation("true")
+		w.Annotation(SymbolToken{Text: newString("true"), LocalSID: SymbolIDUnknown})
 		w.BeginStruct()
 		w.EndStruct()
 
@@ -101,11 +101,11 @@ func TestWriteTextNestedLists(t *testing.T) {
 		w.BeginStruct()
 		w.EndStruct()
 
-		w.Annotation("foo")
+		w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 		w.BeginStruct()
 		w.EndStruct()
 
-		w.Annotation("null")
+		w.Annotation(SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown})
 		w.BeginList()
 		w.EndList()
 
@@ -141,7 +141,7 @@ func TestWriteTextNulls(t *testing.T) {
 		w.BeginList()
 
 		w.WriteNull()
-		w.Annotation("foo")
+		w.Annotation(SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown})
 		w.WriteNullType(NullType)
 		w.WriteNullType(BoolType)
 		w.WriteNullType(IntType)
@@ -153,7 +153,7 @@ func TestWriteTextNulls(t *testing.T) {
 		w.WriteNullType(ClobType)
 		w.WriteNullType(BlobType)
 		w.WriteNullType(ListType)
-		w.Annotation("null")
+		w.Annotation(SymbolToken{Text: newString("null"), LocalSID: SymbolIDUnknown})
 		w.WriteNullType(SexpType)
 		w.WriteNullType(StructType)
 
@@ -169,12 +169,12 @@ func TestWriteTextBool(t *testing.T) {
 		w.BeginSexp()
 
 		w.WriteBool(false)
-		w.Annotation("123")
+		w.Annotation(SymbolToken{Text: newString("123"), LocalSID: SymbolIDUnknown})
 		w.WriteBool(true)
 
 		w.EndSexp()
 
-		w.Annotation("false")
+		w.Annotation(SymbolToken{Text: newString("false"), LocalSID: SymbolIDUnknown})
 		w.WriteBool(false)
 	})
 }
@@ -184,7 +184,7 @@ func TestWriteTextInt(t *testing.T) {
 	testTextWriter(t, expected, func(w Writer) {
 		w.BeginSexp()
 
-		w.Annotation("zero")
+		w.Annotation(SymbolToken{Text: newString("zero"), LocalSID: SymbolIDUnknown})
 		w.WriteInt(0)
 		w.WriteInt(1)
 		w.WriteInt(-1)
@@ -210,7 +210,7 @@ func TestWriteTextBigInt(t *testing.T) {
 		one.SetInt64(1)
 		val.Add(&max, &one)
 
-		w.Annotation("big")
+		w.Annotation(SymbolToken{Text: newString("big"), LocalSID: SymbolIDUnknown})
 		w.WriteBigInt(&val)
 
 		w.EndList()
@@ -274,9 +274,9 @@ func TestWriteTextSymbol(t *testing.T) {
 		w.WriteSymbol("null")
 
 		w.FieldName("f")
-		w.Annotation("a")
-		w.Annotation("b")
-		w.Annotation("u")
+		w.Annotation(SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown})
+		w.Annotation(SymbolToken{Text: newString("b"), LocalSID: SymbolIDUnknown})
+		w.Annotation(SymbolToken{Text: newString("u"), LocalSID: SymbolIDUnknown})
 		w.WriteSymbol("lo🇺🇸")
 
 		w.FieldName("$123")
@@ -295,7 +295,7 @@ func TestWriteTextString(t *testing.T) {
 
 		w.BeginSexp()
 		w.WriteString("\\\"\n\"\\")
-		w.Annotation("zany")
+		w.Annotation(SymbolToken{Text: newString("zany"), LocalSID: SymbolIDUnknown})
 		w.WriteString("🤪")
 		w.EndSexp()
 
@@ -308,7 +308,7 @@ func TestWriteTextBlob(t *testing.T) {
 	testTextWriter(t, expected, func(w Writer) {
 		w.WriteBlob([]byte{0, 1, 2, 0xFD, 0xFE, 0xFF})
 		w.WriteBlob([]byte("Hello World"))
-		w.Annotation("empty")
+		w.Annotation(SymbolToken{Text: newString("empty"), LocalSID: SymbolIDUnknown})
 		w.WriteBlob(nil)
 	})
 }
@@ -362,7 +362,7 @@ func TestWriteTextPretty(t *testing.T) {
 		w.EndStruct()
 
 		w.FieldName("list")
-		w.Annotations("i", "am", "a", "list")
+		w.Annotations(SymbolToken{Text: newString("i"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("am"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("a"), LocalSID: SymbolIDUnknown}, SymbolToken{Text: newString("list"), LocalSID: SymbolIDUnknown})
 		w.BeginList()
 		{
 			w.WriteString("value")
diff --git a/ion/unmarshal_test.go b/ion/unmarshal_test.go
index ec6262f..7310e37 100644
--- a/ion/unmarshal_test.go
+++ b/ion/unmarshal_test.go
@@ -300,6 +300,9 @@ func TestUnmarshalBinary(t *testing.T) {
 			case Timestamp:
 				thisTime := ionTimestamp{thisValue}
 				res = thisTime.eq(ionTimestamp{eval.(Timestamp)})
+			case *SymbolToken:
+				thisSymbol := ionSymbol{thisValue}
+				res = thisSymbol.eq(ionSymbol{eval.(*SymbolToken)})
 			case *interface{}:
 				res = reflect.DeepEqual(*thisValue, eval)
 			default:
@@ -342,7 +345,7 @@ func TestUnmarshalBinary(t *testing.T) {
 
 	var symbolVal string
 	symbolBytes := prefixIVM([]byte{0x71, 0x09}) // $9
-	test(symbolBytes, symbolVal, newString("$ion_shared_symbol_table"))
+	test(symbolBytes, symbolVal, &SymbolToken{Text: newString("$ion_shared_symbol_table"), LocalSID: 9})
 
 	var stringVal string
 	stringBytes := prefixIVM([]byte{0x83, 'a', 'b', 'c'}) // "abc"
@@ -665,7 +668,7 @@ func TestDecode(t *testing.T) {
 
 	test("2020T", NewDateTimestamp(time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC), TimestampPrecisionYear))
 
-	test("hello", newString("hello"))
+	test("hello", &SymbolToken{Text: newString("hello"), LocalSID: SymbolIDUnknown})
 	test("\"hello\"", newString("hello"))
 
 	test("null.blob", nil)
@@ -680,14 +683,14 @@ func TestDecode(t *testing.T) {
 	test("{}", map[string]interface{}{})
 	test("{a:1,b:two}", map[string]interface{}{
 		"a": 1,
-		"b": newString("two"),
+		"b": &SymbolToken{Text: newString("two"), LocalSID: SymbolIDUnknown},
 	})
 
 	test("null.list", nil)
-	test("[1, two]", []interface{}{1, newString("two")})
+	test("[1, two]", []interface{}{1, &SymbolToken{Text: newString("two"), LocalSID: SymbolIDUnknown}})
 
 	test("null.sexp", nil)
-	test("(1 + two)", []interface{}{1, newString("+"), newString("two")})
+	test("(1 + two)", []interface{}{1, &SymbolToken{Text: newString("+"), LocalSID: SymbolIDUnknown}, &SymbolToken{Text: newString("two"), LocalSID: SymbolIDUnknown}})
 
 	var result []interface{}
 	test("()", result)
@@ -728,7 +731,7 @@ func TestDecodeLotsOfInts(t *testing.T) {
 func TestUnmarshalWithAnnotation(t *testing.T) {
 	type foo struct {
 		Value   interface{}
-		AnyName []string `ion:",annotations"`
+		AnyName []SymbolToken `ion:",annotations"`
 	}
 
 	test := func(str, testName string, eval foo) {
@@ -745,24 +748,24 @@ func TestUnmarshalWithAnnotation(t *testing.T) {
 		})
 	}
 
-	test("with::multiple::annotations::null", "null", foo{nil, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::true", "bool", foo{true, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::2", "int", foo{2, []string{"with", "multiple", "annotations"}})
+	test("with::multiple::annotations::null", "null", foo{nil, annotations})
+	test("with::multiple::annotations::true", "bool", foo{true, annotations})
+	test("with::multiple::annotations::2", "int", foo{2, annotations})
 	bi := new(big.Int).Neg(new(big.Int).SetUint64(0xFFFFFFFFFFFFFFFF))
-	test("with::multiple::annotations::-18446744073709551615", "big.Int", foo{bi, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::2.1e1", "float", foo{2.1e1, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::2.2", "decimal", foo{MustParseDecimal("2.2"), []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::\"abc\"", "string", foo{newString("abc"), []string{"with", "multiple", "annotations"}})
+	test("with::multiple::annotations::-18446744073709551615", "big.Int", foo{bi, annotations})
+	test("with::multiple::annotations::2.1e1", "float", foo{2.1e1, annotations})
+	test("with::multiple::annotations::2.2", "decimal", foo{MustParseDecimal("2.2"), annotations})
+	test("with::multiple::annotations::\"abc\"", "string", foo{newString("abc"), annotations})
 	timestamp := NewTimestamp(time.Date(2000, 1, 2, 3, 4, 5, 0, time.UTC), TimestampPrecisionSecond, TimezoneUTC)
-	test("with::multiple::annotations::2000-01-02T03:04:05Z", "timestamp", foo{timestamp, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::{{'''abc'''}}", "clob", foo{[]byte{97, 98, 99}, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::{{/w==}}", "blob", foo{[]byte{255}, []string{"with", "multiple", "annotations"}})
+	test("with::multiple::annotations::2000-01-02T03:04:05Z", "timestamp", foo{timestamp, annotations})
+	test("with::multiple::annotations::{{'''abc'''}}", "clob", foo{[]byte{97, 98, 99}, annotations})
+	test("with::multiple::annotations::{{/w==}}", "blob", foo{[]byte{255}, annotations})
 }
 
 func TestUnmarshalContainersWithAnnotation(t *testing.T) {
 	type foo struct {
 		Value   []int
-		AnyName []string `ion:",annotations"`
+		AnyName []SymbolToken `ion:",annotations"`
 	}
 
 	test := func(str, testName string, eval interface{}) {
@@ -779,24 +782,24 @@ func TestUnmarshalContainersWithAnnotation(t *testing.T) {
 		})
 	}
 
-	test("with::multiple::annotations::[1, 2, 3]", "list", foo{[]int{1, 2, 3}, []string{"with", "multiple", "annotations"}})
-	test("with::multiple::annotations::(1 2 3)", "sexp", foo{[]int{1, 2, 3}, []string{"with", "multiple", "annotations"}})
+	test("with::multiple::annotations::[1, 2, 3]", "list", foo{[]int{1, 2, 3}, annotations})
+	test("with::multiple::annotations::(1 2 3)", "sexp", foo{[]int{1, 2, 3}, annotations})
 }
 
 func TestUnmarshalNestedStructsWithAnnotation(t *testing.T) {
 	type nestedInt struct {
 		Value           int
-		ValueAnnotation []string `ion:",annotations"`
+		ValueAnnotation []SymbolToken `ion:",annotations"`
 	}
 
 	type nestedStruct struct {
 		Field2                nestedInt
-		InnerStructAnnotation []string `ion:",annotations"`
+		InnerStructAnnotation []SymbolToken `ion:",annotations"`
 	}
 
 	type topLevelStruct struct {
 		Field1             nestedStruct
-		TopLevelAnnotation []string `ion:",annotations"`
+		TopLevelAnnotation []SymbolToken `ion:",annotations"`
 	}
 
 	test := func(str, testName string, eval interface{}) {
@@ -820,9 +823,14 @@ func TestUnmarshalNestedStructsWithAnnotation(t *testing.T) {
 		  }
 		}
 	*/
-	innerStructVal := nestedInt{Value: 5, ValueAnnotation: []string{"baz"}}
-	mainStructVal := nestedStruct{Field2: innerStructVal, InnerStructAnnotation: []string{"bar"}}
-	expectedValue := topLevelStruct{Field1: mainStructVal, TopLevelAnnotation: []string{"foo"}}
+	innerStructVal := nestedInt{Value: 5, ValueAnnotation: []SymbolToken{SymbolToken{Text: newString("baz"), LocalSID: SymbolIDUnknown}}}
+	mainStructVal := nestedStruct{Field2: innerStructVal, InnerStructAnnotation: []SymbolToken{SymbolToken{Text: newString("bar"), LocalSID: SymbolIDUnknown}}}
+	expectedValue := topLevelStruct{Field1: mainStructVal, TopLevelAnnotation: []SymbolToken{SymbolToken{Text: newString("foo"), LocalSID: SymbolIDUnknown}}}
 
 	test("foo::{Field1:bar::{Field2:baz::5}}", "nested structs", expectedValue)
 }
+
+var symbolTokenWith = SymbolToken{Text: newString("with"), LocalSID: SymbolIDUnknown}
+var symbolTokenMultiple = SymbolToken{Text: newString("multiple"), LocalSID: SymbolIDUnknown}
+var symbolTokenAnnotations = SymbolToken{Text: newString("annotations"), LocalSID: SymbolIDUnknown}
+var annotations = []SymbolToken{symbolTokenWith, symbolTokenMultiple, symbolTokenAnnotations}