1// Copyright 2015 Jean Niklas L'orange.  All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package edn implements encoding and decoding of EDN values as defined in
6// https://github.com/edn-format/edn. For a full introduction on how to use
7// go-edn, see https://github.com/go-edn/edn/blob/v1/docs/introduction.md. Fully
8// self-contained examples of go-edn can be found at
9// https://github.com/go-edn/edn/tree/v1/examples.
10//
11// Note that the small examples in this package is not checking errors as
12// persively as you should do when you use this package. This is done because
13// I'd like the examples to be easily readable and understandable. The bigger
14// examples provide proper error handling.
15package edn
16
17import (
18	"encoding/base64"
19	"errors"
20	"math/big"
21	"reflect"
22	"sync"
23	"time"
24)
25
26var (
27	ErrNotFunc         = errors.New("Value is not a function")
28	ErrMismatchArities = errors.New("Function does not have single argument in, two argument out")
29	ErrNotConcrete     = errors.New("Value is not a concrete non-function type")
30	ErrTagOverwritten  = errors.New("Previous tag implementation was overwritten")
31)
32
33var globalTags TagMap
34
35// A TagMap contains mappings from tag literals to functions and structs that is
36// used when decoding.
37type TagMap struct {
38	sync.RWMutex
39	m map[string]reflect.Value
40}
41
42var errorType = reflect.TypeOf((*error)(nil)).Elem()
43
44// AddTagFn adds fn as a converter function for tagname tags to this TagMap. fn
45// must have the signature func(T) (U, error), where T is the expected input
46// type and U is the output type. See Decoder.AddTagFn for examples.
47func (tm *TagMap) AddTagFn(tagname string, fn interface{}) error {
48	// TODO: check name
49	rfn := reflect.ValueOf(fn)
50	rtyp := rfn.Type()
51	if rtyp.Kind() != reflect.Func {
52		return ErrNotFunc
53	}
54	if rtyp.NumIn() != 1 || rtyp.NumOut() != 2 || !rtyp.Out(1).Implements(errorType) {
55		// ok to have variadic arity?
56		return ErrMismatchArities
57	}
58	return tm.addVal(tagname, rfn)
59}
60
61func (tm *TagMap) addVal(name string, val reflect.Value) error {
62	tm.Lock()
63	if tm.m == nil {
64		tm.m = map[string]reflect.Value{}
65	}
66	_, ok := tm.m[name]
67	tm.m[name] = val
68	tm.Unlock()
69	if ok {
70		return ErrTagOverwritten
71	} else {
72		return nil
73	}
74}
75
76// AddTagFn adds fn as a converter function for tagname tags to the global
77// TagMap. fn must have the signature func(T) (U, error), where T is the
78// expected input type and U is the output type. See Decoder.AddTagFn for
79// examples.
80func AddTagFn(tagname string, fn interface{}) error {
81	return globalTags.AddTagFn(tagname, fn)
82}
83
84// AddTagStructs adds the struct as a matching struct for tagname tags to this
85// TagMap. val can not be a channel, function, interface or an unsafe pointer.
86// See Decoder.AddTagStruct for examples.
87func (tm *TagMap) AddTagStruct(tagname string, val interface{}) error {
88	rstruct := reflect.ValueOf(val)
89	switch rstruct.Type().Kind() {
90	case reflect.Invalid, reflect.Chan, reflect.Func, reflect.Interface, reflect.UnsafePointer:
91		return ErrNotConcrete
92	}
93	return tm.addVal(tagname, rstruct)
94}
95
96// AddTagStructs adds the struct as a matching struct for tagname tags to the
97// global TagMap. val can not be a channel, function, interface or an unsafe
98// pointer. See Decoder.AddTagStruct for examples.
99func AddTagStruct(tagname string, val interface{}) error {
100	return globalTags.AddTagStruct(tagname, val)
101}
102
103func init() {
104	err := AddTagFn("inst", func(s string) (time.Time, error) {
105		return time.Parse(time.RFC3339Nano, s)
106	})
107	if err != nil {
108		panic(err)
109	}
110	err = AddTagFn("base64", base64.StdEncoding.DecodeString)
111	if err != nil {
112		panic(err)
113	}
114}
115
116// A MathContext specifies the precision and rounding mode for
117// `math/big.Float`s when decoding.
118type MathContext struct {
119	Precision uint
120	Mode      big.RoundingMode
121}
122
123// The GlobalMathContext is the global MathContext. It is used if no other
124// context is provided. See MathContext for example usage.
125var GlobalMathContext = MathContext{
126	Mode:      big.ToNearestEven,
127	Precision: 192,
128}
129