127 lines
2.7 KiB
Go
127 lines
2.7 KiB
Go
// Copyright (c) 2024 Celestino Amoroso (celestino.amoroso@gmail.com).
|
|
// All rights reserved.
|
|
|
|
// scanner.go
|
|
package text
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
)
|
|
|
|
type ScannerFlag uint16
|
|
|
|
const (
|
|
ValueRequired = ScannerFlag(1 << iota)
|
|
DirectValue
|
|
EncryptValue
|
|
)
|
|
|
|
// Flags symbols in the abova iota order
|
|
const flagSymbols = "!=*"
|
|
|
|
const defaultVarIntro = '$'
|
|
|
|
|
|
type VariableHandler interface {
|
|
Handle(varSpec string, flags ScannerFlag) (value string, err error)
|
|
}
|
|
|
|
func Scan(handler VariableHandler, source string) (result string, err error) {
|
|
return ScanExt(defaultVarIntro, handler, source)
|
|
}
|
|
|
|
func ScanExt(varIntro byte, handler VariableHandler, source string) (result string, err error) {
|
|
var spec, value string
|
|
var flags ScannerFlag
|
|
var backSlash bool
|
|
var sb strings.Builder
|
|
offset := 0
|
|
for offset < len(source) {
|
|
ch := source[offset]
|
|
offset++
|
|
if backSlash {
|
|
switch ch {
|
|
case '\\':
|
|
sb.WriteByte('\\')
|
|
case varIntro:
|
|
sb.WriteByte(varIntro)
|
|
default:
|
|
sb.WriteByte(ch)
|
|
}
|
|
backSlash = false
|
|
} else if ch == '\\' {
|
|
backSlash = true
|
|
} else if ch == varIntro {
|
|
if spec, flags, offset, err = getVarSpec(source, offset); err != nil {
|
|
//err = fmt.Errorf("Source:\n%s", source)
|
|
return
|
|
}
|
|
if handler != nil {
|
|
if value, err = handler.Handle(spec, flags); err != nil {
|
|
return
|
|
}
|
|
}
|
|
sb.WriteString(value)
|
|
} else {
|
|
sb.WriteByte(ch)
|
|
}
|
|
}
|
|
result = sb.String()
|
|
return
|
|
}
|
|
|
|
func getVarSpec(source string, startOffset int) (spec string, flags ScannerFlag, offset int, err error) {
|
|
var ch byte
|
|
var lastOpenBrace int = -1
|
|
|
|
braceCount := 0
|
|
if startOffset < len(source) && source[startOffset] == '{' {
|
|
lastOpenBrace = startOffset
|
|
braceCount++
|
|
startOffset++
|
|
}
|
|
count := 0
|
|
beginning := true
|
|
for offset = startOffset; offset < len(source); offset++ {
|
|
ch = source[offset]
|
|
if ch == '{' && braceCount > 0 {
|
|
lastOpenBrace = offset
|
|
count++
|
|
braceCount++
|
|
} else if ch == '}' {
|
|
braceCount--
|
|
if braceCount == 0 {
|
|
break
|
|
}
|
|
count++
|
|
} else if braceCount == 0 && strings.IndexByte("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_", ch) < 0 {
|
|
break
|
|
} else if braceCount == 1 && beginning {
|
|
if flagIndex := strings.IndexByte(flagSymbols, ch); flagIndex >= 0 {
|
|
startOffset++
|
|
flags |= ScannerFlag(1 << flagIndex)
|
|
} else {
|
|
count++
|
|
beginning = false
|
|
}
|
|
} else {
|
|
count++
|
|
beginning = false
|
|
}
|
|
}
|
|
if err == nil {
|
|
if braceCount > 0 {
|
|
err = fmt.Errorf("unbalanced open braces at offset %d", lastOpenBrace)
|
|
} else {
|
|
if offset < len(source) && ch == '}' {
|
|
offset++
|
|
}
|
|
//fmt.Println("count:", count)
|
|
spec = source[startOffset : startOffset+count]
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|