text/scanner.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
}