103 lines
2.3 KiB
Go
103 lines
2.3 KiB
Go
package preproc
|
|
|
|
import (
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/armon/go-radix"
|
|
)
|
|
|
|
type DefineList struct {
|
|
tree *radix.Tree
|
|
}
|
|
|
|
func NewDefineList() *DefineList { return &DefineList{tree: radix.New()} }
|
|
|
|
// Add inserts or updates a define->value mapping.
|
|
func (d *DefineList) Add(define, value string) {
|
|
d.tree.Insert(define, expandDollarEscapes(value))
|
|
}
|
|
|
|
// Delete removes a define. Returns true if it existed.
|
|
func (d *DefineList) Delete(define string) bool {
|
|
_, ok := d.tree.Delete(define)
|
|
return ok
|
|
}
|
|
|
|
// Defined reports whether a define exists.
|
|
func (d *DefineList) Defined(s string) bool {
|
|
_, ok := d.tree.Get(s)
|
|
return ok
|
|
}
|
|
|
|
// typed wrapper to avoid type assertions sprinkled in hot code
|
|
func (d *DefineList) longestPrefixString(s string) (key string, val string, ok bool) {
|
|
k, v, ok := d.tree.LongestPrefix(s)
|
|
if !ok {
|
|
return "", "", false
|
|
}
|
|
return k, v.(string), true
|
|
}
|
|
|
|
// ReplaceDefines performs a single-pass, longest-prefix replacement.
|
|
// Case-sensitive. Matches anywhere. No recursive re-expansion.
|
|
func (d *DefineList) ReplaceDefines(s string) string {
|
|
var b strings.Builder
|
|
b.Grow(len(s)) // baseline; grows if expansions occur
|
|
|
|
for i := 0; i < len(s); {
|
|
if key, val, ok := d.longestPrefixString(s[i:]); ok && key != "" {
|
|
b.WriteString(val)
|
|
i += len(key) // advance by matched bytes (UTF-8-safe)
|
|
continue
|
|
}
|
|
_, size := utf8.DecodeRuneInString(s[i:])
|
|
b.WriteString(s[i : i+size])
|
|
i += size
|
|
}
|
|
return b.String()
|
|
}
|
|
|
|
func isHex(c byte) bool {
|
|
return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F') || (c >= 'a' && c <= 'f')
|
|
}
|
|
|
|
func hexToByte(hi, lo byte) byte {
|
|
return hexDigit(hi)<<4 | hexDigit(lo)
|
|
}
|
|
|
|
func hexDigit(c byte) byte {
|
|
if c >= '0' && c <= '9' {
|
|
return c - '0'
|
|
}
|
|
if c >= 'A' && c <= 'F' {
|
|
return c - 'A' + 10
|
|
}
|
|
if c >= 'a' && c <= 'f' {
|
|
return c - 'a' + 10
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func expandDollarEscapes(s string) string {
|
|
var b strings.Builder
|
|
for i := 0; i < len(s); i++ {
|
|
if s[i] == '$' {
|
|
if i+1 < len(s) && s[i+1] == '$' {
|
|
// $$ -> $
|
|
b.WriteByte('$')
|
|
i++ // skip second $
|
|
} else if i+2 < len(s) && isHex(s[i+1]) && isHex(s[i+2]) {
|
|
// $XX -> character
|
|
val := hexToByte(s[i+1], s[i+2])
|
|
b.WriteByte(val)
|
|
i += 2 // skip hex digits
|
|
} else {
|
|
b.WriteByte('$')
|
|
}
|
|
} else {
|
|
b.WriteByte(s[i])
|
|
}
|
|
}
|
|
return b.String()
|
|
}
|