Last active
May 14, 2024 22:53
-
-
Save theandrew168/75a5d97d794e7835670eaaa6c6d53b30 to your computer and use it in GitHub Desktop.
TurboDLP Discussion
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
import ( | |
"encoding/json" | |
"fmt" | |
"regexp" | |
) | |
// Algebraic data types based on: | |
// https://eli.thegreenplace.net/2018/go-and-algebraic-data-types/ | |
type Rule interface { | |
isRule() | |
} | |
// How to decode a Rule: | |
// 1. Try to decude a Basic (base case) | |
// 2. If that succeeds, it is Basic | |
// 3. Otherwise, try to decode a Composite (recursive case) | |
// 4. If that succeeds, it is Composite | |
// 5. Otherwise, the rule is invalid | |
type Basic struct { | |
Pattern string `json:"pattern"` | |
} | |
func (Basic) isRule() {} | |
type Operation string | |
const ( | |
OperationAnd Operation = "and" | |
OperationOr Operation = "or" | |
OperationNot Operation = "not" | |
) | |
type Composite struct { | |
Operation Operation `json:"operation"` | |
Rules []Rule `json:"rules"` | |
} | |
func (Composite) isRule() {} | |
// Recursively parse a dynamic, polymorphic rule structure. | |
func ParseRule(data []byte) (Rule, error) { | |
// base case | |
// try to decode a basic rule | |
var basic Basic | |
err := json.Unmarshal(data, &basic) | |
if err != nil { | |
return nil, err | |
} | |
// if the rule has a pattern, it is basic so return it | |
if basic.Pattern != "" { | |
return basic, nil | |
} | |
// recursive case | |
// decode a partial composite and its sub-rules | |
var partial struct { | |
Operation Operation `json:"operation"` | |
Rules []json.RawMessage `json:"rules"` | |
} | |
err = json.Unmarshal(data, &partial) | |
if err != nil { | |
return nil, err | |
} | |
// decode each sub-rule recursively and collect into a slice | |
var rules []Rule | |
for _, ruleData := range partial.Rules { | |
rule, err := ParseRule(ruleData) | |
if err != nil { | |
return nil, err | |
} | |
rules = append(rules, rule) | |
} | |
// construct and return the composite rule | |
composite := Composite{ | |
Operation: partial.Operation, | |
Rules: rules, | |
} | |
return composite, nil | |
} | |
// Helper for checking if every item in a slice is the value we want. | |
func every(items []bool, want bool) bool { | |
for _, item := range items { | |
if item != want { | |
return false | |
} | |
} | |
return true | |
} | |
// Helper for checking if some items in a slice are the value we want. | |
func some(items []bool, want bool) bool { | |
for _, item := range items { | |
if item == want { | |
return true | |
} | |
} | |
return false | |
} | |
func evaluate(text string, rule Rule) bool { | |
switch r := rule.(type) { | |
// base case | |
case Basic: | |
match, _ := regexp.MatchString(r.Pattern, text) | |
return match | |
// recursive case | |
case Composite: | |
switch r.Operation { | |
case OperationAnd: | |
var results []bool | |
for _, rr := range r.Rules { | |
result := evaluate(text, rr) | |
results = append(results, result) | |
} | |
return every(results, true) | |
case OperationOr: | |
var results []bool | |
for _, rr := range r.Rules { | |
result := evaluate(text, rr) | |
results = append(results, result) | |
} | |
return some(results, true) | |
case OperationNot: | |
var results []bool | |
for _, rr := range r.Rules { | |
result := evaluate(text, rr) | |
results = append(results, result) | |
} | |
return every(results, false) | |
default: | |
panic("unexpected operation") | |
} | |
default: | |
panic("unexpected rule") | |
} | |
} | |
func main() { | |
var fooOrBar Rule = Composite{ | |
Operation: OperationOr, | |
Rules: []Rule{ | |
Basic{Pattern: "foo"}, | |
Basic{Pattern: "bar"}, | |
}, | |
} | |
encoded, err := json.Marshal(fooOrBar) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Println(string(encoded)) | |
decoded, err := ParseRule(encoded) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Printf("%+v\n", decoded) | |
var fooAndNotBar Rule = Composite{ | |
Operation: OperationAnd, | |
Rules: []Rule{ | |
Basic{Pattern: "foo"}, | |
Composite{ | |
Operation: OperationNot, | |
Rules: []Rule{ | |
Basic{Pattern: "bar"}, | |
}, | |
}, | |
}, | |
} | |
encoded, err = json.Marshal(fooAndNotBar) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Println(string(encoded)) | |
decoded, err = ParseRule(encoded) | |
if err != nil { | |
fmt.Println(err) | |
return | |
} | |
fmt.Printf("%+v\n", decoded) | |
tests := []struct { | |
text string | |
rule Rule | |
want bool | |
}{ | |
{ | |
text: "My cat foo is not a bar.", | |
rule: fooOrBar, | |
want: true, | |
}, | |
{ | |
text: "My cat foo is not a.", | |
rule: fooOrBar, | |
want: true, | |
}, | |
{ | |
text: "My cat is not a bar.", | |
rule: fooOrBar, | |
want: true, | |
}, | |
{ | |
text: "My cat is not a.", | |
rule: fooOrBar, | |
want: false, | |
}, | |
{ | |
text: "My cat foo is not a bar.", | |
rule: fooAndNotBar, | |
want: false, | |
}, | |
{ | |
text: "My cat is not a bar.", | |
rule: fooAndNotBar, | |
want: false, | |
}, | |
{ | |
text: "My cat foo is not a.", | |
rule: fooAndNotBar, | |
want: true, | |
}, | |
} | |
for _, test := range tests { | |
got := evaluate(test.text, test.rule) | |
if got != test.want { | |
fmt.Println("FAILED") | |
} else { | |
fmt.Println("SUCCESS") | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
type Basic = { | |
pattern: string; | |
} | |
type Operation = "and" | "or" | "not"; | |
type Composite = { | |
operation: Operation; | |
rules: Rule[]; | |
} | |
type Rule = Basic | Composite; | |
const fooOrBar: Rule = { | |
operation: "or", | |
rules: [{ | |
pattern: "foo", | |
}, { | |
pattern: "bar", | |
}], | |
} | |
const fooAndNotBar: Rule = { | |
operation: "and", | |
rules: [{ | |
pattern: "foo", | |
}, { | |
operation: "not", | |
rules: [{ | |
pattern: "bar", | |
}, { | |
pattern: "baz", | |
}], | |
}], | |
} | |
function evaluate(text: string, rule: Rule): boolean { | |
// base case | |
if ('pattern' in rule) { | |
return new RegExp(rule.pattern).test(text); | |
} | |
// recursive case | |
switch (rule.operation) { | |
case "and": { | |
const results = rule.rules.map((rule) => evaluate(text, rule)); | |
return results.every((res) => res); | |
} | |
case "or": { | |
const results = rule.rules.map((rule) => evaluate(text, rule)); | |
return results.some((res) => res); | |
} | |
case "not": { | |
const results = rule.rules.map((rule) => evaluate(text, rule)); | |
return results.every((res) => !res); | |
} | |
} | |
} | |
const tests = [{ | |
text: "My cat foo is not a bar.", | |
rule: fooOrBar, | |
want: true, | |
}, { | |
text: "My cat foo is not a.", | |
rule: fooOrBar, | |
want: true, | |
}, { | |
text: "My cat is not a bar.", | |
rule: fooOrBar, | |
want: true, | |
}, { | |
text: "My cat is not a.", | |
rule: fooOrBar, | |
want: false, | |
}, { | |
text: "My cat foo is not a bar.", | |
rule: fooAndNotBar, | |
want: false, | |
}, { | |
text: "My cat is not a bar.", | |
rule: fooAndNotBar, | |
want: false, | |
}, { | |
text: "My cat foo is not a.", | |
rule: fooAndNotBar, | |
want: true, | |
}]; | |
for (const test of tests) { | |
const res = evaluate(test.text, test.rule); | |
if (res != test.want) { | |
console.log('FAILED'); | |
} else { | |
console.log('SUCCESS'); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment