chore: upgrade coredns version (#550)

This commit is contained in:
naison
2025-04-19 10:06:56 +08:00
committed by GitHub
parent c42e3475f9
commit c9f1ce6522
1701 changed files with 235209 additions and 29271 deletions

1
vendor/github.com/expr-lang/expr/.gitattributes generated vendored Normal file
View File

@@ -0,0 +1 @@
*\[generated\].go linguist-language=txt

11
vendor/github.com/expr-lang/expr/.gitignore generated vendored Normal file
View File

@@ -0,0 +1,11 @@
*.exe
*.exe~
*.dll
*.so
*.dylib
*.test
*.out
*.html
custom_tests.json
pro/
test/avs/

21
vendor/github.com/expr-lang/expr/LICENSE generated vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 Anton Medvedev
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

181
vendor/github.com/expr-lang/expr/README.md generated vendored Normal file
View File

@@ -0,0 +1,181 @@
<h1><a href="https://expr-lang.org"><img src="https://expr-lang.org/img/logo.png" alt="Zx logo" height="48"align="right"></a> Expr</h1>
> [!IMPORTANT]
> The repository [github.com/antonmedv/expr](https://github.com/antonmedv/expr) moved to [github.com/**expr-lang**/expr](https://github.com/expr-lang/expr).
[![test](https://github.com/expr-lang/expr/actions/workflows/test.yml/badge.svg)](https://github.com/expr-lang/expr/actions/workflows/test.yml)
[![Go Report Card](https://goreportcard.com/badge/github.com/expr-lang/expr)](https://goreportcard.com/report/github.com/expr-lang/expr)
[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/expr.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:expr)
[![GoDoc](https://godoc.org/github.com/expr-lang/expr?status.svg)](https://godoc.org/github.com/expr-lang/expr)
**Expr** is a Go-centric expression language designed to deliver dynamic configurations with unparalleled accuracy, safety, and speed.
**Expr** combines simple [syntax](https://expr-lang.org/docs/language-definition) with powerful features for ease of use:
```js
// Allow only admins and moderators to moderate comments.
user.Group in ["admin", "moderator"] || user.Id == comment.UserId
```
```js
// Determine whether the request is in the permitted time window.
request.Time - resource.Age < duration("24h")
```
```js
// Ensure all tweets are less than 240 characters.
all(tweets, len(.Content) <= 240)
```
## Features
**Expr** is a safe, fast, and intuitive expression evaluator optimized for the Go language.
Here are its standout features:
### Safety and Isolation
* **Memory-Safe**: Expr is designed with a focus on safety, ensuring that programs do not access unrelated memory or introduce memory vulnerabilities.
* **Side-Effect-Free**: Expressions evaluated in Expr only compute outputs from their inputs, ensuring no side-effects that can change state or produce unintended results.
* **Always Terminating**: Expr is designed to prevent infinite loops, ensuring that every program will conclude in a reasonable amount of time.
### Go Integration
* **Seamless with Go**: Integrate Expr into your Go projects without the need to redefine types.
### Static Typing
* Ensures type correctness and prevents runtime type errors.
```go
out, err := expr.Compile(`name + age`)
// err: invalid operation + (mismatched types string and int)
// | name + age
// | .....^
```
### User-Friendly
* Provides user-friendly error messages to assist with debugging and development.
### Flexibility and Utility
* **Rich Operators**: Offers a reasonable set of basic operators for a variety of applications.
* **Built-in Functions**: Functions like `all`, `none`, `any`, `one`, `filter`, and `map` are provided out-of-the-box.
### Performance
* **Optimized for Speed**: Expr stands out in its performance, utilizing an optimizing compiler and a bytecode virtual machine. Check out these [benchmarks](https://github.com/antonmedv/golang-expression-evaluation-comparison#readme) for more details.
## Install
```
go get github.com/expr-lang/expr
```
## Documentation
* See [Getting Started](https://expr-lang.org/docs/Getting-Started) page for developer documentation.
* See [Language Definition](https://expr-lang.org/docs/language-definition) page to learn the syntax.
## Examples
[Play Online](https://go.dev/play/p/XCoNXEjm3TS)
```go
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
func main() {
env := map[string]interface{}{
"greet": "Hello, %v!",
"names": []string{"world", "you"},
"sprintf": fmt.Sprintf,
}
code := `sprintf(greet, names[0])`
program, err := expr.Compile(code, expr.Env(env))
if err != nil {
panic(err)
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output)
}
```
[Play Online](https://go.dev/play/p/tz-ZneBfSuw)
```go
package main
import (
"fmt"
"github.com/expr-lang/expr"
)
type Tweet struct {
Len int
}
type Env struct {
Tweets []Tweet
}
func main() {
code := `all(Tweets, {.Len <= 240})`
program, err := expr.Compile(code, expr.Env(Env{}))
if err != nil {
panic(err)
}
env := Env{
Tweets: []Tweet{{42}, {98}, {69}},
}
output, err := expr.Run(program, env)
if err != nil {
panic(err)
}
fmt.Println(output)
}
```
## Who uses Expr?
* [Google](https://google.com) uses Expr as one of its expression languages on the [Google Cloud Platform](https://cloud.google.com).
* [Uber](https://uber.com) uses Expr to allow customization of its Uber Eats marketplace.
* [GoDaddy](https://godaddy.com) employs Expr for the customization of its GoDaddy Pro product.
* [ByteDance](https://bytedance.com) incorporates Expr into its internal business rule engine.
* [Aviasales](https://aviasales.ru) utilizes Expr as a business rule engine for its flight search engine.
* [Wish.com](https://www.wish.com) employs Expr in its decision-making rule engine for the Wish Assistant.
* [Argo](https://argoproj.github.io) integrates Expr into Argo Rollouts and Argo Workflows for Kubernetes.
* [OpenTelemetry](https://opentelemetry.io) integrates Expr into the OpenTelemetry Collector.
* [Philips Labs](https://github.com/philips-labs/tabia) employs Expr in Tabia, a tool designed to collect insights on their code bases.
* [CrowdSec](https://crowdsec.net) incorporates Expr into its security automation tool.
* [CoreDNS](https://coredns.io) uses Expr in CoreDNS, which is a DNS server.
* [qiniu](https://www.qiniu.com) implements Expr in its trade systems.
* [Junglee Games](https://www.jungleegames.com/) uses Expr for its in-house marketing retention tool, Project Audience.
* [Faceit](https://www.faceit.com) uses Expr to enhance customization of its eSports matchmaking algorithm.
* [Chaos Mesh](https://chaos-mesh.org) incorporates Expr into Chaos Mesh, a cloud-native Chaos Engineering platform.
* [Visually.io](https://visually.io) employs Expr as a business rule engine for its personalization targeting algorithm.
* [Akvorado](https://github.com/akvorado/akvorado) utilizes Expr to classify exporters and interfaces in network flows.
* [keda.sh](https://keda.sh) uses Expr to allow customization of its Kubernetes-based event-driven autoscaling.
* [Span Digital](https://spandigital.com/) uses Expr in its Knowledge Management products.
* [Xiaohongshu](https://www.xiaohongshu.com/) combining yaml with Expr for dynamically policies delivery.
* [Melrōse](https://melrōse.org) uses Expr to implement its music programming language.
* [Tork](https://www.tork.run/) integrates Expr into its workflow execution.
* [Critical Moments](https://criticalmoments.io) uses Expr for its mobile realtime conditional targeting system.
* [WoodpeckerCI](https://woodpecker-ci.org) uses Expr for [filtering workflows/steps](https://woodpecker-ci.org/docs/usage/workflow-syntax#evaluate).
* [FastSchema](https://github.com/fastschema/fastschema) - A BaaS leveraging Expr for its customizable and dynamic Access Control system.
* [WunderGraph Cosmo](https://github.com/wundergraph/cosmo) - GraphQL Federeration Router uses Expr to customize Middleware behaviour
* [SOLO](https://solo.one) uses Expr interally to allow dynamic code execution with custom defined functions.
[Add your company too](https://github.com/expr-lang/expr/edit/master/README.md)
## License
[MIT](https://github.com/expr-lang/expr/blob/master/LICENSE)
<p align="center"><img src="https://expr-lang.org/img/gopher-small.png" width="150" /></p>

22
vendor/github.com/expr-lang/expr/SECURITY.md generated vendored Normal file
View File

@@ -0,0 +1,22 @@
# Security Policy
## Supported Versions
Expr is generally backwards compatible with very few exceptions, so we
recommend users to always use the latest version to experience stability,
performance and security.
We generally backport security issues to a single previous minor version,
unless this is not possible or feasible with a reasonable effort.
| Version | Supported |
|---------|--------------------|
| 1.x | :white_check_mark: |
| 0.x | :x: |
## Reporting a Vulnerability
If you believe you've discovered a serious vulnerability, please contact the
Expr core team at anton+security@medv.io. We will evaluate your report and if
necessary issue a fix and an advisory. If the issue was previously undisclosed,
we'll also mention your name in the credits.

59
vendor/github.com/expr-lang/expr/ast/dump.go generated vendored Normal file
View File

@@ -0,0 +1,59 @@
package ast
import (
"fmt"
"reflect"
"regexp"
)
func Dump(node Node) string {
return dump(reflect.ValueOf(node), "")
}
func dump(v reflect.Value, ident string) string {
if !v.IsValid() {
return "nil"
}
t := v.Type()
switch t.Kind() {
case reflect.Struct:
out := t.Name() + "{\n"
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if isPrivate(f.Name) {
continue
}
s := v.Field(i)
out += fmt.Sprintf("%v%v: %v,\n", ident+"\t", f.Name, dump(s, ident+"\t"))
}
return out + ident + "}"
case reflect.Slice:
if v.Len() == 0 {
return t.String() + "{}"
}
out := t.String() + "{\n"
for i := 0; i < v.Len(); i++ {
s := v.Index(i)
out += fmt.Sprintf("%v%v,", ident+"\t", dump(s, ident+"\t"))
if i+1 < v.Len() {
out += "\n"
}
}
return out + "\n" + ident + "}"
case reflect.Ptr:
return dump(v.Elem(), ident)
case reflect.Interface:
return dump(reflect.ValueOf(v.Interface()), ident)
case reflect.String:
return fmt.Sprintf("%q", v)
default:
return fmt.Sprintf("%v", v)
}
}
var isCapital = regexp.MustCompile("^[A-Z]")
func isPrivate(s string) bool {
return !isCapital.Match([]byte(s))
}

18
vendor/github.com/expr-lang/expr/ast/find.go generated vendored Normal file
View File

@@ -0,0 +1,18 @@
package ast
func Find(node Node, fn func(node Node) bool) Node {
v := &finder{fn: fn}
Walk(&node, v)
return v.node
}
type finder struct {
node Node
fn func(node Node) bool
}
func (f *finder) Visit(node *Node) {
if f.fn(*node) {
f.node = *node
}
}

243
vendor/github.com/expr-lang/expr/ast/node.go generated vendored Normal file
View File

@@ -0,0 +1,243 @@
package ast
import (
"reflect"
"github.com/expr-lang/expr/checker/nature"
"github.com/expr-lang/expr/file"
)
var (
anyType = reflect.TypeOf(new(any)).Elem()
)
// Node represents items of abstract syntax tree.
type Node interface {
Location() file.Location
SetLocation(file.Location)
Nature() nature.Nature
SetNature(nature.Nature)
Type() reflect.Type
SetType(reflect.Type)
String() string
}
// Patch replaces the node with a new one.
// Location information is preserved.
// Type information is lost.
func Patch(node *Node, newNode Node) {
newNode.SetLocation((*node).Location())
*node = newNode
}
// base is a base struct for all nodes.
type base struct {
loc file.Location
nature nature.Nature
}
// Location returns the location of the node in the source code.
func (n *base) Location() file.Location {
return n.loc
}
// SetLocation sets the location of the node in the source code.
func (n *base) SetLocation(loc file.Location) {
n.loc = loc
}
// Nature returns the nature of the node.
func (n *base) Nature() nature.Nature {
return n.nature
}
// SetNature sets the nature of the node.
func (n *base) SetNature(nature nature.Nature) {
n.nature = nature
}
// Type returns the type of the node.
func (n *base) Type() reflect.Type {
if n.nature.Type == nil {
return anyType
}
return n.nature.Type
}
// SetType sets the type of the node.
func (n *base) SetType(t reflect.Type) {
n.nature.Type = t
}
// NilNode represents nil.
type NilNode struct {
base
}
// IdentifierNode represents an identifier.
type IdentifierNode struct {
base
Value string // Name of the identifier. Like "foo" in "foo.bar".
}
// IntegerNode represents an integer.
type IntegerNode struct {
base
Value int // Value of the integer.
}
// FloatNode represents a float.
type FloatNode struct {
base
Value float64 // Value of the float.
}
// BoolNode represents a boolean.
type BoolNode struct {
base
Value bool // Value of the boolean.
}
// StringNode represents a string.
type StringNode struct {
base
Value string // Value of the string.
}
// ConstantNode represents a constant.
// Constants are predefined values like nil, true, false, array, map, etc.
// The parser.Parse will never generate ConstantNode, it is only generated
// by the optimizer.
type ConstantNode struct {
base
Value any // Value of the constant.
}
// UnaryNode represents a unary operator.
type UnaryNode struct {
base
Operator string // Operator of the unary operator. Like "!" in "!foo" or "not" in "not foo".
Node Node // Node of the unary operator. Like "foo" in "!foo".
}
// BinaryNode represents a binary operator.
type BinaryNode struct {
base
Operator string // Operator of the binary operator. Like "+" in "foo + bar" or "matches" in "foo matches bar".
Left Node // Left node of the binary operator.
Right Node // Right node of the binary operator.
}
// ChainNode represents an optional chaining group.
// A few MemberNode nodes can be chained together,
// and will be wrapped in a ChainNode. Example:
//
// foo.bar?.baz?.qux
//
// The whole chain will be wrapped in a ChainNode.
type ChainNode struct {
base
Node Node // Node of the chain.
}
// MemberNode represents a member access.
// It can be a field access, a method call,
// or an array element access.
// Example:
//
// foo.bar or foo["bar"]
// foo.bar()
// array[0]
type MemberNode struct {
base
Node Node // Node of the member access. Like "foo" in "foo.bar".
Property Node // Property of the member access. For property access it is a StringNode.
Optional bool // If true then the member access is optional. Like "foo?.bar".
Method bool
}
// SliceNode represents access to a slice of an array.
// Example:
//
// array[1:4]
type SliceNode struct {
base
Node Node // Node of the slice. Like "array" in "array[1:4]".
From Node // From an index of the array. Like "1" in "array[1:4]".
To Node // To an index of the array. Like "4" in "array[1:4]".
}
// CallNode represents a function or a method call.
type CallNode struct {
base
Callee Node // Node of the call. Like "foo" in "foo()".
Arguments []Node // Arguments of the call.
}
// BuiltinNode represents a builtin function call.
type BuiltinNode struct {
base
Name string // Name of the builtin function. Like "len" in "len(foo)".
Arguments []Node // Arguments of the builtin function.
Throws bool // If true then accessing a field or array index can throw an error. Used by optimizer.
Map Node // Used by optimizer to fold filter() and map() builtins.
}
// PredicateNode represents a predicate.
// Example:
//
// filter(foo, .bar == 1)
//
// The predicate is ".bar == 1".
type PredicateNode struct {
base
Node Node // Node of the predicate body.
}
// PointerNode represents a pointer to a current value in predicate.
type PointerNode struct {
base
Name string // Name of the pointer. Like "index" in "#index".
}
// ConditionalNode represents a ternary operator.
type ConditionalNode struct {
base
Cond Node // Condition of the ternary operator. Like "foo" in "foo ? bar : baz".
Exp1 Node // Expression 1 of the ternary operator. Like "bar" in "foo ? bar : baz".
Exp2 Node // Expression 2 of the ternary operator. Like "baz" in "foo ? bar : baz".
}
// VariableDeclaratorNode represents a variable declaration.
type VariableDeclaratorNode struct {
base
Name string // Name of the variable. Like "foo" in "let foo = 1; foo + 1".
Value Node // Value of the variable. Like "1" in "let foo = 1; foo + 1".
Expr Node // Expression of the variable. Like "foo + 1" in "let foo = 1; foo + 1".
}
// SequenceNode represents a sequence of nodes separated by semicolons.
// All nodes are executed, only the last node will be returned.
type SequenceNode struct {
base
Nodes []Node
}
// ArrayNode represents an array.
type ArrayNode struct {
base
Nodes []Node // Nodes of the array.
}
// MapNode represents a map.
type MapNode struct {
base
Pairs []Node // PairNode nodes.
}
// PairNode represents a key-value pair of a map.
type PairNode struct {
base
Key Node // Key of the pair.
Value Node // Value of the pair.
}

253
vendor/github.com/expr-lang/expr/ast/print.go generated vendored Normal file
View File

@@ -0,0 +1,253 @@
package ast
import (
"encoding/json"
"fmt"
"strings"
"github.com/expr-lang/expr/parser/operator"
"github.com/expr-lang/expr/parser/utils"
)
func (n *NilNode) String() string {
return "nil"
}
func (n *IdentifierNode) String() string {
return n.Value
}
func (n *IntegerNode) String() string {
return fmt.Sprintf("%d", n.Value)
}
func (n *FloatNode) String() string {
return fmt.Sprintf("%v", n.Value)
}
func (n *BoolNode) String() string {
return fmt.Sprintf("%t", n.Value)
}
func (n *StringNode) String() string {
return fmt.Sprintf("%q", n.Value)
}
func (n *ConstantNode) String() string {
if n.Value == nil {
return "nil"
}
b, err := json.Marshal(n.Value)
if err != nil {
panic(err)
}
return string(b)
}
func (n *UnaryNode) String() string {
op := n.Operator
if n.Operator == "not" {
op = fmt.Sprintf("%s ", n.Operator)
}
wrap := false
switch b := n.Node.(type) {
case *BinaryNode:
if operator.Binary[b.Operator].Precedence <
operator.Unary[n.Operator].Precedence {
wrap = true
}
case *ConditionalNode:
wrap = true
}
if wrap {
return fmt.Sprintf("%s(%s)", op, n.Node.String())
}
return fmt.Sprintf("%s%s", op, n.Node.String())
}
func (n *BinaryNode) String() string {
if n.Operator == ".." {
return fmt.Sprintf("%s..%s", n.Left, n.Right)
}
var lhs, rhs string
var lwrap, rwrap bool
if l, ok := n.Left.(*UnaryNode); ok {
if operator.Unary[l.Operator].Precedence <
operator.Binary[n.Operator].Precedence {
lwrap = true
}
}
if lb, ok := n.Left.(*BinaryNode); ok {
if operator.Less(lb.Operator, n.Operator) {
lwrap = true
}
if operator.Binary[lb.Operator].Precedence ==
operator.Binary[n.Operator].Precedence &&
operator.Binary[n.Operator].Associativity == operator.Right {
lwrap = true
}
if lb.Operator == "??" {
lwrap = true
}
if operator.IsBoolean(lb.Operator) && n.Operator != lb.Operator {
lwrap = true
}
}
if rb, ok := n.Right.(*BinaryNode); ok {
if operator.Less(rb.Operator, n.Operator) {
rwrap = true
}
if operator.Binary[rb.Operator].Precedence ==
operator.Binary[n.Operator].Precedence &&
operator.Binary[n.Operator].Associativity == operator.Left {
rwrap = true
}
if operator.IsBoolean(rb.Operator) && n.Operator != rb.Operator {
rwrap = true
}
}
if _, ok := n.Left.(*ConditionalNode); ok {
lwrap = true
}
if _, ok := n.Right.(*ConditionalNode); ok {
rwrap = true
}
if lwrap {
lhs = fmt.Sprintf("(%s)", n.Left.String())
} else {
lhs = n.Left.String()
}
if rwrap {
rhs = fmt.Sprintf("(%s)", n.Right.String())
} else {
rhs = n.Right.String()
}
return fmt.Sprintf("%s %s %s", lhs, n.Operator, rhs)
}
func (n *ChainNode) String() string {
return n.Node.String()
}
func (n *MemberNode) String() string {
node := n.Node.String()
if _, ok := n.Node.(*BinaryNode); ok {
node = fmt.Sprintf("(%s)", node)
}
if n.Optional {
if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) {
return fmt.Sprintf("%s?.%s", node, str.Value)
} else {
return fmt.Sprintf("%s?.[%s]", node, n.Property.String())
}
}
if str, ok := n.Property.(*StringNode); ok && utils.IsValidIdentifier(str.Value) {
if _, ok := n.Node.(*PointerNode); ok {
return fmt.Sprintf(".%s", str.Value)
}
return fmt.Sprintf("%s.%s", node, str.Value)
}
return fmt.Sprintf("%s[%s]", node, n.Property.String())
}
func (n *SliceNode) String() string {
if n.From == nil && n.To == nil {
return fmt.Sprintf("%s[:]", n.Node.String())
}
if n.From == nil {
return fmt.Sprintf("%s[:%s]", n.Node.String(), n.To.String())
}
if n.To == nil {
return fmt.Sprintf("%s[%s:]", n.Node.String(), n.From.String())
}
return fmt.Sprintf("%s[%s:%s]", n.Node.String(), n.From.String(), n.To.String())
}
func (n *CallNode) String() string {
arguments := make([]string, len(n.Arguments))
for i, arg := range n.Arguments {
arguments[i] = arg.String()
}
return fmt.Sprintf("%s(%s)", n.Callee.String(), strings.Join(arguments, ", "))
}
func (n *BuiltinNode) String() string {
arguments := make([]string, len(n.Arguments))
for i, arg := range n.Arguments {
arguments[i] = arg.String()
}
return fmt.Sprintf("%s(%s)", n.Name, strings.Join(arguments, ", "))
}
func (n *PredicateNode) String() string {
return n.Node.String()
}
func (n *PointerNode) String() string {
return fmt.Sprintf("#%s", n.Name)
}
func (n *VariableDeclaratorNode) String() string {
return fmt.Sprintf("let %s = %s; %s", n.Name, n.Value.String(), n.Expr.String())
}
func (n *SequenceNode) String() string {
nodes := make([]string, len(n.Nodes))
for i, node := range n.Nodes {
nodes[i] = node.String()
}
return strings.Join(nodes, "; ")
}
func (n *ConditionalNode) String() string {
var cond, exp1, exp2 string
if _, ok := n.Cond.(*ConditionalNode); ok {
cond = fmt.Sprintf("(%s)", n.Cond.String())
} else {
cond = n.Cond.String()
}
if _, ok := n.Exp1.(*ConditionalNode); ok {
exp1 = fmt.Sprintf("(%s)", n.Exp1.String())
} else {
exp1 = n.Exp1.String()
}
if _, ok := n.Exp2.(*ConditionalNode); ok {
exp2 = fmt.Sprintf("(%s)", n.Exp2.String())
} else {
exp2 = n.Exp2.String()
}
return fmt.Sprintf("%s ? %s : %s", cond, exp1, exp2)
}
func (n *ArrayNode) String() string {
nodes := make([]string, len(n.Nodes))
for i, node := range n.Nodes {
nodes[i] = node.String()
}
return fmt.Sprintf("[%s]", strings.Join(nodes, ", "))
}
func (n *MapNode) String() string {
pairs := make([]string, len(n.Pairs))
for i, pair := range n.Pairs {
pairs[i] = pair.String()
}
return fmt.Sprintf("{%s}", strings.Join(pairs, ", "))
}
func (n *PairNode) String() string {
if str, ok := n.Key.(*StringNode); ok {
if utils.IsValidIdentifier(str.Value) {
return fmt.Sprintf("%s: %s", str.Value, n.Value.String())
}
return fmt.Sprintf("%s: %s", str.String(), n.Value.String())
}
return fmt.Sprintf("(%s): %s", n.Key.String(), n.Value.String())
}

78
vendor/github.com/expr-lang/expr/ast/visitor.go generated vendored Normal file
View File

@@ -0,0 +1,78 @@
package ast
import "fmt"
type Visitor interface {
Visit(node *Node)
}
func Walk(node *Node, v Visitor) {
if *node == nil {
return
}
switch n := (*node).(type) {
case *NilNode:
case *IdentifierNode:
case *IntegerNode:
case *FloatNode:
case *BoolNode:
case *StringNode:
case *ConstantNode:
case *UnaryNode:
Walk(&n.Node, v)
case *BinaryNode:
Walk(&n.Left, v)
Walk(&n.Right, v)
case *ChainNode:
Walk(&n.Node, v)
case *MemberNode:
Walk(&n.Node, v)
Walk(&n.Property, v)
case *SliceNode:
Walk(&n.Node, v)
if n.From != nil {
Walk(&n.From, v)
}
if n.To != nil {
Walk(&n.To, v)
}
case *CallNode:
Walk(&n.Callee, v)
for i := range n.Arguments {
Walk(&n.Arguments[i], v)
}
case *BuiltinNode:
for i := range n.Arguments {
Walk(&n.Arguments[i], v)
}
case *PredicateNode:
Walk(&n.Node, v)
case *PointerNode:
case *VariableDeclaratorNode:
Walk(&n.Value, v)
Walk(&n.Expr, v)
case *SequenceNode:
for i := range n.Nodes {
Walk(&n.Nodes[i], v)
}
case *ConditionalNode:
Walk(&n.Cond, v)
Walk(&n.Exp1, v)
Walk(&n.Exp2, v)
case *ArrayNode:
for i := range n.Nodes {
Walk(&n.Nodes[i], v)
}
case *MapNode:
for i := range n.Pairs {
Walk(&n.Pairs[i], v)
}
case *PairNode:
Walk(&n.Key, v)
Walk(&n.Value, v)
default:
panic(fmt.Sprintf("undefined node type (%T)", node))
}
v.Visit(node)
}

1069
vendor/github.com/expr-lang/expr/builtin/builtin.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

23
vendor/github.com/expr-lang/expr/builtin/function.go generated vendored Normal file
View File

@@ -0,0 +1,23 @@
package builtin
import (
"reflect"
)
type Function struct {
Name string
Fast func(arg any) any
Func func(args ...any) (any, error)
Safe func(args ...any) (any, uint, error)
Types []reflect.Type
Validate func(args []reflect.Type) (reflect.Type, error)
Deref func(i int, arg reflect.Type) bool
Predicate bool
}
func (f *Function) Type() reflect.Type {
if len(f.Types) > 0 {
return f.Types[0]
}
return reflect.TypeOf(f.Func)
}

433
vendor/github.com/expr-lang/expr/builtin/lib.go generated vendored Normal file
View File

@@ -0,0 +1,433 @@
package builtin
import (
"fmt"
"math"
"reflect"
"strconv"
"unicode/utf8"
"github.com/expr-lang/expr/internal/deref"
"github.com/expr-lang/expr/vm/runtime"
)
func Len(x any) any {
v := reflect.ValueOf(x)
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Map:
return v.Len()
case reflect.String:
return utf8.RuneCountInString(v.String())
default:
panic(fmt.Sprintf("invalid argument for len (type %T)", x))
}
}
func Type(arg any) any {
if arg == nil {
return "nil"
}
v := reflect.ValueOf(arg)
if v.Type().Name() != "" && v.Type().PkgPath() != "" {
return fmt.Sprintf("%s.%s", v.Type().PkgPath(), v.Type().Name())
}
switch v.Type().Kind() {
case reflect.Invalid:
return "invalid"
case reflect.Bool:
return "bool"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
return "int"
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "uint"
case reflect.Float32, reflect.Float64:
return "float"
case reflect.String:
return "string"
case reflect.Array, reflect.Slice:
return "array"
case reflect.Map:
return "map"
case reflect.Func:
return "func"
case reflect.Struct:
return "struct"
default:
return "unknown"
}
}
func Abs(x any) any {
switch x := x.(type) {
case float32:
if x < 0 {
return -x
} else {
return x
}
case float64:
if x < 0 {
return -x
} else {
return x
}
case int:
if x < 0 {
return -x
} else {
return x
}
case int8:
if x < 0 {
return -x
} else {
return x
}
case int16:
if x < 0 {
return -x
} else {
return x
}
case int32:
if x < 0 {
return -x
} else {
return x
}
case int64:
if x < 0 {
return -x
} else {
return x
}
case uint:
if x < 0 {
return -x
} else {
return x
}
case uint8:
if x < 0 {
return -x
} else {
return x
}
case uint16:
if x < 0 {
return -x
} else {
return x
}
case uint32:
if x < 0 {
return -x
} else {
return x
}
case uint64:
if x < 0 {
return -x
} else {
return x
}
}
panic(fmt.Sprintf("invalid argument for abs (type %T)", x))
}
func Ceil(x any) any {
switch x := x.(type) {
case float32:
return math.Ceil(float64(x))
case float64:
return math.Ceil(x)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return Float(x)
}
panic(fmt.Sprintf("invalid argument for ceil (type %T)", x))
}
func Floor(x any) any {
switch x := x.(type) {
case float32:
return math.Floor(float64(x))
case float64:
return math.Floor(x)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return Float(x)
}
panic(fmt.Sprintf("invalid argument for floor (type %T)", x))
}
func Round(x any) any {
switch x := x.(type) {
case float32:
return math.Round(float64(x))
case float64:
return math.Round(x)
case int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64:
return Float(x)
}
panic(fmt.Sprintf("invalid argument for round (type %T)", x))
}
func Int(x any) any {
switch x := x.(type) {
case float32:
return int(x)
case float64:
return int(x)
case int:
return x
case int8:
return int(x)
case int16:
return int(x)
case int32:
return int(x)
case int64:
return int(x)
case uint:
return int(x)
case uint8:
return int(x)
case uint16:
return int(x)
case uint32:
return int(x)
case uint64:
return int(x)
case string:
i, err := strconv.Atoi(x)
if err != nil {
panic(fmt.Sprintf("invalid operation: int(%s)", x))
}
return i
default:
val := reflect.ValueOf(x)
if val.CanConvert(integerType) {
return val.Convert(integerType).Interface()
}
panic(fmt.Sprintf("invalid operation: int(%T)", x))
}
}
func Float(x any) any {
switch x := x.(type) {
case float32:
return float64(x)
case float64:
return x
case int:
return float64(x)
case int8:
return float64(x)
case int16:
return float64(x)
case int32:
return float64(x)
case int64:
return float64(x)
case uint:
return float64(x)
case uint8:
return float64(x)
case uint16:
return float64(x)
case uint32:
return float64(x)
case uint64:
return float64(x)
case string:
f, err := strconv.ParseFloat(x, 64)
if err != nil {
panic(fmt.Sprintf("invalid operation: float(%s)", x))
}
return f
default:
panic(fmt.Sprintf("invalid operation: float(%T)", x))
}
}
func String(arg any) any {
return fmt.Sprintf("%v", arg)
}
func minMax(name string, fn func(any, any) bool, args ...any) (any, error) {
var val any
for _, arg := range args {
rv := reflect.ValueOf(arg)
switch rv.Kind() {
case reflect.Array, reflect.Slice:
size := rv.Len()
for i := 0; i < size; i++ {
elemVal, err := minMax(name, fn, rv.Index(i).Interface())
if err != nil {
return nil, err
}
switch elemVal.(type) {
case int, int8, int16, int32, int64,
uint, uint8, uint16, uint32, uint64,
float32, float64:
if elemVal != nil && (val == nil || fn(val, elemVal)) {
val = elemVal
}
default:
return nil, fmt.Errorf("invalid argument for %s (type %T)", name, elemVal)
}
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64,
reflect.Float32, reflect.Float64:
elemVal := rv.Interface()
if val == nil || fn(val, elemVal) {
val = elemVal
}
default:
if len(args) == 1 {
return args[0], nil
}
return nil, fmt.Errorf("invalid argument for %s (type %T)", name, arg)
}
}
return val, nil
}
func mean(args ...any) (int, float64, error) {
var total float64
var count int
for _, arg := range args {
rv := reflect.ValueOf(arg)
switch rv.Kind() {
case reflect.Array, reflect.Slice:
size := rv.Len()
for i := 0; i < size; i++ {
elemCount, elemSum, err := mean(rv.Index(i).Interface())
if err != nil {
return 0, 0, err
}
total += elemSum
count += elemCount
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
total += float64(rv.Int())
count++
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
total += float64(rv.Uint())
count++
case reflect.Float32, reflect.Float64:
total += rv.Float()
count++
default:
return 0, 0, fmt.Errorf("invalid argument for mean (type %T)", arg)
}
}
return count, total, nil
}
func median(args ...any) ([]float64, error) {
var values []float64
for _, arg := range args {
rv := reflect.ValueOf(arg)
switch rv.Kind() {
case reflect.Array, reflect.Slice:
size := rv.Len()
for i := 0; i < size; i++ {
elems, err := median(rv.Index(i).Interface())
if err != nil {
return nil, err
}
values = append(values, elems...)
}
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
values = append(values, float64(rv.Int()))
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
values = append(values, float64(rv.Uint()))
case reflect.Float32, reflect.Float64:
values = append(values, rv.Float())
default:
return nil, fmt.Errorf("invalid argument for median (type %T)", arg)
}
}
return values, nil
}
func flatten(arg reflect.Value) []any {
ret := []any{}
for i := 0; i < arg.Len(); i++ {
v := deref.Value(arg.Index(i))
if v.Kind() == reflect.Array || v.Kind() == reflect.Slice {
x := flatten(v)
ret = append(ret, x...)
} else {
ret = append(ret, v.Interface())
}
}
return ret
}
func get(params ...any) (out any, err error) {
from := params[0]
i := params[1]
v := reflect.ValueOf(from)
if v.Kind() == reflect.Invalid {
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
}
// Methods can be defined on any type.
if v.NumMethod() > 0 {
if methodName, ok := i.(string); ok {
method := v.MethodByName(methodName)
if method.IsValid() {
return method.Interface(), nil
}
}
}
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
index := runtime.ToInt(i)
l := v.Len()
if index < 0 {
index = l + index
}
if 0 <= index && index < l {
value := v.Index(index)
if value.IsValid() {
return value.Interface(), nil
}
}
case reflect.Map:
var value reflect.Value
if i == nil {
value = v.MapIndex(reflect.Zero(v.Type().Key()))
} else {
value = v.MapIndex(reflect.ValueOf(i))
}
if value.IsValid() {
return value.Interface(), nil
}
case reflect.Struct:
fieldName := i.(string)
value := v.FieldByNameFunc(func(name string) bool {
field, _ := v.Type().FieldByName(name)
if field.Tag.Get("expr") == fieldName {
return true
}
return name == fieldName
})
if value.IsValid() {
return value.Interface(), nil
}
}
// Main difference from runtime.Fetch
// is that we return `nil` instead of panic.
return nil, nil
}

90
vendor/github.com/expr-lang/expr/builtin/utils.go generated vendored Normal file
View File

@@ -0,0 +1,90 @@
package builtin
import (
"fmt"
"reflect"
"time"
"github.com/expr-lang/expr/internal/deref"
)
var (
anyType = reflect.TypeOf(new(any)).Elem()
integerType = reflect.TypeOf(0)
floatType = reflect.TypeOf(float64(0))
arrayType = reflect.TypeOf([]any{})
mapType = reflect.TypeOf(map[any]any{})
timeType = reflect.TypeOf(new(time.Time)).Elem()
locationType = reflect.TypeOf(new(time.Location))
)
func kind(t reflect.Type) reflect.Kind {
if t == nil {
return reflect.Invalid
}
t = deref.Type(t)
return t.Kind()
}
func types(types ...any) []reflect.Type {
ts := make([]reflect.Type, len(types))
for i, t := range types {
t := reflect.TypeOf(t)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Func {
panic("not a function")
}
ts[i] = t
}
return ts
}
func toInt(val any) (int, error) {
switch v := val.(type) {
case int:
return v, nil
case int8:
return int(v), nil
case int16:
return int(v), nil
case int32:
return int(v), nil
case int64:
return int(v), nil
case uint:
return int(v), nil
case uint8:
return int(v), nil
case uint16:
return int(v), nil
case uint32:
return int(v), nil
case uint64:
return int(v), nil
default:
return 0, fmt.Errorf("cannot use %T as argument (type int)", val)
}
}
func bitFunc(name string, fn func(x, y int) (any, error)) *Function {
return &Function{
Name: name,
Func: func(args ...any) (any, error) {
if len(args) != 2 {
return nil, fmt.Errorf("invalid number of arguments for %s (expected 2, got %d)", name, len(args))
}
x, err := toInt(args[0])
if err != nil {
return nil, fmt.Errorf("%v to call %s", err, name)
}
y, err := toInt(args[1])
if err != nil {
return nil, fmt.Errorf("%v to call %s", err, name)
}
return fn(x, y)
},
Types: types(new(func(int, int) int)),
}
}

38
vendor/github.com/expr-lang/expr/builtin/validation.go generated vendored Normal file
View File

@@ -0,0 +1,38 @@
package builtin
import (
"fmt"
"reflect"
"github.com/expr-lang/expr/internal/deref"
)
func validateAggregateFunc(name string, args []reflect.Type) (reflect.Type, error) {
switch len(args) {
case 0:
return anyType, fmt.Errorf("not enough arguments to call %s", name)
default:
for _, arg := range args {
switch kind(deref.Type(arg)) {
case reflect.Interface, reflect.Array, reflect.Slice:
return anyType, nil
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
default:
return anyType, fmt.Errorf("invalid argument for %s (type %s)", name, arg)
}
}
return args[0], nil
}
}
func validateRoundFunc(name string, args []reflect.Type) (reflect.Type, error) {
if len(args) != 1 {
return anyType, fmt.Errorf("invalid number of arguments (expected 1, got %d)", len(args))
}
switch kind(args[0]) {
case reflect.Float32, reflect.Float64, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Interface:
return floatType, nil
default:
return anyType, fmt.Errorf("invalid argument for %s (type %s)", name, args[0])
}
}

1285
vendor/github.com/expr-lang/expr/checker/checker.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

129
vendor/github.com/expr-lang/expr/checker/info.go generated vendored Normal file
View File

@@ -0,0 +1,129 @@
package checker
import (
"reflect"
"github.com/expr-lang/expr/ast"
. "github.com/expr-lang/expr/checker/nature"
"github.com/expr-lang/expr/vm"
)
func FieldIndex(env Nature, node ast.Node) (bool, []int, string) {
switch n := node.(type) {
case *ast.IdentifierNode:
if env.Kind() == reflect.Struct {
if field, ok := env.Get(n.Value); ok && len(field.FieldIndex) > 0 {
return true, field.FieldIndex, n.Value
}
}
case *ast.MemberNode:
base := n.Node.Nature()
base = base.Deref()
if base.Kind() == reflect.Struct {
if prop, ok := n.Property.(*ast.StringNode); ok {
name := prop.Value
if field, ok := base.FieldByName(name); ok {
return true, field.FieldIndex, name
}
}
}
}
return false, nil, ""
}
func MethodIndex(env Nature, node ast.Node) (bool, int, string) {
switch n := node.(type) {
case *ast.IdentifierNode:
if env.Kind() == reflect.Struct {
if m, ok := env.Get(n.Value); ok {
return m.Method, m.MethodIndex, n.Value
}
}
case *ast.MemberNode:
if name, ok := n.Property.(*ast.StringNode); ok {
base := n.Node.Type()
if base != nil && base.Kind() != reflect.Interface {
if m, ok := base.MethodByName(name.Value); ok {
return true, m.Index, name.Value
}
}
}
}
return false, 0, ""
}
func TypedFuncIndex(fn reflect.Type, method bool) (int, bool) {
if fn == nil {
return 0, false
}
if fn.Kind() != reflect.Func {
return 0, false
}
// OnCallTyped doesn't work for functions with variadic arguments.
if fn.IsVariadic() {
return 0, false
}
// OnCallTyped doesn't work named function, like `type MyFunc func() int`.
if fn.PkgPath() != "" { // If PkgPath() is not empty, it means that function is named.
return 0, false
}
fnNumIn := fn.NumIn()
fnInOffset := 0
if method {
fnNumIn--
fnInOffset = 1
}
funcTypes:
for i := range vm.FuncTypes {
if i == 0 {
continue
}
typed := reflect.ValueOf(vm.FuncTypes[i]).Elem().Type()
if typed.Kind() != reflect.Func {
continue
}
if typed.NumOut() != fn.NumOut() {
continue
}
for j := 0; j < typed.NumOut(); j++ {
if typed.Out(j) != fn.Out(j) {
continue funcTypes
}
}
if typed.NumIn() != fnNumIn {
continue
}
for j := 0; j < typed.NumIn(); j++ {
if typed.In(j) != fn.In(j+fnInOffset) {
continue funcTypes
}
}
return i, true
}
return 0, false
}
func IsFastFunc(fn reflect.Type, method bool) bool {
if fn == nil {
return false
}
if fn.Kind() != reflect.Func {
return false
}
numIn := 1
if method {
numIn = 2
}
if fn.IsVariadic() &&
fn.NumIn() == numIn &&
fn.NumOut() == 1 &&
fn.Out(0).Kind() == reflect.Interface {
rest := fn.In(fn.NumIn() - 1) // function has only one param for functions and two for methods
if kind(rest) == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
return true
}
}
return false
}

View File

@@ -0,0 +1,261 @@
package nature
import (
"reflect"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/internal/deref"
)
var (
unknown = Nature{}
)
type Nature struct {
Type reflect.Type // Type of the value. If nil, then value is unknown.
Func *builtin.Function // Used to pass function type from callee to CallNode.
ArrayOf *Nature // Elem nature of array type (usually Type is []any, but ArrayOf can be any nature).
PredicateOut *Nature // Out nature of predicate.
Fields map[string]Nature // Fields of map type.
DefaultMapValue *Nature // Default value of map type.
Strict bool // If map is types.StrictMap.
Nil bool // If value is nil.
Method bool // If value retrieved from method. Usually used to determine amount of in arguments.
MethodIndex int // Index of method in type.
FieldIndex []int // Index of field in type.
}
func (n Nature) IsAny() bool {
return n.Kind() == reflect.Interface && n.NumMethods() == 0
}
func (n Nature) IsUnknown() bool {
switch {
case n.Type == nil && !n.Nil:
return true
case n.IsAny():
return true
}
return false
}
func (n Nature) String() string {
if n.Type != nil {
return n.Type.String()
}
return "unknown"
}
func (n Nature) Deref() Nature {
if n.Type != nil {
n.Type = deref.Type(n.Type)
}
return n
}
func (n Nature) Kind() reflect.Kind {
if n.Type != nil {
return n.Type.Kind()
}
return reflect.Invalid
}
func (n Nature) Key() Nature {
if n.Kind() == reflect.Map {
return Nature{Type: n.Type.Key()}
}
return unknown
}
func (n Nature) Elem() Nature {
switch n.Kind() {
case reflect.Ptr:
return Nature{Type: n.Type.Elem()}
case reflect.Map:
if n.DefaultMapValue != nil {
return *n.DefaultMapValue
}
return Nature{Type: n.Type.Elem()}
case reflect.Array, reflect.Slice:
if n.ArrayOf != nil {
return *n.ArrayOf
}
return Nature{Type: n.Type.Elem()}
}
return unknown
}
func (n Nature) AssignableTo(nt Nature) bool {
if n.Nil {
// Untyped nil is assignable to any interface, but implements only the empty interface.
if nt.IsAny() {
return true
}
}
if n.Type == nil || nt.Type == nil {
return false
}
return n.Type.AssignableTo(nt.Type)
}
func (n Nature) NumMethods() int {
if n.Type == nil {
return 0
}
return n.Type.NumMethod()
}
func (n Nature) MethodByName(name string) (Nature, bool) {
if n.Type == nil {
return unknown, false
}
method, ok := n.Type.MethodByName(name)
if !ok {
return unknown, false
}
if n.Type.Kind() == reflect.Interface {
// In case of interface type method will not have a receiver,
// and to prevent checker decreasing numbers of in arguments
// return method type as not method (second argument is false).
// Also, we can not use m.Index here, because it will be
// different indexes for different types which implement
// the same interface.
return Nature{Type: method.Type}, true
} else {
return Nature{
Type: method.Type,
Method: true,
MethodIndex: method.Index,
}, true
}
}
func (n Nature) NumIn() int {
if n.Type == nil {
return 0
}
return n.Type.NumIn()
}
func (n Nature) In(i int) Nature {
if n.Type == nil {
return unknown
}
return Nature{Type: n.Type.In(i)}
}
func (n Nature) NumOut() int {
if n.Type == nil {
return 0
}
return n.Type.NumOut()
}
func (n Nature) Out(i int) Nature {
if n.Type == nil {
return unknown
}
return Nature{Type: n.Type.Out(i)}
}
func (n Nature) IsVariadic() bool {
if n.Type == nil {
return false
}
return n.Type.IsVariadic()
}
func (n Nature) FieldByName(name string) (Nature, bool) {
if n.Type == nil {
return unknown, false
}
field, ok := fetchField(n.Type, name)
return Nature{Type: field.Type, FieldIndex: field.Index}, ok
}
func (n Nature) PkgPath() string {
if n.Type == nil {
return ""
}
return n.Type.PkgPath()
}
func (n Nature) IsFastMap() bool {
if n.Type == nil {
return false
}
if n.Type.Kind() == reflect.Map &&
n.Type.Key().Kind() == reflect.String &&
n.Type.Elem().Kind() == reflect.Interface {
return true
}
return false
}
func (n Nature) Get(name string) (Nature, bool) {
if n.Type == nil {
return unknown, false
}
if m, ok := n.MethodByName(name); ok {
return m, true
}
t := deref.Type(n.Type)
switch t.Kind() {
case reflect.Struct:
if f, ok := fetchField(t, name); ok {
return Nature{
Type: f.Type,
FieldIndex: f.Index,
}, true
}
case reflect.Map:
if f, ok := n.Fields[name]; ok {
return f, true
}
}
return unknown, false
}
func (n Nature) All() map[string]Nature {
table := make(map[string]Nature)
if n.Type == nil {
return table
}
for i := 0; i < n.Type.NumMethod(); i++ {
method := n.Type.Method(i)
table[method.Name] = Nature{
Type: method.Type,
Method: true,
MethodIndex: method.Index,
}
}
t := deref.Type(n.Type)
switch t.Kind() {
case reflect.Struct:
for name, nt := range StructFields(t) {
if _, ok := table[name]; ok {
continue
}
table[name] = nt
}
case reflect.Map:
for key, nt := range n.Fields {
if _, ok := table[key]; ok {
continue
}
table[key] = nt
}
}
return table
}

View File

@@ -0,0 +1,76 @@
package nature
import (
"reflect"
"github.com/expr-lang/expr/internal/deref"
)
func fieldName(field reflect.StructField) string {
if taggedName := field.Tag.Get("expr"); taggedName != "" {
return taggedName
}
return field.Name
}
func fetchField(t reflect.Type, name string) (reflect.StructField, bool) {
// First check all structs fields.
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
// Search all fields, even embedded structs.
if fieldName(field) == name {
return field, true
}
}
// Second check fields of embedded structs.
for i := 0; i < t.NumField(); i++ {
anon := t.Field(i)
if anon.Anonymous {
anonType := anon.Type
if anonType.Kind() == reflect.Pointer {
anonType = anonType.Elem()
}
if field, ok := fetchField(anonType, name); ok {
field.Index = append(anon.Index, field.Index...)
return field, true
}
}
}
return reflect.StructField{}, false
}
func StructFields(t reflect.Type) map[string]Nature {
table := make(map[string]Nature)
t = deref.Type(t)
if t == nil {
return table
}
switch t.Kind() {
case reflect.Struct:
for i := 0; i < t.NumField(); i++ {
f := t.Field(i)
if f.Anonymous {
for name, typ := range StructFields(f.Type) {
if _, ok := table[name]; ok {
continue
}
typ.FieldIndex = append(f.Index, typ.FieldIndex...)
table[name] = typ
}
}
table[fieldName(f)] = Nature{
Type: f.Type,
FieldIndex: f.Index,
}
}
}
return table
}

190
vendor/github.com/expr-lang/expr/checker/types.go generated vendored Normal file
View File

@@ -0,0 +1,190 @@
package checker
import (
"reflect"
"time"
. "github.com/expr-lang/expr/checker/nature"
)
var (
unknown = Nature{}
nilNature = Nature{Nil: true}
boolNature = Nature{Type: reflect.TypeOf(true)}
integerNature = Nature{Type: reflect.TypeOf(0)}
floatNature = Nature{Type: reflect.TypeOf(float64(0))}
stringNature = Nature{Type: reflect.TypeOf("")}
arrayNature = Nature{Type: reflect.TypeOf([]any{})}
mapNature = Nature{Type: reflect.TypeOf(map[string]any{})}
timeNature = Nature{Type: reflect.TypeOf(time.Time{})}
durationNature = Nature{Type: reflect.TypeOf(time.Duration(0))}
)
var (
anyType = reflect.TypeOf(new(any)).Elem()
timeType = reflect.TypeOf(time.Time{})
durationType = reflect.TypeOf(time.Duration(0))
arrayType = reflect.TypeOf([]any{})
)
func arrayOf(nt Nature) Nature {
return Nature{
Type: arrayType,
ArrayOf: &nt,
}
}
func isNil(nt Nature) bool {
return nt.Nil
}
func combined(l, r Nature) Nature {
if isUnknown(l) || isUnknown(r) {
return unknown
}
if isFloat(l) || isFloat(r) {
return floatNature
}
return integerNature
}
func anyOf(nt Nature, fns ...func(Nature) bool) bool {
for _, fn := range fns {
if fn(nt) {
return true
}
}
return false
}
func or(l, r Nature, fns ...func(Nature) bool) bool {
if isUnknown(l) && isUnknown(r) {
return true
}
if isUnknown(l) && anyOf(r, fns...) {
return true
}
if isUnknown(r) && anyOf(l, fns...) {
return true
}
return false
}
func isUnknown(nt Nature) bool {
return nt.IsUnknown()
}
func isInteger(nt Nature) bool {
switch nt.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
fallthrough
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return nt.PkgPath() == ""
}
return false
}
func isFloat(nt Nature) bool {
switch nt.Kind() {
case reflect.Float32, reflect.Float64:
return nt.PkgPath() == ""
}
return false
}
func isNumber(nt Nature) bool {
return isInteger(nt) || isFloat(nt)
}
func isTime(nt Nature) bool {
switch nt.Type {
case timeType:
return true
}
return false
}
func isDuration(nt Nature) bool {
switch nt.Type {
case durationType:
return true
}
return false
}
func isBool(nt Nature) bool {
switch nt.Kind() {
case reflect.Bool:
return true
}
return false
}
func isString(nt Nature) bool {
switch nt.Kind() {
case reflect.String:
return true
}
return false
}
func isArray(nt Nature) bool {
switch nt.Kind() {
case reflect.Slice, reflect.Array:
return true
}
return false
}
func isMap(nt Nature) bool {
switch nt.Kind() {
case reflect.Map:
return true
}
return false
}
func isStruct(nt Nature) bool {
switch nt.Kind() {
case reflect.Struct:
return true
}
return false
}
func isFunc(nt Nature) bool {
switch nt.Kind() {
case reflect.Func:
return true
}
return false
}
func kind(t reflect.Type) reflect.Kind {
if t == nil {
return reflect.Invalid
}
return t.Kind()
}
func isComparable(l, r Nature) bool {
if isUnknown(l) || isUnknown(r) {
return true
}
if isNil(l) || isNil(r) {
return true
}
if isNumber(l) && isNumber(r) {
return true
}
if isDuration(l) && isDuration(r) {
return true
}
if isTime(l) && isTime(r) {
return true
}
if isArray(l) && isArray(r) {
return true
}
return l.AssignableTo(r)
}

1280
vendor/github.com/expr-lang/expr/compiler/compiler.go generated vendored Normal file

File diff suppressed because it is too large Load Diff

101
vendor/github.com/expr-lang/expr/conf/config.go generated vendored Normal file
View File

@@ -0,0 +1,101 @@
package conf
import (
"fmt"
"reflect"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/checker/nature"
"github.com/expr-lang/expr/vm/runtime"
)
const (
// DefaultMemoryBudget represents an upper limit of memory usage
DefaultMemoryBudget uint = 1e6
// DefaultMaxNodes represents an upper limit of AST nodes
DefaultMaxNodes uint = 10000
)
type FunctionsTable map[string]*builtin.Function
type Config struct {
EnvObject any
Env nature.Nature
Expect reflect.Kind
ExpectAny bool
Optimize bool
Strict bool
Profile bool
MaxNodes uint
MemoryBudget uint
ConstFns map[string]reflect.Value
Visitors []ast.Visitor
Functions FunctionsTable
Builtins FunctionsTable
Disabled map[string]bool // disabled builtins
}
// CreateNew creates new config with default values.
func CreateNew() *Config {
c := &Config{
Optimize: true,
MaxNodes: DefaultMaxNodes,
MemoryBudget: DefaultMemoryBudget,
ConstFns: make(map[string]reflect.Value),
Functions: make(map[string]*builtin.Function),
Builtins: make(map[string]*builtin.Function),
Disabled: make(map[string]bool),
}
for _, f := range builtin.Builtins {
c.Builtins[f.Name] = f
}
return c
}
// New creates new config with environment.
func New(env any) *Config {
c := CreateNew()
c.WithEnv(env)
return c
}
func (c *Config) WithEnv(env any) {
c.EnvObject = env
c.Env = Env(env)
c.Strict = c.Env.Strict
}
func (c *Config) ConstExpr(name string) {
if c.EnvObject == nil {
panic("no environment is specified for ConstExpr()")
}
fn := reflect.ValueOf(runtime.Fetch(c.EnvObject, name))
if fn.Kind() != reflect.Func {
panic(fmt.Errorf("const expression %q must be a function", name))
}
c.ConstFns[name] = fn
}
type Checker interface {
Check()
}
func (c *Config) Check() {
for _, v := range c.Visitors {
if c, ok := v.(Checker); ok {
c.Check()
}
}
}
func (c *Config) IsOverridden(name string) bool {
if _, ok := c.Functions[name]; ok {
return true
}
if _, ok := c.Env.Get(name); ok {
return true
}
return false
}

68
vendor/github.com/expr-lang/expr/conf/env.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package conf
import (
"fmt"
"reflect"
. "github.com/expr-lang/expr/checker/nature"
"github.com/expr-lang/expr/internal/deref"
"github.com/expr-lang/expr/types"
)
func Env(env any) Nature {
if env == nil {
return Nature{
Type: reflect.TypeOf(map[string]any{}),
Strict: true,
}
}
switch env := env.(type) {
case types.Map:
return env.Nature()
}
v := reflect.ValueOf(env)
d := deref.Value(v)
switch d.Kind() {
case reflect.Struct:
return Nature{
Type: v.Type(),
Strict: true,
}
case reflect.Map:
n := Nature{
Type: v.Type(),
Fields: make(map[string]Nature, v.Len()),
Strict: true,
}
for _, key := range v.MapKeys() {
elem := v.MapIndex(key)
if !elem.IsValid() || !elem.CanInterface() {
panic(fmt.Sprintf("invalid map value: %s", key))
}
face := elem.Interface()
switch face := face.(type) {
case types.Map:
n.Fields[key.String()] = face.Nature()
default:
if face == nil {
n.Fields[key.String()] = Nature{Nil: true}
continue
}
n.Fields[key.String()] = Nature{Type: reflect.TypeOf(face)}
}
}
return n
}
panic(fmt.Sprintf("unknown type %T", env))
}

260
vendor/github.com/expr-lang/expr/expr.go generated vendored Normal file
View File

@@ -0,0 +1,260 @@
package expr
import (
"errors"
"fmt"
"reflect"
"time"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/checker"
"github.com/expr-lang/expr/compiler"
"github.com/expr-lang/expr/conf"
"github.com/expr-lang/expr/file"
"github.com/expr-lang/expr/optimizer"
"github.com/expr-lang/expr/parser"
"github.com/expr-lang/expr/patcher"
"github.com/expr-lang/expr/vm"
)
// Option for configuring config.
type Option func(c *conf.Config)
// Env specifies expected input of env for type checks.
// If struct is passed, all fields will be treated as variables,
// as well as all fields of embedded structs and struct itself.
// If map is passed, all items will be treated as variables.
// Methods defined on this type will be available as functions.
func Env(env any) Option {
return func(c *conf.Config) {
c.WithEnv(env)
}
}
// AllowUndefinedVariables allows to use undefined variables inside expressions.
// This can be used with expr.Env option to partially define a few variables.
func AllowUndefinedVariables() Option {
return func(c *conf.Config) {
c.Strict = false
}
}
// Operator allows to replace a binary operator with a function.
func Operator(operator string, fn ...string) Option {
return func(c *conf.Config) {
p := &patcher.OperatorOverloading{
Operator: operator,
Overloads: fn,
Env: &c.Env,
Functions: c.Functions,
}
c.Visitors = append(c.Visitors, p)
}
}
// ConstExpr defines func expression as constant. If all argument to this function is constants,
// then it can be replaced by result of this func call on compile step.
func ConstExpr(fn string) Option {
return func(c *conf.Config) {
c.ConstExpr(fn)
}
}
// AsAny tells the compiler to expect any result.
func AsAny() Option {
return func(c *conf.Config) {
c.ExpectAny = true
}
}
// AsKind tells the compiler to expect kind of the result.
func AsKind(kind reflect.Kind) Option {
return func(c *conf.Config) {
c.Expect = kind
c.ExpectAny = true
}
}
// AsBool tells the compiler to expect a boolean result.
func AsBool() Option {
return func(c *conf.Config) {
c.Expect = reflect.Bool
c.ExpectAny = true
}
}
// AsInt tells the compiler to expect an int result.
func AsInt() Option {
return func(c *conf.Config) {
c.Expect = reflect.Int
c.ExpectAny = true
}
}
// AsInt64 tells the compiler to expect an int64 result.
func AsInt64() Option {
return func(c *conf.Config) {
c.Expect = reflect.Int64
c.ExpectAny = true
}
}
// AsFloat64 tells the compiler to expect a float64 result.
func AsFloat64() Option {
return func(c *conf.Config) {
c.Expect = reflect.Float64
c.ExpectAny = true
}
}
// WarnOnAny tells the compiler to warn if expression return any type.
func WarnOnAny() Option {
return func(c *conf.Config) {
if c.Expect == reflect.Invalid {
panic("WarnOnAny() works only with combination with AsInt(), AsBool(), etc. options")
}
c.ExpectAny = false
}
}
// Optimize turns optimizations on or off.
func Optimize(b bool) Option {
return func(c *conf.Config) {
c.Optimize = b
}
}
// Patch adds visitor to list of visitors what will be applied before compiling AST to bytecode.
func Patch(visitor ast.Visitor) Option {
return func(c *conf.Config) {
c.Visitors = append(c.Visitors, visitor)
}
}
// Function adds function to list of functions what will be available in expressions.
func Function(name string, fn func(params ...any) (any, error), types ...any) Option {
return func(c *conf.Config) {
ts := make([]reflect.Type, len(types))
for i, t := range types {
t := reflect.TypeOf(t)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
if t.Kind() != reflect.Func {
panic(fmt.Sprintf("expr: type of %s is not a function", name))
}
ts[i] = t
}
c.Functions[name] = &builtin.Function{
Name: name,
Func: fn,
Types: ts,
}
}
}
// DisableAllBuiltins disables all builtins.
func DisableAllBuiltins() Option {
return func(c *conf.Config) {
for name := range c.Builtins {
c.Disabled[name] = true
}
}
}
// DisableBuiltin disables builtin function.
func DisableBuiltin(name string) Option {
return func(c *conf.Config) {
c.Disabled[name] = true
}
}
// EnableBuiltin enables builtin function.
func EnableBuiltin(name string) Option {
return func(c *conf.Config) {
delete(c.Disabled, name)
}
}
// WithContext passes context to all functions calls with a context.Context argument.
func WithContext(name string) Option {
return Patch(patcher.WithContext{
Name: name,
})
}
// Timezone sets default timezone for date() and now() builtin functions.
func Timezone(name string) Option {
tz, err := time.LoadLocation(name)
if err != nil {
panic(err)
}
return Patch(patcher.WithTimezone{
Location: tz,
})
}
// Compile parses and compiles given input expression to bytecode program.
func Compile(input string, ops ...Option) (*vm.Program, error) {
config := conf.CreateNew()
for _, op := range ops {
op(config)
}
for name := range config.Disabled {
delete(config.Builtins, name)
}
config.Check()
tree, err := checker.ParseCheck(input, config)
if err != nil {
return nil, err
}
if config.Optimize {
err = optimizer.Optimize(&tree.Node, config)
if err != nil {
var fileError *file.Error
if errors.As(err, &fileError) {
return nil, fileError.Bind(tree.Source)
}
return nil, err
}
}
program, err := compiler.Compile(tree, config)
if err != nil {
return nil, err
}
return program, nil
}
// Run evaluates given bytecode program.
func Run(program *vm.Program, env any) (any, error) {
return vm.Run(program, env)
}
// Eval parses, compiles and runs given input.
func Eval(input string, env any) (any, error) {
if _, ok := env.(Option); ok {
return nil, fmt.Errorf("misused expr.Eval: second argument (env) should be passed without expr.Env")
}
tree, err := parser.Parse(input)
if err != nil {
return nil, err
}
program, err := compiler.Compile(tree, nil)
if err != nil {
return nil, err
}
output, err := Run(program, env)
if err != nil {
return nil, err
}
return output, nil
}

81
vendor/github.com/expr-lang/expr/file/error.go generated vendored Normal file
View File

@@ -0,0 +1,81 @@
package file
import (
"fmt"
"strings"
"unicode/utf8"
)
type Error struct {
Location
Line int `json:"line"`
Column int `json:"column"`
Message string `json:"message"`
Snippet string `json:"snippet"`
Prev error `json:"prev"`
}
func (e *Error) Error() string {
return e.format()
}
func (e *Error) Bind(source Source) *Error {
e.Line = 1
for i, r := range source {
if i == e.From {
break
}
if r == '\n' {
e.Line++
e.Column = 0
} else {
e.Column++
}
}
if snippet, found := source.Snippet(e.Line); found {
snippet := strings.Replace(snippet, "\t", " ", -1)
srcLine := "\n | " + snippet
var bytes = []byte(snippet)
var indLine = "\n | "
for i := 0; i < e.Column && len(bytes) > 0; i++ {
_, sz := utf8.DecodeRune(bytes)
bytes = bytes[sz:]
if sz > 1 {
goto noind
} else {
indLine += "."
}
}
if _, sz := utf8.DecodeRune(bytes); sz > 1 {
goto noind
} else {
indLine += "^"
}
srcLine += indLine
noind:
e.Snippet = srcLine
}
return e
}
func (e *Error) Unwrap() error {
return e.Prev
}
func (e *Error) Wrap(err error) {
e.Prev = err
}
func (e *Error) format() string {
if e.Snippet == "" {
return e.Message
}
return fmt.Sprintf(
"%s (%d:%d)%s",
e.Message,
e.Line,
e.Column+1, // add one to the 0-based column for display
e.Snippet,
)
}

6
vendor/github.com/expr-lang/expr/file/location.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
package file
type Location struct {
From int `json:"from"`
To int `json:"to"`
}

48
vendor/github.com/expr-lang/expr/file/source.go generated vendored Normal file
View File

@@ -0,0 +1,48 @@
package file
import (
"strings"
"unicode/utf8"
)
type Source []rune
func NewSource(contents string) Source {
return []rune(contents)
}
func (s Source) String() string {
return string(s)
}
func (s Source) Snippet(line int) (string, bool) {
if s == nil {
return "", false
}
lines := strings.Split(string(s), "\n")
lineOffsets := make([]int, len(lines))
var offset int
for i, line := range lines {
offset = offset + utf8.RuneCountInString(line) + 1
lineOffsets[i] = offset
}
charStart, found := getLineOffset(lineOffsets, line)
if !found || len(s) == 0 {
return "", false
}
charEnd, found := getLineOffset(lineOffsets, line+1)
if found {
return string(s[charStart : charEnd-1]), true
}
return string(s[charStart:]), true
}
func getLineOffset(lineOffsets []int, line int) (int, bool) {
if line == 1 {
return 0, true
} else if line > 1 && line <= len(lineOffsets) {
offset := lineOffsets[line-2]
return offset, true
}
return -1, false
}

View File

@@ -0,0 +1,47 @@
package deref
import (
"fmt"
"reflect"
)
func Interface(p any) any {
if p == nil {
return nil
}
v := reflect.ValueOf(p)
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
if v.IsNil() {
return nil
}
v = v.Elem()
}
if v.IsValid() {
return v.Interface()
}
panic(fmt.Sprintf("cannot dereference %v", p))
}
func Type(t reflect.Type) reflect.Type {
if t == nil {
return nil
}
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
return t
}
func Value(v reflect.Value) reflect.Value {
for v.Kind() == reflect.Ptr || v.Kind() == reflect.Interface {
if v.IsNil() {
return v
}
v = v.Elem()
}
return v
}

View File

@@ -0,0 +1,81 @@
package optimizer
import (
"fmt"
"reflect"
"strings"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/file"
)
var errorType = reflect.TypeOf((*error)(nil)).Elem()
type constExpr struct {
applied bool
err error
fns map[string]reflect.Value
}
func (c *constExpr) Visit(node *Node) {
defer func() {
if r := recover(); r != nil {
msg := fmt.Sprintf("%v", r)
// Make message more actual, it's a runtime error, but at compile step.
msg = strings.Replace(msg, "runtime error:", "compile error:", 1)
c.err = &file.Error{
Location: (*node).Location(),
Message: msg,
}
}
}()
if call, ok := (*node).(*CallNode); ok {
if name, ok := call.Callee.(*IdentifierNode); ok {
fn, ok := c.fns[name.Value]
if ok {
in := make([]reflect.Value, len(call.Arguments))
for i := 0; i < len(call.Arguments); i++ {
arg := call.Arguments[i]
var param any
switch a := arg.(type) {
case *NilNode:
param = nil
case *IntegerNode:
param = a.Value
case *FloatNode:
param = a.Value
case *BoolNode:
param = a.Value
case *StringNode:
param = a.Value
case *ConstantNode:
param = a.Value
default:
return // Const expr optimization not applicable.
}
if param == nil && reflect.TypeOf(param) == nil {
// In case of nil value and nil type use this hack,
// otherwise reflect.Call will panic on zero value.
in[i] = reflect.ValueOf(&param).Elem()
} else {
in[i] = reflect.ValueOf(param)
}
}
out := fn.Call(in)
value := out[0].Interface()
if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
c.err = out[1].Interface().(error)
return
}
constNode := &ConstantNode{Value: value}
patchWithType(node, constNode)
c.applied = true
}
}
}
}

View File

@@ -0,0 +1,38 @@
package optimizer
import (
. "github.com/expr-lang/expr/ast"
)
type filterFirst struct{}
func (*filterFirst) Visit(node *Node) {
if member, ok := (*node).(*MemberNode); ok && member.Property != nil && !member.Optional {
if prop, ok := member.Property.(*IntegerNode); ok && prop.Value == 0 {
if filter, ok := member.Node.(*BuiltinNode); ok &&
filter.Name == "filter" &&
len(filter.Arguments) == 2 {
patchCopyType(node, &BuiltinNode{
Name: "find",
Arguments: filter.Arguments,
Throws: true, // to match the behavior of filter()[0]
Map: filter.Map,
})
}
}
}
if first, ok := (*node).(*BuiltinNode); ok &&
first.Name == "first" &&
len(first.Arguments) == 1 {
if filter, ok := first.Arguments[0].(*BuiltinNode); ok &&
filter.Name == "filter" &&
len(filter.Arguments) == 2 {
patchCopyType(node, &BuiltinNode{
Name: "find",
Arguments: filter.Arguments,
Throws: false, // as first() will return nil if not found
Map: filter.Map,
})
}
}
}

View File

@@ -0,0 +1,38 @@
package optimizer
import (
. "github.com/expr-lang/expr/ast"
)
type filterLast struct{}
func (*filterLast) Visit(node *Node) {
if member, ok := (*node).(*MemberNode); ok && member.Property != nil && !member.Optional {
if prop, ok := member.Property.(*IntegerNode); ok && prop.Value == -1 {
if filter, ok := member.Node.(*BuiltinNode); ok &&
filter.Name == "filter" &&
len(filter.Arguments) == 2 {
patchCopyType(node, &BuiltinNode{
Name: "findLast",
Arguments: filter.Arguments,
Throws: true, // to match the behavior of filter()[-1]
Map: filter.Map,
})
}
}
}
if first, ok := (*node).(*BuiltinNode); ok &&
first.Name == "last" &&
len(first.Arguments) == 1 {
if filter, ok := first.Arguments[0].(*BuiltinNode); ok &&
filter.Name == "filter" &&
len(filter.Arguments) == 2 {
patchCopyType(node, &BuiltinNode{
Name: "findLast",
Arguments: filter.Arguments,
Throws: false, // as last() will return nil if not found
Map: filter.Map,
})
}
}
}

View File

@@ -0,0 +1,22 @@
package optimizer
import (
. "github.com/expr-lang/expr/ast"
)
type filterLen struct{}
func (*filterLen) Visit(node *Node) {
if ln, ok := (*node).(*BuiltinNode); ok &&
ln.Name == "len" &&
len(ln.Arguments) == 1 {
if filter, ok := ln.Arguments[0].(*BuiltinNode); ok &&
filter.Name == "filter" &&
len(filter.Arguments) == 2 {
patchCopyType(node, &BuiltinNode{
Name: "count",
Arguments: filter.Arguments,
})
}
}
}

View File

@@ -0,0 +1,33 @@
package optimizer
import (
. "github.com/expr-lang/expr/ast"
)
type filterMap struct{}
func (*filterMap) Visit(node *Node) {
if mapBuiltin, ok := (*node).(*BuiltinNode); ok &&
mapBuiltin.Name == "map" &&
len(mapBuiltin.Arguments) == 2 &&
Find(mapBuiltin.Arguments[1], isIndexPointer) == nil {
if predicate, ok := mapBuiltin.Arguments[1].(*PredicateNode); ok {
if filter, ok := mapBuiltin.Arguments[0].(*BuiltinNode); ok &&
filter.Name == "filter" &&
filter.Map == nil /* not already optimized */ {
patchCopyType(node, &BuiltinNode{
Name: "filter",
Arguments: filter.Arguments,
Map: predicate.Node,
})
}
}
}
}
func isIndexPointer(node Node) bool {
if pointer, ok := node.(*PointerNode); ok && pointer.Name == "index" {
return true
}
return false
}

341
vendor/github.com/expr-lang/expr/optimizer/fold.go generated vendored Normal file
View File

@@ -0,0 +1,341 @@
package optimizer
import (
"math"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/file"
)
type fold struct {
applied bool
err *file.Error
}
func (fold *fold) Visit(node *Node) {
patch := func(newNode Node) {
fold.applied = true
patchWithType(node, newNode)
}
patchCopy := func(newNode Node) {
fold.applied = true
patchCopyType(node, newNode)
}
switch n := (*node).(type) {
case *UnaryNode:
switch n.Operator {
case "-":
if i, ok := n.Node.(*IntegerNode); ok {
patch(&IntegerNode{Value: -i.Value})
}
if i, ok := n.Node.(*FloatNode); ok {
patch(&FloatNode{Value: -i.Value})
}
case "+":
if i, ok := n.Node.(*IntegerNode); ok {
patch(&IntegerNode{Value: i.Value})
}
if i, ok := n.Node.(*FloatNode); ok {
patch(&FloatNode{Value: i.Value})
}
case "!", "not":
if a := toBool(n.Node); a != nil {
patch(&BoolNode{Value: !a.Value})
}
}
case *BinaryNode:
switch n.Operator {
case "+":
{
a := toInteger(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&IntegerNode{Value: a.Value + b.Value})
}
}
{
a := toInteger(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: float64(a.Value) + b.Value})
}
}
{
a := toFloat(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value + float64(b.Value)})
}
}
{
a := toFloat(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value + b.Value})
}
}
{
a := toString(n.Left)
b := toString(n.Right)
if a != nil && b != nil {
patch(&StringNode{Value: a.Value + b.Value})
}
}
case "-":
{
a := toInteger(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&IntegerNode{Value: a.Value - b.Value})
}
}
{
a := toInteger(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: float64(a.Value) - b.Value})
}
}
{
a := toFloat(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value - float64(b.Value)})
}
}
{
a := toFloat(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value - b.Value})
}
}
case "*":
{
a := toInteger(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&IntegerNode{Value: a.Value * b.Value})
}
}
{
a := toInteger(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: float64(a.Value) * b.Value})
}
}
{
a := toFloat(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value * float64(b.Value)})
}
}
{
a := toFloat(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value * b.Value})
}
}
case "/":
{
a := toInteger(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: float64(a.Value) / float64(b.Value)})
}
}
{
a := toInteger(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: float64(a.Value) / b.Value})
}
}
{
a := toFloat(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value / float64(b.Value)})
}
}
{
a := toFloat(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: a.Value / b.Value})
}
}
case "%":
if a, ok := n.Left.(*IntegerNode); ok {
if b, ok := n.Right.(*IntegerNode); ok {
if b.Value == 0 {
fold.err = &file.Error{
Location: (*node).Location(),
Message: "integer divide by zero",
}
return
}
patch(&IntegerNode{Value: a.Value % b.Value})
}
}
case "**", "^":
{
a := toInteger(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: math.Pow(float64(a.Value), float64(b.Value))})
}
}
{
a := toInteger(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: math.Pow(float64(a.Value), b.Value)})
}
}
{
a := toFloat(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: math.Pow(a.Value, float64(b.Value))})
}
}
{
a := toFloat(n.Left)
b := toFloat(n.Right)
if a != nil && b != nil {
patch(&FloatNode{Value: math.Pow(a.Value, b.Value)})
}
}
case "and", "&&":
a := toBool(n.Left)
b := toBool(n.Right)
if a != nil && a.Value { // true and x
patchCopy(n.Right)
} else if b != nil && b.Value { // x and true
patchCopy(n.Left)
} else if (a != nil && !a.Value) || (b != nil && !b.Value) { // "x and false" or "false and x"
patch(&BoolNode{Value: false})
}
case "or", "||":
a := toBool(n.Left)
b := toBool(n.Right)
if a != nil && !a.Value { // false or x
patchCopy(n.Right)
} else if b != nil && !b.Value { // x or false
patchCopy(n.Left)
} else if (a != nil && a.Value) || (b != nil && b.Value) { // "x or true" or "true or x"
patch(&BoolNode{Value: true})
}
case "==":
{
a := toInteger(n.Left)
b := toInteger(n.Right)
if a != nil && b != nil {
patch(&BoolNode{Value: a.Value == b.Value})
}
}
{
a := toString(n.Left)
b := toString(n.Right)
if a != nil && b != nil {
patch(&BoolNode{Value: a.Value == b.Value})
}
}
{
a := toBool(n.Left)
b := toBool(n.Right)
if a != nil && b != nil {
patch(&BoolNode{Value: a.Value == b.Value})
}
}
}
case *ArrayNode:
if len(n.Nodes) > 0 {
for _, a := range n.Nodes {
switch a.(type) {
case *IntegerNode, *FloatNode, *StringNode, *BoolNode:
continue
default:
return
}
}
value := make([]any, len(n.Nodes))
for i, a := range n.Nodes {
switch b := a.(type) {
case *IntegerNode:
value[i] = b.Value
case *FloatNode:
value[i] = b.Value
case *StringNode:
value[i] = b.Value
case *BoolNode:
value[i] = b.Value
}
}
patch(&ConstantNode{Value: value})
}
case *BuiltinNode:
// TODO: Move this to a separate visitor filter_filter.go
switch n.Name {
case "filter":
if len(n.Arguments) != 2 {
return
}
if base, ok := n.Arguments[0].(*BuiltinNode); ok && base.Name == "filter" {
patchCopy(&BuiltinNode{
Name: "filter",
Arguments: []Node{
base.Arguments[0],
&BinaryNode{
Operator: "&&",
Left: base.Arguments[1].(*PredicateNode).Node,
Right: n.Arguments[1].(*PredicateNode).Node,
},
},
})
}
}
}
}
func toString(n Node) *StringNode {
switch a := n.(type) {
case *StringNode:
return a
}
return nil
}
func toInteger(n Node) *IntegerNode {
switch a := n.(type) {
case *IntegerNode:
return a
}
return nil
}
func toFloat(n Node) *FloatNode {
switch a := n.(type) {
case *FloatNode:
return a
}
return nil
}
func toBool(n Node) *BoolNode {
switch a := n.(type) {
case *BoolNode:
return a
}
return nil
}

68
vendor/github.com/expr-lang/expr/optimizer/in_array.go generated vendored Normal file
View File

@@ -0,0 +1,68 @@
package optimizer
import (
"reflect"
. "github.com/expr-lang/expr/ast"
)
type inArray struct{}
func (*inArray) Visit(node *Node) {
switch n := (*node).(type) {
case *BinaryNode:
if n.Operator == "in" {
if array, ok := n.Right.(*ArrayNode); ok {
if len(array.Nodes) > 0 {
t := n.Left.Type()
if t == nil || t.Kind() != reflect.Int {
// This optimization can be only performed if left side is int type,
// as runtime.in func uses reflect.Map.MapIndex and keys of map must,
// be same as checked value type.
goto string
}
for _, a := range array.Nodes {
if _, ok := a.(*IntegerNode); !ok {
goto string
}
}
{
value := make(map[int]struct{})
for _, a := range array.Nodes {
value[a.(*IntegerNode).Value] = struct{}{}
}
m := &ConstantNode{Value: value}
m.SetType(reflect.TypeOf(value))
patchCopyType(node, &BinaryNode{
Operator: n.Operator,
Left: n.Left,
Right: m,
})
}
string:
for _, a := range array.Nodes {
if _, ok := a.(*StringNode); !ok {
return
}
}
{
value := make(map[string]struct{})
for _, a := range array.Nodes {
value[a.(*StringNode).Value] = struct{}{}
}
m := &ConstantNode{Value: value}
m.SetType(reflect.TypeOf(value))
patchCopyType(node, &BinaryNode{
Operator: n.Operator,
Left: n.Left,
Right: m,
})
}
}
}
}
}
}

43
vendor/github.com/expr-lang/expr/optimizer/in_range.go generated vendored Normal file
View File

@@ -0,0 +1,43 @@
package optimizer
import (
"reflect"
. "github.com/expr-lang/expr/ast"
)
type inRange struct{}
func (*inRange) Visit(node *Node) {
switch n := (*node).(type) {
case *BinaryNode:
if n.Operator == "in" {
t := n.Left.Type()
if t == nil {
return
}
if t.Kind() != reflect.Int {
return
}
if rangeOp, ok := n.Right.(*BinaryNode); ok && rangeOp.Operator == ".." {
if from, ok := rangeOp.Left.(*IntegerNode); ok {
if to, ok := rangeOp.Right.(*IntegerNode); ok {
patchCopyType(node, &BinaryNode{
Operator: "and",
Left: &BinaryNode{
Operator: ">=",
Left: n.Left,
Right: from,
},
Right: &BinaryNode{
Operator: "<=",
Left: n.Left,
Right: to,
},
})
}
}
}
}
}
}

View File

@@ -0,0 +1,79 @@
package optimizer
import (
"fmt"
"reflect"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/conf"
)
func Optimize(node *Node, config *conf.Config) error {
Walk(node, &inArray{})
for limit := 1000; limit >= 0; limit-- {
fold := &fold{}
Walk(node, fold)
if fold.err != nil {
return fold.err
}
if !fold.applied {
break
}
}
if config != nil && len(config.ConstFns) > 0 {
for limit := 100; limit >= 0; limit-- {
constExpr := &constExpr{
fns: config.ConstFns,
}
Walk(node, constExpr)
if constExpr.err != nil {
return constExpr.err
}
if !constExpr.applied {
break
}
}
}
Walk(node, &inRange{})
Walk(node, &filterMap{})
Walk(node, &filterLen{})
Walk(node, &filterLast{})
Walk(node, &filterFirst{})
Walk(node, &predicateCombination{})
Walk(node, &sumArray{})
Walk(node, &sumMap{})
return nil
}
var (
boolType = reflect.TypeOf(true)
integerType = reflect.TypeOf(0)
floatType = reflect.TypeOf(float64(0))
stringType = reflect.TypeOf("")
)
func patchWithType(node *Node, newNode Node) {
switch n := newNode.(type) {
case *BoolNode:
newNode.SetType(boolType)
case *IntegerNode:
newNode.SetType(integerType)
case *FloatNode:
newNode.SetType(floatType)
case *StringNode:
newNode.SetType(stringType)
case *ConstantNode:
newNode.SetType(reflect.TypeOf(n.Value))
case *BinaryNode:
newNode.SetType(n.Type())
default:
panic(fmt.Sprintf("unknown type %T", newNode))
}
Patch(node, newNode)
}
func patchCopyType(node *Node, newNode Node) {
t := (*node).Type()
newNode.SetType(t)
Patch(node, newNode)
}

View File

@@ -0,0 +1,61 @@
package optimizer
import (
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/parser/operator"
)
/*
predicateCombination is a visitor that combines multiple predicate calls into a single call.
For example, the following expression:
all(x, x > 1) && all(x, x < 10) -> all(x, x > 1 && x < 10)
any(x, x > 1) || any(x, x < 10) -> any(x, x > 1 || x < 10)
none(x, x > 1) && none(x, x < 10) -> none(x, x > 1 || x < 10)
*/
type predicateCombination struct{}
func (v *predicateCombination) Visit(node *Node) {
if op, ok := (*node).(*BinaryNode); ok && operator.IsBoolean(op.Operator) {
if left, ok := op.Left.(*BuiltinNode); ok {
if combinedOp, ok := combinedOperator(left.Name, op.Operator); ok {
if right, ok := op.Right.(*BuiltinNode); ok && right.Name == left.Name {
if left.Arguments[0].Type() == right.Arguments[0].Type() && left.Arguments[0].String() == right.Arguments[0].String() {
predicate := &PredicateNode{
Node: &BinaryNode{
Operator: combinedOp,
Left: left.Arguments[1].(*PredicateNode).Node,
Right: right.Arguments[1].(*PredicateNode).Node,
},
}
v.Visit(&predicate.Node)
patchCopyType(node, &BuiltinNode{
Name: left.Name,
Arguments: []Node{
left.Arguments[0],
predicate,
},
})
}
}
}
}
}
}
func combinedOperator(fn, op string) (string, bool) {
switch {
case fn == "all" && (op == "and" || op == "&&"):
return op, true
case fn == "any" && (op == "or" || op == "||"):
return op, true
case fn == "none" && (op == "and" || op == "&&"):
switch op {
case "and":
return "or", true
case "&&":
return "||", true
}
}
return "", false
}

View File

@@ -0,0 +1,37 @@
package optimizer
import (
"fmt"
. "github.com/expr-lang/expr/ast"
)
type sumArray struct{}
func (*sumArray) Visit(node *Node) {
if sumBuiltin, ok := (*node).(*BuiltinNode); ok &&
sumBuiltin.Name == "sum" &&
len(sumBuiltin.Arguments) == 1 {
if array, ok := sumBuiltin.Arguments[0].(*ArrayNode); ok &&
len(array.Nodes) >= 2 {
patchCopyType(node, sumArrayFold(array))
}
}
}
func sumArrayFold(array *ArrayNode) *BinaryNode {
if len(array.Nodes) > 2 {
return &BinaryNode{
Operator: "+",
Left: array.Nodes[0],
Right: sumArrayFold(&ArrayNode{Nodes: array.Nodes[1:]}),
}
} else if len(array.Nodes) == 2 {
return &BinaryNode{
Operator: "+",
Left: array.Nodes[0],
Right: array.Nodes[1],
}
}
panic(fmt.Errorf("sumArrayFold: invalid array length %d", len(array.Nodes)))
}

25
vendor/github.com/expr-lang/expr/optimizer/sum_map.go generated vendored Normal file
View File

@@ -0,0 +1,25 @@
package optimizer
import (
. "github.com/expr-lang/expr/ast"
)
type sumMap struct{}
func (*sumMap) Visit(node *Node) {
if sumBuiltin, ok := (*node).(*BuiltinNode); ok &&
sumBuiltin.Name == "sum" &&
len(sumBuiltin.Arguments) == 1 {
if mapBuiltin, ok := sumBuiltin.Arguments[0].(*BuiltinNode); ok &&
mapBuiltin.Name == "map" &&
len(mapBuiltin.Arguments) == 2 {
patchCopyType(node, &BuiltinNode{
Name: "sum",
Arguments: []Node{
mapBuiltin.Arguments[0],
mapBuiltin.Arguments[1],
},
})
}
}
}

230
vendor/github.com/expr-lang/expr/parser/lexer/lexer.go generated vendored Normal file
View File

@@ -0,0 +1,230 @@
package lexer
import (
"fmt"
"strings"
"github.com/expr-lang/expr/file"
)
func Lex(source file.Source) ([]Token, error) {
l := &lexer{
source: source,
tokens: make([]Token, 0),
start: 0,
end: 0,
}
l.commit()
for state := root; state != nil; {
state = state(l)
}
if l.err != nil {
return nil, l.err.Bind(source)
}
return l.tokens, nil
}
type lexer struct {
source file.Source
tokens []Token
start, end int
err *file.Error
}
const eof rune = -1
func (l *lexer) commit() {
l.start = l.end
}
func (l *lexer) next() rune {
if l.end >= len(l.source) {
l.end++
return eof
}
r := l.source[l.end]
l.end++
return r
}
func (l *lexer) peek() rune {
r := l.next()
l.backup()
return r
}
func (l *lexer) backup() {
l.end--
}
func (l *lexer) emit(t Kind) {
l.emitValue(t, l.word())
}
func (l *lexer) emitValue(t Kind, value string) {
l.tokens = append(l.tokens, Token{
Location: file.Location{From: l.start, To: l.end},
Kind: t,
Value: value,
})
l.commit()
}
func (l *lexer) emitEOF() {
from := l.end - 2
if from < 0 {
from = 0
}
to := l.end - 1
if to < 0 {
to = 0
}
l.tokens = append(l.tokens, Token{
Location: file.Location{From: from, To: to},
Kind: EOF,
})
l.commit()
}
func (l *lexer) skip() {
l.commit()
}
func (l *lexer) word() string {
// TODO: boundary check is NOT needed here, but for some reason CI fuzz tests are failing.
if l.start > len(l.source) || l.end > len(l.source) {
return "__invalid__"
}
return string(l.source[l.start:l.end])
}
func (l *lexer) accept(valid string) bool {
if strings.ContainsRune(valid, l.next()) {
return true
}
l.backup()
return false
}
func (l *lexer) acceptRun(valid string) {
for strings.ContainsRune(valid, l.next()) {
}
l.backup()
}
func (l *lexer) skipSpaces() {
r := l.peek()
for ; r == ' '; r = l.peek() {
l.next()
}
l.skip()
}
func (l *lexer) acceptWord(word string) bool {
pos := l.end
l.skipSpaces()
for _, ch := range word {
if l.next() != ch {
l.end = pos
return false
}
}
if r := l.peek(); r != ' ' && r != eof {
l.end = pos
return false
}
return true
}
func (l *lexer) error(format string, args ...any) stateFn {
if l.err == nil { // show first error
l.err = &file.Error{
Location: file.Location{
From: l.end - 1,
To: l.end,
},
Message: fmt.Sprintf(format, args...),
}
}
return nil
}
func digitVal(ch rune) int {
switch {
case '0' <= ch && ch <= '9':
return int(ch - '0')
case 'a' <= lower(ch) && lower(ch) <= 'f':
return int(lower(ch) - 'a' + 10)
}
return 16 // larger than any legal digit val
}
func lower(ch rune) rune { return ('a' - 'A') | ch } // returns lower-case ch iff ch is ASCII letter
func (l *lexer) scanDigits(ch rune, base, n int) rune {
for n > 0 && digitVal(ch) < base {
ch = l.next()
n--
}
if n > 0 {
l.error("invalid char escape")
}
return ch
}
func (l *lexer) scanEscape(quote rune) rune {
ch := l.next() // read character after '/'
switch ch {
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', quote:
// nothing to do
ch = l.next()
case '0', '1', '2', '3', '4', '5', '6', '7':
ch = l.scanDigits(ch, 8, 3)
case 'x':
ch = l.scanDigits(l.next(), 16, 2)
case 'u':
ch = l.scanDigits(l.next(), 16, 4)
case 'U':
ch = l.scanDigits(l.next(), 16, 8)
default:
l.error("invalid char escape")
}
return ch
}
func (l *lexer) scanString(quote rune) (n int) {
ch := l.next() // read character after quote
for ch != quote {
if ch == '\n' || ch == eof {
l.error("literal not terminated")
return
}
if ch == '\\' {
ch = l.scanEscape(quote)
} else {
ch = l.next()
}
n++
}
return
}
func (l *lexer) scanRawString(quote rune) (n int) {
ch := l.next() // read character after back tick
for ch != quote {
if ch == eof {
l.error("literal not terminated")
return
}
ch = l.next()
n++
}
l.emitValue(String, string(l.source[l.start+1:l.end-1]))
return
}

226
vendor/github.com/expr-lang/expr/parser/lexer/state.go generated vendored Normal file
View File

@@ -0,0 +1,226 @@
package lexer
import (
"strings"
"github.com/expr-lang/expr/parser/utils"
)
type stateFn func(*lexer) stateFn
func root(l *lexer) stateFn {
switch r := l.next(); {
case r == eof:
l.emitEOF()
return nil
case utils.IsSpace(r):
l.skip()
return root
case r == '\'' || r == '"':
l.scanString(r)
str, err := unescape(l.word())
if err != nil {
l.error("%v", err)
}
l.emitValue(String, str)
case r == '`':
l.scanRawString(r)
case '0' <= r && r <= '9':
l.backup()
return number
case r == '?':
return questionMark
case r == '/':
return slash
case r == '#':
return pointer
case r == '|':
l.accept("|")
l.emit(Operator)
case r == ':':
l.accept(":")
l.emit(Operator)
case strings.ContainsRune("([{", r):
l.emit(Bracket)
case strings.ContainsRune(")]}", r):
l.emit(Bracket)
case strings.ContainsRune(",;%+-^", r): // single rune operator
l.emit(Operator)
case strings.ContainsRune("&!=*<>", r): // possible double rune operator
l.accept("&=*")
l.emit(Operator)
case r == '.':
l.backup()
return dot
case utils.IsAlphaNumeric(r):
l.backup()
return identifier
default:
return l.error("unrecognized character: %#U", r)
}
return root
}
func number(l *lexer) stateFn {
if !l.scanNumber() {
return l.error("bad number syntax: %q", l.word())
}
l.emit(Number)
return root
}
func (l *lexer) scanNumber() bool {
digits := "0123456789_"
// Is it hex?
if l.accept("0") {
// Note: Leading 0 does not mean octal in floats.
if l.accept("xX") {
digits = "0123456789abcdefABCDEF_"
} else if l.accept("oO") {
digits = "01234567_"
} else if l.accept("bB") {
digits = "01_"
}
}
l.acceptRun(digits)
end := l.end
if l.accept(".") {
// Lookup for .. operator: if after dot there is another dot (1..2), it maybe a range operator.
if l.peek() == '.' {
// We can't backup() here, as it would require two backups,
// and backup() func supports only one for now. So, save and
// restore it here.
l.end = end
return true
}
l.acceptRun(digits)
}
if l.accept("eE") {
l.accept("+-")
l.acceptRun(digits)
}
// Next thing mustn't be alphanumeric.
if utils.IsAlphaNumeric(l.peek()) {
l.next()
return false
}
return true
}
func dot(l *lexer) stateFn {
l.next()
if l.accept("0123456789") {
l.backup()
return number
}
l.accept(".")
l.emit(Operator)
return root
}
func identifier(l *lexer) stateFn {
loop:
for {
switch r := l.next(); {
case utils.IsAlphaNumeric(r):
// absorb
default:
l.backup()
switch l.word() {
case "not":
return not
case "in", "or", "and", "matches", "contains", "startsWith", "endsWith", "let", "if", "else":
l.emit(Operator)
default:
l.emit(Identifier)
}
break loop
}
}
return root
}
func not(l *lexer) stateFn {
l.emit(Operator)
l.skipSpaces()
end := l.end
// Get the next word.
for {
r := l.next()
if utils.IsAlphaNumeric(r) {
// absorb
} else {
l.backup()
break
}
}
switch l.word() {
case "in", "matches", "contains", "startsWith", "endsWith":
l.emit(Operator)
default:
l.end = end
}
return root
}
func questionMark(l *lexer) stateFn {
l.accept(".?")
l.emit(Operator)
return root
}
func slash(l *lexer) stateFn {
if l.accept("/") {
return singleLineComment
}
if l.accept("*") {
return multiLineComment
}
l.emit(Operator)
return root
}
func singleLineComment(l *lexer) stateFn {
for {
r := l.next()
if r == eof || r == '\n' {
break
}
}
l.skip()
return root
}
func multiLineComment(l *lexer) stateFn {
for {
r := l.next()
if r == eof {
return l.error("unclosed comment")
}
if r == '*' && l.accept("/") {
break
}
}
l.skip()
return root
}
func pointer(l *lexer) stateFn {
l.accept("#")
l.emit(Operator)
for {
switch r := l.next(); {
case utils.IsAlphaNumeric(r): // absorb
default:
l.backup()
if l.word() != "" {
l.emit(Identifier)
}
return root
}
}
}

47
vendor/github.com/expr-lang/expr/parser/lexer/token.go generated vendored Normal file
View File

@@ -0,0 +1,47 @@
package lexer
import (
"fmt"
"github.com/expr-lang/expr/file"
)
type Kind string
const (
Identifier Kind = "Identifier"
Number Kind = "Number"
String Kind = "String"
Operator Kind = "Operator"
Bracket Kind = "Bracket"
EOF Kind = "EOF"
)
type Token struct {
file.Location
Kind Kind
Value string
}
func (t Token) String() string {
if t.Value == "" {
return string(t.Kind)
}
return fmt.Sprintf("%s(%#v)", t.Kind, t.Value)
}
func (t Token) Is(kind Kind, values ...string) bool {
if len(values) == 0 {
return kind == t.Kind
}
for _, v := range values {
if v == t.Value {
goto found
}
}
return false
found:
return kind == t.Kind
}

186
vendor/github.com/expr-lang/expr/parser/lexer/utils.go generated vendored Normal file
View File

@@ -0,0 +1,186 @@
package lexer
import (
"fmt"
"math"
"strings"
"unicode/utf8"
)
var (
newlineNormalizer = strings.NewReplacer("\r\n", "\n", "\r", "\n")
)
// Unescape takes a quoted string, unquotes, and unescapes it.
func unescape(value string) (string, error) {
// All strings normalize newlines to the \n representation.
value = newlineNormalizer.Replace(value)
n := len(value)
// Nothing to unescape / decode.
if n < 2 {
return value, fmt.Errorf("unable to unescape string")
}
// Quoted string of some form, must have same first and last char.
if value[0] != value[n-1] || (value[0] != '"' && value[0] != '\'') {
return value, fmt.Errorf("unable to unescape string")
}
value = value[1 : n-1]
// The string contains escape characters.
// The following logic is adapted from `strconv/quote.go`
var runeTmp [utf8.UTFMax]byte
size := 3 * uint64(n) / 2
if size >= math.MaxInt {
return "", fmt.Errorf("too large string")
}
buf := make([]byte, 0, size)
for len(value) > 0 {
c, multibyte, rest, err := unescapeChar(value)
if err != nil {
return "", err
}
value = rest
if c < utf8.RuneSelf || !multibyte {
buf = append(buf, byte(c))
} else {
n := utf8.EncodeRune(runeTmp[:], c)
buf = append(buf, runeTmp[:n]...)
}
}
return string(buf), nil
}
// unescapeChar takes a string input and returns the following info:
//
// value - the escaped unicode rune at the front of the string.
// multibyte - whether the rune value might require multiple bytes to represent.
// tail - the remainder of the input string.
// err - error value, if the character could not be unescaped.
//
// When multibyte is true the return value may still fit within a single byte,
// but a multibyte conversion is attempted which is more expensive than when the
// value is known to fit within one byte.
func unescapeChar(s string) (value rune, multibyte bool, tail string, err error) {
// 1. Character is not an escape sequence.
switch c := s[0]; {
case c >= utf8.RuneSelf:
r, size := utf8.DecodeRuneInString(s)
return r, true, s[size:], nil
case c != '\\':
return rune(s[0]), false, s[1:], nil
}
// 2. Last character is the start of an escape sequence.
if len(s) <= 1 {
err = fmt.Errorf("unable to unescape string, found '\\' as last character")
return
}
c := s[1]
s = s[2:]
// 3. Common escape sequences shared with Google SQL
switch c {
case 'a':
value = '\a'
case 'b':
value = '\b'
case 'f':
value = '\f'
case 'n':
value = '\n'
case 'r':
value = '\r'
case 't':
value = '\t'
case 'v':
value = '\v'
case '\\':
value = '\\'
case '\'':
value = '\''
case '"':
value = '"'
case '`':
value = '`'
case '?':
value = '?'
// 4. Unicode escape sequences, reproduced from `strconv/quote.go`
case 'x', 'X', 'u', 'U':
n := 0
switch c {
case 'x', 'X':
n = 2
case 'u':
n = 4
case 'U':
n = 8
}
var v rune
if len(s) < n {
err = fmt.Errorf("unable to unescape string")
return
}
for j := 0; j < n; j++ {
x, ok := unhex(s[j])
if !ok {
err = fmt.Errorf("unable to unescape string")
return
}
v = v<<4 | x
}
s = s[n:]
if v > utf8.MaxRune {
err = fmt.Errorf("unable to unescape string")
return
}
value = v
multibyte = true
// 5. Octal escape sequences, must be three digits \[0-3][0-7][0-7]
case '0', '1', '2', '3':
if len(s) < 2 {
err = fmt.Errorf("unable to unescape octal sequence in string")
return
}
v := rune(c - '0')
for j := 0; j < 2; j++ {
x := s[j]
if x < '0' || x > '7' {
err = fmt.Errorf("unable to unescape octal sequence in string")
return
}
v = v*8 + rune(x-'0')
}
if v > utf8.MaxRune {
err = fmt.Errorf("unable to unescape string")
return
}
value = v
s = s[2:]
multibyte = true
// Unknown escape sequence.
default:
err = fmt.Errorf("unable to unescape string")
}
tail = s
return
}
func unhex(b byte) (rune, bool) {
c := rune(b)
switch {
case '0' <= c && c <= '9':
return c - '0', true
case 'a' <= c && c <= 'f':
return c - 'a' + 10, true
case 'A' <= c && c <= 'F':
return c - 'A' + 10, true
}
return 0, false
}

View File

@@ -0,0 +1,69 @@
package operator
type Associativity int
const (
Left Associativity = iota + 1
Right
)
type Operator struct {
Precedence int
Associativity Associativity
}
func Less(a, b string) bool {
return Binary[a].Precedence < Binary[b].Precedence
}
func IsBoolean(op string) bool {
return op == "and" || op == "or" || op == "&&" || op == "||"
}
func AllowedNegateSuffix(op string) bool {
switch op {
case "contains", "matches", "startsWith", "endsWith", "in":
return true
default:
return false
}
}
var Unary = map[string]Operator{
"not": {50, Left},
"!": {50, Left},
"-": {90, Left},
"+": {90, Left},
}
var Binary = map[string]Operator{
"|": {0, Left},
"or": {10, Left},
"||": {10, Left},
"and": {15, Left},
"&&": {15, Left},
"==": {20, Left},
"!=": {20, Left},
"<": {20, Left},
">": {20, Left},
">=": {20, Left},
"<=": {20, Left},
"in": {20, Left},
"matches": {20, Left},
"contains": {20, Left},
"startsWith": {20, Left},
"endsWith": {20, Left},
"..": {25, Left},
"+": {30, Left},
"-": {30, Left},
"*": {60, Left},
"/": {60, Left},
"%": {60, Left},
"**": {100, Right},
"^": {100, Right},
"??": {500, Left},
}
func IsComparison(op string) bool {
return op == "<" || op == ">" || op == ">=" || op == "<="
}

891
vendor/github.com/expr-lang/expr/parser/parser.go generated vendored Normal file
View File

@@ -0,0 +1,891 @@
package parser
import (
"fmt"
"math"
"strconv"
"strings"
. "github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/conf"
"github.com/expr-lang/expr/file"
. "github.com/expr-lang/expr/parser/lexer"
"github.com/expr-lang/expr/parser/operator"
"github.com/expr-lang/expr/parser/utils"
)
type arg byte
const (
expr arg = 1 << iota
predicate
)
const optional arg = 1 << 7
var predicates = map[string]struct {
args []arg
}{
"all": {[]arg{expr, predicate}},
"none": {[]arg{expr, predicate}},
"any": {[]arg{expr, predicate}},
"one": {[]arg{expr, predicate}},
"filter": {[]arg{expr, predicate}},
"map": {[]arg{expr, predicate}},
"count": {[]arg{expr, predicate | optional}},
"sum": {[]arg{expr, predicate | optional}},
"find": {[]arg{expr, predicate}},
"findIndex": {[]arg{expr, predicate}},
"findLast": {[]arg{expr, predicate}},
"findLastIndex": {[]arg{expr, predicate}},
"groupBy": {[]arg{expr, predicate}},
"sortBy": {[]arg{expr, predicate, expr | optional}},
"reduce": {[]arg{expr, predicate, expr | optional}},
}
type parser struct {
tokens []Token
current Token
pos int
err *file.Error
config *conf.Config
depth int // predicate call depth
nodeCount uint // tracks number of AST nodes created
}
func (p *parser) checkNodeLimit() error {
p.nodeCount++
if p.config == nil {
if p.nodeCount > conf.DefaultMaxNodes {
p.error("compilation failed: expression exceeds maximum allowed nodes")
return nil
}
return nil
}
if p.config.MaxNodes > 0 && p.nodeCount > p.config.MaxNodes {
p.error("compilation failed: expression exceeds maximum allowed nodes")
return nil
}
return nil
}
func (p *parser) createNode(n Node, loc file.Location) Node {
if err := p.checkNodeLimit(); err != nil {
return nil
}
if n == nil || p.err != nil {
return nil
}
n.SetLocation(loc)
return n
}
func (p *parser) createMemberNode(n *MemberNode, loc file.Location) *MemberNode {
if err := p.checkNodeLimit(); err != nil {
return nil
}
if n == nil || p.err != nil {
return nil
}
n.SetLocation(loc)
return n
}
type Tree struct {
Node Node
Source file.Source
}
func Parse(input string) (*Tree, error) {
return ParseWithConfig(input, nil)
}
func ParseWithConfig(input string, config *conf.Config) (*Tree, error) {
source := file.NewSource(input)
tokens, err := Lex(source)
if err != nil {
return nil, err
}
p := &parser{
tokens: tokens,
current: tokens[0],
config: config,
}
node := p.parseSequenceExpression()
if !p.current.Is(EOF) {
p.error("unexpected token %v", p.current)
}
tree := &Tree{
Node: node,
Source: source,
}
if p.err != nil {
return tree, p.err.Bind(source)
}
return tree, nil
}
func (p *parser) error(format string, args ...any) {
p.errorAt(p.current, format, args...)
}
func (p *parser) errorAt(token Token, format string, args ...any) {
if p.err == nil { // show first error
p.err = &file.Error{
Location: token.Location,
Message: fmt.Sprintf(format, args...),
}
}
}
func (p *parser) next() {
p.pos++
if p.pos >= len(p.tokens) {
p.error("unexpected end of expression")
return
}
p.current = p.tokens[p.pos]
}
func (p *parser) expect(kind Kind, values ...string) {
if p.current.Is(kind, values...) {
p.next()
return
}
p.error("unexpected token %v", p.current)
}
// parse functions
func (p *parser) parseSequenceExpression() Node {
nodes := []Node{p.parseExpression(0)}
for p.current.Is(Operator, ";") && p.err == nil {
p.next()
// If a trailing semicolon is present, break out.
if p.current.Is(EOF) {
break
}
nodes = append(nodes, p.parseExpression(0))
}
if len(nodes) == 1 {
return nodes[0]
}
return p.createNode(&SequenceNode{
Nodes: nodes,
}, nodes[0].Location())
}
func (p *parser) parseExpression(precedence int) Node {
if p.err != nil {
return nil
}
if precedence == 0 && p.current.Is(Operator, "let") {
return p.parseVariableDeclaration()
}
if precedence == 0 && p.current.Is(Operator, "if") {
return p.parseConditionalIf()
}
nodeLeft := p.parsePrimary()
prevOperator := ""
opToken := p.current
for opToken.Is(Operator) && p.err == nil {
negate := opToken.Is(Operator, "not")
var notToken Token
// Handle "not *" operator, like "not in" or "not contains".
if negate {
currentPos := p.pos
p.next()
if operator.AllowedNegateSuffix(p.current.Value) {
if op, ok := operator.Binary[p.current.Value]; ok && op.Precedence >= precedence {
notToken = p.current
opToken = p.current
} else {
p.pos = currentPos
p.current = opToken
break
}
} else {
p.error("unexpected token %v", p.current)
break
}
}
if op, ok := operator.Binary[opToken.Value]; ok && op.Precedence >= precedence {
p.next()
if opToken.Value == "|" {
identToken := p.current
p.expect(Identifier)
nodeLeft = p.parseCall(identToken, []Node{nodeLeft}, true)
goto next
}
if prevOperator == "??" && opToken.Value != "??" && !opToken.Is(Bracket, "(") {
p.errorAt(opToken, "Operator (%v) and coalesce expressions (??) cannot be mixed. Wrap either by parentheses.", opToken.Value)
break
}
if operator.IsComparison(opToken.Value) {
nodeLeft = p.parseComparison(nodeLeft, opToken, op.Precedence)
goto next
}
var nodeRight Node
if op.Associativity == operator.Left {
nodeRight = p.parseExpression(op.Precedence + 1)
} else {
nodeRight = p.parseExpression(op.Precedence)
}
nodeLeft = p.createNode(&BinaryNode{
Operator: opToken.Value,
Left: nodeLeft,
Right: nodeRight,
}, opToken.Location)
if nodeLeft == nil {
return nil
}
if negate {
nodeLeft = p.createNode(&UnaryNode{
Operator: "not",
Node: nodeLeft,
}, notToken.Location)
if nodeLeft == nil {
return nil
}
}
goto next
}
break
next:
prevOperator = opToken.Value
opToken = p.current
}
if precedence == 0 {
nodeLeft = p.parseConditional(nodeLeft)
}
return nodeLeft
}
func (p *parser) parseVariableDeclaration() Node {
p.expect(Operator, "let")
variableName := p.current
p.expect(Identifier)
p.expect(Operator, "=")
value := p.parseExpression(0)
p.expect(Operator, ";")
node := p.parseSequenceExpression()
return p.createNode(&VariableDeclaratorNode{
Name: variableName.Value,
Value: value,
Expr: node,
}, variableName.Location)
}
func (p *parser) parseConditionalIf() Node {
p.next()
nodeCondition := p.parseExpression(0)
p.expect(Bracket, "{")
expr1 := p.parseSequenceExpression()
p.expect(Bracket, "}")
p.expect(Operator, "else")
p.expect(Bracket, "{")
expr2 := p.parseSequenceExpression()
p.expect(Bracket, "}")
return &ConditionalNode{
Cond: nodeCondition,
Exp1: expr1,
Exp2: expr2,
}
}
func (p *parser) parseConditional(node Node) Node {
var expr1, expr2 Node
for p.current.Is(Operator, "?") && p.err == nil {
p.next()
if !p.current.Is(Operator, ":") {
expr1 = p.parseExpression(0)
p.expect(Operator, ":")
expr2 = p.parseExpression(0)
} else {
p.next()
expr1 = node
expr2 = p.parseExpression(0)
}
node = p.createNode(&ConditionalNode{
Cond: node,
Exp1: expr1,
Exp2: expr2,
}, p.current.Location)
if node == nil {
return nil
}
}
return node
}
func (p *parser) parsePrimary() Node {
token := p.current
if token.Is(Operator) {
if op, ok := operator.Unary[token.Value]; ok {
p.next()
expr := p.parseExpression(op.Precedence)
node := p.createNode(&UnaryNode{
Operator: token.Value,
Node: expr,
}, token.Location)
if node == nil {
return nil
}
return p.parsePostfixExpression(node)
}
}
if token.Is(Bracket, "(") {
p.next()
expr := p.parseSequenceExpression()
p.expect(Bracket, ")") // "an opened parenthesis is not properly closed"
return p.parsePostfixExpression(expr)
}
if p.depth > 0 {
if token.Is(Operator, "#") || token.Is(Operator, ".") {
name := ""
if token.Is(Operator, "#") {
p.next()
if p.current.Is(Identifier) {
name = p.current.Value
p.next()
}
}
node := p.createNode(&PointerNode{Name: name}, token.Location)
if node == nil {
return nil
}
return p.parsePostfixExpression(node)
}
}
if token.Is(Operator, "::") {
p.next()
token = p.current
p.expect(Identifier)
return p.parsePostfixExpression(p.parseCall(token, []Node{}, false))
}
return p.parseSecondary()
}
func (p *parser) parseSecondary() Node {
var node Node
token := p.current
switch token.Kind {
case Identifier:
p.next()
switch token.Value {
case "true":
node = p.createNode(&BoolNode{Value: true}, token.Location)
if node == nil {
return nil
}
return node
case "false":
node = p.createNode(&BoolNode{Value: false}, token.Location)
if node == nil {
return nil
}
return node
case "nil":
node = p.createNode(&NilNode{}, token.Location)
if node == nil {
return nil
}
return node
default:
if p.current.Is(Bracket, "(") {
node = p.parseCall(token, []Node{}, true)
} else {
node = p.createNode(&IdentifierNode{Value: token.Value}, token.Location)
if node == nil {
return nil
}
}
}
case Number:
p.next()
value := strings.Replace(token.Value, "_", "", -1)
var node Node
valueLower := strings.ToLower(value)
switch {
case strings.HasPrefix(valueLower, "0x"):
number, err := strconv.ParseInt(value, 0, 64)
if err != nil {
p.error("invalid hex literal: %v", err)
}
node = p.toIntegerNode(number)
case strings.ContainsAny(valueLower, ".e"):
number, err := strconv.ParseFloat(value, 64)
if err != nil {
p.error("invalid float literal: %v", err)
}
node = p.toFloatNode(number)
case strings.HasPrefix(valueLower, "0b"):
number, err := strconv.ParseInt(value, 0, 64)
if err != nil {
p.error("invalid binary literal: %v", err)
}
node = p.toIntegerNode(number)
case strings.HasPrefix(valueLower, "0o"):
number, err := strconv.ParseInt(value, 0, 64)
if err != nil {
p.error("invalid octal literal: %v", err)
}
node = p.toIntegerNode(number)
default:
number, err := strconv.ParseInt(value, 10, 64)
if err != nil {
p.error("invalid integer literal: %v", err)
}
node = p.toIntegerNode(number)
}
if node != nil {
node.SetLocation(token.Location)
}
return node
case String:
p.next()
node = p.createNode(&StringNode{Value: token.Value}, token.Location)
if node == nil {
return nil
}
default:
if token.Is(Bracket, "[") {
node = p.parseArrayExpression(token)
} else if token.Is(Bracket, "{") {
node = p.parseMapExpression(token)
} else {
p.error("unexpected token %v", token)
}
}
return p.parsePostfixExpression(node)
}
func (p *parser) toIntegerNode(number int64) Node {
if number > math.MaxInt {
p.error("integer literal is too large")
return nil
}
return p.createNode(&IntegerNode{Value: int(number)}, p.current.Location)
}
func (p *parser) toFloatNode(number float64) Node {
if number > math.MaxFloat64 {
p.error("float literal is too large")
return nil
}
return p.createNode(&FloatNode{Value: number}, p.current.Location)
}
func (p *parser) parseCall(token Token, arguments []Node, checkOverrides bool) Node {
var node Node
isOverridden := false
if p.config != nil {
isOverridden = p.config.IsOverridden(token.Value)
}
isOverridden = isOverridden && checkOverrides
if b, ok := predicates[token.Value]; ok && !isOverridden {
p.expect(Bracket, "(")
// In case of the pipe operator, the first argument is the left-hand side
// of the operator, so we do not parse it as an argument inside brackets.
args := b.args[len(arguments):]
for i, arg := range args {
if arg&optional == optional {
if p.current.Is(Bracket, ")") {
break
}
} else {
if p.current.Is(Bracket, ")") {
p.error("expected at least %d arguments", len(args))
}
}
if i > 0 {
p.expect(Operator, ",")
}
var node Node
switch {
case arg&expr == expr:
node = p.parseExpression(0)
case arg&predicate == predicate:
node = p.parsePredicate()
}
arguments = append(arguments, node)
}
// skip last comma
if p.current.Is(Operator, ",") {
p.next()
}
p.expect(Bracket, ")")
node = p.createNode(&BuiltinNode{
Name: token.Value,
Arguments: arguments,
}, token.Location)
if node == nil {
return nil
}
} else if _, ok := builtin.Index[token.Value]; ok && (p.config == nil || !p.config.Disabled[token.Value]) && !isOverridden {
node = p.createNode(&BuiltinNode{
Name: token.Value,
Arguments: p.parseArguments(arguments),
}, token.Location)
if node == nil {
return nil
}
} else {
callee := p.createNode(&IdentifierNode{Value: token.Value}, token.Location)
if callee == nil {
return nil
}
node = p.createNode(&CallNode{
Callee: callee,
Arguments: p.parseArguments(arguments),
}, token.Location)
if node == nil {
return nil
}
}
return node
}
func (p *parser) parseArguments(arguments []Node) []Node {
// If pipe operator is used, the first argument is the left-hand side
// of the operator, so we do not parse it as an argument inside brackets.
offset := len(arguments)
p.expect(Bracket, "(")
for !p.current.Is(Bracket, ")") && p.err == nil {
if len(arguments) > offset {
p.expect(Operator, ",")
}
if p.current.Is(Bracket, ")") {
break
}
node := p.parseExpression(0)
arguments = append(arguments, node)
}
p.expect(Bracket, ")")
return arguments
}
func (p *parser) parsePredicate() Node {
startToken := p.current
withBrackets := false
if p.current.Is(Bracket, "{") {
p.next()
withBrackets = true
}
p.depth++
var node Node
if withBrackets {
node = p.parseSequenceExpression()
} else {
node = p.parseExpression(0)
if p.current.Is(Operator, ";") {
p.error("wrap predicate with brackets { and }")
}
}
p.depth--
if withBrackets {
p.expect(Bracket, "}")
}
predicateNode := p.createNode(&PredicateNode{
Node: node,
}, startToken.Location)
if predicateNode == nil {
return nil
}
return predicateNode
}
func (p *parser) parseArrayExpression(token Token) Node {
nodes := make([]Node, 0)
p.expect(Bracket, "[")
for !p.current.Is(Bracket, "]") && p.err == nil {
if len(nodes) > 0 {
p.expect(Operator, ",")
if p.current.Is(Bracket, "]") {
goto end
}
}
node := p.parseExpression(0)
nodes = append(nodes, node)
}
end:
p.expect(Bracket, "]")
node := p.createNode(&ArrayNode{Nodes: nodes}, token.Location)
if node == nil {
return nil
}
return node
}
func (p *parser) parseMapExpression(token Token) Node {
p.expect(Bracket, "{")
nodes := make([]Node, 0)
for !p.current.Is(Bracket, "}") && p.err == nil {
if len(nodes) > 0 {
p.expect(Operator, ",")
if p.current.Is(Bracket, "}") {
goto end
}
if p.current.Is(Operator, ",") {
p.error("unexpected token %v", p.current)
}
}
var key Node
// Map key can be one of:
// * number
// * string
// * identifier, which is equivalent to a string
// * expression, which must be enclosed in parentheses -- (1 + 2)
if p.current.Is(Number) || p.current.Is(String) || p.current.Is(Identifier) {
key = p.createNode(&StringNode{Value: p.current.Value}, p.current.Location)
if key == nil {
return nil
}
p.next()
} else if p.current.Is(Bracket, "(") {
key = p.parseExpression(0)
} else {
p.error("a map key must be a quoted string, a number, a identifier, or an expression enclosed in parentheses (unexpected token %v)", p.current)
}
p.expect(Operator, ":")
node := p.parseExpression(0)
pair := p.createNode(&PairNode{Key: key, Value: node}, token.Location)
if pair == nil {
return nil
}
nodes = append(nodes, pair)
}
end:
p.expect(Bracket, "}")
node := p.createNode(&MapNode{Pairs: nodes}, token.Location)
if node == nil {
return nil
}
return node
}
func (p *parser) parsePostfixExpression(node Node) Node {
postfixToken := p.current
for (postfixToken.Is(Operator) || postfixToken.Is(Bracket)) && p.err == nil {
optional := postfixToken.Value == "?."
parseToken:
if postfixToken.Value == "." || postfixToken.Value == "?." {
p.next()
propertyToken := p.current
if optional && propertyToken.Is(Bracket, "[") {
postfixToken = propertyToken
goto parseToken
}
p.next()
if propertyToken.Kind != Identifier &&
// Operators like "not" and "matches" are valid methods or property names.
(propertyToken.Kind != Operator || !utils.IsValidIdentifier(propertyToken.Value)) {
p.error("expected name")
}
property := p.createNode(&StringNode{Value: propertyToken.Value}, propertyToken.Location)
if property == nil {
return nil
}
chainNode, isChain := node.(*ChainNode)
optional := postfixToken.Value == "?."
if isChain {
node = chainNode.Node
}
memberNode := p.createMemberNode(&MemberNode{
Node: node,
Property: property,
Optional: optional,
}, propertyToken.Location)
if memberNode == nil {
return nil
}
if p.current.Is(Bracket, "(") {
memberNode.Method = true
node = p.createNode(&CallNode{
Callee: memberNode,
Arguments: p.parseArguments([]Node{}),
}, propertyToken.Location)
if node == nil {
return nil
}
} else {
node = memberNode
}
if isChain || optional {
node = p.createNode(&ChainNode{Node: node}, propertyToken.Location)
if node == nil {
return nil
}
}
} else if postfixToken.Value == "[" {
p.next()
var from, to Node
if p.current.Is(Operator, ":") { // slice without from [:1]
p.next()
if !p.current.Is(Bracket, "]") { // slice without from and to [:]
to = p.parseExpression(0)
}
node = p.createNode(&SliceNode{
Node: node,
To: to,
}, postfixToken.Location)
if node == nil {
return nil
}
p.expect(Bracket, "]")
} else {
from = p.parseExpression(0)
if p.current.Is(Operator, ":") {
p.next()
if !p.current.Is(Bracket, "]") { // slice without to [1:]
to = p.parseExpression(0)
}
node = p.createNode(&SliceNode{
Node: node,
From: from,
To: to,
}, postfixToken.Location)
if node == nil {
return nil
}
p.expect(Bracket, "]")
} else {
// Slice operator [:] was not found,
// it should be just an index node.
node = p.createNode(&MemberNode{
Node: node,
Property: from,
Optional: optional,
}, postfixToken.Location)
if node == nil {
return nil
}
if optional {
node = p.createNode(&ChainNode{Node: node}, postfixToken.Location)
if node == nil {
return nil
}
}
p.expect(Bracket, "]")
}
}
} else {
break
}
postfixToken = p.current
}
return node
}
func (p *parser) parseComparison(left Node, token Token, precedence int) Node {
var rootNode Node
for {
comparator := p.parseExpression(precedence + 1)
cmpNode := p.createNode(&BinaryNode{
Operator: token.Value,
Left: left,
Right: comparator,
}, token.Location)
if cmpNode == nil {
return nil
}
if rootNode == nil {
rootNode = cmpNode
} else {
rootNode = p.createNode(&BinaryNode{
Operator: "&&",
Left: rootNode,
Right: cmpNode,
}, token.Location)
if rootNode == nil {
return nil
}
}
left = comparator
token = p.current
if !(token.Is(Operator) && operator.IsComparison(token.Value) && p.err == nil) {
break
}
p.next()
}
return rootNode
}

34
vendor/github.com/expr-lang/expr/parser/utils/utils.go generated vendored Normal file
View File

@@ -0,0 +1,34 @@
package utils
import (
"unicode"
"unicode/utf8"
)
func IsValidIdentifier(str string) bool {
if len(str) == 0 {
return false
}
h, w := utf8.DecodeRuneInString(str)
if !IsAlphabetic(h) {
return false
}
for _, r := range str[w:] {
if !IsAlphaNumeric(r) {
return false
}
}
return true
}
func IsSpace(r rune) bool {
return unicode.IsSpace(r)
}
func IsAlphaNumeric(r rune) bool {
return IsAlphabetic(r) || unicode.IsDigit(r)
}
func IsAlphabetic(r rune) bool {
return r == '_' || r == '$' || unicode.IsLetter(r)
}

View File

@@ -0,0 +1,147 @@
package patcher
import (
"fmt"
"reflect"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/checker/nature"
"github.com/expr-lang/expr/conf"
)
type OperatorOverloading struct {
Operator string // Operator token to overload.
Overloads []string // List of function names to replace operator with.
Env *nature.Nature // Env type.
Functions conf.FunctionsTable // Env functions.
applied bool // Flag to indicate if any changes were made to the tree.
}
func (p *OperatorOverloading) Visit(node *ast.Node) {
binaryNode, ok := (*node).(*ast.BinaryNode)
if !ok {
return
}
if binaryNode.Operator != p.Operator {
return
}
leftType := binaryNode.Left.Type()
rightType := binaryNode.Right.Type()
ret, fn, ok := p.FindSuitableOperatorOverload(leftType, rightType)
if ok {
newNode := &ast.CallNode{
Callee: &ast.IdentifierNode{Value: fn},
Arguments: []ast.Node{binaryNode.Left, binaryNode.Right},
}
newNode.SetType(ret)
ast.Patch(node, newNode)
p.applied = true
}
}
// Tracking must be reset before every walk over the AST tree
func (p *OperatorOverloading) Reset() {
p.applied = false
}
func (p *OperatorOverloading) ShouldRepeat() bool {
return p.applied
}
func (p *OperatorOverloading) FindSuitableOperatorOverload(l, r reflect.Type) (reflect.Type, string, bool) {
t, fn, ok := p.findSuitableOperatorOverloadInFunctions(l, r)
if !ok {
t, fn, ok = p.findSuitableOperatorOverloadInTypes(l, r)
}
return t, fn, ok
}
func (p *OperatorOverloading) findSuitableOperatorOverloadInTypes(l, r reflect.Type) (reflect.Type, string, bool) {
for _, fn := range p.Overloads {
fnType, ok := p.Env.Get(fn)
if !ok {
continue
}
firstInIndex := 0
if fnType.Method {
firstInIndex = 1 // As first argument to method is receiver.
}
ret, done := checkTypeSuits(fnType.Type, l, r, firstInIndex)
if done {
return ret, fn, true
}
}
return nil, "", false
}
func (p *OperatorOverloading) findSuitableOperatorOverloadInFunctions(l, r reflect.Type) (reflect.Type, string, bool) {
for _, fn := range p.Overloads {
fnType, ok := p.Functions[fn]
if !ok {
continue
}
firstInIndex := 0
for _, overload := range fnType.Types {
ret, done := checkTypeSuits(overload, l, r, firstInIndex)
if done {
return ret, fn, true
}
}
}
return nil, "", false
}
func checkTypeSuits(t reflect.Type, l reflect.Type, r reflect.Type, firstInIndex int) (reflect.Type, bool) {
firstArgType := t.In(firstInIndex)
secondArgType := t.In(firstInIndex + 1)
firstArgumentFit := l == firstArgType || (firstArgType.Kind() == reflect.Interface && (l == nil || l.Implements(firstArgType)))
secondArgumentFit := r == secondArgType || (secondArgType.Kind() == reflect.Interface && (r == nil || r.Implements(secondArgType)))
if firstArgumentFit && secondArgumentFit {
return t.Out(0), true
}
return nil, false
}
func (p *OperatorOverloading) Check() {
for _, fn := range p.Overloads {
fnType, foundType := p.Env.Get(fn)
fnFunc, foundFunc := p.Functions[fn]
if !foundFunc && (!foundType || fnType.Type.Kind() != reflect.Func) {
panic(fmt.Errorf("function %s for %s operator does not exist in the environment", fn, p.Operator))
}
if foundType {
checkType(fnType, fn, p.Operator)
}
if foundFunc {
checkFunc(fnFunc, fn, p.Operator)
}
}
}
func checkType(fnType nature.Nature, fn string, operator string) {
requiredNumIn := 2
if fnType.Method {
requiredNumIn = 3 // As first argument of method is receiver.
}
if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != 1 {
panic(fmt.Errorf("function %s for %s operator does not have a correct signature", fn, operator))
}
}
func checkFunc(fn *builtin.Function, name string, operator string) {
if len(fn.Types) == 0 {
panic(fmt.Errorf("function %q for %q operator misses types", name, operator))
}
for _, t := range fn.Types {
if t.NumIn() != 2 || t.NumOut() != 1 {
panic(fmt.Errorf("function %q for %q operator does not have a correct signature", name, operator))
}
}
}

View File

@@ -0,0 +1,45 @@
package patcher
import (
"reflect"
"github.com/expr-lang/expr/ast"
)
// WithContext adds WithContext.Name argument to all functions calls with a context.Context argument.
type WithContext struct {
Name string
}
// Visit adds WithContext.Name argument to all functions calls with a context.Context argument.
func (w WithContext) Visit(node *ast.Node) {
switch call := (*node).(type) {
case *ast.CallNode:
fn := call.Callee.Type()
if fn == nil {
return
}
if fn.Kind() != reflect.Func {
return
}
switch fn.NumIn() {
case 0:
return
case 1:
if fn.In(0).String() != "context.Context" {
return
}
default:
if fn.In(0).String() != "context.Context" &&
fn.In(1).String() != "context.Context" {
return
}
}
ast.Patch(node, &ast.CallNode{
Callee: call.Callee,
Arguments: append([]ast.Node{
&ast.IdentifierNode{Value: w.Name},
}, call.Arguments...),
})
}
}

View File

@@ -0,0 +1,25 @@
package patcher
import (
"time"
"github.com/expr-lang/expr/ast"
)
// WithTimezone passes Location to date() and now() functions.
type WithTimezone struct {
Location *time.Location
}
func (t WithTimezone) Visit(node *ast.Node) {
if btin, ok := (*node).(*ast.BuiltinNode); ok {
switch btin.Name {
case "date", "now":
loc := &ast.ConstantNode{Value: t.Location}
ast.Patch(node, &ast.BuiltinNode{
Name: btin.Name,
Arguments: append([]ast.Node{loc}, btin.Arguments...),
})
}
}
}

181
vendor/github.com/expr-lang/expr/types/types.go generated vendored Normal file
View File

@@ -0,0 +1,181 @@
package types
import (
"fmt"
"reflect"
"strings"
. "github.com/expr-lang/expr/checker/nature"
)
// Type is a type that can be used to represent a value.
type Type interface {
Nature() Nature
Equal(Type) bool
String() string
}
var (
Int = TypeOf(0)
Int8 = TypeOf(int8(0))
Int16 = TypeOf(int16(0))
Int32 = TypeOf(int32(0))
Int64 = TypeOf(int64(0))
Uint = TypeOf(uint(0))
Uint8 = TypeOf(uint8(0))
Uint16 = TypeOf(uint16(0))
Uint32 = TypeOf(uint32(0))
Uint64 = TypeOf(uint64(0))
Float = TypeOf(float32(0))
Float64 = TypeOf(float64(0))
String = TypeOf("")
Bool = TypeOf(true)
Nil = nilType{}
Any = anyType{}
)
func TypeOf(v any) Type {
if v == nil {
return Nil
}
return rtype{t: reflect.TypeOf(v)}
}
type anyType struct{}
func (anyType) Nature() Nature {
return Nature{Type: nil}
}
func (anyType) Equal(t Type) bool {
return true
}
func (anyType) String() string {
return "any"
}
type nilType struct{}
func (nilType) Nature() Nature {
return Nature{Nil: true}
}
func (nilType) Equal(t Type) bool {
if t == Any {
return true
}
return t == Nil
}
func (nilType) String() string {
return "nil"
}
type rtype struct {
t reflect.Type
}
func (r rtype) Nature() Nature {
return Nature{Type: r.t}
}
func (r rtype) Equal(t Type) bool {
if t == Any {
return true
}
if rt, ok := t.(rtype); ok {
return r.t.String() == rt.t.String()
}
return false
}
func (r rtype) String() string {
return r.t.String()
}
// Map represents a map[string]any type with defined keys.
type Map map[string]Type
const Extra = "[[__extra_keys__]]"
func (m Map) Nature() Nature {
nt := Nature{
Type: reflect.TypeOf(map[string]any{}),
Fields: make(map[string]Nature, len(m)),
Strict: true,
}
for k, v := range m {
if k == Extra {
nt.Strict = false
natureOfDefaultValue := v.Nature()
nt.DefaultMapValue = &natureOfDefaultValue
continue
}
nt.Fields[k] = v.Nature()
}
return nt
}
func (m Map) Equal(t Type) bool {
if t == Any {
return true
}
mt, ok := t.(Map)
if !ok {
return false
}
if len(m) != len(mt) {
return false
}
for k, v := range m {
if !v.Equal(mt[k]) {
return false
}
}
return true
}
func (m Map) String() string {
pairs := make([]string, 0, len(m))
for k, v := range m {
pairs = append(pairs, fmt.Sprintf("%s: %s", k, v.String()))
}
return fmt.Sprintf("Map{%s}", strings.Join(pairs, ", "))
}
// Array returns a type that represents an array of the given type.
func Array(of Type) Type {
return array{of}
}
type array struct {
of Type
}
func (a array) Nature() Nature {
of := a.of.Nature()
return Nature{
Type: reflect.TypeOf([]any{}),
Fields: make(map[string]Nature, 1),
ArrayOf: &of,
}
}
func (a array) Equal(t Type) bool {
if t == Any {
return true
}
at, ok := t.(array)
if !ok {
return false
}
if a.of.Equal(at.of) {
return true
}
return false
}
func (a array) String() string {
return fmt.Sprintf("Array{%s}", a.of.String())
}

6
vendor/github.com/expr-lang/expr/vm/debug.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
//go:build expr_debug
// +build expr_debug
package vm
const debug = true

6
vendor/github.com/expr-lang/expr/vm/debug_off.go generated vendored Normal file
View File

@@ -0,0 +1,6 @@
//go:build !expr_debug
// +build !expr_debug
package vm
const debug = false

View File

@@ -0,0 +1,370 @@
// Code generated by vm/func_types/main.go. DO NOT EDIT.
package vm
import (
"fmt"
"time"
)
var FuncTypes = []any{
1: new(func() time.Duration),
2: new(func() time.Month),
3: new(func() time.Time),
4: new(func() time.Weekday),
5: new(func() []interface{}),
6: new(func() []uint8),
7: new(func() interface{}),
8: new(func() bool),
9: new(func() uint8),
10: new(func() float32),
11: new(func() float64),
12: new(func() int),
13: new(func() int16),
14: new(func() int32),
15: new(func() int64),
16: new(func() int8),
17: new(func() map[string]interface{}),
18: new(func() int32),
19: new(func() string),
20: new(func() uint),
21: new(func() uint16),
22: new(func() uint32),
23: new(func() uint64),
24: new(func() uint8),
25: new(func(time.Duration) time.Duration),
26: new(func(time.Duration) time.Time),
27: new(func(time.Time) time.Duration),
28: new(func(time.Time) bool),
29: new(func([]interface{}) []interface{}),
30: new(func([]interface{}) interface{}),
31: new(func([]interface{}) map[string]interface{}),
32: new(func([]interface{}, string) string),
33: new(func([]uint8) string),
34: new(func([]string, string) string),
35: new(func(interface{}) []interface{}),
36: new(func(interface{}) interface{}),
37: new(func(interface{}) bool),
38: new(func(interface{}) float64),
39: new(func(interface{}) int),
40: new(func(interface{}) map[string]interface{}),
41: new(func(interface{}) string),
42: new(func(interface{}, interface{}) []interface{}),
43: new(func(interface{}, interface{}) interface{}),
44: new(func(interface{}, interface{}) bool),
45: new(func(interface{}, interface{}) string),
46: new(func(bool) bool),
47: new(func(bool) float64),
48: new(func(bool) int),
49: new(func(bool) string),
50: new(func(bool, bool) bool),
51: new(func(float32) float64),
52: new(func(float64) bool),
53: new(func(float64) float32),
54: new(func(float64) float64),
55: new(func(float64) int),
56: new(func(float64) string),
57: new(func(float64, float64) bool),
58: new(func(int) bool),
59: new(func(int) float64),
60: new(func(int) int),
61: new(func(int) string),
62: new(func(int, int) bool),
63: new(func(int, int) int),
64: new(func(int, int) string),
65: new(func(int16) int32),
66: new(func(int32) float64),
67: new(func(int32) int),
68: new(func(int32) int64),
69: new(func(int64) time.Time),
70: new(func(int8) int),
71: new(func(int8) int16),
72: new(func(string) []uint8),
73: new(func(string) []string),
74: new(func(string) bool),
75: new(func(string) float64),
76: new(func(string) int),
77: new(func(string) string),
78: new(func(string, uint8) int),
79: new(func(string, int) int),
80: new(func(string, int32) int),
81: new(func(string, string) bool),
82: new(func(string, string) string),
83: new(func(uint) float64),
84: new(func(uint) int),
85: new(func(uint) uint),
86: new(func(uint16) uint),
87: new(func(uint32) uint64),
88: new(func(uint64) float64),
89: new(func(uint64) int64),
90: new(func(uint8) uint8),
}
func (vm *VM) call(fn any, kind int) any {
switch kind {
case 1:
return fn.(func() time.Duration)()
case 2:
return fn.(func() time.Month)()
case 3:
return fn.(func() time.Time)()
case 4:
return fn.(func() time.Weekday)()
case 5:
return fn.(func() []interface{})()
case 6:
return fn.(func() []uint8)()
case 7:
return fn.(func() interface{})()
case 8:
return fn.(func() bool)()
case 9:
return fn.(func() uint8)()
case 10:
return fn.(func() float32)()
case 11:
return fn.(func() float64)()
case 12:
return fn.(func() int)()
case 13:
return fn.(func() int16)()
case 14:
return fn.(func() int32)()
case 15:
return fn.(func() int64)()
case 16:
return fn.(func() int8)()
case 17:
return fn.(func() map[string]interface{})()
case 18:
return fn.(func() int32)()
case 19:
return fn.(func() string)()
case 20:
return fn.(func() uint)()
case 21:
return fn.(func() uint16)()
case 22:
return fn.(func() uint32)()
case 23:
return fn.(func() uint64)()
case 24:
return fn.(func() uint8)()
case 25:
arg1 := vm.pop().(time.Duration)
return fn.(func(time.Duration) time.Duration)(arg1)
case 26:
arg1 := vm.pop().(time.Duration)
return fn.(func(time.Duration) time.Time)(arg1)
case 27:
arg1 := vm.pop().(time.Time)
return fn.(func(time.Time) time.Duration)(arg1)
case 28:
arg1 := vm.pop().(time.Time)
return fn.(func(time.Time) bool)(arg1)
case 29:
arg1 := vm.pop().([]interface{})
return fn.(func([]interface{}) []interface{})(arg1)
case 30:
arg1 := vm.pop().([]interface{})
return fn.(func([]interface{}) interface{})(arg1)
case 31:
arg1 := vm.pop().([]interface{})
return fn.(func([]interface{}) map[string]interface{})(arg1)
case 32:
arg2 := vm.pop().(string)
arg1 := vm.pop().([]interface{})
return fn.(func([]interface{}, string) string)(arg1, arg2)
case 33:
arg1 := vm.pop().([]uint8)
return fn.(func([]uint8) string)(arg1)
case 34:
arg2 := vm.pop().(string)
arg1 := vm.pop().([]string)
return fn.(func([]string, string) string)(arg1, arg2)
case 35:
arg1 := vm.pop()
return fn.(func(interface{}) []interface{})(arg1)
case 36:
arg1 := vm.pop()
return fn.(func(interface{}) interface{})(arg1)
case 37:
arg1 := vm.pop()
return fn.(func(interface{}) bool)(arg1)
case 38:
arg1 := vm.pop()
return fn.(func(interface{}) float64)(arg1)
case 39:
arg1 := vm.pop()
return fn.(func(interface{}) int)(arg1)
case 40:
arg1 := vm.pop()
return fn.(func(interface{}) map[string]interface{})(arg1)
case 41:
arg1 := vm.pop()
return fn.(func(interface{}) string)(arg1)
case 42:
arg2 := vm.pop()
arg1 := vm.pop()
return fn.(func(interface{}, interface{}) []interface{})(arg1, arg2)
case 43:
arg2 := vm.pop()
arg1 := vm.pop()
return fn.(func(interface{}, interface{}) interface{})(arg1, arg2)
case 44:
arg2 := vm.pop()
arg1 := vm.pop()
return fn.(func(interface{}, interface{}) bool)(arg1, arg2)
case 45:
arg2 := vm.pop()
arg1 := vm.pop()
return fn.(func(interface{}, interface{}) string)(arg1, arg2)
case 46:
arg1 := vm.pop().(bool)
return fn.(func(bool) bool)(arg1)
case 47:
arg1 := vm.pop().(bool)
return fn.(func(bool) float64)(arg1)
case 48:
arg1 := vm.pop().(bool)
return fn.(func(bool) int)(arg1)
case 49:
arg1 := vm.pop().(bool)
return fn.(func(bool) string)(arg1)
case 50:
arg2 := vm.pop().(bool)
arg1 := vm.pop().(bool)
return fn.(func(bool, bool) bool)(arg1, arg2)
case 51:
arg1 := vm.pop().(float32)
return fn.(func(float32) float64)(arg1)
case 52:
arg1 := vm.pop().(float64)
return fn.(func(float64) bool)(arg1)
case 53:
arg1 := vm.pop().(float64)
return fn.(func(float64) float32)(arg1)
case 54:
arg1 := vm.pop().(float64)
return fn.(func(float64) float64)(arg1)
case 55:
arg1 := vm.pop().(float64)
return fn.(func(float64) int)(arg1)
case 56:
arg1 := vm.pop().(float64)
return fn.(func(float64) string)(arg1)
case 57:
arg2 := vm.pop().(float64)
arg1 := vm.pop().(float64)
return fn.(func(float64, float64) bool)(arg1, arg2)
case 58:
arg1 := vm.pop().(int)
return fn.(func(int) bool)(arg1)
case 59:
arg1 := vm.pop().(int)
return fn.(func(int) float64)(arg1)
case 60:
arg1 := vm.pop().(int)
return fn.(func(int) int)(arg1)
case 61:
arg1 := vm.pop().(int)
return fn.(func(int) string)(arg1)
case 62:
arg2 := vm.pop().(int)
arg1 := vm.pop().(int)
return fn.(func(int, int) bool)(arg1, arg2)
case 63:
arg2 := vm.pop().(int)
arg1 := vm.pop().(int)
return fn.(func(int, int) int)(arg1, arg2)
case 64:
arg2 := vm.pop().(int)
arg1 := vm.pop().(int)
return fn.(func(int, int) string)(arg1, arg2)
case 65:
arg1 := vm.pop().(int16)
return fn.(func(int16) int32)(arg1)
case 66:
arg1 := vm.pop().(int32)
return fn.(func(int32) float64)(arg1)
case 67:
arg1 := vm.pop().(int32)
return fn.(func(int32) int)(arg1)
case 68:
arg1 := vm.pop().(int32)
return fn.(func(int32) int64)(arg1)
case 69:
arg1 := vm.pop().(int64)
return fn.(func(int64) time.Time)(arg1)
case 70:
arg1 := vm.pop().(int8)
return fn.(func(int8) int)(arg1)
case 71:
arg1 := vm.pop().(int8)
return fn.(func(int8) int16)(arg1)
case 72:
arg1 := vm.pop().(string)
return fn.(func(string) []uint8)(arg1)
case 73:
arg1 := vm.pop().(string)
return fn.(func(string) []string)(arg1)
case 74:
arg1 := vm.pop().(string)
return fn.(func(string) bool)(arg1)
case 75:
arg1 := vm.pop().(string)
return fn.(func(string) float64)(arg1)
case 76:
arg1 := vm.pop().(string)
return fn.(func(string) int)(arg1)
case 77:
arg1 := vm.pop().(string)
return fn.(func(string) string)(arg1)
case 78:
arg2 := vm.pop().(uint8)
arg1 := vm.pop().(string)
return fn.(func(string, uint8) int)(arg1, arg2)
case 79:
arg2 := vm.pop().(int)
arg1 := vm.pop().(string)
return fn.(func(string, int) int)(arg1, arg2)
case 80:
arg2 := vm.pop().(int32)
arg1 := vm.pop().(string)
return fn.(func(string, int32) int)(arg1, arg2)
case 81:
arg2 := vm.pop().(string)
arg1 := vm.pop().(string)
return fn.(func(string, string) bool)(arg1, arg2)
case 82:
arg2 := vm.pop().(string)
arg1 := vm.pop().(string)
return fn.(func(string, string) string)(arg1, arg2)
case 83:
arg1 := vm.pop().(uint)
return fn.(func(uint) float64)(arg1)
case 84:
arg1 := vm.pop().(uint)
return fn.(func(uint) int)(arg1)
case 85:
arg1 := vm.pop().(uint)
return fn.(func(uint) uint)(arg1)
case 86:
arg1 := vm.pop().(uint16)
return fn.(func(uint16) uint)(arg1)
case 87:
arg1 := vm.pop().(uint32)
return fn.(func(uint32) uint64)(arg1)
case 88:
arg1 := vm.pop().(uint64)
return fn.(func(uint64) float64)(arg1)
case 89:
arg1 := vm.pop().(uint64)
return fn.(func(uint64) int64)(arg1)
case 90:
arg1 := vm.pop().(uint8)
return fn.(func(uint8) uint8)(arg1)
}
panic(fmt.Sprintf("unknown function kind (%v)", kind))
}

88
vendor/github.com/expr-lang/expr/vm/opcodes.go generated vendored Normal file
View File

@@ -0,0 +1,88 @@
package vm
type Opcode byte
const (
OpInvalid Opcode = iota
OpPush
OpInt
OpPop
OpStore
OpLoadVar
OpLoadConst
OpLoadField
OpLoadFast
OpLoadMethod
OpLoadFunc
OpLoadEnv
OpFetch
OpFetchField
OpMethod
OpTrue
OpFalse
OpNil
OpNegate
OpNot
OpEqual
OpEqualInt
OpEqualString
OpJump
OpJumpIfTrue
OpJumpIfFalse
OpJumpIfNil
OpJumpIfNotNil
OpJumpIfEnd
OpJumpBackward
OpIn
OpLess
OpMore
OpLessOrEqual
OpMoreOrEqual
OpAdd
OpSubtract
OpMultiply
OpDivide
OpModulo
OpExponent
OpRange
OpMatches
OpMatchesConst
OpContains
OpStartsWith
OpEndsWith
OpSlice
OpCall
OpCall0
OpCall1
OpCall2
OpCall3
OpCallN
OpCallFast
OpCallSafe
OpCallTyped
OpCallBuiltin1
OpArray
OpMap
OpLen
OpCast
OpDeref
OpIncrementIndex
OpDecrementIndex
OpIncrementCount
OpGetIndex
OpGetCount
OpGetLen
OpGetAcc
OpSetAcc
OpSetIndex
OpPointer
OpThrow
OpCreate
OpGroupBy
OpSortBy
OpSort
OpProfileStart
OpProfileEnd
OpBegin
OpEnd // This opcode must be at the end of this list.
)

382
vendor/github.com/expr-lang/expr/vm/program.go generated vendored Normal file
View File

@@ -0,0 +1,382 @@
package vm
import (
"bytes"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"text/tabwriter"
"github.com/expr-lang/expr/ast"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/file"
"github.com/expr-lang/expr/vm/runtime"
)
// Program represents a compiled expression.
type Program struct {
Bytecode []Opcode
Arguments []int
Constants []any
source file.Source
node ast.Node
locations []file.Location
variables int
functions []Function
debugInfo map[string]string
span *Span
}
// NewProgram returns a new Program. It's used by the compiler.
func NewProgram(
source file.Source,
node ast.Node,
locations []file.Location,
variables int,
constants []any,
bytecode []Opcode,
arguments []int,
functions []Function,
debugInfo map[string]string,
span *Span,
) *Program {
return &Program{
source: source,
node: node,
locations: locations,
variables: variables,
Constants: constants,
Bytecode: bytecode,
Arguments: arguments,
functions: functions,
debugInfo: debugInfo,
span: span,
}
}
// Source returns origin file.Source.
func (program *Program) Source() file.Source {
return program.source
}
// Node returns origin ast.Node.
func (program *Program) Node() ast.Node {
return program.node
}
// Locations returns a slice of bytecode's locations.
func (program *Program) Locations() []file.Location {
return program.locations
}
// Disassemble returns opcodes as a string.
func (program *Program) Disassemble() string {
var buf bytes.Buffer
w := tabwriter.NewWriter(&buf, 0, 0, 2, ' ', 0)
program.DisassembleWriter(w)
_ = w.Flush()
return buf.String()
}
// DisassembleWriter takes a writer and writes opcodes to it.
func (program *Program) DisassembleWriter(w io.Writer) {
ip := 0
for ip < len(program.Bytecode) {
pp := ip
op := program.Bytecode[ip]
arg := program.Arguments[ip]
ip += 1
code := func(label string) {
_, _ = fmt.Fprintf(w, "%v\t%v\n", pp, label)
}
jump := func(label string) {
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t(%v)\n", pp, label, arg, ip+arg)
}
jumpBack := func(label string) {
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t(%v)\n", pp, label, arg, ip-arg)
}
argument := func(label string) {
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\n", pp, label, arg)
}
argumentWithInfo := func(label string, prefix string) {
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, program.debugInfo[fmt.Sprintf("%s_%d", prefix, arg)])
}
constant := func(label string) {
var c any
if arg < len(program.Constants) {
c = program.Constants[arg]
} else {
c = "out of range"
}
if r, ok := c.(*regexp.Regexp); ok {
c = r.String()
}
if field, ok := c.(*runtime.Field); ok {
c = fmt.Sprintf("{%v %v}", strings.Join(field.Path, "."), field.Index)
}
if method, ok := c.(*runtime.Method); ok {
c = fmt.Sprintf("{%v %v}", method.Name, method.Index)
}
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, c)
}
builtinArg := func(label string) {
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, label, arg, builtin.Builtins[arg].Name)
}
switch op {
case OpInvalid:
code("OpInvalid")
case OpPush:
constant("OpPush")
case OpInt:
argument("OpInt")
case OpPop:
code("OpPop")
case OpStore:
argumentWithInfo("OpStore", "var")
case OpLoadVar:
argumentWithInfo("OpLoadVar", "var")
case OpLoadConst:
constant("OpLoadConst")
case OpLoadField:
constant("OpLoadField")
case OpLoadFast:
constant("OpLoadFast")
case OpLoadMethod:
constant("OpLoadMethod")
case OpLoadFunc:
argumentWithInfo("OpLoadFunc", "func")
case OpLoadEnv:
code("OpLoadEnv")
case OpFetch:
code("OpFetch")
case OpFetchField:
constant("OpFetchField")
case OpMethod:
constant("OpMethod")
case OpTrue:
code("OpTrue")
case OpFalse:
code("OpFalse")
case OpNil:
code("OpNil")
case OpNegate:
code("OpNegate")
case OpNot:
code("OpNot")
case OpEqual:
code("OpEqual")
case OpEqualInt:
code("OpEqualInt")
case OpEqualString:
code("OpEqualString")
case OpJump:
jump("OpJump")
case OpJumpIfTrue:
jump("OpJumpIfTrue")
case OpJumpIfFalse:
jump("OpJumpIfFalse")
case OpJumpIfNil:
jump("OpJumpIfNil")
case OpJumpIfNotNil:
jump("OpJumpIfNotNil")
case OpJumpIfEnd:
jump("OpJumpIfEnd")
case OpJumpBackward:
jumpBack("OpJumpBackward")
case OpIn:
code("OpIn")
case OpLess:
code("OpLess")
case OpMore:
code("OpMore")
case OpLessOrEqual:
code("OpLessOrEqual")
case OpMoreOrEqual:
code("OpMoreOrEqual")
case OpAdd:
code("OpAdd")
case OpSubtract:
code("OpSubtract")
case OpMultiply:
code("OpMultiply")
case OpDivide:
code("OpDivide")
case OpModulo:
code("OpModulo")
case OpExponent:
code("OpExponent")
case OpRange:
code("OpRange")
case OpMatches:
code("OpMatches")
case OpMatchesConst:
constant("OpMatchesConst")
case OpContains:
code("OpContains")
case OpStartsWith:
code("OpStartsWith")
case OpEndsWith:
code("OpEndsWith")
case OpSlice:
code("OpSlice")
case OpCall:
argument("OpCall")
case OpCall0:
argumentWithInfo("OpCall0", "func")
case OpCall1:
argumentWithInfo("OpCall1", "func")
case OpCall2:
argumentWithInfo("OpCall2", "func")
case OpCall3:
argumentWithInfo("OpCall3", "func")
case OpCallN:
argument("OpCallN")
case OpCallFast:
argument("OpCallFast")
case OpCallSafe:
argument("OpCallSafe")
case OpCallTyped:
signature := reflect.TypeOf(FuncTypes[arg]).Elem().String()
_, _ = fmt.Fprintf(w, "%v\t%v\t<%v>\t%v\n", pp, "OpCallTyped", arg, signature)
case OpCallBuiltin1:
builtinArg("OpCallBuiltin1")
case OpArray:
code("OpArray")
case OpMap:
code("OpMap")
case OpLen:
code("OpLen")
case OpCast:
argument("OpCast")
case OpDeref:
code("OpDeref")
case OpIncrementIndex:
code("OpIncrementIndex")
case OpDecrementIndex:
code("OpDecrementIndex")
case OpIncrementCount:
code("OpIncrementCount")
case OpGetIndex:
code("OpGetIndex")
case OpGetCount:
code("OpGetCount")
case OpGetLen:
code("OpGetLen")
case OpGetAcc:
code("OpGetAcc")
case OpSetAcc:
code("OpSetAcc")
case OpSetIndex:
code("OpSetIndex")
case OpPointer:
code("OpPointer")
case OpThrow:
code("OpThrow")
case OpCreate:
argument("OpCreate")
case OpGroupBy:
code("OpGroupBy")
case OpSortBy:
code("OpSortBy")
case OpSort:
code("OpSort")
case OpProfileStart:
code("OpProfileStart")
case OpProfileEnd:
code("OpProfileEnd")
case OpBegin:
code("OpBegin")
case OpEnd:
code("OpEnd")
default:
_, _ = fmt.Fprintf(w, "%v\t%#x (unknown)\n", ip, op)
}
}
}

File diff suppressed because it is too large Load Diff

394
vendor/github.com/expr-lang/expr/vm/runtime/runtime.go generated vendored Normal file
View File

@@ -0,0 +1,394 @@
package runtime
//go:generate sh -c "go run ./helpers > ./helpers[generated].go"
import (
"fmt"
"math"
"reflect"
"github.com/expr-lang/expr/internal/deref"
)
func Fetch(from, i any) any {
v := reflect.ValueOf(from)
if v.Kind() == reflect.Invalid {
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
}
// Methods can be defined on any type.
if v.NumMethod() > 0 {
if methodName, ok := i.(string); ok {
method := v.MethodByName(methodName)
if method.IsValid() {
return method.Interface()
}
}
}
// Structs, maps, and slices can be access through a pointer or through
// a value, when they are accessed through a pointer we don't want to
// copy them to a value.
// De-reference everything if necessary (interface and pointers)
v = deref.Value(v)
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
index := ToInt(i)
l := v.Len()
if index < 0 {
index = l + index
}
if index < 0 || index >= l {
panic(fmt.Sprintf("index out of range: %v (array length is %v)", index, l))
}
value := v.Index(index)
if value.IsValid() {
return value.Interface()
}
case reflect.Map:
var value reflect.Value
if i == nil {
value = v.MapIndex(reflect.Zero(v.Type().Key()))
} else {
value = v.MapIndex(reflect.ValueOf(i))
}
if value.IsValid() {
return value.Interface()
} else {
elem := reflect.TypeOf(from).Elem()
return reflect.Zero(elem).Interface()
}
case reflect.Struct:
fieldName := i.(string)
value := v.FieldByNameFunc(func(name string) bool {
field, _ := v.Type().FieldByName(name)
if field.Tag.Get("expr") == fieldName {
return true
}
return name == fieldName
})
if value.IsValid() {
return value.Interface()
}
}
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
}
type Field struct {
Index []int
Path []string
}
func FetchField(from any, field *Field) any {
v := reflect.ValueOf(from)
if v.Kind() != reflect.Invalid {
v = reflect.Indirect(v)
// We can use v.FieldByIndex here, but it will panic if the field
// is not exists. And we need to recover() to generate a more
// user-friendly error message.
// Also, our fieldByIndex() function is slightly faster than the
// v.FieldByIndex() function as we don't need to verify what a field
// is a struct as we already did it on compilation step.
value := fieldByIndex(v, field)
if value.IsValid() {
return value.Interface()
}
}
panic(fmt.Sprintf("cannot get %v from %T", field.Path[0], from))
}
func fieldByIndex(v reflect.Value, field *Field) reflect.Value {
if len(field.Index) == 1 {
return v.Field(field.Index[0])
}
for i, x := range field.Index {
if i > 0 {
if v.Kind() == reflect.Ptr {
if v.IsNil() {
panic(fmt.Sprintf("cannot get %v from %v", field.Path[i], field.Path[i-1]))
}
v = v.Elem()
}
}
v = v.Field(x)
}
return v
}
type Method struct {
Index int
Name string
}
func FetchMethod(from any, method *Method) any {
v := reflect.ValueOf(from)
kind := v.Kind()
if kind != reflect.Invalid {
// Methods can be defined on any type, no need to dereference.
method := v.Method(method.Index)
if method.IsValid() {
return method.Interface()
}
}
panic(fmt.Sprintf("cannot fetch %v from %T", method.Name, from))
}
func Slice(array, from, to any) any {
v := reflect.ValueOf(array)
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.String:
length := v.Len()
a, b := ToInt(from), ToInt(to)
if a < 0 {
a = length + a
}
if a < 0 {
a = 0
}
if b < 0 {
b = length + b
}
if b < 0 {
b = 0
}
if b > length {
b = length
}
if a > b {
a = b
}
value := v.Slice(a, b)
if value.IsValid() {
return value.Interface()
}
case reflect.Ptr:
value := v.Elem()
if value.IsValid() {
return Slice(value.Interface(), from, to)
}
}
panic(fmt.Sprintf("cannot slice %v", from))
}
func In(needle any, array any) bool {
if array == nil {
return false
}
v := reflect.ValueOf(array)
switch v.Kind() {
case reflect.Array, reflect.Slice:
for i := 0; i < v.Len(); i++ {
value := v.Index(i)
if value.IsValid() {
if Equal(value.Interface(), needle) {
return true
}
}
}
return false
case reflect.Map:
var value reflect.Value
if needle == nil {
value = v.MapIndex(reflect.Zero(v.Type().Key()))
} else {
value = v.MapIndex(reflect.ValueOf(needle))
}
if value.IsValid() {
return true
}
return false
case reflect.Struct:
n := reflect.ValueOf(needle)
if !n.IsValid() || n.Kind() != reflect.String {
panic(fmt.Sprintf("cannot use %T as field name of %T", needle, array))
}
value := v.FieldByName(n.String())
if value.IsValid() {
return true
}
return false
case reflect.Ptr:
value := v.Elem()
if value.IsValid() {
return In(needle, value.Interface())
}
return false
}
panic(fmt.Sprintf(`operator "in" not defined on %T`, array))
}
func Len(a any) int {
v := reflect.ValueOf(a)
switch v.Kind() {
case reflect.Array, reflect.Slice, reflect.Map, reflect.String:
return v.Len()
default:
panic(fmt.Sprintf("invalid argument for len (type %T)", a))
}
}
func Negate(i any) any {
switch v := i.(type) {
case float32:
return -v
case float64:
return -v
case int:
return -v
case int8:
return -v
case int16:
return -v
case int32:
return -v
case int64:
return -v
case uint:
return -v
case uint8:
return -v
case uint16:
return -v
case uint32:
return -v
case uint64:
return -v
default:
panic(fmt.Sprintf("invalid operation: - %T", v))
}
}
func Exponent(a, b any) float64 {
return math.Pow(ToFloat64(a), ToFloat64(b))
}
func MakeRange(min, max int) []int {
size := max - min + 1
if size <= 0 {
return []int{}
}
rng := make([]int, size)
for i := range rng {
rng[i] = min + i
}
return rng
}
func ToInt(a any) int {
switch x := a.(type) {
case float32:
return int(x)
case float64:
return int(x)
case int:
return x
case int8:
return int(x)
case int16:
return int(x)
case int32:
return int(x)
case int64:
return int(x)
case uint:
return int(x)
case uint8:
return int(x)
case uint16:
return int(x)
case uint32:
return int(x)
case uint64:
return int(x)
default:
panic(fmt.Sprintf("invalid operation: int(%T)", x))
}
}
func ToInt64(a any) int64 {
switch x := a.(type) {
case float32:
return int64(x)
case float64:
return int64(x)
case int:
return int64(x)
case int8:
return int64(x)
case int16:
return int64(x)
case int32:
return int64(x)
case int64:
return x
case uint:
return int64(x)
case uint8:
return int64(x)
case uint16:
return int64(x)
case uint32:
return int64(x)
case uint64:
return int64(x)
default:
panic(fmt.Sprintf("invalid operation: int64(%T)", x))
}
}
func ToFloat64(a any) float64 {
switch x := a.(type) {
case float32:
return float64(x)
case float64:
return x
case int:
return float64(x)
case int8:
return float64(x)
case int16:
return float64(x)
case int32:
return float64(x)
case int64:
return float64(x)
case uint:
return float64(x)
case uint8:
return float64(x)
case uint16:
return float64(x)
case uint32:
return float64(x)
case uint64:
return float64(x)
default:
panic(fmt.Sprintf("invalid operation: float(%T)", x))
}
}
func IsNil(v any) bool {
if v == nil {
return true
}
r := reflect.ValueOf(v)
switch r.Kind() {
case reflect.Chan, reflect.Func, reflect.Map, reflect.Ptr, reflect.Interface, reflect.Slice:
return r.IsNil()
default:
return false
}
}

45
vendor/github.com/expr-lang/expr/vm/runtime/sort.go generated vendored Normal file
View File

@@ -0,0 +1,45 @@
package runtime
type SortBy struct {
Desc bool
Array []any
Values []any
}
func (s *SortBy) Len() int {
return len(s.Array)
}
func (s *SortBy) Swap(i, j int) {
s.Array[i], s.Array[j] = s.Array[j], s.Array[i]
s.Values[i], s.Values[j] = s.Values[j], s.Values[i]
}
func (s *SortBy) Less(i, j int) bool {
a, b := s.Values[i], s.Values[j]
if s.Desc {
return Less(b, a)
}
return Less(a, b)
}
type Sort struct {
Desc bool
Array []any
}
func (s *Sort) Len() int {
return len(s.Array)
}
func (s *Sort) Swap(i, j int) {
s.Array[i], s.Array[j] = s.Array[j], s.Array[i]
}
func (s *Sort) Less(i, j int) bool {
a, b := s.Array[i], s.Array[j]
if s.Desc {
return Less(b, a)
}
return Less(a, b)
}

37
vendor/github.com/expr-lang/expr/vm/utils.go generated vendored Normal file
View File

@@ -0,0 +1,37 @@
package vm
import (
"reflect"
"time"
)
type (
Function = func(params ...any) (any, error)
SafeFunction = func(params ...any) (any, uint, error)
)
var (
errorType = reflect.TypeOf((*error)(nil)).Elem()
)
type Scope struct {
Array reflect.Value
Index int
Len int
Count int
Acc any
}
type groupBy = map[any][]any
type Span struct {
Name string `json:"name"`
Expression string `json:"expression"`
Duration int64 `json:"duration"`
Children []*Span `json:"children"`
start time.Time
}
func GetSpan(program *Program) *Span {
return program.span
}

617
vendor/github.com/expr-lang/expr/vm/vm.go generated vendored Normal file
View File

@@ -0,0 +1,617 @@
package vm
//go:generate sh -c "go run ./func_types > ./func_types[generated].go"
import (
"fmt"
"reflect"
"regexp"
"sort"
"strings"
"time"
"github.com/expr-lang/expr/builtin"
"github.com/expr-lang/expr/conf"
"github.com/expr-lang/expr/file"
"github.com/expr-lang/expr/internal/deref"
"github.com/expr-lang/expr/vm/runtime"
)
func Run(program *Program, env any) (any, error) {
if program == nil {
return nil, fmt.Errorf("program is nil")
}
vm := VM{}
return vm.Run(program, env)
}
func Debug() *VM {
vm := &VM{
debug: true,
step: make(chan struct{}, 0),
curr: make(chan int, 0),
}
return vm
}
type VM struct {
Stack []any
Scopes []*Scope
Variables []any
MemoryBudget uint
ip int
memory uint
debug bool
step chan struct{}
curr chan int
}
func (vm *VM) Run(program *Program, env any) (_ any, err error) {
defer func() {
if r := recover(); r != nil {
var location file.Location
if vm.ip-1 < len(program.locations) {
location = program.locations[vm.ip-1]
}
f := &file.Error{
Location: location,
Message: fmt.Sprintf("%v", r),
}
if err, ok := r.(error); ok {
f.Wrap(err)
}
err = f.Bind(program.source)
}
}()
if vm.Stack == nil {
vm.Stack = make([]any, 0, 2)
} else {
vm.Stack = vm.Stack[0:0]
}
if vm.Scopes != nil {
vm.Scopes = vm.Scopes[0:0]
}
if len(vm.Variables) < program.variables {
vm.Variables = make([]any, program.variables)
}
if vm.MemoryBudget == 0 {
vm.MemoryBudget = conf.DefaultMemoryBudget
}
vm.memory = 0
vm.ip = 0
for vm.ip < len(program.Bytecode) {
if debug && vm.debug {
<-vm.step
}
op := program.Bytecode[vm.ip]
arg := program.Arguments[vm.ip]
vm.ip += 1
switch op {
case OpInvalid:
panic("invalid opcode")
case OpPush:
vm.push(program.Constants[arg])
case OpInt:
vm.push(arg)
case OpPop:
vm.pop()
case OpStore:
vm.Variables[arg] = vm.pop()
case OpLoadVar:
vm.push(vm.Variables[arg])
case OpLoadConst:
vm.push(runtime.Fetch(env, program.Constants[arg]))
case OpLoadField:
vm.push(runtime.FetchField(env, program.Constants[arg].(*runtime.Field)))
case OpLoadFast:
vm.push(env.(map[string]any)[program.Constants[arg].(string)])
case OpLoadMethod:
vm.push(runtime.FetchMethod(env, program.Constants[arg].(*runtime.Method)))
case OpLoadFunc:
vm.push(program.functions[arg])
case OpFetch:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Fetch(a, b))
case OpFetchField:
a := vm.pop()
vm.push(runtime.FetchField(a, program.Constants[arg].(*runtime.Field)))
case OpLoadEnv:
vm.push(env)
case OpMethod:
a := vm.pop()
vm.push(runtime.FetchMethod(a, program.Constants[arg].(*runtime.Method)))
case OpTrue:
vm.push(true)
case OpFalse:
vm.push(false)
case OpNil:
vm.push(nil)
case OpNegate:
v := runtime.Negate(vm.pop())
vm.push(v)
case OpNot:
v := vm.pop().(bool)
vm.push(!v)
case OpEqual:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Equal(a, b))
case OpEqualInt:
b := vm.pop()
a := vm.pop()
vm.push(a.(int) == b.(int))
case OpEqualString:
b := vm.pop()
a := vm.pop()
vm.push(a.(string) == b.(string))
case OpJump:
vm.ip += arg
case OpJumpIfTrue:
if vm.current().(bool) {
vm.ip += arg
}
case OpJumpIfFalse:
if !vm.current().(bool) {
vm.ip += arg
}
case OpJumpIfNil:
if runtime.IsNil(vm.current()) {
vm.ip += arg
}
case OpJumpIfNotNil:
if !runtime.IsNil(vm.current()) {
vm.ip += arg
}
case OpJumpIfEnd:
scope := vm.scope()
if scope.Index >= scope.Len {
vm.ip += arg
}
case OpJumpBackward:
vm.ip -= arg
case OpIn:
b := vm.pop()
a := vm.pop()
vm.push(runtime.In(a, b))
case OpLess:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Less(a, b))
case OpMore:
b := vm.pop()
a := vm.pop()
vm.push(runtime.More(a, b))
case OpLessOrEqual:
b := vm.pop()
a := vm.pop()
vm.push(runtime.LessOrEqual(a, b))
case OpMoreOrEqual:
b := vm.pop()
a := vm.pop()
vm.push(runtime.MoreOrEqual(a, b))
case OpAdd:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Add(a, b))
case OpSubtract:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Subtract(a, b))
case OpMultiply:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Multiply(a, b))
case OpDivide:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Divide(a, b))
case OpModulo:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Modulo(a, b))
case OpExponent:
b := vm.pop()
a := vm.pop()
vm.push(runtime.Exponent(a, b))
case OpRange:
b := vm.pop()
a := vm.pop()
min := runtime.ToInt(a)
max := runtime.ToInt(b)
size := max - min + 1
if size <= 0 {
size = 0
}
vm.memGrow(uint(size))
vm.push(runtime.MakeRange(min, max))
case OpMatches:
b := vm.pop()
a := vm.pop()
if runtime.IsNil(a) || runtime.IsNil(b) {
vm.push(false)
break
}
match, err := regexp.MatchString(b.(string), a.(string))
if err != nil {
panic(err)
}
vm.push(match)
case OpMatchesConst:
a := vm.pop()
if runtime.IsNil(a) {
vm.push(false)
break
}
r := program.Constants[arg].(*regexp.Regexp)
vm.push(r.MatchString(a.(string)))
case OpContains:
b := vm.pop()
a := vm.pop()
if runtime.IsNil(a) || runtime.IsNil(b) {
vm.push(false)
break
}
vm.push(strings.Contains(a.(string), b.(string)))
case OpStartsWith:
b := vm.pop()
a := vm.pop()
if runtime.IsNil(a) || runtime.IsNil(b) {
vm.push(false)
break
}
vm.push(strings.HasPrefix(a.(string), b.(string)))
case OpEndsWith:
b := vm.pop()
a := vm.pop()
if runtime.IsNil(a) || runtime.IsNil(b) {
vm.push(false)
break
}
vm.push(strings.HasSuffix(a.(string), b.(string)))
case OpSlice:
from := vm.pop()
to := vm.pop()
node := vm.pop()
vm.push(runtime.Slice(node, from, to))
case OpCall:
fn := reflect.ValueOf(vm.pop())
size := arg
in := make([]reflect.Value, size)
for i := int(size) - 1; i >= 0; i-- {
param := vm.pop()
if param == nil {
in[i] = reflect.Zero(fn.Type().In(i))
} else {
in[i] = reflect.ValueOf(param)
}
}
out := fn.Call(in)
if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
panic(out[1].Interface().(error))
}
vm.push(out[0].Interface())
case OpCall0:
out, err := program.functions[arg]()
if err != nil {
panic(err)
}
vm.push(out)
case OpCall1:
a := vm.pop()
out, err := program.functions[arg](a)
if err != nil {
panic(err)
}
vm.push(out)
case OpCall2:
b := vm.pop()
a := vm.pop()
out, err := program.functions[arg](a, b)
if err != nil {
panic(err)
}
vm.push(out)
case OpCall3:
c := vm.pop()
b := vm.pop()
a := vm.pop()
out, err := program.functions[arg](a, b, c)
if err != nil {
panic(err)
}
vm.push(out)
case OpCallN:
fn := vm.pop().(Function)
size := arg
in := make([]any, size)
for i := int(size) - 1; i >= 0; i-- {
in[i] = vm.pop()
}
out, err := fn(in...)
if err != nil {
panic(err)
}
vm.push(out)
case OpCallFast:
fn := vm.pop().(func(...any) any)
size := arg
in := make([]any, size)
for i := int(size) - 1; i >= 0; i-- {
in[i] = vm.pop()
}
vm.push(fn(in...))
case OpCallSafe:
fn := vm.pop().(SafeFunction)
size := arg
in := make([]any, size)
for i := int(size) - 1; i >= 0; i-- {
in[i] = vm.pop()
}
out, mem, err := fn(in...)
if err != nil {
panic(err)
}
vm.memGrow(mem)
vm.push(out)
case OpCallTyped:
vm.push(vm.call(vm.pop(), arg))
case OpCallBuiltin1:
vm.push(builtin.Builtins[arg].Fast(vm.pop()))
case OpArray:
size := vm.pop().(int)
vm.memGrow(uint(size))
array := make([]any, size)
for i := size - 1; i >= 0; i-- {
array[i] = vm.pop()
}
vm.push(array)
case OpMap:
size := vm.pop().(int)
vm.memGrow(uint(size))
m := make(map[string]any)
for i := size - 1; i >= 0; i-- {
value := vm.pop()
key := vm.pop()
m[key.(string)] = value
}
vm.push(m)
case OpLen:
vm.push(runtime.Len(vm.current()))
case OpCast:
switch arg {
case 0:
vm.push(runtime.ToInt(vm.pop()))
case 1:
vm.push(runtime.ToInt64(vm.pop()))
case 2:
vm.push(runtime.ToFloat64(vm.pop()))
}
case OpDeref:
a := vm.pop()
vm.push(deref.Interface(a))
case OpIncrementIndex:
vm.scope().Index++
case OpDecrementIndex:
scope := vm.scope()
scope.Index--
case OpIncrementCount:
scope := vm.scope()
scope.Count++
case OpGetIndex:
vm.push(vm.scope().Index)
case OpGetCount:
scope := vm.scope()
vm.push(scope.Count)
case OpGetLen:
scope := vm.scope()
vm.push(scope.Len)
case OpGetAcc:
vm.push(vm.scope().Acc)
case OpSetAcc:
vm.scope().Acc = vm.pop()
case OpSetIndex:
scope := vm.scope()
scope.Index = vm.pop().(int)
case OpPointer:
scope := vm.scope()
vm.push(scope.Array.Index(scope.Index).Interface())
case OpThrow:
panic(vm.pop().(error))
case OpCreate:
switch arg {
case 1:
vm.push(make(groupBy))
case 2:
scope := vm.scope()
var desc bool
switch vm.pop().(string) {
case "asc":
desc = false
case "desc":
desc = true
default:
panic("unknown order, use asc or desc")
}
vm.push(&runtime.SortBy{
Desc: desc,
Array: make([]any, 0, scope.Len),
Values: make([]any, 0, scope.Len),
})
default:
panic(fmt.Sprintf("unknown OpCreate argument %v", arg))
}
case OpGroupBy:
scope := vm.scope()
key := vm.pop()
item := scope.Array.Index(scope.Index).Interface()
scope.Acc.(groupBy)[key] = append(scope.Acc.(groupBy)[key], item)
case OpSortBy:
scope := vm.scope()
value := vm.pop()
item := scope.Array.Index(scope.Index).Interface()
sortable := scope.Acc.(*runtime.SortBy)
sortable.Array = append(sortable.Array, item)
sortable.Values = append(sortable.Values, value)
case OpSort:
scope := vm.scope()
sortable := scope.Acc.(*runtime.SortBy)
sort.Sort(sortable)
vm.memGrow(uint(scope.Len))
vm.push(sortable.Array)
case OpProfileStart:
span := program.Constants[arg].(*Span)
span.start = time.Now()
case OpProfileEnd:
span := program.Constants[arg].(*Span)
span.Duration += time.Since(span.start).Nanoseconds()
case OpBegin:
a := vm.pop()
array := reflect.ValueOf(a)
vm.Scopes = append(vm.Scopes, &Scope{
Array: array,
Len: array.Len(),
})
case OpEnd:
vm.Scopes = vm.Scopes[:len(vm.Scopes)-1]
default:
panic(fmt.Sprintf("unknown bytecode %#x", op))
}
if debug && vm.debug {
vm.curr <- vm.ip
}
}
if debug && vm.debug {
close(vm.curr)
close(vm.step)
}
if len(vm.Stack) > 0 {
return vm.pop(), nil
}
return nil, nil
}
func (vm *VM) push(value any) {
vm.Stack = append(vm.Stack, value)
}
func (vm *VM) current() any {
return vm.Stack[len(vm.Stack)-1]
}
func (vm *VM) pop() any {
value := vm.Stack[len(vm.Stack)-1]
vm.Stack = vm.Stack[:len(vm.Stack)-1]
return value
}
func (vm *VM) memGrow(size uint) {
vm.memory += size
if vm.memory >= vm.MemoryBudget {
panic("memory budget exceeded")
}
}
func (vm *VM) scope() *Scope {
return vm.Scopes[len(vm.Scopes)-1]
}
func (vm *VM) Step() {
vm.step <- struct{}{}
}
func (vm *VM) Position() chan int {
return vm.curr
}