// Copyright 2009 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. //go:generate go run makeisprint.go -output isprint.go package strconv import "unicode/utf8" const lowerhex = "0123456789abcdef" func quoteWith(s string, quote byte, ASCIIonly, graphicOnly bool) string { return string(appendQuotedWith(make([]byte, 0, 3*len(s)/2), s, quote, ASCIIonly, graphicOnly)) } func quoteRuneWith(r rune, quote byte, ASCIIonly, graphicOnly bool) string { return string(appendQuotedRuneWith(nil, r, quote, ASCIIonly, graphicOnly)) } func appendQuotedWith(buf []byte, s string, quote byte, ASCIIonly, graphicOnly bool) []byte { buf = append(buf, quote) for width := 0; len(s) > 0; s = s[width:] { r := rune(s[0]) width = 1 if r >= utf8.RuneSelf { r, width = utf8.DecodeRuneInString(s) } if width == 1 && r == utf8.RuneError { buf = append(buf, `\x`...) buf = append(buf, lowerhex[s[0]>>4]) buf = append(buf, lowerhex[s[0]&0xF]) continue } buf = appendEscapedRune(buf, r, width, quote, ASCIIonly, graphicOnly) } buf = append(buf, quote) return buf } func appendQuotedRuneWith(buf []byte, r rune, quote byte, ASCIIonly, graphicOnly bool) []byte { buf = append(buf, quote) if !utf8.ValidRune(r) { r = utf8.RuneError } buf = appendEscapedRune(buf, r, utf8.RuneLen(r), quote, ASCIIonly, graphicOnly) buf = append(buf, quote) return buf } func appendEscapedRune(buf []byte, r rune, width int, quote byte, ASCIIonly, graphicOnly bool) []byte { var runeTmp [utf8.UTFMax]byte if r == rune(quote) || r == '\\' { // always backslashed buf = append(buf, '\\') buf = append(buf, byte(r)) return buf } if ASCIIonly { if r < utf8.RuneSelf && IsPrint(r) { buf = append(buf, byte(r)) return buf } } else if IsPrint(r) || graphicOnly && isInGraphicList(r) { n := utf8.EncodeRune(runeTmp[:], r) buf = append(buf, runeTmp[:n]...) return buf } switch r { case '\a': buf = append(buf, `\a`...) case '\b': buf = append(buf, `\b`...) case '\f': buf = append(buf, `\f`...) case '\n': buf = append(buf, `\n`...) case '\r': buf = append(buf, `\r`...) case '\t': buf = append(buf, `\t`...) case '\v': buf = append(buf, `\v`...) default: switch { case r < ' ': buf = append(buf, `\x`...) buf = append(buf, lowerhex[byte(r)>>4]) buf = append(buf, lowerhex[byte(r)&0xF]) case r > utf8.MaxRune: r = 0xFFFD fallthrough case r < 0x10000: buf = append(buf, `\u`...) for s := 12; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } default: buf = append(buf, `\U`...) for s := 28; s >= 0; s -= 4 { buf = append(buf, lowerhex[r>>uint(s)&0xF]) } } } return buf } // Quote returns a double-quoted Go string literal representing s. The // returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for // control characters and non-printable characters as defined by // IsPrint. func Quote(s string) string { return quoteWith(s, '"', false, false) } // AppendQuote appends a double-quoted Go string literal representing s, // as generated by Quote, to dst and returns the extended buffer. func AppendQuote(dst []byte, s string) []byte { return appendQuotedWith(dst, s, '"', false, false) } // QuoteToASCII returns a double-quoted Go string literal representing s. // The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for // non-ASCII characters and non-printable characters as defined by IsPrint. func QuoteToASCII(s string) string { return quoteWith(s, '"', true, false) } // AppendQuoteToASCII appends a double-quoted Go string literal representing s, // as generated by QuoteToASCII, to dst and returns the extended buffer. func AppendQuoteToASCII(dst []byte, s string) []byte { return appendQuotedWith(dst, s, '"', true, false) } // QuoteToGraphic returns a double-quoted Go string literal representing s. // The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) for // non-ASCII characters and non-printable characters as defined by IsGraphic. func QuoteToGraphic(s string) string { return quoteWith(s, '"', false, true) } // AppendQuoteToGraphic appends a double-quoted Go string literal representing s, // as generated by QuoteToGraphic, to dst and returns the extended buffer. func AppendQuoteToGraphic(dst []byte, s string) []byte { return appendQuotedWith(dst, s, '"', false, true) } // QuoteRune returns a single-quoted Go character literal representing the // rune. The returned string uses Go escape sequences (\t, \n, \xFF, \u0100) // for control characters and non-printable characters as defined by IsPrint. func QuoteRune(r rune) string { return quoteRuneWith(r, '\'', false, false) } // AppendQuoteRune appends a single-quoted Go character literal representing the rune, // as generated by QuoteRune, to dst and returns the extended buffer. func AppendQuoteRune(dst []byte, r rune) []byte { return appendQuotedRuneWith(dst, r, '\'', false, false) } // QuoteRuneToASCII returns a single-quoted Go character literal representing // the rune. The returned string uses Go escape sequences (\t, \n, \xFF, // \u0100) for non-ASCII characters and non-printable characters as defined // by IsPrint. func QuoteRuneToASCII(r rune) string { return quoteRuneWith(r, '\'', true, false) } // AppendQuoteRuneToASCII appends a single-quoted Go character literal representing the rune, // as generated by QuoteRuneToASCII, to dst and returns the extended buffer. func AppendQuoteRuneToASCII(dst []byte, r rune) []byte { return appendQuotedRuneWith(dst, r, '\'', true, false) } // QuoteRuneToGraphic returns a single-quoted Go character literal representing // the rune. The returned string uses Go escape sequences (\t, \n, \xFF, // \u0100) for non-ASCII characters and non-printable characters as defined // by IsGraphic. func QuoteRuneToGraphic(r rune) string { return quoteRuneWith(r, '\'', false, true) } // AppendQuoteRuneToGraphic appends a single-quoted Go character literal representing the rune, // as generated by QuoteRuneToGraphic, to dst and returns the extended buffer. func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte { return appendQuotedRuneWith(dst, r, '\'', false, true) } // CanBackquote reports whether the string s can be represented // unchanged as a single-line backquoted string without control // characters other than tab. func CanBackquote(s string) bool { for len(s) > 0 { r, wid := utf8.DecodeRuneInString(s) s = s[wid:] if wid > 1 { if r == '\ufeff' { return false // BOMs are invisible and should not be quoted. } continue // All other multibyte runes are correctly encoded and assumed printable. } if r == utf8.RuneError { return false } if (r < ' ' && r != '\t') || r == '`' || r == '\u007F' { return false } } return true } func unhex(b byte) (v rune, ok bool) { c := rune(b) switch { case '0' <= c && c <= '9': return c - '0', true case 'a' <= c && c <= 'f': return c - 'a' + 10, true case 'A' <= c && c <= 'F': return c - 'A' + 10, true } return } // UnquoteChar decodes the first character or byte in the escaped string // or character literal represented by the string s. // It returns four values: // // 1) value, the decoded Unicode code point or byte value; // 2) multibyte, a boolean indicating whether the decoded character requires a multibyte UTF-8 representation; // 3) tail, the remainder of the string after the character; and // 4) an error that will be nil if the character is syntactically valid. // // The second argument, quote, specifies the type of literal being parsed // and therefore which escaped quote character is permitted. // If set to a single quote, it permits the sequence \' and disallows unescaped '. // If set to a double quote, it permits \" and disallows unescaped ". // If set to zero, it does not permit either escape and allows both quote characters to appear unescaped. func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { // easy cases switch c := s[0]; { case c == quote && (quote == '\'' || quote == '"'): err = ErrSyntax return case c >= utf8.RuneSelf: r, size := utf8.DecodeRuneInString(s) return r, true, s[size:], nil case c != '\\': return rune(s[0]), false, s[1:], nil } // hard case: c is backslash if len(s) <= 1 { err = ErrSyntax return } c := s[1] s = s[2:] switch c { case 'a': value = '\a' case 'b': value = '\b' case 'f': value = '\f' case 'n': value = '\n' case 'r': value = '\r' case 't': value = '\t' case 'v': value = '\v' case 'x', 'u', 'U': n := 0 switch c { case 'x': n = 2 case 'u': n = 4 case 'U': n = 8 } var v rune if len(s) < n { err = ErrSyntax return } for j := 0; j < n; j++ { x, ok := unhex(s[j]) if !ok { err = ErrSyntax return } v = v<<4 | x } s = s[n:] if c == 'x' { // single-byte string, possibly not UTF-8 value = v break } if v > utf8.MaxRune { err = ErrSyntax return } value = v multibyte = true case '0', '1', '2', '3', '4', '5', '6', '7': v := rune(c) - '0' if len(s) < 2 { err = ErrSyntax return } for j := 0; j < 2; j++ { // one digit already; two more x := rune(s[j]) - '0' if x < 0 || x > 7 { err = ErrSyntax return } v = (v << 3) | x } s = s[2:] if v > 255 { err = ErrSyntax return } value = v case '\\': value = '\\' case '\'', '"': if c != quote { err = ErrSyntax return } value = rune(c) default: err = ErrSyntax return } tail = s return } // Unquote interprets s as a single-quoted, double-quoted, // or backquoted Go string literal, returning the string value // that s quotes. (If s is single-quoted, it would be a Go // character literal; Unquote returns the corresponding // one-character string.) func Unquote(s string) (string, error) { n := len(s) if n < 2 { return "", ErrSyntax } quote := s[0] if quote != s[n-1] { return "", ErrSyntax } s = s[1 : n-1] if quote == '`' { if contains(s, '`') { return "", ErrSyntax } if contains(s, '\r') { // -1 because we know there is at least one \r to remove. buf := make([]byte, 0, len(s)-1) for i := 0; i < len(s); i++ { if s[i] != '\r' { buf = append(buf, s[i]) } } return string(buf), nil } return s, nil } if quote != '"' && quote != '\'' { return "", ErrSyntax } if contains(s, '\n') { return "", ErrSyntax } // Is it trivial? Avoid allocation. if !contains(s, '\\') && !contains(s, quote) { switch quote { case '"': return s, nil case '\'': r, size := utf8.DecodeRuneInString(s) if size == len(s) && (r != utf8.RuneError || size != 1) { return s, nil } } } var runeTmp [utf8.UTFMax]byte buf := make([]byte, 0, 3*len(s)/2) // Try to avoid more allocations. for len(s) > 0 { c, multibyte, ss, err := UnquoteChar(s, quote) if err != nil { return "", err } s = ss if c < utf8.RuneSelf || !multibyte { buf = append(buf, byte(c)) } else { n := utf8.EncodeRune(runeTmp[:], c) buf = append(buf, runeTmp[:n]...) } if quote == '\'' && len(s) != 0 { // single-quoted must be single character return "", ErrSyntax } } return string(buf), nil } // contains reports whether the string contains the byte c. func contains(s string, c byte) bool { for i := 0; i < len(s); i++ { if s[i] == c { return true } } return false } // bsearch16 returns the smallest i such that a[i] >= x. // If there is no such i, bsearch16 returns len(a). func bsearch16(a []uint16, x uint16) int { i, j := 0, len(a) for i < j { h := i + (j-i)/2 if a[h] < x { i = h + 1 } else { j = h } } return i } // bsearch32 returns the smallest i such that a[i] >= x. // If there is no such i, bsearch32 returns len(a). func bsearch32(a []uint32, x uint32) int { i, j := 0, len(a) for i < j { h := i + (j-i)/2 if a[h] < x { i = h + 1 } else { j = h } } return i } // TODO: IsPrint is a local implementation of unicode.IsPrint, verified by the tests // to give the same answer. It allows this package not to depend on unicode, // and therefore not pull in all the Unicode tables. If the linker were better // at tossing unused tables, we could get rid of this implementation. // That would be nice. // IsPrint reports whether the rune is defined as printable by Go, with // the same definition as unicode.IsPrint: letters, numbers, punctuation, // symbols and ASCII space. func IsPrint(r rune) bool { // Fast check for Latin-1 if r <= 0xFF { if 0x20 <= r && r <= 0x7E { // All the ASCII is printable from space through DEL-1. return true } if 0xA1 <= r && r <= 0xFF { // Similarly for ¡ through ÿ... return r != 0xAD // ...except for the bizarre soft hyphen. } return false } // Same algorithm, either on uint16 or uint32 value. // First, find first i such that isPrint[i] >= x. // This is the index of either the start or end of a pair that might span x. // The start is even (isPrint[i&^1]) and the end is odd (isPrint[i|1]). // If we find x in a range, make sure x is not in isNotPrint list. if 0 <= r && r < 1<<16 { rr, isPrint, isNotPrint := uint16(r), isPrint16, isNotPrint16 i := bsearch16(isPrint, rr) if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr { return false } j := bsearch16(isNotPrint, rr) return j >= len(isNotPrint) || isNotPrint[j] != rr } rr, isPrint, isNotPrint := uint32(r), isPrint32, isNotPrint32 i := bsearch32(isPrint, rr) if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr { return false } if r >= 0x20000 { return true } r -= 0x10000 j := bsearch16(isNotPrint, uint16(r)) return j >= len(isNotPrint) || isNotPrint[j] != uint16(r) } // IsGraphic reports whether the rune is defined as a Graphic by Unicode. Such // characters include letters, marks, numbers, punctuation, symbols, and // spaces, from categories L, M, N, P, S, and Zs. func IsGraphic(r rune) bool { if IsPrint(r) { return true } return isInGraphicList(r) } // isInGraphicList reports whether the rune is in the isGraphic list. This separation // from IsGraphic allows quoteWith to avoid two calls to IsPrint. // Should be called only if IsPrint fails. func isInGraphicList(r rune) bool { // We know r must fit in 16 bits - see makeisprint.go. if r > 0xFFFF { return false } rr := uint16(r) i := bsearch16(isGraphic, rr) return i < len(isGraphic) && rr == isGraphic[i] }