blob: 7a6b857eab6de60c29089ebc12a3cc4ab464b96c [file] [log] [blame]
Marcel van Lohuizen1d1295b2020-03-04 15:47:03 +01001// Copyright 2020 CUE Authors
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package json
16
17import (
18 "bytes"
19 "encoding/json"
20 "math/big"
21 "strings"
22
23 "cuelang.org/go/cue/ast"
24 "cuelang.org/go/cue/errors"
25 "cuelang.org/go/cue/literal"
26 "cuelang.org/go/cue/token"
27 "cuelang.org/go/internal"
28)
29
30// Encode converts a CUE AST to JSON.
31//
32// The given file must only contain values that can be directly supported by
33// JSON:
34// Type Restrictions
35// BasicLit
36// File no imports, aliases, or definitions
37// StructLit no embeddings, aliases, or definitions
38// List
39// Field must be regular; label must be a BasicLit or Ident
40//
41// Comments and attributes are ignored.
42func Encode(n ast.Node) (b []byte, err error) {
43 e := encoder{}
44 err = e.encode(n)
45 if err != nil {
46 return nil, err
47 }
48 return e.w.Bytes(), nil
49}
50
51type encoder struct {
52 w bytes.Buffer
53 tab []byte
54 indentsAtLevel []int
55 indenting bool
56 unIndenting int
57}
58
59func (e *encoder) writeIndent(b byte) {
60 if e.indenting {
61 e.indentsAtLevel[len(e.indentsAtLevel)-1]++
62 } else {
63 e.indentsAtLevel = append(e.indentsAtLevel, 0)
64 }
65 e.indenting = true
66 _ = e.w.WriteByte(b)
67}
68
69func (e *encoder) writeUnindent(b byte, pos, def token.Pos) {
70 if e.unIndenting > 0 {
71 e.unIndenting--
72 } else {
73 e.unIndenting = e.indentsAtLevel[len(e.indentsAtLevel)-1]
74 e.indentsAtLevel = e.indentsAtLevel[:len(e.indentsAtLevel)-1]
75 }
76 e.indenting = false
77 e.ws(pos, def.RelPos())
78 _ = e.w.WriteByte(b)
79}
80
81func (e *encoder) writeString(s string) {
82 _, _ = e.w.WriteString(s)
83 e.indenting = false
84}
85
86func (e *encoder) writeByte(b byte) {
87 _ = e.w.WriteByte(b)
88}
89
90func (e *encoder) write(b []byte) {
91 _, _ = e.w.Write(b)
92 e.indenting = false
93}
94
95func (e *encoder) indent() {
96 for range e.indentsAtLevel {
97 e.write(e.tab)
98 }
99}
100
101func (e *encoder) ws(pos token.Pos, default_ token.RelPos) {
102 rel := pos.RelPos()
103 if pos == token.NoPos {
104 rel = default_
105 }
106 switch rel {
107 case token.NoSpace:
108 case token.Blank:
109 e.writeByte(' ')
110 case token.Newline:
111 e.writeByte('\n')
112 e.indent()
113 case token.NewSection:
114 e.writeString("\n\n")
115 e.indent()
116 }
117}
118func (e *encoder) encode(n ast.Node) error {
119 if e.tab == nil {
120 e.tab = []byte(" ")
121 }
122 const defPos = token.NoSpace
123 switch x := n.(type) {
124 case *ast.BasicLit:
125 e.ws(x.Pos(), defPos)
126 return e.encodeScalar(x, true)
127
128 case *ast.ListLit:
129 e.ws(foldNewline(x.Pos()), token.NoRelPos)
130 if len(x.Elts) == 0 {
131 e.writeString("[]")
132 return nil
133 }
134 e.writeIndent('[')
135 for i, x := range x.Elts {
136 if i > 0 {
137 e.writeString(",")
138 }
139 if err := e.encode(x); err != nil {
140 return err
141 }
142 }
143 e.writeUnindent(']', x.Rbrack, compactNewline(x.Elts[0].Pos()))
144 return nil
145
146 case *ast.StructLit:
147 e.ws(foldNewline(n.Pos()), token.NoRelPos)
148 return e.encodeDecls(x.Elts, x.Rbrace)
149
150 case *ast.File:
151 return e.encodeDecls(x.Decls, token.NoPos)
152
153 case *ast.UnaryExpr:
154 e.ws(foldNewline(x.Pos()), defPos)
155 l, ok := x.X.(*ast.BasicLit)
156 if ok && x.Op == token.SUB && (l.Kind == token.INT || l.Kind == token.FLOAT) {
157 e.writeByte('-')
158 return e.encodeScalar(l, false)
159 }
160 }
161 return errors.Newf(n.Pos(), "json: unsupported node %s (%T)", internal.DebugStr(n), n)
162}
163
164func (e *encoder) encodeScalar(l *ast.BasicLit, allowMinus bool) error {
165 switch l.Kind {
166 case token.INT:
167 var x big.Int
168 return e.setNum(l, allowMinus, &x)
169
170 case token.FLOAT:
171 var x big.Float
172 return e.setNum(l, allowMinus, &x)
173
174 case token.TRUE:
175 e.writeString("true")
176
177 case token.FALSE:
178 e.writeString("false")
179
180 case token.NULL:
181 e.writeString("null")
182
183 case token.STRING:
184 str, err := literal.Unquote(l.Value)
185 if err != nil {
186 return err
187 }
188 b, err := json.Marshal(str)
189 if err != nil {
190 return err
191 }
192 e.write(b)
193
194 default:
195 return errors.Newf(l.Pos(), "unknown literal type %v", l.Kind)
196 }
197 return nil
198}
199
200func (e *encoder) setNum(l *ast.BasicLit, allowMinus bool, x interface{}) error {
201 if !allowMinus && strings.HasPrefix(l.Value, "-") {
202 return errors.Newf(l.Pos(), "double minus not allowed")
203 }
204 var ni literal.NumInfo
205 if err := literal.ParseNum(l.Value, &ni); err != nil {
206 return err
207 }
208 e.writeString(ni.String())
209 return nil
210}
211
212// encodeDecls converts a sequence of declarations to a value. If it encounters
213// an embedded value, it will return this expression. This is more relaxed for
214// structs than is currently allowed for CUE, but the expectation is that this
215// will be allowed at some point. The input would still be illegal CUE.
216func (e *encoder) encodeDecls(decls []ast.Decl, endPos token.Pos) error {
217 var embed ast.Expr
218 var fields []*ast.Field
219
220 for _, d := range decls {
221 switch x := d.(type) {
222 default:
223 return errors.Newf(x.Pos(), "json: unsupported node %s (%T)", internal.DebugStr(x), x)
224
225 case *ast.Package:
226 if embed != nil || fields != nil {
227 return errors.Newf(x.Pos(), "invalid package clause")
228 }
229 continue
230
231 case *ast.Field:
232 if x.Token == token.ISA {
233 return errors.Newf(x.TokenPos, "json: definition not allowed")
234 }
235 if x.Optional != token.NoPos {
236 return errors.Newf(x.Optional, "json: optional fields not allowed")
237 }
238 fields = append(fields, x)
239
240 case *ast.EmbedDecl:
241 if embed != nil {
242 return errors.Newf(x.Pos(), "json: multiple embedded values")
243 }
244 embed = x.Expr
245
246 case *ast.CommentGroup:
247 }
248 }
249
250 if embed != nil {
251 if fields != nil {
252 return errors.Newf(embed.Pos(), "json: embedding mixed with fields")
253 }
254 return e.encode(embed)
255 }
256
257 if len(fields) == 0 {
258 e.writeString("{}")
259 return nil
260 }
261
262 e.writeIndent('{')
263 pos := compactNewline(fields[0].Pos())
264 if endPos == token.NoPos && pos.RelPos() == token.Blank {
265 pos = token.NoPos
266 }
267 firstPos := pos
268 const defPos = token.NoRelPos
269 for i, x := range fields {
270 if i > 0 {
271 e.writeByte(',')
272 pos = x.Pos()
273 }
274 name, _, err := ast.LabelName(x.Label)
275 if err != nil {
276 return errors.Newf(x.Label.Pos(), "json: only literal labels allowed")
277 }
278 b, err := json.Marshal(name)
279 if err != nil {
280 return err
281 }
282 e.ws(pos, defPos)
283 e.write(b)
284 e.writeByte(':')
285
286 if err := e.encode(x.Value); err != nil {
287 return err
288 }
289 }
290 e.writeUnindent('}', endPos, firstPos)
291 return nil
292}
293
294func compactNewline(pos token.Pos) token.Pos {
295 if pos.RelPos() == token.NewSection {
296 pos = token.Newline.Pos()
297 }
298 return pos
299}
300
301func foldNewline(pos token.Pos) token.Pos {
302 if pos.RelPos() >= token.Newline {
303 pos = token.Blank.Pos()
304 }
305 return pos
306}