| // 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, 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, quote, ASCIIonly, graphicOnly) | |
| buf = append(buf, quote) | |
| return buf | |
| } | |
| func appendEscapedRune(buf []byte, r rune, 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 | |
| if len(s) == 0 { | |
| err = ErrSyntax | |
| return | |
| } | |
| 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 '"': | |
| if utf8.ValidString(s) { | |
| 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] | |
| } |