1/*
2 * gomacro - A Go interpreter with Lisp-like macros
3 *
4 * Copyright (C) 2017-2019 Massimiliano Ghilardi
5 *
6 *     This Source Code Form is subject to the terms of the Mozilla Public
7 *     License, v. 2.0. If a copy of the MPL was not distributed with this
8 *     file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 *
11 * val.go
12 *
13 *  Created on May 27, 2017
14 *      Author Massimiliano Ghilardi
15 */
16
17package untyped
18
19import (
20	"fmt"
21	"go/constant"
22	"go/token"
23	"go/types"
24	"math/big"
25	"strings"
26
27	"github.com/cosmos72/gomacro/base/output"
28)
29
30// untyped value
31type Val struct {
32	Kind Kind // default type
33	Val  constant.Value
34}
35
36func GoUntypedToKind(gkind types.BasicKind) Kind {
37	var kind Kind
38	switch gkind {
39	case types.UntypedBool:
40		kind = Bool
41	case types.UntypedInt:
42		kind = Int
43	case types.UntypedRune:
44		kind = Rune
45	case types.UntypedFloat:
46		kind = Float
47	case types.UntypedComplex:
48		kind = Complex
49	case types.UntypedString:
50		kind = String
51	case types.UntypedNil:
52		kind = None
53	default:
54		output.Errorf("unsupported types.BasicKind: %v", gkind)
55	}
56	return kind
57}
58
59func (val *Val) Marshal() string {
60	return Marshal(val.Kind, val.Val)
61}
62
63func Marshal(kind Kind, val constant.Value) string {
64	// untyped constants have arbitrary precision... they may overflow integers
65	var s string
66	switch kind {
67	case None:
68		s = "nil"
69	case Bool:
70		if constant.BoolVal(val) {
71			s = "bool:true"
72		} else {
73			s = "bool:false"
74		}
75	case Int:
76		s = fmt.Sprintf("int:%s", val.ExactString())
77	case Rune:
78		s = fmt.Sprintf("rune:%s", val.ExactString())
79	case Float:
80		s = fmt.Sprintf("float:%s", val.ExactString())
81	case Complex:
82		s = fmt.Sprintf("complex:%s:%s", constant.Real(val).ExactString(), constant.Imag(val).ExactString())
83	case String:
84		s = fmt.Sprintf("string:%s", constant.StringVal(val))
85	}
86	return s
87}
88
89func UnmarshalVal(marshalled string) *Val {
90	kind, val := Unmarshal(marshalled)
91	return &Val{kind, val}
92}
93
94func Unmarshal(marshalled string) (Kind, constant.Value) {
95	var skind, str string
96	if sep := strings.IndexByte(marshalled, ':'); sep >= 0 {
97		skind = marshalled[:sep]
98		str = marshalled[sep+1:]
99	} else {
100		skind = marshalled
101	}
102
103	var kind Kind
104	var val constant.Value
105	switch skind {
106	case "bool":
107		kind = Bool
108		if str == "true" {
109			val = constant.MakeBool(true)
110		} else {
111			val = constant.MakeBool(false)
112		}
113	case "int":
114		kind = Int
115		val = constant.MakeFromLiteral(str, token.INT, 0)
116	case "rune":
117		kind = Rune
118		val = constant.MakeFromLiteral(str, token.INT, 0)
119	case "float":
120		kind = Float
121		val = unmarshalFloat(str)
122	case "complex":
123		kind = Complex
124		if sep := strings.IndexByte(str, ':'); sep >= 0 {
125			re := unmarshalFloat(str[:sep])
126			im := unmarshalFloat(str[sep+1:])
127			val = constant.BinaryOp(constant.ToComplex(re), token.ADD, constant.MakeImag(im))
128		} else {
129			val = constant.ToComplex(unmarshalFloat(str))
130		}
131	case "string":
132		kind = String
133		val = constant.MakeString(str)
134	case "nil":
135		kind = None
136	default:
137		kind = None
138	}
139	return kind, val
140}
141
142// generalization of constant.MakeFromLiteral, accepts the fractions generated by
143// constant.Value.ExactString() for floating-point values
144func unmarshalFloat(str string) constant.Value {
145	if sep := strings.IndexByte(str, '/'); sep >= 0 {
146		x := constant.MakeFromLiteral(str[:sep], token.FLOAT, 0)
147		y := constant.MakeFromLiteral(str[sep+1:], token.FLOAT, 0)
148		return constant.BinaryOp(x, token.QUO, y)
149	}
150	return constant.MakeFromLiteral(str, token.FLOAT, 0)
151}
152
153func (lit *Val) BigInt() (*big.Int, error) {
154	val := lit.Val
155	switch lit.Kind {
156	case Int, Rune:
157		if i, ok := constant.Int64Val(val); ok {
158			return big.NewInt(i), nil
159		}
160		if bi, ok := new(big.Int).SetString(val.ExactString(), 10); ok {
161			return bi, nil
162		}
163	}
164	return nil, output.MakeRuntimeError("cannot convert untyped %s to math/big.Int: %v", lit.Kind, lit.Val)
165}
166
167func (lit *Val) BigRat() (*big.Rat, error) {
168	val := lit.Val
169	switch lit.Kind {
170	case Int, Rune:
171		if i, ok := constant.Int64Val(val); ok {
172			return big.NewRat(i, 1), nil
173		}
174		fallthrough
175	case Float:
176		if br, ok := new(big.Rat).SetString(val.ExactString()); ok {
177			return br, nil
178		}
179	}
180	return nil, output.MakeRuntimeError("cannot convert untyped %s to math/big.Rat: %v", lit.Kind, lit.Val)
181}
182