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
5package edn
6
7import (
8	"bytes"
9	"io"
10)
11
12func tokNeedsDelim(t tokenType) bool {
13	switch t {
14	case tokenString, tokenListStart, tokenListEnd, tokenVectorStart,
15		tokenVectorEnd, tokenMapEnd, tokenMapStart, tokenSetStart, tokenDiscard, tokenError:
16		return false
17	}
18	return true
19}
20
21func delimits(r rune) bool {
22	switch r {
23	case '{', '}', '[', ']', '(', ')', '\\', '"':
24		return true
25	}
26	return isWhitespace(r)
27}
28
29// Compact appends to dst a compacted form of the EDN-encoded src. It does not
30// remove discard values.
31func Compact(dst *bytes.Buffer, src []byte) error {
32	origLen := dst.Len()
33	var lex lexer
34	lex.reset()
35	buf := bytes.NewBuffer(src)
36	start, pos := 0, 0
37	needsDelim := false
38	prevIgnore := '\uFFFD'
39	r, size, err := buf.ReadRune()
40	for ; err == nil; r, size, err = buf.ReadRune() {
41		ls := lex.state(r)
42		ppos := pos
43		pos += size
44		switch ls {
45		case lexCont:
46			if ppos == start && needsDelim && !delimits(r) {
47				dst.WriteRune(prevIgnore)
48			}
49			continue
50		case lexIgnore:
51			prevIgnore = r
52			start = pos
53		case lexError:
54			dst.Truncate(origLen)
55			return lex.err
56		case lexEnd:
57			// here we might want to discard #_ and the like. Currently we don't.
58			dst.Write(src[start:pos])
59			needsDelim = tokNeedsDelim(lex.token)
60			lex.reset()
61			start = pos
62		case lexEndPrev:
63			dst.Write(src[start:ppos])
64			lex.reset()
65			lss := lex.state(r)
66			needsDelim = tokNeedsDelim(lex.token)
67			switch lss {
68			case lexIgnore:
69				prevIgnore = r
70				start = pos
71			case lexCont:
72				start = ppos
73			case lexEnd:
74				dst.WriteRune(r)
75				lex.reset()
76				start = pos
77			case lexEndPrev:
78				dst.Truncate(origLen)
79				return errInternal
80			case lexError:
81				dst.Truncate(origLen)
82				return lex.err
83			}
84		}
85	}
86	if err != io.EOF {
87		return err
88	}
89	ls := lex.eof()
90	switch ls {
91	case lexEnd:
92		dst.Write(src[start:pos])
93	case lexError:
94		dst.Truncate(origLen)
95		return lex.err
96	}
97	return nil
98}
99