1// Copyright 2009 The Go Authors. 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//go:generate go run makeisprint.go -output isprint.go
6
7package strconv
8
9import (
10	"unicode/utf8"
11)
12
13const lowerhex = "0123456789abcdef"
14
15func quoteWith(s string, quote byte, ASCIIonly, graphicOnly bool) string {
16	var runeTmp [utf8.UTFMax]byte
17	buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
18	buf = append(buf, quote)
19	for width := 0; len(s) > 0; s = s[width:] {
20		r := rune(s[0])
21		width = 1
22		if r >= utf8.RuneSelf {
23			r, width = utf8.DecodeRuneInString(s)
24		}
25		if width == 1 && r == utf8.RuneError {
26			buf = append(buf, `\x`...)
27			buf = append(buf, lowerhex[s[0]>>4])
28			buf = append(buf, lowerhex[s[0]&0xF])
29			continue
30		}
31		if r == rune(quote) || r == '\\' { // always backslashed
32			buf = append(buf, '\\')
33			buf = append(buf, byte(r))
34			continue
35		}
36		if ASCIIonly {
37			if r < utf8.RuneSelf && IsPrint(r) {
38				buf = append(buf, byte(r))
39				continue
40			}
41		} else if IsPrint(r) || graphicOnly && isInGraphicList(r) {
42			n := utf8.EncodeRune(runeTmp[:], r)
43			buf = append(buf, runeTmp[:n]...)
44			continue
45		}
46		switch r {
47		case '\a':
48			buf = append(buf, `\a`...)
49		case '\b':
50			buf = append(buf, `\b`...)
51		case '\f':
52			buf = append(buf, `\f`...)
53		case '\n':
54			buf = append(buf, `\n`...)
55		case '\r':
56			buf = append(buf, `\r`...)
57		case '\t':
58			buf = append(buf, `\t`...)
59		case '\v':
60			buf = append(buf, `\v`...)
61		default:
62			switch {
63			case r < ' ':
64				buf = append(buf, `\x`...)
65				buf = append(buf, lowerhex[s[0]>>4])
66				buf = append(buf, lowerhex[s[0]&0xF])
67			case r > utf8.MaxRune:
68				r = 0xFFFD
69				fallthrough
70			case r < 0x10000:
71				buf = append(buf, `\u`...)
72				for s := 12; s >= 0; s -= 4 {
73					buf = append(buf, lowerhex[r>>uint(s)&0xF])
74				}
75			default:
76				buf = append(buf, `\U`...)
77				for s := 28; s >= 0; s -= 4 {
78					buf = append(buf, lowerhex[r>>uint(s)&0xF])
79				}
80			}
81		}
82	}
83	buf = append(buf, quote)
84	return string(buf)
85
86}
87
88// Quote returns a double-quoted Go string literal representing s.  The
89// returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for
90// control characters and non-printable characters as defined by
91// IsPrint.
92func Quote(s string) string {
93	return quoteWith(s, '"', false, false)
94}
95
96// AppendQuote appends a double-quoted Go string literal representing s,
97// as generated by Quote, to dst and returns the extended buffer.
98func AppendQuote(dst []byte, s string) []byte {
99	return append(dst, Quote(s)...)
100}
101
102// QuoteToASCII returns a double-quoted Go string literal representing s.
103// The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for
104// non-ASCII characters and non-printable characters as defined by IsPrint.
105func QuoteToASCII(s string) string {
106	return quoteWith(s, '"', true, false)
107}
108
109// AppendQuoteToASCII appends a double-quoted Go string literal representing s,
110// as generated by QuoteToASCII, to dst and returns the extended buffer.
111func AppendQuoteToASCII(dst []byte, s string) []byte {
112	return append(dst, QuoteToASCII(s)...)
113}
114
115// QuoteToGraphic returns a double-quoted Go string literal representing s.
116// The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for
117// non-ASCII characters and non-printable characters as defined by IsGraphic.
118func QuoteToGraphic(s string) string {
119	return quoteWith(s, '"', false, true)
120}
121
122// AppendQuoteToGraphic appends a double-quoted Go string literal representing s,
123// as generated by QuoteToGraphic, to dst and returns the extended buffer.
124func AppendQuoteToGraphic(dst []byte, s string) []byte {
125	return append(dst, QuoteToGraphic(s)...)
126}
127
128// QuoteRune returns a single-quoted Go character literal representing the
129// rune. The returned string uses Go escape sequences (\t, \n, \xFF, \u0100)
130// for control characters and non-printable characters as defined by IsPrint.
131func QuoteRune(r rune) string {
132	// TODO: avoid the allocation here.
133	return quoteWith(string(r), '\'', false, false)
134}
135
136// AppendQuoteRune appends a single-quoted Go character literal representing the rune,
137// as generated by QuoteRune, to dst and returns the extended buffer.
138func AppendQuoteRune(dst []byte, r rune) []byte {
139	return append(dst, QuoteRune(r)...)
140}
141
142// QuoteRuneToASCII returns a single-quoted Go character literal representing
143// the rune. The returned string uses Go escape sequences (\t, \n, \xFF,
144// \u0100) for non-ASCII characters and non-printable characters as defined
145// by IsPrint.
146func QuoteRuneToASCII(r rune) string {
147	// TODO: avoid the allocation here.
148	return quoteWith(string(r), '\'', true, false)
149}
150
151// AppendQuoteRuneToASCII appends a single-quoted Go character literal representing the rune,
152// as generated by QuoteRuneToASCII, to dst and returns the extended buffer.
153func AppendQuoteRuneToASCII(dst []byte, r rune) []byte {
154	return append(dst, QuoteRuneToASCII(r)...)
155}
156
157// QuoteRuneToGraphic returns a single-quoted Go character literal representing
158// the rune. The returned string uses Go escape sequences (\t, \n, \xFF,
159// \u0100) for non-ASCII characters and non-printable characters as defined
160// by IsGraphic.
161func QuoteRuneToGraphic(r rune) string {
162	// TODO: avoid the allocation here.
163	return quoteWith(string(r), '\'', false, true)
164}
165
166// AppendQuoteRuneToGraphic appends a single-quoted Go character literal representing the rune,
167// as generated by QuoteRuneToGraphic, to dst and returns the extended buffer.
168func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte {
169	return append(dst, QuoteRuneToGraphic(r)...)
170}
171
172// CanBackquote reports whether the string s can be represented
173// unchanged as a single-line backquoted string without control
174// characters other than tab.
175func CanBackquote(s string) bool {
176	for len(s) > 0 {
177		r, wid := utf8.DecodeRuneInString(s)
178		s = s[wid:]
179		if wid > 1 {
180			if r == '\ufeff' {
181				return false // BOMs are invisible and should not be quoted.
182			}
183			continue // All other multibyte runes are correctly encoded and assumed printable.
184		}
185		if r == utf8.RuneError {
186			return false
187		}
188		if (r < ' ' && r != '\t') || r == '`' || r == '\u007F' {
189			return false
190		}
191	}
192	return true
193}
194
195func unhex(b byte) (v rune, ok bool) {
196	c := rune(b)
197	switch {
198	case '0' <= c && c <= '9':
199		return c - '0', true
200	case 'a' <= c && c <= 'f':
201		return c - 'a' + 10, true
202	case 'A' <= c && c <= 'F':
203		return c - 'A' + 10, true
204	}
205	return
206}
207
208// UnquoteChar decodes the first character or byte in the escaped string
209// or character literal represented by the string s.
210// It returns four values:
211//
212//	1) value, the decoded Unicode code point or byte value;
213//	2) multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation;
214//	3) tail, the remainder of the string after the character; and
215//	4) an error that will be nil if the character is syntactically valid.
216//
217// The second argument, quote, specifies the type of literal being parsed
218// and therefore which escaped quote character is permitted.
219// If set to a single quote, it permits the sequence \' and disallows unescaped '.
220// If set to a double quote, it permits \" and disallows unescaped ".
221// If set to zero, it does not permit either escape and allows both quote characters to appear unescaped.
222func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) {
223	// easy cases
224	switch c := s[0]; {
225	case c == quote && (quote == '\'' || quote == '"'):
226		err = ErrSyntax
227		return
228	case c >= utf8.RuneSelf:
229		r, size := utf8.DecodeRuneInString(s)
230		return r, true, s[size:], nil
231	case c != '\\':
232		return rune(s[0]), false, s[1:], nil
233	}
234
235	// hard case: c is backslash
236	if len(s) <= 1 {
237		err = ErrSyntax
238		return
239	}
240	c := s[1]
241	s = s[2:]
242
243	switch c {
244	case 'a':
245		value = '\a'
246	case 'b':
247		value = '\b'
248	case 'f':
249		value = '\f'
250	case 'n':
251		value = '\n'
252	case 'r':
253		value = '\r'
254	case 't':
255		value = '\t'
256	case 'v':
257		value = '\v'
258	case 'x', 'u', 'U':
259		n := 0
260		switch c {
261		case 'x':
262			n = 2
263		case 'u':
264			n = 4
265		case 'U':
266			n = 8
267		}
268		var v rune
269		if len(s) < n {
270			err = ErrSyntax
271			return
272		}
273		for j := 0; j < n; j++ {
274			x, ok := unhex(s[j])
275			if !ok {
276				err = ErrSyntax
277				return
278			}
279			v = v<<4 | x
280		}
281		s = s[n:]
282		if c == 'x' {
283			// single-byte string, possibly not UTF-8
284			value = v
285			break
286		}
287		if v > utf8.MaxRune {
288			err = ErrSyntax
289			return
290		}
291		value = v
292		multibyte = true
293	case '0', '1', '2', '3', '4', '5', '6', '7':
294		v := rune(c) - '0'
295		if len(s) < 2 {
296			err = ErrSyntax
297			return
298		}
299		for j := 0; j < 2; j++ { // one digit already; two more
300			x := rune(s[j]) - '0'
301			if x < 0 || x > 7 {
302				err = ErrSyntax
303				return
304			}
305			v = (v << 3) | x
306		}
307		s = s[2:]
308		if v > 255 {
309			err = ErrSyntax
310			return
311		}
312		value = v
313	case '\\':
314		value = '\\'
315	case '\'', '"':
316		if c != quote {
317			err = ErrSyntax
318			return
319		}
320		value = rune(c)
321	default:
322		err = ErrSyntax
323		return
324	}
325	tail = s
326	return
327}
328
329// Unquote interprets s as a single-quoted, double-quoted,
330// or backquoted Go string literal, returning the string value
331// that s quotes.  (If s is single-quoted, it would be a Go
332// character literal; Unquote returns the corresponding
333// one-character string.)
334func Unquote(s string) (t string, err error) {
335	n := len(s)
336	if n < 2 {
337		return "", ErrSyntax
338	}
339	quote := s[0]
340	if quote != s[n-1] {
341		return "", ErrSyntax
342	}
343	s = s[1 : n-1]
344
345	if quote == '`' {
346		if contains(s, '`') {
347			return "", ErrSyntax
348		}
349		return s, nil
350	}
351	if quote != '"' && quote != '\'' {
352		return "", ErrSyntax
353	}
354	if contains(s, '\n') {
355		return "", ErrSyntax
356	}
357
358	// Is it trivial?  Avoid allocation.
359	if !contains(s, '\\') && !contains(s, quote) {
360		switch quote {
361		case '"':
362			return s, nil
363		case '\'':
364			r, size := utf8.DecodeRuneInString(s)
365			if size == len(s) && (r != utf8.RuneError || size != 1) {
366				return s, nil
367			}
368		}
369	}
370
371	var runeTmp [utf8.UTFMax]byte
372	buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations.
373	for len(s) > 0 {
374		c, multibyte, ss, err := UnquoteChar(s, quote)
375		if err != nil {
376			return "", err
377		}
378		s = ss
379		if c < utf8.RuneSelf || !multibyte {
380			buf = append(buf, byte(c))
381		} else {
382			n := utf8.EncodeRune(runeTmp[:], c)
383			buf = append(buf, runeTmp[:n]...)
384		}
385		if quote == '\'' && len(s) != 0 {
386			// single-quoted must be single character
387			return "", ErrSyntax
388		}
389	}
390	return string(buf), nil
391}
392
393// contains reports whether the string contains the byte c.
394func contains(s string, c byte) bool {
395	for i := 0; i < len(s); i++ {
396		if s[i] == c {
397			return true
398		}
399	}
400	return false
401}
402
403// bsearch16 returns the smallest i such that a[i] >= x.
404// If there is no such i, bsearch16 returns len(a).
405func bsearch16(a []uint16, x uint16) int {
406	i, j := 0, len(a)
407	for i < j {
408		h := i + (j-i)/2
409		if a[h] < x {
410			i = h + 1
411		} else {
412			j = h
413		}
414	}
415	return i
416}
417
418// bsearch32 returns the smallest i such that a[i] >= x.
419// If there is no such i, bsearch32 returns len(a).
420func bsearch32(a []uint32, x uint32) int {
421	i, j := 0, len(a)
422	for i < j {
423		h := i + (j-i)/2
424		if a[h] < x {
425			i = h + 1
426		} else {
427			j = h
428		}
429	}
430	return i
431}
432
433// TODO: IsPrint is a local implementation of unicode.IsPrint, verified by the tests
434// to give the same answer. It allows this package not to depend on unicode,
435// and therefore not pull in all the Unicode tables. If the linker were better
436// at tossing unused tables, we could get rid of this implementation.
437// That would be nice.
438
439// IsPrint reports whether the rune is defined as printable by Go, with
440// the same definition as unicode.IsPrint: letters, numbers, punctuation,
441// symbols and ASCII space.
442func IsPrint(r rune) bool {
443	// Fast check for Latin-1
444	if r <= 0xFF {
445		if 0x20 <= r && r <= 0x7E {
446			// All the ASCII is printable from space through DEL-1.
447			return true
448		}
449		if 0xA1 <= r && r <= 0xFF {
450			// Similarly for ¡ through ÿ...
451			return r != 0xAD // ...except for the bizarre soft hyphen.
452		}
453		return false
454	}
455
456	// Same algorithm, either on uint16 or uint32 value.
457	// First, find first i such that isPrint[i] >= x.
458	// This is the index of either the start or end of a pair that might span x.
459	// The start is even (isPrint[i&^1]) and the end is odd (isPrint[i|1]).
460	// If we find x in a range, make sure x is not in isNotPrint list.
461
462	if 0 <= r && r < 1<<16 {
463		rr, isPrint, isNotPrint := uint16(r), isPrint16, isNotPrint16
464		i := bsearch16(isPrint, rr)
465		if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr {
466			return false
467		}
468		j := bsearch16(isNotPrint, rr)
469		return j >= len(isNotPrint) || isNotPrint[j] != rr
470	}
471
472	rr, isPrint, isNotPrint := uint32(r), isPrint32, isNotPrint32
473	i := bsearch32(isPrint, rr)
474	if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr {
475		return false
476	}
477	if r >= 0x20000 {
478		return true
479	}
480	r -= 0x10000
481	j := bsearch16(isNotPrint, uint16(r))
482	return j >= len(isNotPrint) || isNotPrint[j] != uint16(r)
483}
484
485// IsGraphic reports whether the rune is defined as a Graphic by Unicode. Such
486// characters include letters, marks, numbers, punctuation, symbols, and
487// spaces, from categories L, M, N, P, S, and Zs.
488func IsGraphic(r rune) bool {
489	if IsPrint(r) {
490		return true
491	}
492	return isInGraphicList(r)
493}
494
495// isInGraphicList reports whether the rune is in the isGraphic list. This separation
496// from IsGraphic allows quoteWith to avoid two calls to IsPrint.
497// Should be called only if IsPrint fails.
498func isInGraphicList(r rune) bool {
499	// We know r must fit in 16 bits - see makeisprint.go.
500	if r > 0xFFFF {
501		return false
502	}
503	rr := uint16(r)
504	i := bsearch16(isGraphic, rr)
505	return i < len(isGraphic) && rr == isGraphic[i]
506}
507