blob: f3d2d33bd0b5298071c9e1a90019e36a6439b4bd [file] [log] [blame]
// Copyright 2021 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.
package demangle
import (
"fmt"
"math"
"math/bits"
"strings"
"unicode/utf8"
)
// rustToString demangles a Rust symbol.
func rustToString(name string, options []Option) (ret string, err error) {
if !strings.HasPrefix(name, "_R") {
return "", ErrNotMangledName
}
// When the demangling routines encounter an error, they panic
// with a value of type demangleErr.
defer func() {
if r := recover(); r != nil {
if de, ok := r.(demangleErr); ok {
ret = ""
err = de
return
}
panic(r)
}
}()
suffix := ""
dot := strings.Index(name, ".")
if dot >= 0 {
suffix = name[dot:]
name = name[:dot]
}
name = name[2:]
rst := &rustState{orig: name, str: name}
for _, o := range options {
if o == NoTemplateParams {
rst.noGenericArgs = true
} else if isMaxLength(o) {
rst.max = maxLength(o)
}
}
rst.symbolName()
if len(rst.str) > 0 {
rst.fail("unparsed characters at end of mangled name")
}
if suffix != "" {
llvmStyle := false
for _, o := range options {
if o == LLVMStyle {
llvmStyle = true
break
}
}
if llvmStyle {
rst.skip = false
rst.writeString(" (")
rst.writeString(suffix)
rst.writeByte(')')
}
}
s := rst.buf.String()
if rst.max > 0 && len(s) > rst.max {
s = s[:rst.max]
}
return s, nil
}
// A rustState holds the current state of demangling a Rust string.
type rustState struct {
orig string // the original string being demangled
str string // remainder of string to demangle
off int // offset of str within original string
buf strings.Builder // demangled string being built
skip bool // don't print, just skip
lifetimes int64 // number of bound lifetimes
last byte // last byte written to buffer
noGenericArgs bool // don't demangle generic arguments
max int // maximum output length
}
// fail panics with demangleErr, to be caught in rustToString.
func (rst *rustState) fail(err string) {
panic(demangleErr{err: err, off: rst.off})
}
// advance advances the current string offset.
func (rst *rustState) advance(add int) {
if len(rst.str) < add {
panic("internal error")
}
rst.str = rst.str[add:]
rst.off += add
}
// checkChar requires that the next character in the string be c,
// and advances past it.
func (rst *rustState) checkChar(c byte) {
if len(rst.str) == 0 || rst.str[0] != c {
rst.fail("expected " + string(c))
}
rst.advance(1)
}
// writeByte writes a byte to the buffer.
func (rst *rustState) writeByte(c byte) {
if rst.skip {
return
}
if rst.max > 0 && rst.buf.Len() > rst.max {
rst.skip = true
return
}
rst.last = c
rst.buf.WriteByte(c)
}
// writeString writes a string to the buffer.
func (rst *rustState) writeString(s string) {
if rst.skip {
return
}
if rst.max > 0 && rst.buf.Len() > rst.max {
rst.skip = true
return
}
if len(s) > 0 {
rst.last = s[len(s)-1]
rst.buf.WriteString(s)
}
}
// symbolName parses:
//
// <symbol-name> = "_R" [<decimal-number>] <path> [<instantiating-crate>]
// <instantiating-crate> = <path>
//
// We've already skipped the "_R".
func (rst *rustState) symbolName() {
if len(rst.str) < 1 {
rst.fail("expected symbol-name")
}
if isDigit(rst.str[0]) {
rst.fail("unsupported Rust encoding version")
}
rst.path(true)
if len(rst.str) > 0 {
rst.skip = true
rst.path(false)
}
}
// path parses:
//
// <path> = "C" <identifier> // crate root
// | "M" <impl-path> <type> // <T> (inherent impl)
// | "X" <impl-path> <type> <path> // <T as Trait> (trait impl)
// | "Y" <type> <path> // <T as Trait> (trait definition)
// | "N" <namespace> <path> <identifier> // ...::ident (nested path)
// | "I" <path> {<generic-arg>} "E" // ...<T, U> (generic args)
// | <backref>
// <namespace> = "C" // closure
// | "S" // shim
// | <A-Z> // other special namespaces
// | <a-z> // internal namespaces
//
// needsSeparator is true if we need to write out :: for a generic;
// it is passed as false if we are in the middle of a type.
func (rst *rustState) path(needsSeparator bool) {
if len(rst.str) < 1 {
rst.fail("expected path")
}
switch c := rst.str[0]; c {
case 'C':
rst.advance(1)
_, ident := rst.identifier()
rst.writeString(ident)
case 'M', 'X':
rst.advance(1)
rst.implPath()
rst.writeByte('<')
rst.demangleType()
if c == 'X' {
rst.writeString(" as ")
rst.path(false)
}
rst.writeByte('>')
case 'Y':
rst.advance(1)
rst.writeByte('<')
rst.demangleType()
rst.writeString(" as ")
rst.path(false)
rst.writeByte('>')
case 'N':
rst.advance(1)
if len(rst.str) < 1 {
rst.fail("expected namespace")
}
ns := rst.str[0]
switch {
case ns >= 'a' && ns <= 'z':
case ns >= 'A' && ns <= 'Z':
default:
rst.fail("invalid namespace character")
}
rst.advance(1)
rst.path(needsSeparator)
dis, ident := rst.identifier()
if ns >= 'A' && ns <= 'Z' {
rst.writeString("::{")
switch ns {
case 'C':
rst.writeString("closure")
case 'S':
rst.writeString("shim")
default:
rst.writeByte(ns)
}
if len(ident) > 0 {
rst.writeByte(':')
rst.writeString(ident)
}
if !rst.skip {
fmt.Fprintf(&rst.buf, "#%d}", dis)
rst.last = '}'
}
} else {
rst.writeString("::")
rst.writeString(ident)
}
case 'I':
rst.advance(1)
rst.path(needsSeparator)
if needsSeparator {
rst.writeString("::")
}
rst.writeByte('<')
rst.genericArgs()
rst.writeByte('>')
rst.checkChar('E')
case 'B':
rst.backref(func() { rst.path(needsSeparator) })
default:
rst.fail("unrecognized letter in path")
}
}
// implPath parses:
//
// <impl-path> = [<disambiguator>] <path>
func (rst *rustState) implPath() {
// This path is not part of the demangled string.
hold := rst.skip
rst.skip = true
defer func() {
rst.skip = hold
}()
rst.disambiguator()
rst.path(false)
}
// identifier parses:
//
// <identifier> = [<disambiguator>] <undisambiguated-identifier>
//
// It returns the disambiguator and the identifier.
func (rst *rustState) identifier() (int64, string) {
dis := rst.disambiguator()
ident, _ := rst.undisambiguatedIdentifier()
return dis, ident
}
// disambiguator parses an optional:
//
// <disambiguator> = "s" <base-62-number>
func (rst *rustState) disambiguator() int64 {
if len(rst.str) == 0 || rst.str[0] != 's' {
return 0
}
rst.advance(1)
return rst.base62Number() + 1
}
// undisambiguatedIdentifier parses:
//
// <undisambiguated-identifier> = ["u"] <decimal-number> ["_"] <bytes>
func (rst *rustState) undisambiguatedIdentifier() (id string, isPunycode bool) {
isPunycode = false
if len(rst.str) > 0 && rst.str[0] == 'u' {
rst.advance(1)
isPunycode = true
}
val := rst.decimalNumber()
if len(rst.str) > 0 && rst.str[0] == '_' {
rst.advance(1)
}
if len(rst.str) < val {
rst.fail("not enough characters for identifier")
}
id = rst.str[:val]
rst.advance(val)
for i := 0; i < len(id); i++ {
c := id[i]
switch {
case c >= '0' && c <= '9':
case c >= 'A' && c <= 'Z':
case c >= 'a' && c <= 'z':
case c == '_':
default:
rst.fail("invalid character in identifier")
}
}
if isPunycode {
id = rst.expandPunycode(id)
}
return id, isPunycode
}
// expandPunycode decodes the Rust version of punycode.
// This algorithm is taken from RFC 3492 section 6.2.
func (rst *rustState) expandPunycode(s string) string {
const (
base = 36
tmin = 1
tmax = 26
skew = 38
damp = 700
initialBias = 72
initialN = 128
)
var (
output []rune
encoding string
)
idx := strings.LastIndex(s, "_")
if idx >= 0 {
output = []rune(s[:idx])
encoding = s[idx+1:]
} else {
encoding = s
}
i := 0
n := initialN
bias := initialBias
pos := 0
for pos < len(encoding) {
oldI := i
w := 1
for k := base; ; k += base {
if pos == len(encoding) {
rst.fail("unterminated punycode")
}
var digit byte
d := encoding[pos]
pos++
switch {
case '0' <= d && d <= '9':
digit = d - '0' + 26
case 'A' <= d && d <= 'Z':
digit = d - 'A'
case 'a' <= d && d <= 'z':
digit = d - 'a'
default:
rst.fail("invalid punycode digit")
}
i += int(digit) * w
if i < 0 {
rst.fail("punycode number overflow")
}
var t int
if k <= bias {
t = tmin
} else if k > bias+tmax {
t = tmax
} else {
t = k - bias
}
if int(digit) < t {
break
}
if w >= math.MaxInt32/base {
rst.fail("punycode number overflow")
}
w *= base - t
}
delta := i - oldI
numPoints := len(output) + 1
firstTime := oldI == 0
if firstTime {
delta /= damp
} else {
delta /= 2
}
delta += delta / numPoints
k := 0
for delta > ((base-tmin)*tmax)/2 {
delta /= base - tmin
k += base
}
bias = k + ((base-tmin+1)*delta)/(delta+skew)
n += i / (len(output) + 1)
if n > utf8.MaxRune {
rst.fail("punycode rune overflow")
} else if !utf8.ValidRune(rune(n)) {
rst.fail("punycode invalid code point")
}
i %= len(output) + 1
output = append(output, 0)
copy(output[i+1:], output[i:])
output[i] = rune(n)
i++
}
return string(output)
}
// genericArgs prints a list of generic arguments, without angle brackets.
func (rst *rustState) genericArgs() {
if rst.noGenericArgs {
hold := rst.skip
rst.skip = true
defer func() {
rst.skip = hold
}()
}
first := true
for len(rst.str) > 0 && rst.str[0] != 'E' {
if first {
first = false
} else {
rst.writeString(", ")
}
rst.genericArg()
}
}
// genericArg parses:
//
// <generic-arg> = <lifetime>
// | <type>
// | "K" <const> // forward-compat for const generics
// <lifetime> = "L" <base-62-number>
func (rst *rustState) genericArg() {
if len(rst.str) < 1 {
rst.fail("expected generic-arg")
}
if rst.str[0] == 'L' {
rst.advance(1)
rst.writeLifetime(rst.base62Number())
} else if rst.str[0] == 'K' {
rst.advance(1)
rst.demangleConst()
} else {
rst.demangleType()
}
}
// binder parses an optional:
//
// <binder> = "G" <base-62-number>
func (rst *rustState) binder() {
if len(rst.str) < 1 || rst.str[0] != 'G' {
return
}
rst.advance(1)
binderLifetimes := rst.base62Number() + 1
// Every bound lifetime should be referenced later.
if binderLifetimes >= int64(len(rst.str))-rst.lifetimes {
rst.fail("binder lifetimes overflow")
}
rst.writeString("for<")
for i := int64(0); i < binderLifetimes; i++ {
if i > 0 {
rst.writeString(", ")
}
rst.lifetimes++
rst.writeLifetime(1)
}
rst.writeString("> ")
}
// demangleType parses:
//
// <type> = <basic-type>
// | <path> // named type
// | "A" <type> <const> // [T; N]
// | "S" <type> // [T]
// | "T" {<type>} "E" // (T1, T2, T3, ...)
// | "R" [<lifetime>] <type> // &T
// | "Q" [<lifetime>] <type> // &mut T
// | "P" <type> // *const T
// | "O" <type> // *mut T
// | "F" <fn-sig> // fn(...) -> ...
// | "D" <dyn-bounds> <lifetime> // dyn Trait<Assoc = X> + Send + 'a
// | <backref>
func (rst *rustState) demangleType() {
if len(rst.str) < 1 {
rst.fail("expected type")
}
c := rst.str[0]
if c >= 'a' && c <= 'z' {
rst.basicType()
return
}
switch c {
case 'C', 'M', 'X', 'Y', 'N', 'I':
rst.path(false)
case 'A', 'S':
rst.advance(1)
rst.writeByte('[')
rst.demangleType()
if c == 'A' {
rst.writeString("; ")
rst.demangleConst()
}
rst.writeByte(']')
case 'T':
rst.advance(1)
rst.writeByte('(')
c := 0
for len(rst.str) > 0 && rst.str[0] != 'E' {
if c > 0 {
rst.writeString(", ")
}
c++
rst.demangleType()
}
if c == 1 {
rst.writeByte(',')
}
rst.writeByte(')')
rst.checkChar('E')
case 'R', 'Q':
rst.advance(1)
rst.writeByte('&')
if len(rst.str) > 0 && rst.str[0] == 'L' {
rst.advance(1)
if lifetime := rst.base62Number(); lifetime > 0 {
rst.writeLifetime(lifetime)
rst.writeByte(' ')
}
}
if c == 'Q' {
rst.writeString("mut ")
}
rst.demangleType()
case 'P':
rst.advance(1)
rst.writeString("*const ")
rst.demangleType()
case 'O':
rst.advance(1)
rst.writeString("*mut ")
rst.demangleType()
case 'F':
rst.advance(1)
hold := rst.lifetimes
rst.fnSig()
rst.lifetimes = hold
case 'D':
rst.advance(1)
hold := rst.lifetimes
rst.dynBounds()
rst.lifetimes = hold
if len(rst.str) == 0 || rst.str[0] != 'L' {
rst.fail("expected L")
}
rst.advance(1)
if lifetime := rst.base62Number(); lifetime > 0 {
if rst.last != ' ' {
rst.writeByte(' ')
}
rst.writeString("+ ")
rst.writeLifetime(lifetime)
}
case 'B':
rst.backref(rst.demangleType)
default:
rst.fail("unrecognized character in type")
}
}
var rustBasicTypes = map[byte]string{
'a': "i8",
'b': "bool",
'c': "char",
'd': "f64",
'e': "str",
'f': "f32",
'h': "u8",
'i': "isize",
'j': "usize",
'l': "i32",
'm': "u32",
'n': "i128",
'o': "u128",
'p': "_",
's': "i16",
't': "u16",
'u': "()",
'v': "...",
'x': "i64",
'y': "u64",
'z': "!",
}
// basicType parses:
//
// <basic-type>
func (rst *rustState) basicType() {
if len(rst.str) < 1 {
rst.fail("expected basic type")
}
str, ok := rustBasicTypes[rst.str[0]]
if !ok {
rst.fail("unrecognized basic type character")
}
rst.advance(1)
rst.writeString(str)
}
// fnSig parses:
//
// <fn-sig> = [<binder>] ["U"] ["K" <abi>] {<type>} "E" <type>
// <abi> = "C"
// | <undisambiguated-identifier>
func (rst *rustState) fnSig() {
rst.binder()
if len(rst.str) > 0 && rst.str[0] == 'U' {
rst.advance(1)
rst.writeString("unsafe ")
}
if len(rst.str) > 0 && rst.str[0] == 'K' {
rst.advance(1)
if len(rst.str) > 0 && rst.str[0] == 'C' {
rst.advance(1)
rst.writeString(`extern "C" `)
} else {
rst.writeString(`extern "`)
id, isPunycode := rst.undisambiguatedIdentifier()
if isPunycode {
rst.fail("punycode used in ABI string")
}
id = strings.ReplaceAll(id, "_", "-")
rst.writeString(id)
rst.writeString(`" `)
}
}
rst.writeString("fn(")
first := true
for len(rst.str) > 0 && rst.str[0] != 'E' {
if first {
first = false
} else {
rst.writeString(", ")
}
rst.demangleType()
}
rst.checkChar('E')
rst.writeByte(')')
if len(rst.str) > 0 && rst.str[0] == 'u' {
rst.advance(1)
} else {
rst.writeString(" -> ")
rst.demangleType()
}
}
// dynBounds parses:
//
// <dyn-bounds> = [<binder>] {<dyn-trait>} "E"
func (rst *rustState) dynBounds() {
rst.writeString("dyn ")
rst.binder()
first := true
for len(rst.str) > 0 && rst.str[0] != 'E' {
if first {
first = false
} else {
rst.writeString(" + ")
}
rst.dynTrait()
}
rst.checkChar('E')
}
// dynTrait parses:
//
// <dyn-trait> = <path> {<dyn-trait-assoc-binding>}
// <dyn-trait-assoc-binding> = "p" <undisambiguated-identifier> <type>
func (rst *rustState) dynTrait() {
started := rst.pathStartGenerics()
for len(rst.str) > 0 && rst.str[0] == 'p' {
rst.advance(1)
if started {
rst.writeString(", ")
} else {
rst.writeByte('<')
started = true
}
id, _ := rst.undisambiguatedIdentifier()
rst.writeString(id)
rst.writeString(" = ")
rst.demangleType()
}
if started {
rst.writeByte('>')
}
}
// pathStartGenerics is like path but if it sees an I to start generic
// arguments it won't close them. It reports whether it started generics.
func (rst *rustState) pathStartGenerics() bool {
if len(rst.str) < 1 {
rst.fail("expected path")
}
switch rst.str[0] {
case 'I':
rst.advance(1)
rst.path(false)
rst.writeByte('<')
rst.genericArgs()
rst.checkChar('E')
return true
case 'B':
var started bool
rst.backref(func() { started = rst.pathStartGenerics() })
return started
default:
rst.path(false)
return false
}
}
// writeLifetime writes out a lifetime binding.
func (rst *rustState) writeLifetime(lifetime int64) {
rst.writeByte('\'')
if lifetime == 0 {
rst.writeByte('_')
return
}
depth := rst.lifetimes - lifetime
if depth < 0 {
rst.fail("invalid lifetime")
} else if depth < 26 {
rst.writeByte('a' + byte(depth))
} else {
rst.writeByte('z')
if !rst.skip {
fmt.Fprintf(&rst.buf, "%d", depth-26+1)
rst.last = '0'
}
}
}
// demangleConst parses:
//
// <const> = <type> <const-data>
// | "p" // placeholder, shown as _
// | <backref>
// <const-data> = ["n"] {<hex-digit>} "_"
func (rst *rustState) demangleConst() {
if len(rst.str) < 1 {
rst.fail("expected constant")
}
if rst.str[0] == 'B' {
rst.backref(rst.demangleConst)
return
}
if rst.str[0] == 'p' {
rst.advance(1)
rst.writeByte('_')
return
}
typ := rst.str[0]
const (
invalid = iota
signedInt
unsignedInt
boolean
character
)
var kind int
switch typ {
case 'a', 's', 'l', 'x', 'n', 'i':
kind = signedInt
case 'h', 't', 'm', 'y', 'o', 'j':
kind = unsignedInt
case 'b':
kind = boolean
case 'c':
kind = character
default:
rst.fail("unrecognized constant type")
}
rst.advance(1)
if kind == signedInt && len(rst.str) > 0 && rst.str[0] == 'n' {
rst.advance(1)
rst.writeByte('-')
}
start := rst.str
digits := 0
val := uint64(0)
digitLoop:
for len(rst.str) > 0 {
c := rst.str[0]
var digit uint64
switch {
case c >= '0' && c <= '9':
digit = uint64(c - '0')
case c >= 'a' && c <= 'f':
digit = uint64(c - 'a' + 10)
case c == '_':
rst.advance(1)
break digitLoop
default:
rst.fail("expected hex digit or _")
}
rst.advance(1)
if val == 0 && digit == 0 && (len(rst.str) == 0 || rst.str[0] != '_') {
rst.fail("invalid leading 0 in constant")
}
val *= 16
val += digit
digits++
}
if digits == 0 {
rst.fail("expected constant")
}
switch kind {
case signedInt, unsignedInt:
if digits > 16 {
// Value too big, just write out the string.
rst.writeString("0x")
rst.writeString(start[:digits])
} else {
if !rst.skip {
fmt.Fprintf(&rst.buf, "%d", val)
rst.last = '0'
}
}
case boolean:
if digits > 1 {
rst.fail("boolean value too large")
} else if val == 0 {
rst.writeString("false")
} else if val == 1 {
rst.writeString("true")
} else {
rst.fail("invalid boolean value")
}
case character:
if digits > 6 {
rst.fail("character value too large")
}
rst.writeByte('\'')
if val == '\t' {
rst.writeString(`\t`)
} else if val == '\r' {
rst.writeString(`\r`)
} else if val == '\n' {
rst.writeString(`\n`)
} else if val == '\\' {
rst.writeString(`\\`)
} else if val == '\'' {
rst.writeString(`\'`)
} else if val >= ' ' && val <= '~' {
// printable ASCII character
rst.writeByte(byte(val))
} else {
if !rst.skip {
fmt.Fprintf(&rst.buf, `\u{%x}`, val)
rst.last = '}'
}
}
rst.writeByte('\'')
default:
panic("internal error")
}
}
// base62Number parses:
//
// <base-62-number> = {<0-9a-zA-Z>} "_"
func (rst *rustState) base62Number() int64 {
if len(rst.str) > 0 && rst.str[0] == '_' {
rst.advance(1)
return 0
}
val := int64(0)
for len(rst.str) > 0 {
c := rst.str[0]
rst.advance(1)
if c == '_' {
return val + 1
}
val *= 62
if c >= '0' && c <= '9' {
val += int64(c - '0')
} else if c >= 'a' && c <= 'z' {
val += int64(c - 'a' + 10)
} else if c >= 'A' && c <= 'Z' {
val += int64(c - 'A' + 36)
} else {
rst.fail("invalid digit in base 62 number")
}
}
rst.fail("expected _ after base 62 number")
return 0
}
// backref parses:
//
// <backref> = "B" <base-62-number>
func (rst *rustState) backref(demangle func()) {
backoff := rst.off
rst.checkChar('B')
idx64 := rst.base62Number()
if rst.skip {
return
}
if rst.max > 0 && rst.buf.Len() > rst.max {
return
}
idx := int(idx64)
if int64(idx) != idx64 {
rst.fail("backref index overflow")
}
if idx < 0 || idx >= backoff {
rst.fail("invalid backref index")
}
holdStr := rst.str
holdOff := rst.off
rst.str = rst.orig[idx:backoff]
rst.off = idx
defer func() {
rst.str = holdStr
rst.off = holdOff
}()
demangle()
}
func (rst *rustState) decimalNumber() int {
if len(rst.str) == 0 {
rst.fail("expected number")
}
val := 0
for len(rst.str) > 0 && isDigit(rst.str[0]) {
add := int(rst.str[0] - '0')
if val >= math.MaxInt32/10-add {
rst.fail("decimal number overflow")
}
val *= 10
val += add
rst.advance(1)
}
return val
}
// oldRustToString demangles a Rust symbol using the old demangling.
// The second result reports whether this is a valid Rust mangled name.
func oldRustToString(name string, options []Option) (string, bool) {
max := 0
for _, o := range options {
if isMaxLength(o) {
max = maxLength(o)
}
}
// We know that the string starts with _ZN.
name = name[3:]
hexDigit := func(c byte) (byte, bool) {
switch {
case c >= '0' && c <= '9':
return c - '0', true
case c >= 'a' && c <= 'f':
return c - 'a' + 10, true
default:
return 0, false
}
}
// We know that the strings end with "17h" followed by 16 characters
// followed by "E". We check that the 16 characters are all hex digits.
// Also the hex digits must contain at least 5 distinct digits.
seen := uint16(0)
for i := len(name) - 17; i < len(name)-1; i++ {
digit, ok := hexDigit(name[i])
if !ok {
return "", false
}
seen |= 1 << digit
}
if bits.OnesCount16(seen) < 5 {
return "", false
}
name = name[:len(name)-20]
// The name is a sequence of length-preceded identifiers.
var sb strings.Builder
for len(name) > 0 {
if max > 0 && sb.Len() > max {
break
}
if !isDigit(name[0]) {
return "", false
}
val := 0
for len(name) > 0 && isDigit(name[0]) {
add := int(name[0] - '0')
if val >= math.MaxInt32/10-add {
return "", false
}
val *= 10
val += add
name = name[1:]
}
// An optional trailing underscore can separate the
// length from the identifier.
if len(name) > 0 && name[0] == '_' {
name = name[1:]
val--
}
if len(name) < val {
return "", false
}
id := name[:val]
name = name[val:]
if sb.Len() > 0 {
sb.WriteString("::")
}
// Ignore leading underscores preceding escape sequences.
if strings.HasPrefix(id, "_$") {
id = id[1:]
}
// The identifier can have escape sequences.
escape:
for len(id) > 0 {
switch c := id[0]; c {
case '$':
codes := map[string]byte{
"SP": '@',
"BP": '*',
"RF": '&',
"LT": '<',
"GT": '>',
"LP": '(',
"RP": ')',
}
valid := true
if len(id) > 2 && id[1] == 'C' && id[2] == '$' {
sb.WriteByte(',')
id = id[3:]
} else if len(id) > 4 && id[1] == 'u' && id[4] == '$' {
dig1, ok1 := hexDigit(id[2])
dig2, ok2 := hexDigit(id[3])
val := (dig1 << 4) | dig2
if !ok1 || !ok2 || dig1 > 7 || val < ' ' {
valid = false
} else {
sb.WriteByte(val)
id = id[5:]
}
} else if len(id) > 3 && id[3] == '$' {
if code, ok := codes[id[1:3]]; !ok {
valid = false
} else {
sb.WriteByte(code)
id = id[4:]
}
} else {
valid = false
}
if !valid {
sb.WriteString(id)
break escape
}
case '.':
if strings.HasPrefix(id, "..") {
sb.WriteString("::")
id = id[2:]
} else {
sb.WriteByte(c)
id = id[1:]
}
default:
sb.WriteByte(c)
id = id[1:]
}
}
}
s := sb.String()
if max > 0 && len(s) > max {
s = s[:max]
}
return s, true
}