mirror of
https://github.com/hanzoai/expr.git
synced 2026-09-24 16:58:58 +00:00
Add test data generator
This commit is contained in:
@@ -35,6 +35,7 @@ type Env struct {
|
||||
ArrayOfFoo []*Foo
|
||||
MapOfFoo map[string]Foo
|
||||
MapOfAny map[string]interface{}
|
||||
MapIntAny map[int]string
|
||||
FuncParam func(_ bool, _ int, _ string) bool
|
||||
FuncParamAny func(_ interface{}) bool
|
||||
FuncTooManyReturns func() (int, int, error)
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
package playground
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func ExampleData() interface{} {
|
||||
profileJohn := UserProfile{
|
||||
Birthday: time.Date(1990, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
Biography: "A passionate writer about Go.",
|
||||
Website: "https://johndoe.com",
|
||||
}
|
||||
|
||||
profileJane := UserProfile{
|
||||
Birthday: time.Date(1985, 2, 15, 0, 0, 0, 0, time.UTC),
|
||||
Biography: "A web developer and writer.",
|
||||
Website: "https://jane.com",
|
||||
}
|
||||
|
||||
authorJohn := Author{
|
||||
ID: 1,
|
||||
FirstName: "John",
|
||||
LastName: "Doe",
|
||||
Email: "[email protected]",
|
||||
Profile: profileJohn,
|
||||
}
|
||||
|
||||
authorJane := Author{
|
||||
ID: 2,
|
||||
FirstName: "Jane",
|
||||
LastName: "Smith",
|
||||
Email: "[email protected]",
|
||||
Profile: profileJane,
|
||||
}
|
||||
|
||||
posts := []Post{
|
||||
{
|
||||
ID: 1,
|
||||
Title: "Understanding Golang",
|
||||
Content: "Go is an open-source programming language...",
|
||||
PublishDate: time.Now().AddDate(0, -1, 0),
|
||||
Author: authorJohn,
|
||||
Comments: generateComments(3),
|
||||
Tags: []string{"Go", "Programming"},
|
||||
Likes: 100,
|
||||
},
|
||||
{
|
||||
ID: 2,
|
||||
Title: "Exploring Python",
|
||||
Content: "Python is versatile...",
|
||||
PublishDate: time.Now().AddDate(0, -2, 0),
|
||||
Author: authorJane,
|
||||
Comments: generateComments(4),
|
||||
Tags: []string{"Python", "Development"},
|
||||
Likes: 150,
|
||||
},
|
||||
{
|
||||
ID: 3,
|
||||
Title: "Web Development Basics",
|
||||
Content: "The world of web development...",
|
||||
PublishDate: time.Now().AddDate(0, -3, 0),
|
||||
Author: authorJane,
|
||||
Comments: generateComments(5),
|
||||
Tags: []string{"Web", "HTML", "CSS"},
|
||||
Likes: 125,
|
||||
},
|
||||
{
|
||||
ID: 4,
|
||||
Title: "Machine Learning in a Nutshell",
|
||||
Content: "ML is revolutionizing industries...",
|
||||
PublishDate: time.Now().AddDate(0, -5, 0),
|
||||
Author: authorJohn,
|
||||
Comments: generateComments(6),
|
||||
Tags: []string{"ML", "AI"},
|
||||
Likes: 200,
|
||||
},
|
||||
{
|
||||
ID: 5,
|
||||
Title: "JavaScript: The Good Parts",
|
||||
Content: "JavaScript powers the web...",
|
||||
PublishDate: time.Now().AddDate(0, -4, 0),
|
||||
Author: authorJane,
|
||||
Comments: generateComments(3),
|
||||
Tags: []string{"JavaScript", "Web"},
|
||||
Likes: 170,
|
||||
},
|
||||
}
|
||||
|
||||
blog := Blog{
|
||||
Posts: make([]Post, len(posts)),
|
||||
Authors: map[int]Author{authorJohn.ID: authorJohn, authorJane.ID: authorJane},
|
||||
TotalViews: 10000,
|
||||
TotalPosts: len(posts),
|
||||
TotalLikes: 0,
|
||||
}
|
||||
|
||||
for i, post := range posts {
|
||||
blog.Posts[i] = post
|
||||
blog.TotalLikes += post.Likes
|
||||
}
|
||||
|
||||
return blog
|
||||
}
|
||||
|
||||
func generateComments(count int) []Comment {
|
||||
var comments []Comment
|
||||
for i := 1; i <= count; i++ {
|
||||
comment := Comment{
|
||||
ID: i,
|
||||
AuthorName: fmt.Sprintf("Commenter %d", i),
|
||||
Content: fmt.Sprintf("This is comment %d!", i),
|
||||
CommentDate: time.Now().AddDate(0, 0, -i),
|
||||
Upvotes: i * 5,
|
||||
}
|
||||
comments = append(comments, comment)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
@@ -10,6 +10,10 @@ type UserProfile struct {
|
||||
Website string
|
||||
}
|
||||
|
||||
func (u UserProfile) Age() int {
|
||||
return time.Now().Year() - u.Birthday.Year()
|
||||
}
|
||||
|
||||
type Author struct {
|
||||
ID int
|
||||
FirstName string
|
||||
@@ -18,6 +22,14 @@ type Author struct {
|
||||
Profile UserProfile
|
||||
}
|
||||
|
||||
func (a Author) FullName() string {
|
||||
return a.FirstName + " " + a.LastName
|
||||
}
|
||||
|
||||
func (a Author) IsAdmin() bool {
|
||||
return a.ID == 1
|
||||
}
|
||||
|
||||
type Post struct {
|
||||
ID int
|
||||
Title string
|
||||
@@ -29,6 +41,35 @@ type Post struct {
|
||||
Likes int
|
||||
}
|
||||
|
||||
func (p Post) Published() bool {
|
||||
return !p.PublishDate.IsZero()
|
||||
}
|
||||
|
||||
func (p Post) After(date time.Time) bool {
|
||||
return p.PublishDate.After(date)
|
||||
}
|
||||
|
||||
func (p Post) Before(date time.Time) bool {
|
||||
return p.PublishDate.Before(date)
|
||||
}
|
||||
|
||||
func (p Post) Compare(other Post) int {
|
||||
if p.PublishDate.Before(other.PublishDate) {
|
||||
return -1
|
||||
} else if p.PublishDate.After(other.PublishDate) {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (p Post) Equal(other Post) bool {
|
||||
return p.Compare(other) == 0
|
||||
}
|
||||
|
||||
func (p Post) IsZero() bool {
|
||||
return p.ID == 0 && p.Title == "" && p.Content == "" && p.PublishDate.IsZero()
|
||||
}
|
||||
|
||||
type Comment struct {
|
||||
ID int
|
||||
AuthorName string
|
||||
@@ -37,6 +78,14 @@ type Comment struct {
|
||||
Upvotes int
|
||||
}
|
||||
|
||||
func (c Comment) Upvoted() bool {
|
||||
return c.Upvotes > 0
|
||||
}
|
||||
|
||||
func (c Comment) AuthorEmail() string {
|
||||
return c.AuthorName + "@example.com"
|
||||
}
|
||||
|
||||
type Blog struct {
|
||||
Posts []Post
|
||||
Authors map[int]Author
|
||||
@@ -44,3 +93,79 @@ type Blog struct {
|
||||
TotalPosts int
|
||||
TotalLikes int
|
||||
}
|
||||
|
||||
func (b Blog) RecentPosts() []Post {
|
||||
var posts []Post
|
||||
for _, post := range b.Posts {
|
||||
if post.Published() {
|
||||
posts = append(posts, post)
|
||||
}
|
||||
}
|
||||
return posts
|
||||
}
|
||||
|
||||
func (b Blog) PopularPosts() []Post {
|
||||
var posts []Post
|
||||
for _, post := range b.Posts {
|
||||
if post.Likes > 150 {
|
||||
posts = append(posts, post)
|
||||
}
|
||||
}
|
||||
return posts
|
||||
}
|
||||
|
||||
func (b Blog) TotalUpvotes() int {
|
||||
var upvotes int
|
||||
for _, post := range b.Posts {
|
||||
for _, comment := range post.Comments {
|
||||
upvotes += comment.Upvotes
|
||||
}
|
||||
}
|
||||
return upvotes
|
||||
}
|
||||
|
||||
func (b Blog) TotalComments() int {
|
||||
var comments int
|
||||
for _, post := range b.Posts {
|
||||
comments += len(post.Comments)
|
||||
}
|
||||
return comments
|
||||
}
|
||||
|
||||
func (Blog) Add(a, b float64) float64 {
|
||||
return a + b
|
||||
}
|
||||
|
||||
func (Blog) Sub(a, b float64) float64 {
|
||||
return a - b
|
||||
}
|
||||
|
||||
func (Blog) Title(post Post) string {
|
||||
return post.Title
|
||||
}
|
||||
|
||||
func (Blog) HasTag(post Post, tag string) bool {
|
||||
for _, t := range post.Tags {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (Blog) IsAdmin(author Author) bool {
|
||||
return author.IsAdmin()
|
||||
}
|
||||
|
||||
func (Blog) IsZero(post Post) bool {
|
||||
return post.IsZero()
|
||||
}
|
||||
|
||||
func (Blog) WithID(posts []Post, id int) Post {
|
||||
for _, post := range posts {
|
||||
if post.ID == id {
|
||||
return post
|
||||
}
|
||||
}
|
||||
return Post{}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,358 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
|
||||
"github.com/antonmedv/expr"
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/builtin"
|
||||
"github.com/antonmedv/expr/test/playground"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var code string
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Printf("==========================\n%s\n==========================\n", code)
|
||||
panic(r)
|
||||
}
|
||||
}()
|
||||
|
||||
corpus := map[string]struct{}{}
|
||||
for {
|
||||
code = node(weightedRandomInt([]intWeight{
|
||||
{3, 100},
|
||||
{4, 40},
|
||||
{5, 50},
|
||||
{6, 30},
|
||||
{7, 20},
|
||||
{8, 10},
|
||||
{9, 5},
|
||||
{10, 5},
|
||||
})).String()
|
||||
|
||||
program, err := expr.Compile(code, expr.Env(playground.Blog{}))
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
_, err = expr.Run(program, playground.ExampleData())
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if _, ok := corpus[code]; ok {
|
||||
continue
|
||||
}
|
||||
corpus[code] = struct{}{}
|
||||
fmt.Println(code)
|
||||
}
|
||||
}
|
||||
|
||||
func node(depth int) ast.Node {
|
||||
if depth <= 0 {
|
||||
return weightedRandom([]fnWeight{
|
||||
{nilNode, 1},
|
||||
{floatNode, 1},
|
||||
{integerNode, 1},
|
||||
{stringNode, 1},
|
||||
{booleanNode, 1},
|
||||
{identifierNode, 10},
|
||||
})(depth - 1)
|
||||
}
|
||||
return weightedRandom([]fnWeight{
|
||||
{arrayNode, 1},
|
||||
{mapNode, 1},
|
||||
{identifierNode, 1000},
|
||||
{memberNode, 1500},
|
||||
{unaryNode, 100},
|
||||
{binaryNode, 2000},
|
||||
{callNode, 2000},
|
||||
{builtinNode, 500},
|
||||
{predicateNode, 1000},
|
||||
{pointerNode, 500},
|
||||
{sliceNode, 100},
|
||||
{conditionalNode, 100},
|
||||
})(depth - 1)
|
||||
}
|
||||
|
||||
func nilNode(depth int) ast.Node {
|
||||
return &ast.NilNode{}
|
||||
}
|
||||
|
||||
func floatNode(depth int) ast.Node {
|
||||
cases := []float64{
|
||||
0.0,
|
||||
0.5,
|
||||
}
|
||||
return &ast.FloatNode{
|
||||
Value: cases[rand.Intn(len(cases))],
|
||||
}
|
||||
}
|
||||
|
||||
func integerNode(depth int) ast.Node {
|
||||
return &ast.IntegerNode{
|
||||
Value: rand.Intn(3),
|
||||
}
|
||||
}
|
||||
|
||||
func stringNode(depth int) ast.Node {
|
||||
corpus := []string{
|
||||
"Go", "JavaScript", " ",
|
||||
}
|
||||
return &ast.StringNode{
|
||||
Value: corpus[rand.Intn(len(corpus))],
|
||||
}
|
||||
}
|
||||
|
||||
func booleanNode(depth int) ast.Node {
|
||||
return &ast.BoolNode{
|
||||
Value: maybe(),
|
||||
}
|
||||
}
|
||||
|
||||
func identifierNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"Posts",
|
||||
"Authors",
|
||||
"TotalViews",
|
||||
}
|
||||
|
||||
return &ast.IdentifierNode{
|
||||
Value: cases[rand.Intn(len(cases))],
|
||||
}
|
||||
}
|
||||
|
||||
func memberNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"Birthday",
|
||||
"Biography",
|
||||
"Website",
|
||||
"ID",
|
||||
"FirstName",
|
||||
"LastName",
|
||||
"Email",
|
||||
"Profile",
|
||||
"ID",
|
||||
"Title",
|
||||
"Content",
|
||||
"PublishDate",
|
||||
"Author",
|
||||
"Comments",
|
||||
"Tags",
|
||||
"Likes",
|
||||
"ID",
|
||||
"AuthorName",
|
||||
"Content",
|
||||
"CommentDate",
|
||||
"Upvotes",
|
||||
}
|
||||
|
||||
return &ast.MemberNode{
|
||||
Node: node(depth - 1),
|
||||
Property: weightedRandom([]fnWeight{
|
||||
{func(_ int) ast.Node { return &ast.StringNode{Value: cases[rand.Intn(len(cases))]} }, 5},
|
||||
{node, 1},
|
||||
})(depth - 1),
|
||||
Optional: maybe(),
|
||||
}
|
||||
}
|
||||
|
||||
func unaryNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"-",
|
||||
"!",
|
||||
"not",
|
||||
}
|
||||
return &ast.UnaryNode{
|
||||
Operator: cases[rand.Intn(len(cases))],
|
||||
Node: node(depth - 1),
|
||||
}
|
||||
}
|
||||
|
||||
func binaryNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"or",
|
||||
"||",
|
||||
"and",
|
||||
"&&",
|
||||
"==",
|
||||
"!=",
|
||||
"<",
|
||||
">",
|
||||
">=",
|
||||
"<=",
|
||||
"in",
|
||||
"matches",
|
||||
"contains",
|
||||
"startsWith",
|
||||
"endsWith",
|
||||
"..",
|
||||
"+",
|
||||
"-",
|
||||
"*",
|
||||
"/",
|
||||
"%",
|
||||
"**",
|
||||
"^",
|
||||
}
|
||||
return &ast.BinaryNode{
|
||||
Operator: cases[rand.Intn(len(cases))],
|
||||
Left: node(depth - 1),
|
||||
Right: node(depth - 1),
|
||||
}
|
||||
}
|
||||
|
||||
func methodNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"Age",
|
||||
"FullName",
|
||||
"IsAdmin",
|
||||
"Published",
|
||||
"After",
|
||||
"Before",
|
||||
"Compare",
|
||||
"Equal",
|
||||
"IsZero",
|
||||
"Upvoted",
|
||||
"AuthorEmail",
|
||||
}
|
||||
|
||||
return &ast.MemberNode{
|
||||
Node: node(depth - 1),
|
||||
Property: &ast.StringNode{Value: cases[rand.Intn(len(cases))]},
|
||||
Optional: maybe(),
|
||||
}
|
||||
}
|
||||
|
||||
func funcNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"RecentPosts",
|
||||
"PopularPosts",
|
||||
"TotalUpvotes",
|
||||
"TotalComments",
|
||||
"Add",
|
||||
"Sub",
|
||||
"Title",
|
||||
"HasTag",
|
||||
"IsAdmin",
|
||||
"IsZero",
|
||||
"WithID",
|
||||
}
|
||||
|
||||
return &ast.IdentifierNode{
|
||||
Value: cases[rand.Intn(len(cases))],
|
||||
}
|
||||
}
|
||||
|
||||
func callNode(depth int) ast.Node {
|
||||
var args []ast.Node
|
||||
max := weightedRandomInt([]intWeight{
|
||||
{1, 100},
|
||||
{2, 50},
|
||||
{3, 25},
|
||||
{4, 10},
|
||||
{5, 5},
|
||||
})
|
||||
for i := 0; i < max; i++ {
|
||||
args = append(args, node(depth-1))
|
||||
}
|
||||
return &ast.CallNode{
|
||||
Callee: weightedRandom([]fnWeight{
|
||||
{methodNode, 2},
|
||||
{funcNode, 2},
|
||||
})(depth - 1),
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func builtinNode(depth int) ast.Node {
|
||||
var args []ast.Node
|
||||
max := weightedRandomInt([]intWeight{
|
||||
{1, 100},
|
||||
{2, 50},
|
||||
{3, 25},
|
||||
{4, 10},
|
||||
{5, 5},
|
||||
})
|
||||
for i := 0; i < max; i++ {
|
||||
args = append(args, node(depth-1))
|
||||
}
|
||||
return &ast.BuiltinNode{
|
||||
Name: builtin.Names[rand.Intn(len(builtin.Names))],
|
||||
Arguments: args,
|
||||
}
|
||||
}
|
||||
|
||||
func predicateNode(depth int) ast.Node {
|
||||
cases := []string{
|
||||
"all",
|
||||
"none",
|
||||
"any",
|
||||
"one",
|
||||
"filter",
|
||||
"map",
|
||||
"count",
|
||||
}
|
||||
return &ast.BuiltinNode{
|
||||
Name: cases[rand.Intn(len(cases))],
|
||||
Arguments: []ast.Node{node(depth - 1), node(depth - 1)},
|
||||
}
|
||||
}
|
||||
|
||||
func pointerNode(depth int) ast.Node {
|
||||
return &ast.PointerNode{}
|
||||
}
|
||||
|
||||
func arrayNode(depth int) ast.Node {
|
||||
var items []ast.Node
|
||||
max := weightedRandomInt([]intWeight{
|
||||
{1, 100},
|
||||
{2, 50},
|
||||
{3, 25},
|
||||
{4, 10},
|
||||
{5, 5},
|
||||
})
|
||||
for i := 0; i < max; i++ {
|
||||
items = append(items, node(depth-1))
|
||||
}
|
||||
return &ast.ArrayNode{
|
||||
Nodes: items,
|
||||
}
|
||||
}
|
||||
|
||||
func mapNode(depth int) ast.Node {
|
||||
var items []ast.Node
|
||||
max := weightedRandomInt([]intWeight{
|
||||
{1, 100},
|
||||
{2, 50},
|
||||
{3, 25},
|
||||
{4, 10},
|
||||
{5, 5},
|
||||
})
|
||||
for i := 0; i < max; i++ {
|
||||
items = append(items, &ast.PairNode{
|
||||
Key: stringNode(depth - 1),
|
||||
Value: node(depth - 1),
|
||||
})
|
||||
}
|
||||
return &ast.MapNode{
|
||||
Pairs: items,
|
||||
}
|
||||
}
|
||||
|
||||
func sliceNode(depth int) ast.Node {
|
||||
return &ast.SliceNode{
|
||||
Node: node(depth - 1),
|
||||
From: node(depth - 1),
|
||||
To: node(depth - 1),
|
||||
}
|
||||
}
|
||||
|
||||
func conditionalNode(depth int) ast.Node {
|
||||
return &ast.ConditionalNode{
|
||||
Cond: node(depth - 1),
|
||||
Exp1: node(depth - 1),
|
||||
Exp2: node(depth - 1),
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
)
|
||||
|
||||
func maybe() bool {
|
||||
return rand.Intn(2) == 0
|
||||
}
|
||||
|
||||
type fn func(int) ast.Node
|
||||
|
||||
type fnWeight struct {
|
||||
value fn
|
||||
weight int
|
||||
}
|
||||
|
||||
func weightedRandom(cases []fnWeight) fn {
|
||||
totalWeight := 0
|
||||
for _, c := range cases {
|
||||
totalWeight += c.weight
|
||||
}
|
||||
r := rand.Intn(totalWeight)
|
||||
for _, c := range cases {
|
||||
if r < c.weight {
|
||||
return c.value
|
||||
}
|
||||
r -= c.weight
|
||||
}
|
||||
return cases[0].value
|
||||
}
|
||||
|
||||
type intWeight struct {
|
||||
value int
|
||||
weight int
|
||||
}
|
||||
|
||||
func weightedRandomInt(cases []intWeight) int {
|
||||
totalWeight := 0
|
||||
for _, c := range cases {
|
||||
totalWeight += c.weight
|
||||
}
|
||||
r := rand.Intn(totalWeight)
|
||||
for _, c := range cases {
|
||||
if r < c.weight {
|
||||
return c.value
|
||||
}
|
||||
r -= c.weight
|
||||
}
|
||||
return cases[0].value
|
||||
}
|
||||
Reference in New Issue
Block a user