Skip to content

Commit

Permalink
rewrite placeholder parser
Browse files Browse the repository at this point in the history
The old parser logic was quite complicated and does not support nested placeholder evaluation for defaults, as does not support counting closing braces.

I assume the given approach using two loops and switch statements is more readable, and changes braces matching behavior to support nested defaulting provided in another commit.

Signed-off-by: Jens Erat <[email protected]>
  • Loading branch information
JensErat committed Dec 30, 2022
1 parent 47f43bd commit a8249c0
Showing 1 changed file with 37 additions and 28 deletions.
65 changes: 37 additions & 28 deletions replacer.go
Original file line number Diff line number Diff line change
Expand Up @@ -141,46 +141,55 @@ func (r *Replacer) replace(input, empty string,
// iterate the input to find each placeholder
var lastWriteCursor int

// fail fast if too many placeholders are unclosed
var unclosedCount int

scan:
//scan:
for i := 0; i < len(input); i++ {
// check for escaped braces
if i > 0 && input[i-1] == phEscape && (input[i] == phClose || input[i] == phOpen) {
sb.WriteString(input[lastWriteCursor : i-1])
switch input[i] {
case phOpen:
// process possible placeholder in remaining loop (not drop into default)
case phEscape:
// escape character at the end of the input or next character not a brace or escape character
if i+1 == len(input) || (input[i+1] != phOpen && input[i+1] != phClose && input[i+1] != phEscape) {
continue
}
// if there's anything to copy (until the escape character), do so
if i > lastWriteCursor {
sb.WriteString(input[lastWriteCursor:i])
}
// skip handling escaped character, get it copied with the next special character
i++
lastWriteCursor = i
continue
}

if input[i] != phOpen {
default:
// just copy anything else
continue
}

// our iterator is now on an unescaped open brace (start of placeholder)

// too many unclosed placeholders in absolutely ridiculous input can be extremely slow (issue #4170)
if unclosedCount > 100 {
return "", fmt.Errorf("too many unclosed placeholders")
end := 0
bracesLevel := 0
for j := i + 1; j < len(input); j++ {
switch input[j] {
case phOpen:
bracesLevel++
case phClose:
if bracesLevel > 0 {
bracesLevel--
continue
}
end = j
break
case phEscape:
// skip scaped character
j++
default:
}
}

// find the end of the placeholder
end := strings.Index(input[i:], string(phClose)) + i
if end < i {
unclosedCount++
// no matching closing brace found
if end == 0 {
continue
}

// if necessary look for the first closing brace that is not escaped
for end > 0 && end < len(input)-1 && input[end-1] == phEscape {
nextEnd := strings.Index(input[end+1:], string(phClose))
if nextEnd < 0 {
unclosedCount++
continue scan
}
end += nextEnd + 1
}

// write the substring from the last cursor to this point
sb.WriteString(input[lastWriteCursor:i])

Expand Down

0 comments on commit a8249c0

Please sign in to comment.