mirror of
https://github.com/cloudflare/redoctober.git
synced 2026-01-03 11:45:41 +00:00
226 lines
5.3 KiB
Go
226 lines
5.3 KiB
Go
package msp
|
|
|
|
import (
|
|
"container/list"
|
|
"errors"
|
|
"strings"
|
|
)
|
|
|
|
type NodeType int // Types of node in the binary expression tree.
|
|
|
|
const (
|
|
NodeAnd NodeType = iota
|
|
NodeOr
|
|
)
|
|
|
|
func (t NodeType) Type() NodeType {
|
|
return t
|
|
}
|
|
|
|
type Raw struct { // Represents one node in the tree.
|
|
NodeType
|
|
|
|
Left *Condition
|
|
Right *Condition
|
|
}
|
|
|
|
func StringToRaw(r string) (out Raw, err error) {
|
|
// Automaton. Modification of Dijkstra's Two-Stack Algorithm for parsing
|
|
// infix notation. Reads one long unbroken expression (several operators and
|
|
// operands with no parentheses) at a time and parses it into a binary
|
|
// expression tree (giving AND operators precedence). Running time linear in
|
|
// the size of the predicate?
|
|
//
|
|
// Steps to the next (un)parenthesis.
|
|
// ( -> 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 BET, and push tree
|
|
// onto the back of the top queue.
|
|
//
|
|
// To build the binary expression tree, for each type of operation we iterate
|
|
// through the (Condition, operator) lists compacting where that operation
|
|
// occurs into tree nodes.
|
|
//
|
|
// Staging stack is empty on initialization and should have exactly 1 node
|
|
// (the root node) at the end of the string.
|
|
r = "(" + r + ")"
|
|
|
|
min := func(a, b, c int) int { // Return smallest non-negative argument.
|
|
if a > b { // Sort {a, b, c}
|
|
a, b = b, a
|
|
}
|
|
if b > c {
|
|
b, c = c, b
|
|
}
|
|
if a > b {
|
|
a, b = b, a
|
|
}
|
|
|
|
if a != -1 {
|
|
return a
|
|
} else if b != -1 {
|
|
return b
|
|
} else {
|
|
return c
|
|
}
|
|
}
|
|
|
|
getNext := func(r string) (string, string) { // r -> (next, rest)
|
|
r = strings.TrimSpace(r)
|
|
|
|
if r[0] == '(' || r[0] == ')' || r[0] == '&' || r[0] == '|' {
|
|
return r[0:1], r[1:]
|
|
}
|
|
|
|
nextOper := min(
|
|
strings.Index(r, "&"),
|
|
strings.Index(r, "|"),
|
|
strings.Index(r, ")"),
|
|
)
|
|
|
|
if nextOper == -1 {
|
|
return r, ""
|
|
}
|
|
return strings.TrimSpace(r[0:nextOper]), r[nextOper:]
|
|
}
|
|
|
|
staging := list.New() // Stack of (Condition list, operator list)
|
|
indices := make(map[string]int, 0)
|
|
|
|
var nxt string
|
|
for len(r) > 0 {
|
|
nxt, r = getNext(r)
|
|
|
|
switch nxt {
|
|
case "(":
|
|
staging.PushFront([2]*list.List{list.New(), list.New()})
|
|
case ")":
|
|
top := staging.Remove(staging.Front()).([2]*list.List)
|
|
if top[0].Len() != (top[1].Len() + 1) {
|
|
return out, errors.New("Invalid string: There needs to be an operator (& or |) for every pair of operands.")
|
|
}
|
|
|
|
for typ := NodeAnd; typ <= NodeOr; typ++ {
|
|
var step *list.Element
|
|
leftOperand := top[0].Front()
|
|
|
|
for oper := top[1].Front(); oper != nil; oper = step {
|
|
step = oper.Next()
|
|
|
|
if oper.Value.(NodeType) == typ {
|
|
left := leftOperand.Value.(Condition)
|
|
right := leftOperand.Next().Value.(Condition)
|
|
|
|
leftOperand.Next().Value = Raw{
|
|
NodeType: typ,
|
|
Left: &left,
|
|
Right: &right,
|
|
}
|
|
|
|
leftOperand = leftOperand.Next()
|
|
|
|
top[0].Remove(leftOperand.Prev())
|
|
top[1].Remove(oper)
|
|
} else {
|
|
leftOperand = leftOperand.Next()
|
|
}
|
|
}
|
|
}
|
|
|
|
if top[0].Len() != 1 || top[1].Len() != 0 {
|
|
return out, errors.New("Invalid string: Couldn't evaluate all of the operators.")
|
|
}
|
|
|
|
if staging.Len() == 0 {
|
|
if len(r) == 0 {
|
|
res := top[0].Front().Value
|
|
|
|
switch res.(type) {
|
|
case Raw:
|
|
return res.(Raw), nil
|
|
default:
|
|
return out, errors.New("Invalid string: Only one condition was found.")
|
|
}
|
|
}
|
|
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.Front().Value.([2]*list.List)[0].PushBack(top[0].Front().Value)
|
|
|
|
case "&":
|
|
staging.Front().Value.([2]*list.List)[1].PushBack(NodeAnd)
|
|
case "|":
|
|
staging.Front().Value.([2]*list.List)[1].PushBack(NodeOr)
|
|
default:
|
|
if _, there := indices[nxt]; !there {
|
|
indices[nxt] = 0
|
|
}
|
|
|
|
staging.Front().Value.([2]*list.List)[0].PushBack(String{nxt, indices[nxt]})
|
|
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 (r Raw) String() string {
|
|
out := ""
|
|
|
|
switch (*r.Left).(type) {
|
|
case String:
|
|
out += (*r.Left).(String).string
|
|
default:
|
|
out += "(" + (*r.Left).(Raw).String() + ")"
|
|
}
|
|
|
|
if r.Type() == NodeAnd {
|
|
out += " & "
|
|
} else {
|
|
out += " | "
|
|
}
|
|
|
|
switch (*r.Right).(type) {
|
|
case String:
|
|
out += (*r.Right).(String).string
|
|
default:
|
|
out += "(" + (*r.Right).(Raw).String() + ")"
|
|
}
|
|
|
|
return out
|
|
}
|
|
|
|
func (r Raw) Formatted() (out Formatted) {
|
|
// Recursively maps a raw predicate to a formatted predicate by mapping AND
|
|
// gates to (2, A, B) treshold gates and OR gates to (1, A, B) gates.
|
|
if r.Type() == NodeAnd {
|
|
out.Min = 2
|
|
} else {
|
|
out.Min = 1
|
|
}
|
|
|
|
switch (*r.Left).(type) {
|
|
case String:
|
|
out.Conds = []Condition{(*r.Left).(String)}
|
|
default:
|
|
out.Conds = []Condition{(*r.Left).(Raw).Formatted()}
|
|
}
|
|
|
|
switch (*r.Right).(type) {
|
|
case String:
|
|
out.Conds = append(out.Conds, (*r.Right).(String))
|
|
default:
|
|
out.Conds = append(out.Conds, (*r.Right).(Raw).Formatted())
|
|
}
|
|
|
|
out.Compress() // Small amount of predicate compression.
|
|
return
|
|
}
|
|
|
|
func (r Raw) Ok(db *UserDatabase) bool {
|
|
if r.Type() == NodeAnd {
|
|
return (*r.Left).Ok(db) && (*r.Right).Ok(db)
|
|
} else {
|
|
return (*r.Left).Ok(db) || (*r.Right).Ok(db)
|
|
}
|
|
}
|