| |
| |
| |
|
|
| |
|
|
| package strconv |
|
|
| import ( |
| "unicode/utf8" |
| ) |
|
|
| const ( |
| lowerhex = "0123456789abcdef" |
| upperhex = "0123456789ABCDEF" |
| ) |
|
|
| |
| func contains(s string, c byte) bool { |
| return index(s, c) != -1 |
| } |
|
|
| 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 { |
| |
| |
| if cap(buf)-len(buf) < len(s) { |
| nBuf := make([]byte, len(buf), len(buf)+1+len(s)+1) |
| copy(nBuf, buf) |
| buf = nBuf |
| } |
| buf = append(buf, quote) |
| for r, width := rune(0), 0; len(s) > 0; s = s[width:] { |
| 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 { |
| if r == rune(quote) || r == '\\' { |
| 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) { |
| return utf8.AppendRune(buf, r) |
| } |
| 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 < ' ' || r == 0x7f: |
| buf = append(buf, `\x`...) |
| buf = append(buf, lowerhex[byte(r)>>4]) |
| buf = append(buf, lowerhex[byte(r)&0xF]) |
| case !utf8.ValidRune(r): |
| 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 |
| } |
|
|
| |
| |
| |
| |
| func Quote(s string) string { |
| return quoteWith(s, '"', false, false) |
| } |
|
|
| |
| |
| func AppendQuote(dst []byte, s string) []byte { |
| return appendQuotedWith(dst, s, '"', false, false) |
| } |
|
|
| |
| |
| |
| func QuoteToASCII(s string) string { |
| return quoteWith(s, '"', true, false) |
| } |
|
|
| |
| |
| func AppendQuoteToASCII(dst []byte, s string) []byte { |
| return appendQuotedWith(dst, s, '"', true, false) |
| } |
|
|
| |
| |
| |
| |
| func QuoteToGraphic(s string) string { |
| return quoteWith(s, '"', false, true) |
| } |
|
|
| |
| |
| func AppendQuoteToGraphic(dst []byte, s string) []byte { |
| return appendQuotedWith(dst, s, '"', false, true) |
| } |
|
|
| |
| |
| |
| |
| |
| func QuoteRune(r rune) string { |
| return quoteRuneWith(r, '\'', false, false) |
| } |
|
|
| |
| |
| func AppendQuoteRune(dst []byte, r rune) []byte { |
| return appendQuotedRuneWith(dst, r, '\'', false, false) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| func QuoteRuneToASCII(r rune) string { |
| return quoteRuneWith(r, '\'', true, false) |
| } |
|
|
| |
| |
| func AppendQuoteRuneToASCII(dst []byte, r rune) []byte { |
| return appendQuotedRuneWith(dst, r, '\'', true, false) |
| } |
|
|
| |
| |
| |
| |
| |
| |
| func QuoteRuneToGraphic(r rune) string { |
| return quoteRuneWith(r, '\'', false, true) |
| } |
|
|
| |
| |
| func AppendQuoteRuneToGraphic(dst []byte, r rune) []byte { |
| return appendQuotedRuneWith(dst, r, '\'', false, true) |
| } |
|
|
| |
| |
| |
| 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 |
| } |
| continue |
| } |
| 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 |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| func UnquoteChar(s string, quote byte) (value rune, multibyte bool, tail string, err error) { |
| |
| 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 |
| } |
|
|
| |
| 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' { |
| |
| value = v |
| break |
| } |
| if !utf8.ValidRune(v) { |
| 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++ { |
| 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 |
| } |
|
|
| |
| |
| func QuotedPrefix(s string) (string, error) { |
| out, _, err := unquote(s, false) |
| return out, err |
| } |
|
|
| |
| |
| |
| |
| |
| |
| func Unquote(s string) (string, error) { |
| out, rem, err := unquote(s, true) |
| if len(rem) > 0 { |
| return "", ErrSyntax |
| } |
| return out, err |
| } |
|
|
| |
| |
| |
| |
| func unquote(in string, unescape bool) (out, rem string, err error) { |
| |
| if len(in) < 2 { |
| return "", in, ErrSyntax |
| } |
| quote := in[0] |
| end := index(in[1:], quote) |
| if end < 0 { |
| return "", in, ErrSyntax |
| } |
| end += 2 |
|
|
| switch quote { |
| case '`': |
| switch { |
| case !unescape: |
| out = in[:end] |
| case !contains(in[:end], '\r'): |
| out = in[len("`") : end-len("`")] |
| default: |
| |
| |
| buf := make([]byte, 0, end-len("`")-len("\r")-len("`")) |
| for i := len("`"); i < end-len("`"); i++ { |
| if in[i] != '\r' { |
| buf = append(buf, in[i]) |
| } |
| } |
| out = string(buf) |
| } |
| |
| |
| |
| |
| |
| return out, in[end:], nil |
| case '"', '\'': |
| |
| if !contains(in[:end], '\\') && !contains(in[:end], '\n') { |
| var valid bool |
| switch quote { |
| case '"': |
| valid = utf8.ValidString(in[len(`"`) : end-len(`"`)]) |
| case '\'': |
| r, n := utf8.DecodeRuneInString(in[len("'") : end-len("'")]) |
| valid = len("'")+n+len("'") == end && (r != utf8.RuneError || n != 1) |
| } |
| if valid { |
| out = in[:end] |
| if unescape { |
| out = out[1 : end-1] |
| } |
| return out, in[end:], nil |
| } |
| } |
|
|
| |
| var buf []byte |
| in0 := in |
| in = in[1:] |
| if unescape { |
| buf = make([]byte, 0, 3*end/2) |
| } |
| for len(in) > 0 && in[0] != quote { |
| |
| |
| r, multibyte, rem, err := UnquoteChar(in, quote) |
| if in[0] == '\n' || err != nil { |
| return "", in0, ErrSyntax |
| } |
| in = rem |
|
|
| |
| if unescape { |
| if r < utf8.RuneSelf || !multibyte { |
| buf = append(buf, byte(r)) |
| } else { |
| buf = utf8.AppendRune(buf, r) |
| } |
| } |
|
|
| |
| if quote == '\'' { |
| break |
| } |
| } |
|
|
| |
| if !(len(in) > 0 && in[0] == quote) { |
| return "", in0, ErrSyntax |
| } |
| in = in[1:] |
|
|
| if unescape { |
| return string(buf), in, nil |
| } |
| return in0[:len(in0)-len(in)], in, nil |
| default: |
| return "", in, ErrSyntax |
| } |
| } |
|
|
| |
| |
| func bsearch[S ~[]E, E ~uint16 | ~uint32](s S, v E) (int, bool) { |
| n := len(s) |
| i, j := 0, n |
| for i < j { |
| h := i + (j-i)>>1 |
| if s[h] < v { |
| i = h + 1 |
| } else { |
| j = h |
| } |
| } |
| return i, i < n && s[i] == v |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| |
| |
| |
| func IsPrint(r rune) bool { |
| |
| if r <= 0xFF { |
| if 0x20 <= r && r <= 0x7E { |
| |
| return true |
| } |
| if 0xA1 <= r && r <= 0xFF { |
| |
| return r != 0xAD |
| } |
| return false |
| } |
|
|
| |
| |
| |
| |
| |
|
|
| if 0 <= r && r < 1<<16 { |
| rr, isPrint, isNotPrint := uint16(r), isPrint16, isNotPrint16 |
| i, _ := bsearch(isPrint, rr) |
| if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr { |
| return false |
| } |
| _, found := bsearch(isNotPrint, rr) |
| return !found |
| } |
|
|
| rr, isPrint, isNotPrint := uint32(r), isPrint32, isNotPrint32 |
| i, _ := bsearch(isPrint, rr) |
| if i >= len(isPrint) || rr < isPrint[i&^1] || isPrint[i|1] < rr { |
| return false |
| } |
| if r >= 0x20000 { |
| return true |
| } |
| r -= 0x10000 |
| _, found := bsearch(isNotPrint, uint16(r)) |
| return !found |
| } |
|
|
| |
| |
| |
| func IsGraphic(r rune) bool { |
| if IsPrint(r) { |
| return true |
| } |
| return isInGraphicList(r) |
| } |
|
|
| |
| |
| |
| func isInGraphicList(r rune) bool { |
| |
| if r > 0xFFFF { |
| return false |
| } |
| _, found := bsearch(isGraphic, uint16(r)) |
| return found |
| } |
|
|