mirror of
https://github.com/cloudflare/redoctober.git
synced 2025-12-23 14:25:46 +00:00
200 lines
4.6 KiB
Go
200 lines
4.6 KiB
Go
package msp
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
type Formatted struct { // Represents threshold gate (also type of condition)
|
|
Min int
|
|
Conds []Condition
|
|
}
|
|
|
|
func StringToFormatted(f string) (out Formatted, err error) {
|
|
// Automaton. Modification of Dijkstra's Two-Stack Algorithm for parsing
|
|
// infix notation. Running time linear in the size of the predicate?
|
|
//
|
|
// Steps either to the next comma or the next unparenthesis.
|
|
// ( -> Push new queue onto staging stack
|
|
// value -> Push onto back of queue at top of staging stack.
|
|
// ) -> Pop queue off top of staging stack, build threshold gate,
|
|
// and push gate onto the back of the top queue.
|
|
//
|
|
// Staging stack is empty on initialization and should have exactly 1 built
|
|
// threshold gate at the end of the string.
|
|
if len(f) == 0 || f[0] != '(' || f[len(f)-1] != ')' {
|
|
return out, errors.New("Invalid string: Needs to begin and end with parentheses.")
|
|
}
|
|
|
|
staging := [][]Condition{}
|
|
indices := make(map[string]int, 0)
|
|
|
|
var nxt string
|
|
for len(f) > 0 {
|
|
nxt, f = getNext(f)
|
|
|
|
switch nxt {
|
|
case "(":
|
|
staging = append([][]Condition{[]Condition{}}, staging...)
|
|
case ")":
|
|
if len(staging) < 1 || len(staging[0]) < 1 { // Check 1
|
|
return out, errors.New("Invalid string: Illegal close parenthesis.")
|
|
}
|
|
|
|
top := staging[0] // Legal because of check 1.
|
|
staging = staging[1:]
|
|
|
|
var min int
|
|
minStr, ok := top[0].(Name) // Legal because of check 1.
|
|
if !ok {
|
|
return out, errors.New("Invalid string: First argument wasn't a threshold!")
|
|
}
|
|
|
|
min, err = strconv.Atoi(minStr.string)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
built := Formatted{
|
|
Min: min,
|
|
Conds: []Condition{},
|
|
}
|
|
|
|
for _, cond := range top[1:] {
|
|
built.Conds = append(built.Conds, cond)
|
|
}
|
|
|
|
if len(staging) == 0 { // Check 2
|
|
if len(f) == 0 {
|
|
return built, nil
|
|
}
|
|
return out, errors.New("Invalid string: Can't parse anymore, but there's still data. Too many closing parentheses or too few opening parentheses?")
|
|
}
|
|
|
|
staging[0] = append(staging[0], built) // Legal because of check 2.
|
|
|
|
default:
|
|
if len(staging) < 1 {
|
|
return out, errors.New("Invalid string: Name is not encapsulated!")
|
|
}
|
|
|
|
if _, there := indices[nxt]; !there {
|
|
indices[nxt] = 0
|
|
}
|
|
|
|
staging[0] = append(staging[0], Name{nxt, indices[nxt]}) // Legal because of check above.
|
|
indices[nxt]++
|
|
}
|
|
}
|
|
|
|
return out, errors.New("Invalid string: Not finished parsing, but out of data. Too many opening parentheses or too few closing parentheses?")
|
|
}
|
|
|
|
func getNext(f string) (string, string) { // f -> (next, rest)
|
|
f = strings.TrimSpace(f)
|
|
|
|
if f[0] == '(' {
|
|
return f[0:1], f[1:]
|
|
}
|
|
|
|
nextComma := strings.Index(f, ",")
|
|
if f[0] == ')' {
|
|
if nextComma == -1 {
|
|
return f[0:1], ""
|
|
}
|
|
return f[0:1], f[nextComma+1:]
|
|
} else if nextComma == -1 {
|
|
return f[0 : len(f)-1], f[len(f)-1:]
|
|
}
|
|
|
|
nextUnParen := strings.Index(f, ")")
|
|
if nextComma < nextUnParen {
|
|
return strings.TrimSpace(f[0:nextComma]), f[nextComma+1:]
|
|
}
|
|
|
|
return strings.TrimSpace(f[0:nextUnParen]), f[nextUnParen:]
|
|
}
|
|
|
|
func (f Formatted) String() string {
|
|
out := fmt.Sprintf("(%v", f.Min)
|
|
|
|
for _, cond := range f.Conds {
|
|
switch cond := cond.(type) {
|
|
case Name:
|
|
out += fmt.Sprintf(", %v", cond.string)
|
|
case Formatted:
|
|
out += fmt.Sprintf(", %v", cond.String())
|
|
}
|
|
}
|
|
|
|
return out + ")"
|
|
}
|
|
|
|
func (f Formatted) Ok(db UserDatabase) bool {
|
|
// Goes through the smallest number of conditions possible to check if the
|
|
// threshold gate returns true. Sometimes requires recursing down to check
|
|
// nested threshold gates.
|
|
rest := f.Min
|
|
|
|
for _, cond := range f.Conds {
|
|
if cond.Ok(db) {
|
|
rest--
|
|
}
|
|
|
|
if rest == 0 {
|
|
return true
|
|
}
|
|
}
|
|
|
|
return false
|
|
}
|
|
|
|
func (f *Formatted) Compress() {
|
|
if f.Min == len(f.Conds) {
|
|
// AND Compression: (n, ..., (m, ...), ...) = (n + m, ...)
|
|
skip := 0
|
|
for i, cond := range f.Conds {
|
|
if skip > 0 {
|
|
skip--
|
|
continue
|
|
}
|
|
|
|
switch cond := cond.(type) {
|
|
case Formatted:
|
|
cond.Compress()
|
|
f.Conds[i] = cond
|
|
|
|
if cond.Min == len(cond.Conds) {
|
|
f.Min += cond.Min - 1
|
|
f.Conds = append(f.Conds[0:i],
|
|
append(cond.Conds, f.Conds[i+1:]...)...)
|
|
skip = len(cond.Conds) - 1
|
|
}
|
|
}
|
|
}
|
|
} else if f.Min == 1 {
|
|
// OR Compression: (1, ..., (1, ...), ...) = (1, ...)
|
|
skip := 0
|
|
for i, cond := range f.Conds {
|
|
if skip > 0 {
|
|
skip--
|
|
continue
|
|
}
|
|
|
|
switch cond := cond.(type) {
|
|
case Formatted:
|
|
cond.Compress()
|
|
f.Conds[i] = cond
|
|
|
|
if cond.Min == 1 {
|
|
f.Conds = append(f.Conds[0:i],
|
|
append(cond.Conds, f.Conds[i+1:]...)...)
|
|
skip = len(cond.Conds) - 1
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|