mirror of https://github.com/miguelmota/cointop
Add vendor/github.com/antonmedv
parent
09b66439fc
commit
9e41452eed
@ -0,0 +1,7 @@
|
||||
*.exe
|
||||
*.exe~
|
||||
*.dll
|
||||
*.so
|
||||
*.dylib
|
||||
*.test
|
||||
*.out
|
@ -0,0 +1,3 @@
|
||||
language: go
|
||||
go:
|
||||
- 1.13.x
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2019 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.
|
@ -0,0 +1,163 @@
|
||||
# Expr
|
||||
[![Build Status](https://travis-ci.org/antonmedv/expr.svg?branch=master)](https://travis-ci.org/antonmedv/expr)
|
||||
[![Go Report Card](https://goreportcard.com/badge/github.com/antonmedv/expr)](https://goreportcard.com/report/github.com/antonmedv/expr)
|
||||
[![GoDoc](https://godoc.org/github.com/antonmedv/expr?status.svg)](https://godoc.org/github.com/antonmedv/expr)
|
||||
|
||||
<img src="docs/images/logo-small.png" width="150" alt="expr logo" align="right">
|
||||
|
||||
**Expr** package provides an engine that can compile and evaluate expressions.
|
||||
An expression is a one-liner that returns a value (mostly, but not limited to, booleans).
|
||||
It is designed for simplicity, speed and safety.
|
||||
|
||||
The purpose of the package is to allow users to use expressions inside configuration for more complex logic.
|
||||
It is a perfect candidate for the foundation of a _business rule engine_.
|
||||
The idea is to let configure things in a dynamic way without recompile of a program:
|
||||
|
||||
```coffeescript
|
||||
# Get the special price if
|
||||
user.Group in ["good_customers", "collaborator"]
|
||||
|
||||
# Promote article to the homepage when
|
||||
len(article.Comments) > 100 and article.Category not in ["misc"]
|
||||
|
||||
# Send an alert when
|
||||
product.Stock < 15
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
* Seamless integration with Go (no need to redefine types)
|
||||
* Static typing ([example](https://godoc.org/github.com/antonmedv/expr#example-Env)).
|
||||
```go
|
||||
out, err := expr.Compile(`name + age`)
|
||||
// err: invalid operation + (mismatched types string and int)
|
||||
// | name + age
|
||||
// | .....^
|
||||
```
|
||||
* User-friendly error messages.
|
||||
* Reasonable set of basic operators.
|
||||
* Builtins `all`, `none`, `any`, `one`, `filter`, `map`.
|
||||
```coffeescript
|
||||
all(Tweets, {.Size <= 280})
|
||||
```
|
||||
* Fast ([benchmarks](https://github.com/antonmedv/golang-expression-evaluation-comparison#readme)): uses bytecode virtual machine and optimizing compiler.
|
||||
|
||||
## Install
|
||||
|
||||
```
|
||||
go get github.com/antonmedv/expr
|
||||
```
|
||||
|
||||
## Documentation
|
||||
|
||||
* See [Getting Started](docs/Getting-Started.md) page for developer documentation.
|
||||
* See [Language Definition](docs/Language-Definition.md) page to learn the syntax.
|
||||
|
||||
## Expr Code Editor
|
||||
|
||||
<a href="http://bit.ly/expr-code-editor">
|
||||
<img src="https://antonmedv.github.io/expr/ogimage.png" align="center" alt="Expr Code Editor" width="1200">
|
||||
</a>
|
||||
|
||||
Also, I have an embeddable code editor written in JavaScript which allows editing expressions with syntax highlighting and autocomplete based on your types declaration.
|
||||
|
||||
[Learn more →](https://antonmedv.github.io/expr/)
|
||||
|
||||
## Examples
|
||||
|
||||
[Play Online](https://play.golang.org/p/z7T8ytJ1T1d)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antonmedv/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://play.golang.org/p/4S4brsIvU4i)
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antonmedv/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)
|
||||
}
|
||||
```
|
||||
|
||||
## Contributing
|
||||
|
||||
**Expr** consist of a few packages for parsing source code to AST, type checking AST, compiling to bytecode and VM for running bytecode program.
|
||||
|
||||
Also expr provides powerful tool [exe](cmd/exe) for debugging. It has interactive terminal debugger for our bytecode virtual machine.
|
||||
|
||||
<p align="center">
|
||||
<img src="docs/images/debug.gif" alt="debugger" width="605">
|
||||
</p>
|
||||
|
||||
|
||||
## Who is using Expr?
|
||||
|
||||
* <a href="https://aviasales.ru"><img alt="Aviasales" height="18" src="https://cdn.worldvectorlogo.com/logos/aviasales-4.svg"></a> [Aviasales](https://aviasales.ru) are actively using Expr for different parts of the search engine.
|
||||
* <a href="https://argoproj.github.io/argo-rollouts/"><img alt="Argo" height="18" src="https://argoproj.github.io/argo-rollouts/assets/logo.png"></a> [Argo Rollouts](https://argoproj.github.io/argo-rollouts/) - Progressive Delivery for Kubernetes.
|
||||
* <a href="https://argoproj.github.io/argo/"><img alt="Argo" height="18" src="https://argoproj.github.io/argo/assets/logo.png"></a> [Argo Workflows](https://argoproj.github.io/argo/) - The workflow engine for KubernetesOverview.
|
||||
* <a href="https://crowdsec.net"><img alt="CrowdSec" height="18" src="https://crowdsec.net/wp-content/uploads/thegem-logos/logo_8b2bcaf21851f390f18ea9600e6a9fa3_1x.png"></a> [Crowdsec](https://crowdsec.net/) - A security automation tool.
|
||||
* [Mystery Minds](https://www.mysteryminds.com/en/) uses Expr to allow easy yet powerful customization of its matching algorithm.
|
||||
* <a href="https://www.qiniu.com/"><img height="18" src="https://www.qiniu.com/assets/img-horizontal-white-en-572b4c91fddcae4c9cf38ba89c9477397a2e1ffb74ec1c8f43e73cdfb860bbc6.png"></a> [qiniu](https://www.qiniu.com/) qiniu cloud use Expr in trade systems.
|
||||
|
||||
[Add your company too](https://github.com/antonmedv/expr/edit/master/README.md)
|
||||
|
||||
## License
|
||||
|
||||
[MIT](LICENSE)
|
@ -0,0 +1,171 @@
|
||||
package ast
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"regexp"
|
||||
|
||||
"github.com/antonmedv/expr/file"
|
||||
)
|
||||
|
||||
// Node represents items of abstract syntax tree.
|
||||
type Node interface {
|
||||
Location() file.Location
|
||||
SetLocation(file.Location)
|
||||
Type() reflect.Type
|
||||
SetType(reflect.Type)
|
||||
}
|
||||
|
||||
func Patch(node *Node, newNode Node) {
|
||||
newNode.SetType((*node).Type())
|
||||
newNode.SetLocation((*node).Location())
|
||||
*node = newNode
|
||||
}
|
||||
|
||||
type base struct {
|
||||
loc file.Location
|
||||
nodeType reflect.Type
|
||||
}
|
||||
|
||||
func (n *base) Location() file.Location {
|
||||
return n.loc
|
||||
}
|
||||
|
||||
func (n *base) SetLocation(loc file.Location) {
|
||||
n.loc = loc
|
||||
}
|
||||
|
||||
func (n *base) Type() reflect.Type {
|
||||
return n.nodeType
|
||||
}
|
||||
|
||||
func (n *base) SetType(t reflect.Type) {
|
||||
n.nodeType = t
|
||||
}
|
||||
|
||||
type NilNode struct {
|
||||
base
|
||||
}
|
||||
|
||||
type IdentifierNode struct {
|
||||
base
|
||||
Value string
|
||||
NilSafe bool
|
||||
}
|
||||
|
||||
type IntegerNode struct {
|
||||
base
|
||||
Value int
|
||||
}
|
||||
|
||||
type FloatNode struct {
|
||||
base
|
||||
Value float64
|
||||
}
|
||||
|
||||
type BoolNode struct {
|
||||
base
|
||||
Value bool
|
||||
}
|
||||
|
||||
type StringNode struct {
|
||||
base
|
||||
Value string
|
||||
}
|
||||
|
||||
type ConstantNode struct {
|
||||
base
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type UnaryNode struct {
|
||||
base
|
||||
Operator string
|
||||
Node Node
|
||||
}
|
||||
|
||||
type BinaryNode struct {
|
||||
base
|
||||
Operator string
|
||||
Left Node
|
||||
Right Node
|
||||
}
|
||||
|
||||
type MatchesNode struct {
|
||||
base
|
||||
Regexp *regexp.Regexp
|
||||
Left Node
|
||||
Right Node
|
||||
}
|
||||
|
||||
type PropertyNode struct {
|
||||
base
|
||||
Node Node
|
||||
Property string
|
||||
NilSafe bool
|
||||
}
|
||||
|
||||
type IndexNode struct {
|
||||
base
|
||||
Node Node
|
||||
Index Node
|
||||
}
|
||||
|
||||
type SliceNode struct {
|
||||
base
|
||||
Node Node
|
||||
From Node
|
||||
To Node
|
||||
}
|
||||
|
||||
type MethodNode struct {
|
||||
base
|
||||
Node Node
|
||||
Method string
|
||||
Arguments []Node
|
||||
NilSafe bool
|
||||
}
|
||||
|
||||
type FunctionNode struct {
|
||||
base
|
||||
Name string
|
||||
Arguments []Node
|
||||
Fast bool
|
||||
}
|
||||
|
||||
type BuiltinNode struct {
|
||||
base
|
||||
Name string
|
||||
Arguments []Node
|
||||
}
|
||||
|
||||
type ClosureNode struct {
|
||||
base
|
||||
Node Node
|
||||
}
|
||||
|
||||
type PointerNode struct {
|
||||
base
|
||||
}
|
||||
|
||||
type ConditionalNode struct {
|
||||
base
|
||||
Cond Node
|
||||
Exp1 Node
|
||||
Exp2 Node
|
||||
}
|
||||
|
||||
type ArrayNode struct {
|
||||
base
|
||||
Nodes []Node
|
||||
}
|
||||
|
||||
type MapNode struct {
|
||||
base
|
||||
Pairs []Node
|
||||
}
|
||||
|
||||
type PairNode struct {
|
||||
base
|
||||
Key Node
|
||||
Value Node
|
||||
}
|
@ -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 "[]"
|
||||
}
|
||||
out := "[\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))
|
||||
}
|
@ -0,0 +1,108 @@
|
||||
package ast
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Visitor interface {
|
||||
Enter(node *Node)
|
||||
Exit(node *Node)
|
||||
}
|
||||
|
||||
type walker struct {
|
||||
visitor Visitor
|
||||
}
|
||||
|
||||
func Walk(node *Node, visitor Visitor) {
|
||||
w := walker{
|
||||
visitor: visitor,
|
||||
}
|
||||
w.walk(node)
|
||||
}
|
||||
|
||||
func (w *walker) walk(node *Node) {
|
||||
w.visitor.Enter(node)
|
||||
|
||||
switch n := (*node).(type) {
|
||||
case *NilNode:
|
||||
w.visitor.Exit(node)
|
||||
case *IdentifierNode:
|
||||
w.visitor.Exit(node)
|
||||
case *IntegerNode:
|
||||
w.visitor.Exit(node)
|
||||
case *FloatNode:
|
||||
w.visitor.Exit(node)
|
||||
case *BoolNode:
|
||||
w.visitor.Exit(node)
|
||||
case *StringNode:
|
||||
w.visitor.Exit(node)
|
||||
case *ConstantNode:
|
||||
w.visitor.Exit(node)
|
||||
case *UnaryNode:
|
||||
w.walk(&n.Node)
|
||||
w.visitor.Exit(node)
|
||||
case *BinaryNode:
|
||||
w.walk(&n.Left)
|
||||
w.walk(&n.Right)
|
||||
w.visitor.Exit(node)
|
||||
case *MatchesNode:
|
||||
w.walk(&n.Left)
|
||||
w.walk(&n.Right)
|
||||
w.visitor.Exit(node)
|
||||
case *PropertyNode:
|
||||
w.walk(&n.Node)
|
||||
w.visitor.Exit(node)
|
||||
case *IndexNode:
|
||||
w.walk(&n.Node)
|
||||
w.walk(&n.Index)
|
||||
w.visitor.Exit(node)
|
||||
case *SliceNode:
|
||||
if n.From != nil {
|
||||
w.walk(&n.From)
|
||||
}
|
||||
if n.To != nil {
|
||||
w.walk(&n.To)
|
||||
}
|
||||
w.visitor.Exit(node)
|
||||
case *MethodNode:
|
||||
w.walk(&n.Node)
|
||||
for i := range n.Arguments {
|
||||
w.walk(&n.Arguments[i])
|
||||
}
|
||||
w.visitor.Exit(node)
|
||||
case *FunctionNode:
|
||||
for i := range n.Arguments {
|
||||
w.walk(&n.Arguments[i])
|
||||
}
|
||||
w.visitor.Exit(node)
|
||||
case *BuiltinNode:
|
||||
for i := range n.Arguments {
|
||||
w.walk(&n.Arguments[i])
|
||||
}
|
||||
w.visitor.Exit(node)
|
||||
case *ClosureNode:
|
||||
w.walk(&n.Node)
|
||||
w.visitor.Exit(node)
|
||||
case *PointerNode:
|
||||
w.visitor.Exit(node)
|
||||
case *ConditionalNode:
|
||||
w.walk(&n.Cond)
|
||||
w.walk(&n.Exp1)
|
||||
w.walk(&n.Exp2)
|
||||
w.visitor.Exit(node)
|
||||
case *ArrayNode:
|
||||
for i := range n.Nodes {
|
||||
w.walk(&n.Nodes[i])
|
||||
}
|
||||
w.visitor.Exit(node)
|
||||
case *MapNode:
|
||||
for i := range n.Pairs {
|
||||
w.walk(&n.Pairs[i])
|
||||
}
|
||||
w.visitor.Exit(node)
|
||||
case *PairNode:
|
||||
w.walk(&n.Key)
|
||||
w.walk(&n.Value)
|
||||
w.visitor.Exit(node)
|
||||
default:
|
||||
panic(fmt.Sprintf("undefined node type (%T)", node))
|
||||
}
|
||||
}
|
@ -0,0 +1,615 @@
|
||||
package checker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/conf"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"github.com/antonmedv/expr/parser"
|
||||
)
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
func Check(tree *parser.Tree, config *conf.Config) (reflect.Type, error) {
|
||||
v := &visitor{
|
||||
collections: make([]reflect.Type, 0),
|
||||
}
|
||||
if config != nil {
|
||||
v.types = config.Types
|
||||
v.operators = config.Operators
|
||||
v.expect = config.Expect
|
||||
v.strict = config.Strict
|
||||
v.defaultType = config.DefaultType
|
||||
}
|
||||
|
||||
t := v.visit(tree.Node)
|
||||
|
||||
if v.expect != reflect.Invalid {
|
||||
switch v.expect {
|
||||
case reflect.Int64, reflect.Float64:
|
||||
if !isNumber(t) {
|
||||
return nil, fmt.Errorf("expected %v, but got %v", v.expect, t)
|
||||
}
|
||||
default:
|
||||
if t.Kind() != v.expect {
|
||||
return nil, fmt.Errorf("expected %v, but got %v", v.expect, t)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if v.err != nil {
|
||||
return t, v.err.Bind(tree.Source)
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
||||
type visitor struct {
|
||||
types conf.TypesTable
|
||||
operators conf.OperatorsTable
|
||||
expect reflect.Kind
|
||||
collections []reflect.Type
|
||||
strict bool
|
||||
defaultType reflect.Type
|
||||
err *file.Error
|
||||
}
|
||||
|
||||
func (v *visitor) visit(node ast.Node) reflect.Type {
|
||||
var t reflect.Type
|
||||
switch n := node.(type) {
|
||||
case *ast.NilNode:
|
||||
t = v.NilNode(n)
|
||||
case *ast.IdentifierNode:
|
||||
t = v.IdentifierNode(n)
|
||||
case *ast.IntegerNode:
|
||||
t = v.IntegerNode(n)
|
||||
case *ast.FloatNode:
|
||||
t = v.FloatNode(n)
|
||||
case *ast.BoolNode:
|
||||
t = v.BoolNode(n)
|
||||
case *ast.StringNode:
|
||||
t = v.StringNode(n)
|
||||
case *ast.ConstantNode:
|
||||
t = v.ConstantNode(n)
|
||||
case *ast.UnaryNode:
|
||||
t = v.UnaryNode(n)
|
||||
case *ast.BinaryNode:
|
||||
t = v.BinaryNode(n)
|
||||
case *ast.MatchesNode:
|
||||
t = v.MatchesNode(n)
|
||||
case *ast.PropertyNode:
|
||||
t = v.PropertyNode(n)
|
||||
case *ast.IndexNode:
|
||||
t = v.IndexNode(n)
|
||||
case *ast.SliceNode:
|
||||
t = v.SliceNode(n)
|
||||
case *ast.MethodNode:
|
||||
t = v.MethodNode(n)
|
||||
case *ast.FunctionNode:
|
||||
t = v.FunctionNode(n)
|
||||
case *ast.BuiltinNode:
|
||||
t = v.BuiltinNode(n)
|
||||
case *ast.ClosureNode:
|
||||
t = v.ClosureNode(n)
|
||||
case *ast.PointerNode:
|
||||
t = v.PointerNode(n)
|
||||
case *ast.ConditionalNode:
|
||||
t = v.ConditionalNode(n)
|
||||
case *ast.ArrayNode:
|
||||
t = v.ArrayNode(n)
|
||||
case *ast.MapNode:
|
||||
t = v.MapNode(n)
|
||||
case *ast.PairNode:
|
||||
t = v.PairNode(n)
|
||||
default:
|
||||
panic(fmt.Sprintf("undefined node type (%T)", node))
|
||||
}
|
||||
node.SetType(t)
|
||||
return t
|
||||
}
|
||||
|
||||
func (v *visitor) error(node ast.Node, format string, args ...interface{}) reflect.Type {
|
||||
if v.err == nil { // show first error
|
||||
v.err = &file.Error{
|
||||
Location: node.Location(),
|
||||
Message: fmt.Sprintf(format, args...),
|
||||
}
|
||||
}
|
||||
return interfaceType // interface represent undefined type
|
||||
}
|
||||
|
||||
func (v *visitor) NilNode(*ast.NilNode) reflect.Type {
|
||||
return nilType
|
||||
}
|
||||
|
||||
func (v *visitor) IdentifierNode(node *ast.IdentifierNode) reflect.Type {
|
||||
if v.types == nil {
|
||||
return interfaceType
|
||||
}
|
||||
if t, ok := v.types[node.Value]; ok {
|
||||
if t.Ambiguous {
|
||||
return v.error(node, "ambiguous identifier %v", node.Value)
|
||||
}
|
||||
return t.Type
|
||||
}
|
||||
if !v.strict {
|
||||
if v.defaultType != nil {
|
||||
return v.defaultType
|
||||
}
|
||||
return interfaceType
|
||||
}
|
||||
if !node.NilSafe {
|
||||
return v.error(node, "unknown name %v", node.Value)
|
||||
}
|
||||
return nilType
|
||||
}
|
||||
|
||||
func (v *visitor) IntegerNode(*ast.IntegerNode) reflect.Type {
|
||||
return integerType
|
||||
}
|
||||
|
||||
func (v *visitor) FloatNode(*ast.FloatNode) reflect.Type {
|
||||
return floatType
|
||||
}
|
||||
|
||||
func (v *visitor) BoolNode(*ast.BoolNode) reflect.Type {
|
||||
return boolType
|
||||
}
|
||||
|
||||
func (v *visitor) StringNode(*ast.StringNode) reflect.Type {
|
||||
return stringType
|
||||
}
|
||||
|
||||
func (v *visitor) ConstantNode(node *ast.ConstantNode) reflect.Type {
|
||||
return reflect.TypeOf(node.Value)
|
||||
}
|
||||
|
||||
func (v *visitor) UnaryNode(node *ast.UnaryNode) reflect.Type {
|
||||
t := v.visit(node.Node)
|
||||
|
||||
switch node.Operator {
|
||||
|
||||
case "!", "not":
|
||||
if isBool(t) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
case "+", "-":
|
||||
if isNumber(t) {
|
||||
return t
|
||||
}
|
||||
|
||||
default:
|
||||
return v.error(node, "unknown operator (%v)", node.Operator)
|
||||
}
|
||||
|
||||
return v.error(node, `invalid operation: %v (mismatched type %v)`, node.Operator, t)
|
||||
}
|
||||
|
||||
func (v *visitor) BinaryNode(node *ast.BinaryNode) reflect.Type {
|
||||
l := v.visit(node.Left)
|
||||
r := v.visit(node.Right)
|
||||
|
||||
// check operator overloading
|
||||
if fns, ok := v.operators[node.Operator]; ok {
|
||||
t, _, ok := conf.FindSuitableOperatorOverload(fns, v.types, l, r)
|
||||
if ok {
|
||||
return t
|
||||
}
|
||||
}
|
||||
|
||||
switch node.Operator {
|
||||
case "==", "!=":
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return boolType
|
||||
}
|
||||
if isComparable(l, r) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
case "or", "||", "and", "&&":
|
||||
if isBool(l) && isBool(r) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
case "in", "not in":
|
||||
if isString(l) && isStruct(r) {
|
||||
return boolType
|
||||
}
|
||||
if isMap(r) {
|
||||
return boolType
|
||||
}
|
||||
if isArray(r) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
case "<", ">", ">=", "<=":
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return boolType
|
||||
}
|
||||
if isString(l) && isString(r) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
case "/", "-", "*":
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return combined(l, r)
|
||||
}
|
||||
|
||||
case "**":
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return floatType
|
||||
}
|
||||
|
||||
case "%":
|
||||
if isInteger(l) && isInteger(r) {
|
||||
return combined(l, r)
|
||||
}
|
||||
|
||||
case "+":
|
||||
if isNumber(l) && isNumber(r) {
|
||||
return combined(l, r)
|
||||
}
|
||||
if isString(l) && isString(r) {
|
||||
return stringType
|
||||
}
|
||||
|
||||
case "contains", "startsWith", "endsWith":
|
||||
if isString(l) && isString(r) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
case "..":
|
||||
if isInteger(l) && isInteger(r) {
|
||||
return reflect.SliceOf(integerType)
|
||||
}
|
||||
|
||||
default:
|
||||
return v.error(node, "unknown operator (%v)", node.Operator)
|
||||
|
||||
}
|
||||
|
||||
return v.error(node, `invalid operation: %v (mismatched types %v and %v)`, node.Operator, l, r)
|
||||
}
|
||||
|
||||
func (v *visitor) MatchesNode(node *ast.MatchesNode) reflect.Type {
|
||||
l := v.visit(node.Left)
|
||||
r := v.visit(node.Right)
|
||||
|
||||
if isString(l) && isString(r) {
|
||||
return boolType
|
||||
}
|
||||
|
||||
return v.error(node, `invalid operation: matches (mismatched types %v and %v)`, l, r)
|
||||
}
|
||||
|
||||
func (v *visitor) PropertyNode(node *ast.PropertyNode) reflect.Type {
|
||||
t := v.visit(node.Node)
|
||||
if t, ok := fieldType(t, node.Property); ok {
|
||||
return t
|
||||
}
|
||||
if !node.NilSafe {
|
||||
return v.error(node, "type %v has no field %v", t, node.Property)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *visitor) IndexNode(node *ast.IndexNode) reflect.Type {
|
||||
t := v.visit(node.Node)
|
||||
i := v.visit(node.Index)
|
||||
|
||||
if t, ok := indexType(t); ok {
|
||||
if !isInteger(i) && !isString(i) {
|
||||
return v.error(node, "invalid operation: cannot use %v as index to %v", i, t)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
return v.error(node, "invalid operation: type %v does not support indexing", t)
|
||||
}
|
||||
|
||||
func (v *visitor) SliceNode(node *ast.SliceNode) reflect.Type {
|
||||
t := v.visit(node.Node)
|
||||
|
||||
_, isIndex := indexType(t)
|
||||
|
||||
if isIndex || isString(t) {
|
||||
if node.From != nil {
|
||||
from := v.visit(node.From)
|
||||
if !isInteger(from) {
|
||||
return v.error(node.From, "invalid operation: non-integer slice index %v", from)
|
||||
}
|
||||
}
|
||||
if node.To != nil {
|
||||
to := v.visit(node.To)
|
||||
if !isInteger(to) {
|
||||
return v.error(node.To, "invalid operation: non-integer slice index %v", to)
|
||||
}
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
return v.error(node, "invalid operation: cannot slice %v", t)
|
||||
}
|
||||
|
||||
func (v *visitor) FunctionNode(node *ast.FunctionNode) reflect.Type {
|
||||
if f, ok := v.types[node.Name]; ok {
|
||||
if fn, ok := isFuncType(f.Type); ok {
|
||||
|
||||
inputParamsCount := 1 // for functions
|
||||
if f.Method {
|
||||
inputParamsCount = 2 // for methods
|
||||
}
|
||||
|
||||
if !isInterface(fn) &&
|
||||
fn.IsVariadic() &&
|
||||
fn.NumIn() == inputParamsCount &&
|
||||
((fn.NumOut() == 1 && // Function with one return value
|
||||
fn.Out(0).Kind() == reflect.Interface) ||
|
||||
(fn.NumOut() == 2 && // Function with one return value and an error
|
||||
fn.Out(0).Kind() == reflect.Interface &&
|
||||
fn.Out(1) == errorType)) {
|
||||
rest := fn.In(fn.NumIn() - 1) // function has only one param for functions and two for methods
|
||||
if rest.Kind() == reflect.Slice && rest.Elem().Kind() == reflect.Interface {
|
||||
node.Fast = true
|
||||
}
|
||||
}
|
||||
|
||||
return v.checkFunc(fn, f.Method, node, node.Name, node.Arguments)
|
||||
}
|
||||
}
|
||||
if !v.strict {
|
||||
if v.defaultType != nil {
|
||||
return v.defaultType
|
||||
}
|
||||
return interfaceType
|
||||
}
|
||||
return v.error(node, "unknown func %v", node.Name)
|
||||
}
|
||||
|
||||
func (v *visitor) MethodNode(node *ast.MethodNode) reflect.Type {
|
||||
t := v.visit(node.Node)
|
||||
if f, method, ok := methodType(t, node.Method); ok {
|
||||
if fn, ok := isFuncType(f); ok {
|
||||
return v.checkFunc(fn, method, node, node.Method, node.Arguments)
|
||||
}
|
||||
}
|
||||
if !node.NilSafe {
|
||||
return v.error(node, "type %v has no method %v", t, node.Method)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkFunc checks func arguments and returns "return type" of func or method.
|
||||
func (v *visitor) checkFunc(fn reflect.Type, method bool, node ast.Node, name string, arguments []ast.Node) reflect.Type {
|
||||
if isInterface(fn) {
|
||||
return interfaceType
|
||||
}
|
||||
|
||||
if fn.NumOut() == 0 {
|
||||
return v.error(node, "func %v doesn't return value", name)
|
||||
}
|
||||
if numOut := fn.NumOut(); numOut > 2 {
|
||||
return v.error(node, "func %v returns more then two values", name)
|
||||
}
|
||||
|
||||
numIn := fn.NumIn()
|
||||
|
||||
// If func is method on an env, first argument should be a receiver,
|
||||
// and actual arguments less then numIn by one.
|
||||
if method {
|
||||
numIn--
|
||||
}
|
||||
|
||||
if fn.IsVariadic() {
|
||||
if len(arguments) < numIn-1 {
|
||||
return v.error(node, "not enough arguments to call %v", name)
|
||||
}
|
||||
} else {
|
||||
if len(arguments) > numIn {
|
||||
return v.error(node, "too many arguments to call %v", name)
|
||||
}
|
||||
if len(arguments) < numIn {
|
||||
return v.error(node, "not enough arguments to call %v", name)
|
||||
}
|
||||
}
|
||||
|
||||
offset := 0
|
||||
|
||||
// Skip first argument in case of the receiver.
|
||||
if method {
|
||||
offset = 1
|
||||
}
|
||||
|
||||
for i, arg := range arguments {
|
||||
t := v.visit(arg)
|
||||
|
||||
var in reflect.Type
|
||||
if fn.IsVariadic() && i >= numIn-1 {
|
||||
// For variadic arguments fn(xs ...int), go replaces type of xs (int) with ([]int).
|
||||
// As we compare arguments one by one, we need underling type.
|
||||
in = fn.In(fn.NumIn() - 1)
|
||||
in, _ = indexType(in)
|
||||
} else {
|
||||
in = fn.In(i + offset)
|
||||
}
|
||||
|
||||
if isIntegerOrArithmeticOperation(arg) {
|
||||
t = in
|
||||
setTypeForIntegers(arg, t)
|
||||
}
|
||||
|
||||
if t == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if !t.AssignableTo(in) && t.Kind() != reflect.Interface {
|
||||
return v.error(arg, "cannot use %v as argument (type %v) to call %v ", t, in, name)
|
||||
}
|
||||
}
|
||||
|
||||
return fn.Out(0)
|
||||
}
|
||||
|
||||
func (v *visitor) BuiltinNode(node *ast.BuiltinNode) reflect.Type {
|
||||
switch node.Name {
|
||||
|
||||
case "len":
|
||||
param := v.visit(node.Arguments[0])
|
||||
if isArray(param) || isMap(param) || isString(param) {
|
||||
return integerType
|
||||
}
|
||||
return v.error(node, "invalid argument for len (type %v)", param)
|
||||
|
||||
case "all", "none", "any", "one":
|
||||
collection := v.visit(node.Arguments[0])
|
||||
if !isArray(collection) {
|
||||
return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
|
||||
}
|
||||
|
||||
v.collections = append(v.collections, collection)
|
||||
closure := v.visit(node.Arguments[1])
|
||||
v.collections = v.collections[:len(v.collections)-1]
|
||||
|
||||
if isFunc(closure) &&
|
||||
closure.NumOut() == 1 &&
|
||||
closure.NumIn() == 1 && isInterface(closure.In(0)) {
|
||||
|
||||
if !isBool(closure.Out(0)) {
|
||||
return v.error(node.Arguments[1], "closure should return boolean (got %v)", closure.Out(0).String())
|
||||
}
|
||||
return boolType
|
||||
}
|
||||
return v.error(node.Arguments[1], "closure should has one input and one output param")
|
||||
|
||||
case "filter":
|
||||
collection := v.visit(node.Arguments[0])
|
||||
if !isArray(collection) {
|
||||
return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
|
||||
}
|
||||
|
||||
v.collections = append(v.collections, collection)
|
||||
closure := v.visit(node.Arguments[1])
|
||||
v.collections = v.collections[:len(v.collections)-1]
|
||||
|
||||
if isFunc(closure) &&
|
||||
closure.NumOut() == 1 &&
|
||||
closure.NumIn() == 1 && isInterface(closure.In(0)) {
|
||||
|
||||
if !isBool(closure.Out(0)) {
|
||||
return v.error(node.Arguments[1], "closure should return boolean (got %v)", closure.Out(0).String())
|
||||
}
|
||||
if isInterface(collection) {
|
||||
return arrayType
|
||||
}
|
||||
return reflect.SliceOf(collection.Elem())
|
||||
}
|
||||
return v.error(node.Arguments[1], "closure should has one input and one output param")
|
||||
|
||||
case "map":
|
||||
collection := v.visit(node.Arguments[0])
|
||||
if !isArray(collection) {
|
||||
return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
|
||||
}
|
||||
|
||||
v.collections = append(v.collections, collection)
|
||||
closure := v.visit(node.Arguments[1])
|
||||
v.collections = v.collections[:len(v.collections)-1]
|
||||
|
||||
if isFunc(closure) &&
|
||||
closure.NumOut() == 1 &&
|
||||
closure.NumIn() == 1 && isInterface(closure.In(0)) {
|
||||
|
||||
return reflect.SliceOf(closure.Out(0))
|
||||
}
|
||||
return v.error(node.Arguments[1], "closure should has one input and one output param")
|
||||
|
||||
case "count":
|
||||
collection := v.visit(node.Arguments[0])
|
||||
if !isArray(collection) {
|
||||
return v.error(node.Arguments[0], "builtin %v takes only array (got %v)", node.Name, collection)
|
||||
}
|
||||
|
||||
v.collections = append(v.collections, collection)
|
||||
closure := v.visit(node.Arguments[1])
|
||||
v.collections = v.collections[:len(v.collections)-1]
|
||||
|
||||
if isFunc(closure) &&
|
||||
closure.NumOut() == 1 &&
|
||||
closure.NumIn() == 1 && isInterface(closure.In(0)) {
|
||||
if !isBool(closure.Out(0)) {
|
||||
return v.error(node.Arguments[1], "closure should return boolean (got %v)", closure.Out(0).String())
|
||||
}
|
||||
|
||||
return integerType
|
||||
}
|
||||
return v.error(node.Arguments[1], "closure should has one input and one output param")
|
||||
|
||||
default:
|
||||
return v.error(node, "unknown builtin %v", node.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func (v *visitor) ClosureNode(node *ast.ClosureNode) reflect.Type {
|
||||
t := v.visit(node.Node)
|
||||
return reflect.FuncOf([]reflect.Type{interfaceType}, []reflect.Type{t}, false)
|
||||
}
|
||||
|
||||
func (v *visitor) PointerNode(node *ast.PointerNode) reflect.Type {
|
||||
if len(v.collections) == 0 {
|
||||
return v.error(node, "cannot use pointer accessor outside closure")
|
||||
}
|
||||
|
||||
collection := v.collections[len(v.collections)-1]
|
||||
|
||||
if t, ok := indexType(collection); ok {
|
||||
return t
|
||||
}
|
||||
return v.error(node, "cannot use %v as array", collection)
|
||||
}
|
||||
|
||||
func (v *visitor) ConditionalNode(node *ast.ConditionalNode) reflect.Type {
|
||||
c := v.visit(node.Cond)
|
||||
if !isBool(c) {
|
||||
return v.error(node.Cond, "non-bool expression (type %v) used as condition", c)
|
||||
}
|
||||
|
||||
t1 := v.visit(node.Exp1)
|
||||
t2 := v.visit(node.Exp2)
|
||||
|
||||
if t1 == nil && t2 != nil {
|
||||
return t2
|
||||
}
|
||||
if t1 != nil && t2 == nil {
|
||||
return t1
|
||||
}
|
||||
if t1 == nil && t2 == nil {
|
||||
return nilType
|
||||
}
|
||||
if t1.AssignableTo(t2) {
|
||||
return t1
|
||||
}
|
||||
return interfaceType
|
||||
}
|
||||
|
||||
func (v *visitor) ArrayNode(node *ast.ArrayNode) reflect.Type {
|
||||
for _, node := range node.Nodes {
|
||||
_ = v.visit(node)
|
||||
}
|
||||
return arrayType
|
||||
}
|
||||
|
||||
func (v *visitor) MapNode(node *ast.MapNode) reflect.Type {
|
||||
for _, pair := range node.Pairs {
|
||||
v.visit(pair)
|
||||
}
|
||||
return mapType
|
||||
}
|
||||
|
||||
func (v *visitor) PairNode(node *ast.PairNode) reflect.Type {
|
||||
v.visit(node.Key)
|
||||
v.visit(node.Value)
|
||||
return nilType
|
||||
}
|
@ -0,0 +1,349 @@
|
||||
package checker
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
)
|
||||
|
||||
var (
|
||||
nilType = reflect.TypeOf(nil)
|
||||
boolType = reflect.TypeOf(true)
|
||||
integerType = reflect.TypeOf(int(0))
|
||||
floatType = reflect.TypeOf(float64(0))
|
||||
stringType = reflect.TypeOf("")
|
||||
arrayType = reflect.TypeOf([]interface{}{})
|
||||
mapType = reflect.TypeOf(map[string]interface{}{})
|
||||
interfaceType = reflect.TypeOf(new(interface{})).Elem()
|
||||
)
|
||||
|
||||
func typeWeight(t reflect.Type) int {
|
||||
switch t.Kind() {
|
||||
case reflect.Uint:
|
||||
return 1
|
||||
case reflect.Uint8:
|
||||
return 2
|
||||
case reflect.Uint16:
|
||||
return 3
|
||||
case reflect.Uint32:
|
||||
return 4
|
||||
case reflect.Uint64:
|
||||
return 5
|
||||
case reflect.Int:
|
||||
return 6
|
||||
case reflect.Int8:
|
||||
return 7
|
||||
case reflect.Int16:
|
||||
return 8
|
||||
case reflect.Int32:
|
||||
return 9
|
||||
case reflect.Int64:
|
||||
return 10
|
||||
case reflect.Float32:
|
||||
return 11
|
||||
case reflect.Float64:
|
||||
return 12
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func combined(a, b reflect.Type) reflect.Type {
|
||||
if typeWeight(a) > typeWeight(b) {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func dereference(t reflect.Type) reflect.Type {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = dereference(t.Elem())
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func isComparable(l, r reflect.Type) bool {
|
||||
l = dereference(l)
|
||||
r = dereference(r)
|
||||
|
||||
if l == nil || r == nil { // It is possible to compare with nil.
|
||||
return true
|
||||
}
|
||||
if l.Kind() == r.Kind() {
|
||||
return true
|
||||
}
|
||||
if isInterface(l) || isInterface(r) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isInterface(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isInteger(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.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 true
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isFloat(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Float32, reflect.Float64:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isNumber(t reflect.Type) bool {
|
||||
return isInteger(t) || isFloat(t)
|
||||
}
|
||||
|
||||
func isBool(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Bool:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isString(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.String:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isArray(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Slice, reflect.Array:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isMap(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Map:
|
||||
return true
|
||||
case reflect.Interface:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isStruct(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isFunc(t reflect.Type) bool {
|
||||
t = dereference(t)
|
||||
if t != nil {
|
||||
switch t.Kind() {
|
||||
case reflect.Func:
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func fieldType(ntype reflect.Type, name string) (reflect.Type, bool) {
|
||||
ntype = dereference(ntype)
|
||||
if ntype != nil {
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, true
|
||||
case reflect.Struct:
|
||||
// First check all struct's fields.
|
||||
for i := 0; i < ntype.NumField(); i++ {
|
||||
f := ntype.Field(i)
|
||||
if f.Name == name {
|
||||
return f.Type, true
|
||||
}
|
||||
}
|
||||
|
||||
// Second check fields of embedded structs.
|
||||
for i := 0; i < ntype.NumField(); i++ {
|
||||
f := ntype.Field(i)
|
||||
if f.Anonymous {
|
||||
if t, ok := fieldType(f.Type, name); ok {
|
||||
return t, true
|
||||
}
|
||||
}
|
||||
}
|
||||
case reflect.Map:
|
||||
return ntype.Elem(), true
|
||||
}
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func methodType(t reflect.Type, name string) (reflect.Type, bool, bool) {
|
||||
if t != nil {
|
||||
// First, check methods defined on type itself,
|
||||
// independent of which type it is.
|
||||
if m, ok := t.MethodByName(name); ok {
|
||||
if t.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).
|
||||
return m.Type, false, true
|
||||
} else {
|
||||
return m.Type, true, true
|
||||
}
|
||||
}
|
||||
|
||||
d := t
|
||||
if t.Kind() == reflect.Ptr {
|
||||
d = t.Elem()
|
||||
}
|
||||
|
||||
switch d.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, false, true
|
||||
case reflect.Struct:
|
||||
// First, check all struct's fields.
|
||||
for i := 0; i < d.NumField(); i++ {
|
||||
f := d.Field(i)
|
||||
if !f.Anonymous && f.Name == name {
|
||||
return f.Type, false, true
|
||||
}
|
||||
}
|
||||
|
||||
// Second, check fields of embedded structs.
|
||||
for i := 0; i < d.NumField(); i++ {
|
||||
f := d.Field(i)
|
||||
if f.Anonymous {
|
||||
if t, method, ok := methodType(f.Type, name); ok {
|
||||
return t, method, true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
return d.Elem(), false, true
|
||||
}
|
||||
}
|
||||
return nil, false, false
|
||||
}
|
||||
|
||||
func indexType(ntype reflect.Type) (reflect.Type, bool) {
|
||||
ntype = dereference(ntype)
|
||||
if ntype == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, true
|
||||
case reflect.Map, reflect.Array, reflect.Slice:
|
||||
return ntype.Elem(), true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func isFuncType(ntype reflect.Type) (reflect.Type, bool) {
|
||||
ntype = dereference(ntype)
|
||||
if ntype == nil {
|
||||
return nil, false
|
||||
}
|
||||
|
||||
switch ntype.Kind() {
|
||||
case reflect.Interface:
|
||||
return interfaceType, true
|
||||
case reflect.Func:
|
||||
return ntype, true
|
||||
}
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
func isIntegerOrArithmeticOperation(node ast.Node) bool {
|
||||
switch n := node.(type) {
|
||||
case *ast.IntegerNode:
|
||||
return true
|
||||
case *ast.UnaryNode:
|
||||
switch n.Operator {
|
||||
case "+", "-":
|
||||
return true
|
||||
}
|
||||
case *ast.BinaryNode:
|
||||
switch n.Operator {
|
||||
case "+", "/", "-", "*":
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func setTypeForIntegers(node ast.Node, t reflect.Type) {
|
||||
switch n := node.(type) {
|
||||
case *ast.IntegerNode:
|
||||
n.SetType(t)
|
||||
case *ast.UnaryNode:
|
||||
switch n.Operator {
|
||||
case "+", "-":
|
||||
setTypeForIntegers(n.Node, t)
|
||||
}
|
||||
case *ast.BinaryNode:
|
||||
switch n.Operator {
|
||||
case "+", "/", "-", "*":
|
||||
setTypeForIntegers(n.Left, t)
|
||||
setTypeForIntegers(n.Right, t)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,673 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/conf"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"github.com/antonmedv/expr/parser"
|
||||
. "github.com/antonmedv/expr/vm"
|
||||
)
|
||||
|
||||
func Compile(tree *parser.Tree, config *conf.Config) (program *Program, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
c := &compiler{
|
||||
index: make(map[interface{}]uint16),
|
||||
locations: make(map[int]file.Location),
|
||||
}
|
||||
|
||||
if config != nil {
|
||||
c.mapEnv = config.MapEnv
|
||||
c.cast = config.Expect
|
||||
}
|
||||
|
||||
c.compile(tree.Node)
|
||||
|
||||
switch c.cast {
|
||||
case reflect.Int64:
|
||||
c.emit(OpCast, encode(0)...)
|
||||
case reflect.Float64:
|
||||
c.emit(OpCast, encode(1)...)
|
||||
}
|
||||
|
||||
program = &Program{
|
||||
Source: tree.Source,
|
||||
Locations: c.locations,
|
||||
Constants: c.constants,
|
||||
Bytecode: c.bytecode,
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
type compiler struct {
|
||||
locations map[int]file.Location
|
||||
constants []interface{}
|
||||
bytecode []byte
|
||||
index map[interface{}]uint16
|
||||
mapEnv bool
|
||||
cast reflect.Kind
|
||||
nodes []ast.Node
|
||||
}
|
||||
|
||||
func (c *compiler) emit(op byte, b ...byte) int {
|
||||
c.bytecode = append(c.bytecode, op)
|
||||
current := len(c.bytecode)
|
||||
c.bytecode = append(c.bytecode, b...)
|
||||
|
||||
var loc file.Location
|
||||
if len(c.nodes) > 0 {
|
||||
loc = c.nodes[len(c.nodes)-1].Location()
|
||||
}
|
||||
c.locations[current-1] = loc
|
||||
|
||||
return current
|
||||
}
|
||||
|
||||
func (c *compiler) emitPush(value interface{}) int {
|
||||
return c.emit(OpPush, c.makeConstant(value)...)
|
||||
}
|
||||
|
||||
func (c *compiler) makeConstant(i interface{}) []byte {
|
||||
hashable := true
|
||||
switch reflect.TypeOf(i).Kind() {
|
||||
case reflect.Slice, reflect.Map:
|
||||
hashable = false
|
||||
}
|
||||
|
||||
if hashable {
|
||||
if p, ok := c.index[i]; ok {
|
||||
return encode(p)
|
||||
}
|
||||
}
|
||||
|
||||
c.constants = append(c.constants, i)
|
||||
if len(c.constants) > math.MaxUint16 {
|
||||
panic("exceeded constants max space limit")
|
||||
}
|
||||
|
||||
p := uint16(len(c.constants) - 1)
|
||||
if hashable {
|
||||
c.index[i] = p
|
||||
}
|
||||
return encode(p)
|
||||
}
|
||||
|
||||
func (c *compiler) placeholder() []byte {
|
||||
return []byte{0xFF, 0xFF}
|
||||
}
|
||||
|
||||
func (c *compiler) patchJump(placeholder int) {
|
||||
offset := len(c.bytecode) - 2 - placeholder
|
||||
b := encode(uint16(offset))
|
||||
c.bytecode[placeholder] = b[0]
|
||||
c.bytecode[placeholder+1] = b[1]
|
||||
}
|
||||
|
||||
func (c *compiler) calcBackwardJump(to int) []byte {
|
||||
return encode(uint16(len(c.bytecode) + 1 + 2 - to))
|
||||
}
|
||||
|
||||
func (c *compiler) compile(node ast.Node) {
|
||||
c.nodes = append(c.nodes, node)
|
||||
defer func() {
|
||||
c.nodes = c.nodes[:len(c.nodes)-1]
|
||||
}()
|
||||
|
||||
switch n := node.(type) {
|
||||
case *ast.NilNode:
|
||||
c.NilNode(n)
|
||||
case *ast.IdentifierNode:
|
||||
c.IdentifierNode(n)
|
||||
case *ast.IntegerNode:
|
||||
c.IntegerNode(n)
|
||||
case *ast.FloatNode:
|
||||
c.FloatNode(n)
|
||||
case *ast.BoolNode:
|
||||
c.BoolNode(n)
|
||||
case *ast.StringNode:
|
||||
c.StringNode(n)
|
||||
case *ast.ConstantNode:
|
||||
c.ConstantNode(n)
|
||||
case *ast.UnaryNode:
|
||||
c.UnaryNode(n)
|
||||
case *ast.BinaryNode:
|
||||
c.BinaryNode(n)
|
||||
case *ast.MatchesNode:
|
||||
c.MatchesNode(n)
|
||||
case *ast.PropertyNode:
|
||||
c.PropertyNode(n)
|
||||
case *ast.IndexNode:
|
||||
c.IndexNode(n)
|
||||
case *ast.SliceNode:
|
||||
c.SliceNode(n)
|
||||
case *ast.MethodNode:
|
||||
c.MethodNode(n)
|
||||
case *ast.FunctionNode:
|
||||
c.FunctionNode(n)
|
||||
case *ast.BuiltinNode:
|
||||
c.BuiltinNode(n)
|
||||
case *ast.ClosureNode:
|
||||
c.ClosureNode(n)
|
||||
case *ast.PointerNode:
|
||||
c.PointerNode(n)
|
||||
case *ast.ConditionalNode:
|
||||
c.ConditionalNode(n)
|
||||
case *ast.ArrayNode:
|
||||
c.ArrayNode(n)
|
||||
case *ast.MapNode:
|
||||
c.MapNode(n)
|
||||
case *ast.PairNode:
|
||||
c.PairNode(n)
|
||||
default:
|
||||
panic(fmt.Sprintf("undefined node type (%T)", node))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) NilNode(node *ast.NilNode) {
|
||||
c.emit(OpNil)
|
||||
}
|
||||
|
||||
func (c *compiler) IdentifierNode(node *ast.IdentifierNode) {
|
||||
v := c.makeConstant(node.Value)
|
||||
if c.mapEnv {
|
||||
c.emit(OpFetchMap, v...)
|
||||
} else if node.NilSafe {
|
||||
c.emit(OpFetchNilSafe, v...)
|
||||
} else {
|
||||
c.emit(OpFetch, v...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) IntegerNode(node *ast.IntegerNode) {
|
||||
t := node.Type()
|
||||
if t == nil {
|
||||
c.emitPush(node.Value)
|
||||
return
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Float32:
|
||||
c.emitPush(float32(node.Value))
|
||||
case reflect.Float64:
|
||||
c.emitPush(float64(node.Value))
|
||||
|
||||
case reflect.Int:
|
||||
c.emitPush(int(node.Value))
|
||||
case reflect.Int8:
|
||||
c.emitPush(int8(node.Value))
|
||||
case reflect.Int16:
|
||||
c.emitPush(int16(node.Value))
|
||||
case reflect.Int32:
|
||||
c.emitPush(int32(node.Value))
|
||||
case reflect.Int64:
|
||||
c.emitPush(int64(node.Value))
|
||||
|
||||
case reflect.Uint:
|
||||
c.emitPush(uint(node.Value))
|
||||
case reflect.Uint8:
|
||||
c.emitPush(uint8(node.Value))
|
||||
case reflect.Uint16:
|
||||
c.emitPush(uint16(node.Value))
|
||||
case reflect.Uint32:
|
||||
c.emitPush(uint32(node.Value))
|
||||
case reflect.Uint64:
|
||||
c.emitPush(uint64(node.Value))
|
||||
|
||||
default:
|
||||
c.emitPush(node.Value)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) FloatNode(node *ast.FloatNode) {
|
||||
c.emitPush(node.Value)
|
||||
}
|
||||
|
||||
func (c *compiler) BoolNode(node *ast.BoolNode) {
|
||||
if node.Value {
|
||||
c.emit(OpTrue)
|
||||
} else {
|
||||
c.emit(OpFalse)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) StringNode(node *ast.StringNode) {
|
||||
c.emitPush(node.Value)
|
||||
}
|
||||
|
||||
func (c *compiler) ConstantNode(node *ast.ConstantNode) {
|
||||
c.emitPush(node.Value)
|
||||
}
|
||||
|
||||
func (c *compiler) UnaryNode(node *ast.UnaryNode) {
|
||||
c.compile(node.Node)
|
||||
|
||||
switch node.Operator {
|
||||
|
||||
case "!", "not":
|
||||
c.emit(OpNot)
|
||||
|
||||
case "+":
|
||||
// Do nothing
|
||||
|
||||
case "-":
|
||||
c.emit(OpNegate)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown operator (%v)", node.Operator))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) BinaryNode(node *ast.BinaryNode) {
|
||||
l := kind(node.Left)
|
||||
r := kind(node.Right)
|
||||
|
||||
switch node.Operator {
|
||||
case "==":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
|
||||
if l == r && l == reflect.Int {
|
||||
c.emit(OpEqualInt)
|
||||
} else if l == r && l == reflect.String {
|
||||
c.emit(OpEqualString)
|
||||
} else {
|
||||
c.emit(OpEqual)
|
||||
}
|
||||
|
||||
case "!=":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpEqual)
|
||||
c.emit(OpNot)
|
||||
|
||||
case "or", "||":
|
||||
c.compile(node.Left)
|
||||
end := c.emit(OpJumpIfTrue, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
c.compile(node.Right)
|
||||
c.patchJump(end)
|
||||
|
||||
case "and", "&&":
|
||||
c.compile(node.Left)
|
||||
end := c.emit(OpJumpIfFalse, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
c.compile(node.Right)
|
||||
c.patchJump(end)
|
||||
|
||||
case "in":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpIn)
|
||||
|
||||
case "not in":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpIn)
|
||||
c.emit(OpNot)
|
||||
|
||||
case "<":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpLess)
|
||||
|
||||
case ">":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpMore)
|
||||
|
||||
case "<=":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpLessOrEqual)
|
||||
|
||||
case ">=":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpMoreOrEqual)
|
||||
|
||||
case "+":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpAdd)
|
||||
|
||||
case "-":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpSubtract)
|
||||
|
||||
case "*":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpMultiply)
|
||||
|
||||
case "/":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpDivide)
|
||||
|
||||
case "%":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpModulo)
|
||||
|
||||
case "**":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpExponent)
|
||||
|
||||
case "contains":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpContains)
|
||||
|
||||
case "startsWith":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpStartsWith)
|
||||
|
||||
case "endsWith":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpEndsWith)
|
||||
|
||||
case "..":
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpRange)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown operator (%v)", node.Operator))
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) MatchesNode(node *ast.MatchesNode) {
|
||||
if node.Regexp != nil {
|
||||
c.compile(node.Left)
|
||||
c.emit(OpMatchesConst, c.makeConstant(node.Regexp)...)
|
||||
return
|
||||
}
|
||||
c.compile(node.Left)
|
||||
c.compile(node.Right)
|
||||
c.emit(OpMatches)
|
||||
}
|
||||
|
||||
func (c *compiler) PropertyNode(node *ast.PropertyNode) {
|
||||
c.compile(node.Node)
|
||||
if !node.NilSafe {
|
||||
c.emit(OpProperty, c.makeConstant(node.Property)...)
|
||||
} else {
|
||||
c.emit(OpPropertyNilSafe, c.makeConstant(node.Property)...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) IndexNode(node *ast.IndexNode) {
|
||||
c.compile(node.Node)
|
||||
c.compile(node.Index)
|
||||
c.emit(OpIndex)
|
||||
}
|
||||
|
||||
func (c *compiler) SliceNode(node *ast.SliceNode) {
|
||||
c.compile(node.Node)
|
||||
if node.To != nil {
|
||||
c.compile(node.To)
|
||||
} else {
|
||||
c.emit(OpLen)
|
||||
}
|
||||
if node.From != nil {
|
||||
c.compile(node.From)
|
||||
} else {
|
||||
c.emitPush(0)
|
||||
}
|
||||
c.emit(OpSlice)
|
||||
}
|
||||
|
||||
func (c *compiler) MethodNode(node *ast.MethodNode) {
|
||||
c.compile(node.Node)
|
||||
for _, arg := range node.Arguments {
|
||||
c.compile(arg)
|
||||
}
|
||||
if !node.NilSafe {
|
||||
c.emit(OpMethod, c.makeConstant(Call{Name: node.Method, Size: len(node.Arguments)})...)
|
||||
} else {
|
||||
c.emit(OpMethodNilSafe, c.makeConstant(Call{Name: node.Method, Size: len(node.Arguments)})...)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) FunctionNode(node *ast.FunctionNode) {
|
||||
for _, arg := range node.Arguments {
|
||||
c.compile(arg)
|
||||
}
|
||||
op := OpCall
|
||||
if node.Fast {
|
||||
op = OpCallFast
|
||||
}
|
||||
c.emit(op, c.makeConstant(Call{Name: node.Name, Size: len(node.Arguments)})...)
|
||||
}
|
||||
|
||||
func (c *compiler) BuiltinNode(node *ast.BuiltinNode) {
|
||||
switch node.Name {
|
||||
case "len":
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpLen)
|
||||
c.emit(OpRot)
|
||||
c.emit(OpPop)
|
||||
|
||||
case "all":
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
var loopBreak int
|
||||
c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
loopBreak = c.emit(OpJumpIfFalse, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
})
|
||||
c.emit(OpTrue)
|
||||
c.patchJump(loopBreak)
|
||||
c.emit(OpEnd)
|
||||
|
||||
case "none":
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
var loopBreak int
|
||||
c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
c.emit(OpNot)
|
||||
loopBreak = c.emit(OpJumpIfFalse, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
})
|
||||
c.emit(OpTrue)
|
||||
c.patchJump(loopBreak)
|
||||
c.emit(OpEnd)
|
||||
|
||||
case "any":
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
var loopBreak int
|
||||
c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
loopBreak = c.emit(OpJumpIfTrue, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
})
|
||||
c.emit(OpFalse)
|
||||
c.patchJump(loopBreak)
|
||||
c.emit(OpEnd)
|
||||
|
||||
case "one":
|
||||
count := c.makeConstant("count")
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
c.emitPush(0)
|
||||
c.emit(OpStore, count...)
|
||||
c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
c.emitCond(func() {
|
||||
c.emit(OpInc, count...)
|
||||
})
|
||||
})
|
||||
c.emit(OpLoad, count...)
|
||||
c.emitPush(1)
|
||||
c.emit(OpEqual)
|
||||
c.emit(OpEnd)
|
||||
|
||||
case "filter":
|
||||
count := c.makeConstant("count")
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
c.emitPush(0)
|
||||
c.emit(OpStore, count...)
|
||||
c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
c.emitCond(func() {
|
||||
c.emit(OpInc, count...)
|
||||
|
||||
c.emit(OpLoad, c.makeConstant("array")...)
|
||||
c.emit(OpLoad, c.makeConstant("i")...)
|
||||
c.emit(OpIndex)
|
||||
})
|
||||
})
|
||||
c.emit(OpLoad, count...)
|
||||
c.emit(OpEnd)
|
||||
c.emit(OpArray)
|
||||
|
||||
case "map":
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
size := c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
})
|
||||
c.emit(OpLoad, size...)
|
||||
c.emit(OpEnd)
|
||||
c.emit(OpArray)
|
||||
|
||||
case "count":
|
||||
count := c.makeConstant("count")
|
||||
c.compile(node.Arguments[0])
|
||||
c.emit(OpBegin)
|
||||
c.emitPush(0)
|
||||
c.emit(OpStore, count...)
|
||||
c.emitLoop(func() {
|
||||
c.compile(node.Arguments[1])
|
||||
c.emitCond(func() {
|
||||
c.emit(OpInc, count...)
|
||||
})
|
||||
})
|
||||
c.emit(OpLoad, count...)
|
||||
c.emit(OpEnd)
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown builtin %v", node.Name))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *compiler) emitCond(body func()) {
|
||||
noop := c.emit(OpJumpIfFalse, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
|
||||
body()
|
||||
|
||||
jmp := c.emit(OpJump, c.placeholder()...)
|
||||
c.patchJump(noop)
|
||||
c.emit(OpPop)
|
||||
c.patchJump(jmp)
|
||||
}
|
||||
|
||||
func (c *compiler) emitLoop(body func()) []byte {
|
||||
i := c.makeConstant("i")
|
||||
size := c.makeConstant("size")
|
||||
array := c.makeConstant("array")
|
||||
|
||||
c.emit(OpLen)
|
||||
c.emit(OpStore, size...)
|
||||
c.emit(OpStore, array...)
|
||||
c.emitPush(0)
|
||||
c.emit(OpStore, i...)
|
||||
|
||||
cond := len(c.bytecode)
|
||||
c.emit(OpLoad, i...)
|
||||
c.emit(OpLoad, size...)
|
||||
c.emit(OpLess)
|
||||
end := c.emit(OpJumpIfFalse, c.placeholder()...)
|
||||
c.emit(OpPop)
|
||||
|
||||
body()
|
||||
|
||||
c.emit(OpInc, i...)
|
||||
c.emit(OpJumpBackward, c.calcBackwardJump(cond)...)
|
||||
|
||||
c.patchJump(end)
|
||||
c.emit(OpPop)
|
||||
|
||||
return size
|
||||
}
|
||||
|
||||
func (c *compiler) ClosureNode(node *ast.ClosureNode) {
|
||||
c.compile(node.Node)
|
||||
}
|
||||
|
||||
func (c *compiler) PointerNode(node *ast.PointerNode) {
|
||||
c.emit(OpLoad, c.makeConstant("array")...)
|
||||
c.emit(OpLoad, c.makeConstant("i")...)
|
||||
c.emit(OpIndex)
|
||||
}
|
||||
|
||||
func (c *compiler) ConditionalNode(node *ast.ConditionalNode) {
|
||||
c.compile(node.Cond)
|
||||
otherwise := c.emit(OpJumpIfFalse, c.placeholder()...)
|
||||
|
||||
c.emit(OpPop)
|
||||
c.compile(node.Exp1)
|
||||
end := c.emit(OpJump, c.placeholder()...)
|
||||
|
||||
c.patchJump(otherwise)
|
||||
c.emit(OpPop)
|
||||
c.compile(node.Exp2)
|
||||
|
||||
c.patchJump(end)
|
||||
}
|
||||
|
||||
func (c *compiler) ArrayNode(node *ast.ArrayNode) {
|
||||
for _, node := range node.Nodes {
|
||||
c.compile(node)
|
||||
}
|
||||
|
||||
c.emitPush(len(node.Nodes))
|
||||
c.emit(OpArray)
|
||||
}
|
||||
|
||||
func (c *compiler) MapNode(node *ast.MapNode) {
|
||||
for _, pair := range node.Pairs {
|
||||
c.compile(pair)
|
||||
}
|
||||
|
||||
c.emitPush(len(node.Pairs))
|
||||
c.emit(OpMap)
|
||||
}
|
||||
|
||||
func (c *compiler) PairNode(node *ast.PairNode) {
|
||||
c.compile(node.Key)
|
||||
c.compile(node.Value)
|
||||
}
|
||||
|
||||
func encode(i uint16) []byte {
|
||||
b := make([]byte, 2)
|
||||
binary.LittleEndian.PutUint16(b, i)
|
||||
return b
|
||||
}
|
||||
|
||||
func kind(node ast.Node) reflect.Kind {
|
||||
t := node.Type()
|
||||
if t == nil {
|
||||
return reflect.Invalid
|
||||
}
|
||||
return t.Kind()
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
package compiler
|
||||
|
||||
import (
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/conf"
|
||||
)
|
||||
|
||||
type operatorPatcher struct {
|
||||
ops map[string][]string
|
||||
types conf.TypesTable
|
||||
}
|
||||
|
||||
func (p *operatorPatcher) Enter(node *ast.Node) {}
|
||||
func (p *operatorPatcher) Exit(node *ast.Node) {
|
||||
binaryNode, ok := (*node).(*ast.BinaryNode)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
fns, ok := p.ops[binaryNode.Operator]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
leftType := binaryNode.Left.Type()
|
||||
rightType := binaryNode.Right.Type()
|
||||
|
||||
_, fn, ok := conf.FindSuitableOperatorOverload(fns, p.types, leftType, rightType)
|
||||
if ok {
|
||||
newNode := &ast.FunctionNode{
|
||||
Name: fn,
|
||||
Arguments: []ast.Node{binaryNode.Left, binaryNode.Right},
|
||||
}
|
||||
ast.Patch(node, newNode)
|
||||
}
|
||||
}
|
||||
|
||||
func PatchOperators(node *ast.Node, config *conf.Config) {
|
||||
if len(config.Operators) == 0 {
|
||||
return
|
||||
}
|
||||
patcher := &operatorPatcher{ops: config.Operators, types: config.Types}
|
||||
ast.Walk(node, patcher)
|
||||
}
|
@ -0,0 +1,89 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Env interface{}
|
||||
MapEnv bool
|
||||
Types TypesTable
|
||||
Operators OperatorsTable
|
||||
Expect reflect.Kind
|
||||
Optimize bool
|
||||
Strict bool
|
||||
DefaultType reflect.Type
|
||||
ConstExprFns map[string]reflect.Value
|
||||
Visitors []ast.Visitor
|
||||
err error
|
||||
}
|
||||
|
||||
func New(env interface{}) *Config {
|
||||
var mapEnv bool
|
||||
var mapValueType reflect.Type
|
||||
if _, ok := env.(map[string]interface{}); ok {
|
||||
mapEnv = true
|
||||
} else {
|
||||
if reflect.ValueOf(env).Kind() == reflect.Map {
|
||||
mapValueType = reflect.TypeOf(env).Elem()
|
||||
}
|
||||
}
|
||||
|
||||
return &Config{
|
||||
Env: env,
|
||||
MapEnv: mapEnv,
|
||||
Types: CreateTypesTable(env),
|
||||
Optimize: true,
|
||||
Strict: true,
|
||||
DefaultType: mapValueType,
|
||||
ConstExprFns: make(map[string]reflect.Value),
|
||||
}
|
||||
}
|
||||
|
||||
// Check validates the compiler configuration.
|
||||
func (c *Config) Check() error {
|
||||
// Check that all functions that define operator overloading
|
||||
// exist in environment and have correct signatures.
|
||||
for op, fns := range c.Operators {
|
||||
for _, fn := range fns {
|
||||
fnType, ok := c.Types[fn]
|
||||
if !ok || fnType.Type.Kind() != reflect.Func {
|
||||
return fmt.Errorf("function %s for %s operator does not exist in environment", fn, op)
|
||||
}
|
||||
requiredNumIn := 2
|
||||
if fnType.Method {
|
||||
requiredNumIn = 3 // As first argument of method is receiver.
|
||||
}
|
||||
if fnType.Type.NumIn() != requiredNumIn || fnType.Type.NumOut() != 1 {
|
||||
return fmt.Errorf("function %s for %s operator does not have a correct signature", fn, op)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all ConstExprFns are functions.
|
||||
for name, fn := range c.ConstExprFns {
|
||||
if fn.Kind() != reflect.Func {
|
||||
return fmt.Errorf("const expression %q must be a function", name)
|
||||
}
|
||||
}
|
||||
|
||||
return c.err
|
||||
}
|
||||
|
||||
func (c *Config) ConstExpr(name string) {
|
||||
if c.Env == nil {
|
||||
c.Error(fmt.Errorf("no environment for const expression: %v", name))
|
||||
return
|
||||
}
|
||||
c.ConstExprFns[name] = vm.FetchFn(c.Env, name)
|
||||
}
|
||||
|
||||
func (c *Config) Error(err error) {
|
||||
if c.err == nil {
|
||||
c.err = err
|
||||
}
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package conf
|
||||
|
||||
import "reflect"
|
||||
|
||||
// OperatorsTable maps binary operators to corresponding list of functions.
|
||||
// Functions should be provided in the environment to allow operator overloading.
|
||||
type OperatorsTable map[string][]string
|
||||
|
||||
func FindSuitableOperatorOverload(fns []string, types TypesTable, l, r reflect.Type) (reflect.Type, string, bool) {
|
||||
for _, fn := range fns {
|
||||
fnType := types[fn]
|
||||
firstInIndex := 0
|
||||
if fnType.Method {
|
||||
firstInIndex = 1 // As first argument to method is receiver.
|
||||
}
|
||||
firstArgType := fnType.Type.In(firstInIndex)
|
||||
secondArgType := fnType.Type.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 fnType.Type.Out(0), fn, true
|
||||
}
|
||||
}
|
||||
return nil, "", false
|
||||
}
|
@ -0,0 +1,100 @@
|
||||
package conf
|
||||
|
||||
import "reflect"
|
||||
|
||||
type Tag struct {
|
||||
Type reflect.Type
|
||||
Method bool
|
||||
Ambiguous bool
|
||||
}
|
||||
|
||||
type TypesTable map[string]Tag
|
||||
|
||||
// CreateTypesTable creates types table for type checks during parsing.
|
||||
// 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
|
||||
// (key as name, value as type).
|
||||
func CreateTypesTable(i interface{}) TypesTable {
|
||||
if i == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
types := make(TypesTable)
|
||||
v := reflect.ValueOf(i)
|
||||
t := reflect.TypeOf(i)
|
||||
|
||||
d := t
|
||||
if t.Kind() == reflect.Ptr {
|
||||
d = t.Elem()
|
||||
}
|
||||
|
||||
switch d.Kind() {
|
||||
case reflect.Struct:
|
||||
types = FieldsFromStruct(d)
|
||||
|
||||
// Methods of struct should be gathered from original struct with pointer,
|
||||
// as methods maybe declared on pointer receiver. Also this method retrieves
|
||||
// all embedded structs methods as well, no need to recursion.
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
m := t.Method(i)
|
||||
types[m.Name] = Tag{Type: m.Type, Method: true}
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
for _, key := range v.MapKeys() {
|
||||
value := v.MapIndex(key)
|
||||
if key.Kind() == reflect.String && value.IsValid() && value.CanInterface() {
|
||||
types[key.String()] = Tag{Type: reflect.TypeOf(value.Interface())}
|
||||
}
|
||||
}
|
||||
|
||||
// A map may have method too.
|
||||
for i := 0; i < t.NumMethod(); i++ {
|
||||
m := t.Method(i)
|
||||
types[m.Name] = Tag{Type: m.Type, Method: true}
|
||||
}
|
||||
}
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
func FieldsFromStruct(t reflect.Type) TypesTable {
|
||||
types := make(TypesTable)
|
||||
t = dereference(t)
|
||||
if t == nil {
|
||||
return types
|
||||
}
|
||||
|
||||
switch t.Kind() {
|
||||
case reflect.Struct:
|
||||
for i := 0; i < t.NumField(); i++ {
|
||||
f := t.Field(i)
|
||||
|
||||
if f.Anonymous {
|
||||
for name, typ := range FieldsFromStruct(f.Type) {
|
||||
if _, ok := types[name]; ok {
|
||||
types[name] = Tag{Ambiguous: true}
|
||||
} else {
|
||||
types[name] = typ
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
types[f.Name] = Tag{Type: f.Type}
|
||||
}
|
||||
}
|
||||
|
||||
return types
|
||||
}
|
||||
|
||||
func dereference(t reflect.Type) reflect.Type {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if t.Kind() == reflect.Ptr {
|
||||
t = dereference(t.Elem())
|
||||
}
|
||||
return t
|
||||
}
|
@ -0,0 +1,187 @@
|
||||
package expr
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"reflect"
|
||||
|
||||
"github.com/antonmedv/expr/checker"
|
||||
"github.com/antonmedv/expr/compiler"
|
||||
"github.com/antonmedv/expr/conf"
|
||||
"github.com/antonmedv/expr/optimizer"
|
||||
"github.com/antonmedv/expr/parser"
|
||||
"github.com/antonmedv/expr/vm"
|
||||
)
|
||||
|
||||
// Option for configuring config.
|
||||
type Option func(c *conf.Config)
|
||||
|
||||
// Eval parses, compiles and runs given input.
|
||||
func Eval(input string, env interface{}) (interface{}, 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 := vm.Run(program, env)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return output, nil
|
||||
}
|
||||
|
||||
// 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 interface{}) Option {
|
||||
return func(c *conf.Config) {
|
||||
if _, ok := env.(map[string]interface{}); ok {
|
||||
c.MapEnv = true
|
||||
} else {
|
||||
if reflect.ValueOf(env).Kind() == reflect.Map {
|
||||
c.DefaultType = reflect.TypeOf(env).Elem()
|
||||
}
|
||||
}
|
||||
c.Strict = true
|
||||
c.Types = conf.CreateTypesTable(env)
|
||||
c.Env = env
|
||||
}
|
||||
}
|
||||
|
||||
// AllowUndefinedVariables allows to use undefined variables inside expressions.
|
||||
// This can be used with expr.Env option to partially define a few variables.
|
||||
// Note what this option is only works in map environment are used, otherwise
|
||||
// runtime.fetch will panic as there is no way to get missing field zero value.
|
||||
func AllowUndefinedVariables() Option {
|
||||
return func(c *conf.Config) {
|
||||
c.Strict = false
|
||||
}
|
||||
}
|
||||
|
||||
// Operator allows to override binary operator with function.
|
||||
func Operator(operator string, fn ...string) Option {
|
||||
return func(c *conf.Config) {
|
||||
c.Operators[operator] = append(c.Operators[operator], fn...)
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// AsBool tells the compiler to expect boolean result.
|
||||
func AsBool() Option {
|
||||
return func(c *conf.Config) {
|
||||
c.Expect = reflect.Bool
|
||||
}
|
||||
}
|
||||
|
||||
// AsInt64 tells the compiler to expect int64 result.
|
||||
func AsInt64() Option {
|
||||
return func(c *conf.Config) {
|
||||
c.Expect = reflect.Int64
|
||||
}
|
||||
}
|
||||
|
||||
// AsFloat64 tells the compiler to expect float64 result.
|
||||
func AsFloat64() Option {
|
||||
return func(c *conf.Config) {
|
||||
c.Expect = reflect.Float64
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Compile parses and compiles given input expression to bytecode program.
|
||||
func Compile(input string, ops ...Option) (*vm.Program, error) {
|
||||
config := &conf.Config{
|
||||
Operators: make(map[string][]string),
|
||||
ConstExprFns: make(map[string]reflect.Value),
|
||||
Optimize: true,
|
||||
}
|
||||
|
||||
for _, op := range ops {
|
||||
op(config)
|
||||
}
|
||||
|
||||
if err := config.Check(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tree, err := parser.Parse(input)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = checker.Check(tree, config)
|
||||
|
||||
// If we have a patch to apply, it may fix out error and
|
||||
// second type check is needed. Otherwise it is an error.
|
||||
if err != nil && len(config.Visitors) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Patch operators before Optimize, as we may also mark it as ConstExpr.
|
||||
compiler.PatchOperators(&tree.Node, config)
|
||||
|
||||
if len(config.Visitors) >= 0 {
|
||||
for _, v := range config.Visitors {
|
||||
ast.Walk(&tree.Node, v)
|
||||
}
|
||||
_, err = checker.Check(tree, config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if config.Optimize {
|
||||
err = optimizer.Optimize(&tree.Node, config)
|
||||
if err != nil {
|
||||
if fileError, ok := err.(*file.Error); ok {
|
||||
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 interface{}) (interface{}, error) {
|
||||
return vm.Run(program, env)
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Location
|
||||
Message string
|
||||
Snippet string
|
||||
}
|
||||
|
||||
func (e *Error) Error() string {
|
||||
return e.format()
|
||||
}
|
||||
|
||||
func (e *Error) Bind(source *Source) *Error {
|
||||
if snippet, found := source.Snippet(e.Location.Line); found {
|
||||
snippet := strings.Replace(snippet, "\t", " ", -1)
|
||||
srcLine := "\n | " + snippet
|
||||
var bytes = []byte(snippet)
|
||||
var indLine = "\n | "
|
||||
for i := 0; i < e.Location.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) format() string {
|
||||
if e.Location.Empty() {
|
||||
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,
|
||||
)
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package file
|
||||
|
||||
type Location struct {
|
||||
Line int // The 1-based line of the location.
|
||||
Column int // The 0-based column number of the location.
|
||||
}
|
||||
|
||||
func (l Location) Empty() bool {
|
||||
return l.Column == 0 && l.Line == 0
|
||||
}
|
@ -0,0 +1,95 @@
|
||||
package file
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
type Source struct {
|
||||
contents []rune
|
||||
lineOffsets []int32
|
||||
}
|
||||
|
||||
func NewSource(contents string) *Source {
|
||||
s := &Source{
|
||||
contents: []rune(contents),
|
||||
}
|
||||
s.updateOffsets()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *Source) MarshalJSON() ([]byte, error) {
|
||||
return json.Marshal(s.contents)
|
||||
}
|
||||
|
||||
func (s *Source) UnmarshalJSON(b []byte) error {
|
||||
contents := make([]rune, 0)
|
||||
err := json.Unmarshal(b, &contents)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.contents = contents
|
||||
s.updateOffsets()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Source) Content() string {
|
||||
return string(s.contents)
|
||||
}
|
||||
|
||||
func (s *Source) Snippet(line int) (string, bool) {
|
||||
charStart, found := s.findLineOffset(line)
|
||||
if !found || len(s.contents) == 0 {
|
||||
return "", false
|
||||
}
|
||||
charEnd, found := s.findLineOffset(line + 1)
|
||||
if found {
|
||||
return string(s.contents[charStart : charEnd-1]), true
|
||||
}
|
||||
return string(s.contents[charStart:]), true
|
||||
}
|
||||
|
||||
// updateOffsets compute line offsets up front as they are referred to frequently.
|
||||
func (s *Source) updateOffsets() {
|
||||
lines := strings.Split(string(s.contents), "\n")
|
||||
offsets := make([]int32, len(lines))
|
||||
var offset int32
|
||||
for i, line := range lines {
|
||||
offset = offset + int32(utf8.RuneCountInString(line)) + 1
|
||||
offsets[int32(i)] = offset
|
||||
}
|
||||
s.lineOffsets = offsets
|
||||
}
|
||||
|
||||
// findLineOffset returns the offset where the (1-indexed) line begins,
|
||||
// or false if line doesn't exist.
|
||||
func (s *Source) findLineOffset(line int) (int32, bool) {
|
||||
if line == 1 {
|
||||
return 0, true
|
||||
} else if line > 1 && line <= len(s.lineOffsets) {
|
||||
offset := s.lineOffsets[line-2]
|
||||
return offset, true
|
||||
}
|
||||
return -1, false
|
||||
}
|
||||
|
||||
// findLine finds the line that contains the given character offset and
|
||||
// returns the line number and offset of the beginning of that line.
|
||||
// Note that the last line is treated as if it contains all offsets
|
||||
// beyond the end of the actual source.
|
||||
func (s *Source) findLine(characterOffset int32) (int32, int32) {
|
||||
var line int32 = 1
|
||||
for _, lineOffset := range s.lineOffsets {
|
||||
if lineOffset > characterOffset {
|
||||
break
|
||||
} else {
|
||||
line++
|
||||
}
|
||||
}
|
||||
if line == 1 {
|
||||
return line, 0
|
||||
}
|
||||
return line, s.lineOffsets[line-2]
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
module github.com/antonmedv/expr
|
||||
|
||||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/gdamore/tcell v1.3.0
|
||||
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498
|
||||
github.com/sanity-io/litter v1.2.0
|
||||
github.com/stretchr/testify v1.5.1
|
||||
)
|
@ -0,0 +1,38 @@
|
||||
github.com/DATA-DOG/go-sqlmock v1.3.3/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
|
||||
github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gdamore/encoding v1.0.0 h1:+7OoQ1Bc6eTm5niUzBa0Ctsh6JbMW6Ra+YNuAtDBdko=
|
||||
github.com/gdamore/encoding v1.0.0/go.mod h1:alR0ol34c49FCSBLjhosxzcPHQbf2trDkoo5dl+VrEg=
|
||||
github.com/gdamore/tcell v1.3.0 h1:r35w0JBADPZCVQijYebl6YMWWtHRqVEGt7kL2eBADRM=
|
||||
github.com/gdamore/tcell v1.3.0/go.mod h1:Hjvr+Ofd+gLglo7RYKxxnzCBmev3BzsS67MebKS4zMM=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.2/go.mod h1:0MS4r+7BZKSJ5mw4/S5MPN+qHFF1fYclkSPilDOKW0s=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3 h1:QIbQXiugsb+q10B+MI+7DI1oQLdmnep86tWFlaaUAac=
|
||||
github.com/lucasb-eyer/go-colorful v1.0.3/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||
github.com/mattn/go-runewidth v0.0.4/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU=
|
||||
github.com/mattn/go-runewidth v0.0.8 h1:3tS41NlGYSmhhe/8fhGRzc+z3AYCw1Fe1WAyLuujKs0=
|
||||
github.com/mattn/go-runewidth v0.0.8/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
|
||||
github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498 h1:4CFNy7/q7P06AsIONZzuWy7jcdqEmYQvOZ9FAFZdbls=
|
||||
github.com/rivo/tview v0.0.0-20200219210816-cd38d7432498/go.mod h1:6lkG1x+13OShEf0EaOCaTQYyB7d5nSbb181KtjlS+84=
|
||||
github.com/rivo/uniseg v0.1.0 h1:+2KBaVoUmb9XzDsrx/Ct0W/EYOSFf/nWTauy++DprtY=
|
||||
github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||
github.com/sanity-io/litter v1.2.0 h1:DGJO0bxH/+C2EukzOSBmAlxmkhVMGqzvcx/rvySYw9M=
|
||||
github.com/sanity-io/litter v1.2.0/go.mod h1:JF6pZUFgu2Q0sBZ+HSV35P8TVPI1TTzEwyu9FXAw2W4=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
||||
github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
golang.org/x/sys v0.0.0-20190626150813-e07cf5db2756/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4 h1:sfkvUWPNGwSV+8/fNqctR5lS2AqCSqYwXdrjCxp/dXo=
|
||||
golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
|
||||
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
|
||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
@ -0,0 +1,77 @@
|
||||
package optimizer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/file"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type constExpr struct {
|
||||
applied bool
|
||||
err error
|
||||
fns map[string]reflect.Value
|
||||
}
|
||||
|
||||
func (*constExpr) Enter(*Node) {}
|
||||
func (c *constExpr) Exit(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,
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
patch := func(newNode Node) {
|
||||
c.applied = true
|
||||
Patch(node, newNode)
|
||||
}
|
||||
|
||||
switch n := (*node).(type) {
|
||||
case *FunctionNode:
|
||||
fn, ok := c.fns[n.Name]
|
||||
if ok {
|
||||
in := make([]reflect.Value, len(n.Arguments))
|
||||
for i := 0; i < len(n.Arguments); i++ {
|
||||
arg := n.Arguments[i]
|
||||
var param interface{}
|
||||
|
||||
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(¶m).Elem()
|
||||
} else {
|
||||
in[i] = reflect.ValueOf(param)
|
||||
}
|
||||
}
|
||||
|
||||
out := fn.Call(in)
|
||||
constNode := &ConstantNode{Value: out[0].Interface()}
|
||||
patch(constNode)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package optimizer
|
||||
|
||||
import (
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
)
|
||||
|
||||
type constRange struct{}
|
||||
|
||||
func (*constRange) Enter(*Node) {}
|
||||
func (*constRange) Exit(node *Node) {
|
||||
switch n := (*node).(type) {
|
||||
case *BinaryNode:
|
||||
if n.Operator == ".." {
|
||||
if min, ok := n.Left.(*IntegerNode); ok {
|
||||
if max, ok := n.Right.(*IntegerNode); ok {
|
||||
size := max.Value - min.Value + 1
|
||||
// In case the max < min, patch empty slice
|
||||
// as max must be greater than equal to min.
|
||||
if size < 1 {
|
||||
Patch(node, &ConstantNode{
|
||||
Value: make([]int, 0),
|
||||
})
|
||||
return
|
||||
}
|
||||
// In this case array is too big. Skip generation,
|
||||
// and wait for memory budget detection on runtime.
|
||||
if size > 1e6 {
|
||||
return
|
||||
}
|
||||
value := make([]int, size)
|
||||
for i := range value {
|
||||
value[i] = min.Value + i
|
||||
}
|
||||
Patch(node, &ConstantNode{
|
||||
Value: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,133 @@
|
||||
package optimizer
|
||||
|
||||
import (
|
||||
"math"
|
||||
"reflect"
|
||||
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/file"
|
||||
)
|
||||
|
||||
type fold struct {
|
||||
applied bool
|
||||
err *file.Error
|
||||
}
|
||||
|
||||
func (*fold) Enter(*Node) {}
|
||||
func (fold *fold) Exit(node *Node) {
|
||||
patch := func(newNode Node) {
|
||||
fold.applied = true
|
||||
Patch(node, newNode)
|
||||
}
|
||||
// for IntegerNode the type may have been changed from int->float
|
||||
// preserve this information by setting the type after the Patch
|
||||
patchWithType := func(newNode Node, leafType reflect.Type) {
|
||||
patch(newNode)
|
||||
newNode.SetType(leafType)
|
||||
}
|
||||
|
||||
switch n := (*node).(type) {
|
||||
case *UnaryNode:
|
||||
switch n.Operator {
|
||||
case "-":
|
||||
if i, ok := n.Node.(*IntegerNode); ok {
|
||||
patchWithType(&IntegerNode{Value: -i.Value}, n.Node.Type())
|
||||
}
|
||||
case "+":
|
||||
if i, ok := n.Node.(*IntegerNode); ok {
|
||||
patchWithType(&IntegerNode{Value: i.Value}, n.Node.Type())
|
||||
}
|
||||
}
|
||||
|
||||
case *BinaryNode:
|
||||
switch n.Operator {
|
||||
case "+":
|
||||
if a, ok := n.Left.(*IntegerNode); ok {
|
||||
if b, ok := n.Right.(*IntegerNode); ok {
|
||||
patchWithType(&IntegerNode{Value: a.Value + b.Value}, a.Type())
|
||||
}
|
||||
}
|
||||
if a, ok := n.Left.(*StringNode); ok {
|
||||
if b, ok := n.Right.(*StringNode); ok {
|
||||
patch(&StringNode{Value: a.Value + b.Value})
|
||||
}
|
||||
}
|
||||
case "-":
|
||||
if a, ok := n.Left.(*IntegerNode); ok {
|
||||
if b, ok := n.Right.(*IntegerNode); ok {
|
||||
patchWithType(&IntegerNode{Value: a.Value - b.Value}, a.Type())
|
||||
}
|
||||
}
|
||||
case "*":
|
||||
if a, ok := n.Left.(*IntegerNode); ok {
|
||||
if b, ok := n.Right.(*IntegerNode); ok {
|
||||
patchWithType(&IntegerNode{Value: a.Value * b.Value}, a.Type())
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
patchWithType(&IntegerNode{Value: a.Value / b.Value}, a.Type())
|
||||
}
|
||||
}
|
||||
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 "**":
|
||||
if a, ok := n.Left.(*IntegerNode); ok {
|
||||
if b, ok := n.Right.(*IntegerNode); ok {
|
||||
patch(&FloatNode{Value: math.Pow(float64(a.Value), float64(b.Value))})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case *ArrayNode:
|
||||
if len(n.Nodes) > 0 {
|
||||
|
||||
for _, a := range n.Nodes {
|
||||
if _, ok := a.(*IntegerNode); !ok {
|
||||
goto string
|
||||
}
|
||||
}
|
||||
{
|
||||
value := make([]int, len(n.Nodes))
|
||||
for i, a := range n.Nodes {
|
||||
value[i] = a.(*IntegerNode).Value
|
||||
}
|
||||
patch(&ConstantNode{Value: value})
|
||||
}
|
||||
|
||||
string:
|
||||
for _, a := range n.Nodes {
|
||||
if _, ok := a.(*StringNode); !ok {
|
||||
return
|
||||
}
|
||||
}
|
||||
{
|
||||
value := make([]string, len(n.Nodes))
|
||||
for i, a := range n.Nodes {
|
||||
value[i] = a.(*StringNode).Value
|
||||
}
|
||||
patch(&ConstantNode{Value: value})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,65 @@
|
||||
package optimizer
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
)
|
||||
|
||||
type inArray struct{}
|
||||
|
||||
func (*inArray) Enter(*Node) {}
|
||||
func (*inArray) Exit(node *Node) {
|
||||
switch n := (*node).(type) {
|
||||
case *BinaryNode:
|
||||
if n.Operator == "in" || n.Operator == "not 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{}{}
|
||||
}
|
||||
Patch(node, &BinaryNode{
|
||||
Operator: n.Operator,
|
||||
Left: n.Left,
|
||||
Right: &ConstantNode{Value: value},
|
||||
})
|
||||
}
|
||||
|
||||
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{}{}
|
||||
}
|
||||
Patch(node, &BinaryNode{
|
||||
Operator: n.Operator,
|
||||
Left: n.Left,
|
||||
Right: &ConstantNode{Value: value},
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package optimizer
|
||||
|
||||
import (
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
)
|
||||
|
||||
type inRange struct{}
|
||||
|
||||
func (*inRange) Enter(*Node) {}
|
||||
func (*inRange) Exit(node *Node) {
|
||||
switch n := (*node).(type) {
|
||||
case *BinaryNode:
|
||||
if n.Operator == "in" || n.Operator == "not in" {
|
||||
if rng, ok := n.Right.(*BinaryNode); ok && rng.Operator == ".." {
|
||||
if from, ok := rng.Left.(*IntegerNode); ok {
|
||||
if to, ok := rng.Right.(*IntegerNode); ok {
|
||||
Patch(node, &BinaryNode{
|
||||
Operator: "and",
|
||||
Left: &BinaryNode{
|
||||
Operator: ">=",
|
||||
Left: n.Left,
|
||||
Right: from,
|
||||
},
|
||||
Right: &BinaryNode{
|
||||
Operator: "<=",
|
||||
Left: n.Left,
|
||||
Right: to,
|
||||
},
|
||||
})
|
||||
if n.Operator == "not in" {
|
||||
Patch(node, &UnaryNode{
|
||||
Operator: "not",
|
||||
Node: *node,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
package optimizer
|
||||
|
||||
import (
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/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.ConstExprFns) > 0 {
|
||||
for limit := 100; limit >= 0; limit-- {
|
||||
constExpr := &constExpr{
|
||||
fns: config.ConstExprFns,
|
||||
}
|
||||
Walk(node, constExpr)
|
||||
if constExpr.err != nil {
|
||||
return constExpr.err
|
||||
}
|
||||
if !constExpr.applied {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Walk(node, &inRange{})
|
||||
Walk(node, &constRange{})
|
||||
return nil
|
||||
}
|
@ -0,0 +1,212 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/antonmedv/expr/file"
|
||||
)
|
||||
|
||||
func Lex(source *file.Source) ([]Token, error) {
|
||||
l := &lexer{
|
||||
input: source.Content(),
|
||||
tokens: make([]Token, 0),
|
||||
}
|
||||
|
||||
l.loc = file.Location{Line: 1, Column: 0}
|
||||
l.prev = l.loc
|
||||
l.startLoc = l.loc
|
||||
|
||||
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 {
|
||||
input string
|
||||
tokens []Token
|
||||
start, end int // current position in input
|
||||
width int // last rune width
|
||||
startLoc file.Location // start location
|
||||
prev, loc file.Location // prev location of end location, end location
|
||||
err *file.Error
|
||||
}
|
||||
|
||||
const eof rune = -1
|
||||
|
||||
func (l *lexer) next() rune {
|
||||
if l.end >= len(l.input) {
|
||||
l.width = 0
|
||||
return eof
|
||||
}
|
||||
r, w := utf8.DecodeRuneInString(l.input[l.end:])
|
||||
l.width = w
|
||||
l.end += w
|
||||
|
||||
l.prev = l.loc
|
||||
if r == '\n' {
|
||||
l.loc.Line++
|
||||
l.loc.Column = 0
|
||||
} else {
|
||||
l.loc.Column++
|
||||
}
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *lexer) peek() rune {
|
||||
r := l.next()
|
||||
l.backup()
|
||||
return r
|
||||
}
|
||||
|
||||
func (l *lexer) backup() {
|
||||
l.end -= l.width
|
||||
l.loc = l.prev
|
||||
}
|
||||
|
||||
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: l.startLoc,
|
||||
Kind: t,
|
||||
Value: value,
|
||||
})
|
||||
l.start = l.end
|
||||
l.startLoc = l.loc
|
||||
}
|
||||
|
||||
func (l *lexer) emitEOF() {
|
||||
l.tokens = append(l.tokens, Token{
|
||||
Location: l.prev, // Point to previous position for better error messages.
|
||||
Kind: EOF,
|
||||
})
|
||||
l.start = l.end
|
||||
l.startLoc = l.loc
|
||||
}
|
||||
|
||||
func (l *lexer) word() string {
|
||||
return l.input[l.start:l.end]
|
||||
}
|
||||
|
||||
func (l *lexer) ignore() {
|
||||
l.start = l.end
|
||||
l.startLoc = l.loc
|
||||
}
|
||||
|
||||
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) acceptWord(word string) bool {
|
||||
pos, loc, prev := l.end, l.loc, l.prev
|
||||
|
||||
// Skip spaces (U+0020) if any
|
||||
r := l.peek()
|
||||
for ; r == ' '; r = l.peek() {
|
||||
l.next()
|
||||
}
|
||||
|
||||
for _, ch := range word {
|
||||
if l.next() != ch {
|
||||
l.end, l.loc, l.prev = pos, loc, prev
|
||||
return false
|
||||
}
|
||||
}
|
||||
if r = l.peek(); r != ' ' && r != eof {
|
||||
l.end, l.loc, l.prev = pos, loc, prev
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (l *lexer) error(format string, args ...interface{}) stateFn {
|
||||
if l.err == nil { // show first error
|
||||
l.err = &file.Error{
|
||||
Location: l.loc,
|
||||
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
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
type stateFn func(*lexer) stateFn
|
||||
|
||||
func root(l *lexer) stateFn {
|
||||
switch r := l.next(); {
|
||||
case r == eof:
|
||||
l.emitEOF()
|
||||
return nil
|
||||
case IsSpace(r):
|
||||
l.ignore()
|
||||
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 '0' <= r && r <= '9':
|
||||
l.backup()
|
||||
return number
|
||||
case r == '?':
|
||||
if l.peek() == '.' {
|
||||
return nilsafe
|
||||
}
|
||||
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 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)
|
||||
loc, prev, end := l.loc, l.prev, 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.loc, l.prev, l.end = loc, prev, end
|
||||
return true
|
||||
}
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
if l.accept("eE") {
|
||||
l.accept("+-")
|
||||
l.acceptRun(digits)
|
||||
}
|
||||
// Next thing mustn't be alphanumeric.
|
||||
if 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 nilsafe(l *lexer) stateFn {
|
||||
l.next()
|
||||
l.accept("?.")
|
||||
l.emit(Operator)
|
||||
return root
|
||||
}
|
||||
|
||||
func identifier(l *lexer) stateFn {
|
||||
loop:
|
||||
for {
|
||||
switch r := l.next(); {
|
||||
case IsAlphaNumeric(r):
|
||||
// absorb
|
||||
default:
|
||||
l.backup()
|
||||
switch l.word() {
|
||||
case "not":
|
||||
return not
|
||||
case "in", "or", "and", "matches", "contains", "startsWith", "endsWith":
|
||||
l.emit(Operator)
|
||||
default:
|
||||
l.emit(Identifier)
|
||||
}
|
||||
break loop
|
||||
}
|
||||
}
|
||||
return root
|
||||
}
|
||||
|
||||
func not(l *lexer) stateFn {
|
||||
switch l.acceptWord("in") {
|
||||
case true:
|
||||
l.emitValue(Operator, "not in")
|
||||
case false:
|
||||
l.emitValue(Operator, "not")
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
@ -0,0 +1,47 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/antonmedv/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
|
||||
}
|
@ -0,0 +1,194 @@
|
||||
package lexer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
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
|
||||
buf := make([]byte, 0, 3*n/2)
|
||||
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
|
||||
}
|
@ -0,0 +1,588 @@
|
||||
package parser
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
|
||||
. "github.com/antonmedv/expr/ast"
|
||||
"github.com/antonmedv/expr/file"
|
||||
. "github.com/antonmedv/expr/parser/lexer"
|
||||
)
|
||||
|
||||
type associativity int
|
||||
|
||||
const (
|
||||
left associativity = iota + 1
|
||||
right
|
||||
)
|
||||
|
||||
type operator struct {
|
||||
precedence int
|
||||
associativity associativity
|
||||
}
|
||||
|
||||
type builtin struct {
|
||||
arity int
|
||||
}
|
||||
|
||||
var unaryOperators = map[string]operator{
|
||||
"not": {50, left},
|
||||
"!": {50, left},
|
||||
"-": {500, left},
|
||||
"+": {500, left},
|
||||
}
|
||||
|
||||
var binaryOperators = map[string]operator{
|
||||
"or": {10, left},
|
||||
"||": {10, left},
|
||||
"and": {15, left},
|
||||
"&&": {15, left},
|
||||
"==": {20, left},
|
||||
"!=": {20, left},
|
||||
"<": {20, left},
|
||||
">": {20, left},
|
||||
">=": {20, left},
|
||||
"<=": {20, left},
|
||||
"not in": {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},
|
||||
"**": {70, right},
|
||||
}
|
||||
|
||||
var builtins = map[string]builtin{
|
||||
"len": {1},
|
||||
"all": {2},
|
||||
"none": {2},
|
||||
"any": {2},
|
||||
"one": {2},
|
||||
"filter": {2},
|
||||
"map": {2},
|
||||
"count": {2},
|
||||
}
|
||||
|
||||
type parser struct {
|
||||
tokens []Token
|
||||
current Token
|
||||
pos int
|
||||
err *file.Error
|
||||
depth int // closure call depth
|
||||
}
|
||||
|
||||
type Tree struct {
|
||||
Node Node
|
||||
Source *file.Source
|
||||
}
|
||||
|
||||
func Parse(input string) (*Tree, error) {
|
||||
source := file.NewSource(input)
|
||||
|
||||
tokens, err := Lex(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
p := &parser{
|
||||
tokens: tokens,
|
||||
current: tokens[0],
|
||||
}
|
||||
|
||||
node := p.parseExpression(0)
|
||||
|
||||
if !p.current.Is(EOF) {
|
||||
p.error("unexpected token %v", p.current)
|
||||
}
|
||||
|
||||
if p.err != nil {
|
||||
return nil, p.err.Bind(source)
|
||||
}
|
||||
|
||||
return &Tree{
|
||||
Node: node,
|
||||
Source: source,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *parser) error(format string, args ...interface{}) {
|
||||
if p.err == nil { // show first error
|
||||
p.err = &file.Error{
|
||||
Location: p.current.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) parseExpression(precedence int) Node {
|
||||
nodeLeft := p.parsePrimary()
|
||||
|
||||
token := p.current
|
||||
for token.Is(Operator) && p.err == nil {
|
||||
if op, ok := binaryOperators[token.Value]; ok {
|
||||
if op.precedence >= precedence {
|
||||
p.next()
|
||||
|
||||
var nodeRight Node
|
||||
if op.associativity == left {
|
||||
nodeRight = p.parseExpression(op.precedence + 1)
|
||||
} else {
|
||||
nodeRight = p.parseExpression(op.precedence)
|
||||
}
|
||||
|
||||
if token.Is(Operator, "matches") {
|
||||
var r *regexp.Regexp
|
||||
var err error
|
||||
|
||||
if s, ok := nodeRight.(*StringNode); ok {
|
||||
r, err = regexp.Compile(s.Value)
|
||||
if err != nil {
|
||||
p.error("%v", err)
|
||||
}
|
||||
}
|
||||
nodeLeft = &MatchesNode{
|
||||
Regexp: r,
|
||||
Left: nodeLeft,
|
||||
Right: nodeRight,
|
||||
}
|
||||
nodeLeft.SetLocation(token.Location)
|
||||
} else {
|
||||
nodeLeft = &BinaryNode{
|
||||
Operator: token.Value,
|
||||
Left: nodeLeft,
|
||||
Right: nodeRight,
|
||||
}
|
||||
nodeLeft.SetLocation(token.Location)
|
||||
}
|
||||
token = p.current
|
||||
continue
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
if precedence == 0 {
|
||||
nodeLeft = p.parseConditionalExpression(nodeLeft)
|
||||
}
|
||||
|
||||
return nodeLeft
|
||||
}
|
||||
|
||||
func (p *parser) parsePrimary() Node {
|
||||
token := p.current
|
||||
|
||||
if token.Is(Operator) {
|
||||
if op, ok := unaryOperators[token.Value]; ok {
|
||||
p.next()
|
||||
expr := p.parseExpression(op.precedence)
|
||||
node := &UnaryNode{
|
||||
Operator: token.Value,
|
||||
Node: expr,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
return p.parsePostfixExpression(node)
|
||||
}
|
||||
}
|
||||
|
||||
if token.Is(Bracket, "(") {
|
||||
p.next()
|
||||
expr := p.parseExpression(0)
|
||||
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, ".") {
|
||||
if token.Is(Operator, "#") {
|
||||
p.next()
|
||||
}
|
||||
node := &PointerNode{}
|
||||
node.SetLocation(token.Location)
|
||||
return p.parsePostfixExpression(node)
|
||||
}
|
||||
} else {
|
||||
if token.Is(Operator, "#") || token.Is(Operator, ".") {
|
||||
p.error("cannot use pointer accessor outside closure")
|
||||
}
|
||||
}
|
||||
|
||||
return p.parsePrimaryExpression()
|
||||
}
|
||||
|
||||
func (p *parser) parseConditionalExpression(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 = &ConditionalNode{
|
||||
Cond: node,
|
||||
Exp1: expr1,
|
||||
Exp2: expr2,
|
||||
}
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *parser) parsePrimaryExpression() Node {
|
||||
var node Node
|
||||
token := p.current
|
||||
|
||||
switch token.Kind {
|
||||
|
||||
case Identifier:
|
||||
p.next()
|
||||
switch token.Value {
|
||||
case "true":
|
||||
node := &BoolNode{Value: true}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
case "false":
|
||||
node := &BoolNode{Value: false}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
case "nil":
|
||||
node := &NilNode{}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
default:
|
||||
node = p.parseIdentifierExpression(token, p.current)
|
||||
}
|
||||
|
||||
case Number:
|
||||
p.next()
|
||||
value := strings.Replace(token.Value, "_", "", -1)
|
||||
if strings.ContainsAny(value, ".eE") {
|
||||
number, err := strconv.ParseFloat(value, 64)
|
||||
if err != nil {
|
||||
p.error("invalid float literal: %v", err)
|
||||
}
|
||||
node := &FloatNode{Value: number}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
} else if strings.Contains(value, "x") {
|
||||
number, err := strconv.ParseInt(value, 0, 64)
|
||||
if err != nil {
|
||||
p.error("invalid hex literal: %v", err)
|
||||
}
|
||||
node := &IntegerNode{Value: int(number)}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
} else {
|
||||
number, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
p.error("invalid integer literal: %v", err)
|
||||
}
|
||||
node := &IntegerNode{Value: int(number)}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
}
|
||||
|
||||
case String:
|
||||
p.next()
|
||||
node := &StringNode{Value: token.Value}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
|
||||
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) parseIdentifierExpression(token, next Token) Node {
|
||||
var node Node
|
||||
if p.current.Is(Bracket, "(") {
|
||||
var arguments []Node
|
||||
|
||||
if b, ok := builtins[token.Value]; ok {
|
||||
p.expect(Bracket, "(")
|
||||
// TODO: Add builtins signatures.
|
||||
if b.arity == 1 {
|
||||
arguments = make([]Node, 1)
|
||||
arguments[0] = p.parseExpression(0)
|
||||
} else if b.arity == 2 {
|
||||
arguments = make([]Node, 2)
|
||||
arguments[0] = p.parseExpression(0)
|
||||
p.expect(Operator, ",")
|
||||
arguments[1] = p.parseClosure()
|
||||
}
|
||||
p.expect(Bracket, ")")
|
||||
|
||||
node = &BuiltinNode{
|
||||
Name: token.Value,
|
||||
Arguments: arguments,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
} else {
|
||||
arguments = p.parseArguments()
|
||||
node = &FunctionNode{
|
||||
Name: token.Value,
|
||||
Arguments: arguments,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
}
|
||||
} else {
|
||||
var nilsafe bool
|
||||
if next.Value == "?." {
|
||||
nilsafe = true
|
||||
}
|
||||
node = &IdentifierNode{Value: token.Value, NilSafe: nilsafe}
|
||||
node.SetLocation(token.Location)
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *parser) parseClosure() Node {
|
||||
token := p.current
|
||||
p.expect(Bracket, "{")
|
||||
|
||||
p.depth++
|
||||
node := p.parseExpression(0)
|
||||
p.depth--
|
||||
|
||||
p.expect(Bracket, "}")
|
||||
closure := &ClosureNode{
|
||||
Node: node,
|
||||
}
|
||||
closure.SetLocation(token.Location)
|
||||
return closure
|
||||
}
|
||||
|
||||
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 := &ArrayNode{Nodes: nodes}
|
||||
node.SetLocation(token.Location)
|
||||
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
|
||||
// a map key can be:
|
||||
// * a number
|
||||
// * a string
|
||||
// * a identifier, which is equivalent to a string
|
||||
// * an expression, which must be enclosed in parentheses -- (1 + 2)
|
||||
if p.current.Is(Number) || p.current.Is(String) || p.current.Is(Identifier) {
|
||||
key = &StringNode{Value: p.current.Value}
|
||||
key.SetLocation(token.Location)
|
||||
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 := &PairNode{Key: key, Value: node}
|
||||
pair.SetLocation(token.Location)
|
||||
nodes = append(nodes, pair)
|
||||
}
|
||||
|
||||
end:
|
||||
p.expect(Bracket, "}")
|
||||
|
||||
node := &MapNode{Pairs: nodes}
|
||||
node.SetLocation(token.Location)
|
||||
return node
|
||||
}
|
||||
|
||||
func (p *parser) parsePostfixExpression(node Node) Node {
|
||||
token := p.current
|
||||
var nilsafe bool
|
||||
for (token.Is(Operator) || token.Is(Bracket)) && p.err == nil {
|
||||
if token.Value == "." || token.Value == "?." {
|
||||
if token.Value == "?." {
|
||||
nilsafe = true
|
||||
}
|
||||
p.next()
|
||||
|
||||
token = p.current
|
||||
p.next()
|
||||
|
||||
if token.Kind != Identifier &&
|
||||
// Operators like "not" and "matches" are valid methods or property names.
|
||||
(token.Kind != Operator || !isValidIdentifier(token.Value)) {
|
||||
p.error("expected name")
|
||||
}
|
||||
|
||||
if p.current.Is(Bracket, "(") {
|
||||
arguments := p.parseArguments()
|
||||
node = &MethodNode{
|
||||
Node: node,
|
||||
Method: token.Value,
|
||||
Arguments: arguments,
|
||||
NilSafe: nilsafe,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
} else {
|
||||
node = &PropertyNode{
|
||||
Node: node,
|
||||
Property: token.Value,
|
||||
NilSafe: nilsafe,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
}
|
||||
|
||||
} else if token.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 = &SliceNode{
|
||||
Node: node,
|
||||
To: to,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
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 = &SliceNode{
|
||||
Node: node,
|
||||
From: from,
|
||||
To: to,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
p.expect(Bracket, "]")
|
||||
|
||||
} else {
|
||||
// Slice operator [:] was not found, it should by just index node.
|
||||
|
||||
node = &IndexNode{
|
||||
Node: node,
|
||||
Index: from,
|
||||
}
|
||||
node.SetLocation(token.Location)
|
||||
p.expect(Bracket, "]")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
|
||||
token = p.current
|
||||
}
|
||||
return node
|
||||
}
|
||||
|
||||
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 (p *parser) parseArguments() []Node {
|
||||
p.expect(Bracket, "(")
|
||||
nodes := make([]Node, 0)
|
||||
for !p.current.Is(Bracket, ")") && p.err == nil {
|
||||
if len(nodes) > 0 {
|
||||
p.expect(Operator, ",")
|
||||
}
|
||||
node := p.parseExpression(0)
|
||||
nodes = append(nodes, node)
|
||||
}
|
||||
p.expect(Bracket, ")")
|
||||
|
||||
return nodes
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,56 @@
|
||||
package vm
|
||||
|
||||
const (
|
||||
OpPush byte = iota
|
||||
OpPop
|
||||
OpRot
|
||||
OpFetch
|
||||
OpFetchNilSafe
|
||||
OpFetchMap
|
||||
OpTrue
|
||||
OpFalse
|
||||
OpNil
|
||||
OpNegate
|
||||
OpNot
|
||||
OpEqual
|
||||
OpEqualInt
|
||||
OpEqualString
|
||||
OpJump
|
||||
OpJumpIfTrue
|
||||
OpJumpIfFalse
|
||||
OpJumpBackward
|
||||
OpIn
|
||||
OpLess
|
||||
OpMore
|
||||
OpLessOrEqual
|
||||
OpMoreOrEqual
|
||||
OpAdd
|
||||
OpSubtract
|
||||
OpMultiply
|
||||
OpDivide
|
||||
OpModulo
|
||||
OpExponent
|
||||
OpRange
|
||||
OpMatches
|
||||
OpMatchesConst
|
||||
OpContains
|
||||
OpStartsWith
|
||||
OpEndsWith
|
||||
OpIndex
|
||||
OpSlice
|
||||
OpProperty
|
||||
OpPropertyNilSafe
|
||||
OpCall
|
||||
OpCallFast
|
||||
OpMethod
|
||||
OpMethodNilSafe
|
||||
OpArray
|
||||
OpMap
|
||||
OpLen
|
||||
OpCast
|
||||
OpStore
|
||||
OpLoad
|
||||
OpInc
|
||||
OpBegin
|
||||
OpEnd // This opcode must be at the end of this list.
|
||||
)
|
@ -0,0 +1,225 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"regexp"
|
||||
|
||||
"github.com/antonmedv/expr/file"
|
||||
)
|
||||
|
||||
type Program struct {
|
||||
Source *file.Source
|
||||
Locations map[int]file.Location
|
||||
Constants []interface{}
|
||||
Bytecode []byte
|
||||
}
|
||||
|
||||
func (program *Program) Disassemble() string {
|
||||
out := ""
|
||||
ip := 0
|
||||
for ip < len(program.Bytecode) {
|
||||
pp := ip
|
||||
op := program.Bytecode[ip]
|
||||
ip++
|
||||
|
||||
readArg := func() uint16 {
|
||||
if ip+1 >= len(program.Bytecode) {
|
||||
return 0
|
||||
}
|
||||
|
||||
i := binary.LittleEndian.Uint16([]byte{program.Bytecode[ip], program.Bytecode[ip+1]})
|
||||
ip += 2
|
||||
return i
|
||||
}
|
||||
|
||||
code := func(label string) {
|
||||
out += fmt.Sprintf("%v\t%v\n", pp, label)
|
||||
}
|
||||
jump := func(label string) {
|
||||
a := readArg()
|
||||
out += fmt.Sprintf("%v\t%v\t%v\t(%v)\n", pp, label, a, ip+int(a))
|
||||
}
|
||||
back := func(label string) {
|
||||
a := readArg()
|
||||
out += fmt.Sprintf("%v\t%v\t%v\t(%v)\n", pp, label, a, ip-int(a))
|
||||
}
|
||||
argument := func(label string) {
|
||||
a := readArg()
|
||||
out += fmt.Sprintf("%v\t%v\t%v\n", pp, label, a)
|
||||
}
|
||||
constant := func(label string) {
|
||||
a := readArg()
|
||||
var c interface{}
|
||||
if int(a) < len(program.Constants) {
|
||||
c = program.Constants[a]
|
||||
}
|
||||
if r, ok := c.(*regexp.Regexp); ok {
|
||||
c = r.String()
|
||||
}
|
||||
out += fmt.Sprintf("%v\t%v\t%v\t%#v\n", pp, label, a, c)
|
||||
}
|
||||
|
||||
switch op {
|
||||
case OpPush:
|
||||
constant("OpPush")
|
||||
|
||||
case OpPop:
|
||||
code("OpPop")
|
||||
|
||||
case OpRot:
|
||||
code("OpRot")
|
||||
|
||||
case OpFetch:
|
||||
constant("OpFetch")
|
||||
|
||||
case OpFetchNilSafe:
|
||||
constant("OpFetchNilSafe")
|
||||
|
||||
case OpFetchMap:
|
||||
constant("OpFetchMap")
|
||||
|
||||
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 OpJumpBackward:
|
||||
back("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 OpIndex:
|
||||
code("OpIndex")
|
||||
|
||||
case OpSlice:
|
||||
code("OpSlice")
|
||||
|
||||
case OpProperty:
|
||||
constant("OpProperty")
|
||||
|
||||
case OpPropertyNilSafe:
|
||||
constant("OpPropertyNilSafe")
|
||||
|
||||
case OpCall:
|
||||
constant("OpCall")
|
||||
|
||||
case OpCallFast:
|
||||
constant("OpCallFast")
|
||||
|
||||
case OpMethod:
|
||||
constant("OpMethod")
|
||||
|
||||
case OpMethodNilSafe:
|
||||
constant("OpMethodNilSafe")
|
||||
|
||||
case OpArray:
|
||||
code("OpArray")
|
||||
|
||||
case OpMap:
|
||||
code("OpMap")
|
||||
|
||||
case OpLen:
|
||||
code("OpLen")
|
||||
|
||||
case OpCast:
|
||||
argument("OpCast")
|
||||
|
||||
case OpStore:
|
||||
constant("OpStore")
|
||||
|
||||
case OpLoad:
|
||||
constant("OpLoad")
|
||||
|
||||
case OpInc:
|
||||
constant("OpInc")
|
||||
|
||||
case OpBegin:
|
||||
code("OpBegin")
|
||||
|
||||
case OpEnd:
|
||||
code("OpEnd")
|
||||
|
||||
default:
|
||||
out += fmt.Sprintf("%v\t%#x\n", pp, op)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
@ -0,0 +1,370 @@
|
||||
package vm
|
||||
|
||||
//go:generate go run ./generate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
type Call struct {
|
||||
Name string
|
||||
Size int
|
||||
}
|
||||
|
||||
type Scope map[string]interface{}
|
||||
|
||||
type Fetcher interface {
|
||||
Fetch(interface{}) interface{}
|
||||
}
|
||||
|
||||
func fetch(from, i interface{}, nilsafe bool) interface{} {
|
||||
if fetcher, ok := from.(Fetcher); ok {
|
||||
value := fetcher.Fetch(i)
|
||||
if value != nil {
|
||||
return value
|
||||
}
|
||||
if !nilsafe {
|
||||
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
v := reflect.ValueOf(from)
|
||||
kind := v.Kind()
|
||||
|
||||
// Structures 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.
|
||||
if kind == reflect.Ptr && reflect.Indirect(v).Kind() == reflect.Struct {
|
||||
v = reflect.Indirect(v)
|
||||
kind = v.Kind()
|
||||
}
|
||||
|
||||
switch kind {
|
||||
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
value := v.Index(toInt(i))
|
||||
if value.IsValid() && value.CanInterface() {
|
||||
return value.Interface()
|
||||
}
|
||||
|
||||
case reflect.Map:
|
||||
value := v.MapIndex(reflect.ValueOf(i))
|
||||
if value.IsValid() {
|
||||
if value.CanInterface() {
|
||||
return value.Interface()
|
||||
}
|
||||
} else {
|
||||
elem := reflect.TypeOf(from).Elem()
|
||||
return reflect.Zero(elem).Interface()
|
||||
}
|
||||
|
||||
case reflect.Struct:
|
||||
value := v.FieldByName(reflect.ValueOf(i).String())
|
||||
if value.IsValid() && value.CanInterface() {
|
||||
return value.Interface()
|
||||
}
|
||||
}
|
||||
if !nilsafe {
|
||||
panic(fmt.Sprintf("cannot fetch %v from %T", i, from))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func slice(array, from, to interface{}) interface{} {
|
||||
v := reflect.ValueOf(array)
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Array, reflect.Slice, reflect.String:
|
||||
length := v.Len()
|
||||
a, b := toInt(from), toInt(to)
|
||||
|
||||
if b > length {
|
||||
b = length
|
||||
}
|
||||
if a > b {
|
||||
a = b
|
||||
}
|
||||
|
||||
value := v.Slice(a, b)
|
||||
if value.IsValid() && value.CanInterface() {
|
||||
return value.Interface()
|
||||
}
|
||||
|
||||
case reflect.Ptr:
|
||||
value := v.Elem()
|
||||
if value.IsValid() && value.CanInterface() {
|
||||
return slice(value.Interface(), from, to)
|
||||
}
|
||||
|
||||
}
|
||||
panic(fmt.Sprintf("cannot slice %v", from))
|
||||
}
|
||||
|
||||
func FetchFn(from interface{}, name string) reflect.Value {
|
||||
v := reflect.ValueOf(from)
|
||||
|
||||
// Methods can be defined on any type.
|
||||
if v.NumMethod() > 0 {
|
||||
method := v.MethodByName(name)
|
||||
if method.IsValid() {
|
||||
return method
|
||||
}
|
||||
}
|
||||
|
||||
d := v
|
||||
if v.Kind() == reflect.Ptr {
|
||||
d = v.Elem()
|
||||
}
|
||||
|
||||
switch d.Kind() {
|
||||
case reflect.Map:
|
||||
value := d.MapIndex(reflect.ValueOf(name))
|
||||
if value.IsValid() && value.CanInterface() {
|
||||
return value.Elem()
|
||||
}
|
||||
case reflect.Struct:
|
||||
// If struct has not method, maybe it has func field.
|
||||
// To access this field we need dereference value.
|
||||
value := d.FieldByName(name)
|
||||
if value.IsValid() {
|
||||
return value
|
||||
}
|
||||
}
|
||||
panic(fmt.Sprintf(`cannot get "%v" from %T`, name, from))
|
||||
}
|
||||
|
||||
func FetchFnNil(from interface{}, name string) reflect.Value {
|
||||
if v := reflect.ValueOf(from); !v.IsValid() {
|
||||
return v
|
||||
}
|
||||
return FetchFn(from, name)
|
||||
}
|
||||
|
||||
func in(needle interface{}, array interface{}) 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() && value.CanInterface() {
|
||||
if equal(value.Interface(), needle).(bool) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
|
||||
case reflect.Map:
|
||||
n := reflect.ValueOf(needle)
|
||||
if !n.IsValid() {
|
||||
panic(fmt.Sprintf("cannot use %T as index to %T", needle, array))
|
||||
}
|
||||
value := v.MapIndex(n)
|
||||
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() && value.CanInterface() {
|
||||
return in(needle, value.Interface())
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
panic(fmt.Sprintf(`operator "in"" not defined on %T`, array))
|
||||
}
|
||||
|
||||
func length(a interface{}) 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 interface{}) interface{} {
|
||||
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 interface{}) 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 interface{}) 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 interface{}) 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 interface{}) 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: float64(%T)", x))
|
||||
}
|
||||
}
|
||||
|
||||
func isNil(v interface{}) 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
|
||||
}
|
||||
}
|
@ -0,0 +1,484 @@
|
||||
package vm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
"github.com/antonmedv/expr/file"
|
||||
)
|
||||
|
||||
var errorType = reflect.TypeOf((*error)(nil)).Elem()
|
||||
|
||||
var (
|
||||
MemoryBudget int = 1e6
|
||||
)
|
||||
|
||||
func Run(program *Program, env interface{}) (interface{}, error) {
|
||||
if program == nil {
|
||||
return nil, fmt.Errorf("program is nil")
|
||||
}
|
||||
|
||||
vm := VM{}
|
||||
return vm.Run(program, env)
|
||||
}
|
||||
|
||||
type VM struct {
|
||||
stack []interface{}
|
||||
constants []interface{}
|
||||
bytecode []byte
|
||||
ip int
|
||||
pp int
|
||||
scopes []Scope
|
||||
debug bool
|
||||
step chan struct{}
|
||||
curr chan int
|
||||
memory int
|
||||
limit int
|
||||
}
|
||||
|
||||
func Debug() *VM {
|
||||
vm := &VM{
|
||||
debug: true,
|
||||
step: make(chan struct{}, 0),
|
||||
curr: make(chan int, 0),
|
||||
}
|
||||
return vm
|
||||
}
|
||||
|
||||
func (vm *VM) Run(program *Program, env interface{}) (out interface{}, err error) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
f := &file.Error{
|
||||
Location: program.Locations[vm.pp],
|
||||
Message: fmt.Sprintf("%v", r),
|
||||
}
|
||||
err = f.Bind(program.Source)
|
||||
}
|
||||
}()
|
||||
|
||||
vm.limit = MemoryBudget
|
||||
vm.ip = 0
|
||||
vm.pp = 0
|
||||
|
||||
if vm.stack == nil {
|
||||
vm.stack = make([]interface{}, 0, 2)
|
||||
} else {
|
||||
vm.stack = vm.stack[0:0]
|
||||
}
|
||||
|
||||
if vm.scopes != nil {
|
||||
vm.scopes = vm.scopes[0:0]
|
||||
}
|
||||
|
||||
vm.bytecode = program.Bytecode
|
||||
vm.constants = program.Constants
|
||||
|
||||
for vm.ip < len(vm.bytecode) {
|
||||
|
||||
if vm.debug {
|
||||
<-vm.step
|
||||
}
|
||||
|
||||
vm.pp = vm.ip
|
||||
vm.ip++
|
||||
op := vm.bytecode[vm.pp]
|
||||
|
||||
switch op {
|
||||
|
||||
case OpPush:
|
||||
vm.push(vm.constant())
|
||||
|
||||
case OpPop:
|
||||
vm.pop()
|
||||
|
||||
case OpRot:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(b)
|
||||
vm.push(a)
|
||||
|
||||
case OpFetch:
|
||||
vm.push(fetch(env, vm.constant(), false))
|
||||
|
||||
case OpFetchNilSafe:
|
||||
vm.push(fetch(env, vm.constant(), true))
|
||||
|
||||
case OpFetchMap:
|
||||
vm.push(env.(map[string]interface{})[vm.constant().(string)])
|
||||
|
||||
case OpTrue:
|
||||
vm.push(true)
|
||||
|
||||
case OpFalse:
|
||||
vm.push(false)
|
||||
|
||||
case OpNil:
|
||||
vm.push(nil)
|
||||
|
||||
case OpNegate:
|
||||
v := 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(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:
|
||||
offset := vm.arg()
|
||||
vm.ip += int(offset)
|
||||
|
||||
case OpJumpIfTrue:
|
||||
offset := vm.arg()
|
||||
if vm.current().(bool) {
|
||||
vm.ip += int(offset)
|
||||
}
|
||||
|
||||
case OpJumpIfFalse:
|
||||
offset := vm.arg()
|
||||
if !vm.current().(bool) {
|
||||
vm.ip += int(offset)
|
||||
}
|
||||
|
||||
case OpJumpBackward:
|
||||
offset := vm.arg()
|
||||
vm.ip -= int(offset)
|
||||
|
||||
case OpIn:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(in(a, b))
|
||||
|
||||
case OpLess:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(less(a, b))
|
||||
|
||||
case OpMore:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(more(a, b))
|
||||
|
||||
case OpLessOrEqual:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(lessOrEqual(a, b))
|
||||
|
||||
case OpMoreOrEqual:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(moreOrEqual(a, b))
|
||||
|
||||
case OpAdd:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(add(a, b))
|
||||
|
||||
case OpSubtract:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(subtract(a, b))
|
||||
|
||||
case OpMultiply:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(multiply(a, b))
|
||||
|
||||
case OpDivide:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(divide(a, b))
|
||||
|
||||
case OpModulo:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(modulo(a, b))
|
||||
|
||||
case OpExponent:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(exponent(a, b))
|
||||
|
||||
case OpRange:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
min := toInt(a)
|
||||
max := toInt(b)
|
||||
size := max - min + 1
|
||||
if vm.memory+size >= vm.limit {
|
||||
panic("memory budget exceeded")
|
||||
}
|
||||
vm.push(makeRange(min, max))
|
||||
vm.memory += size
|
||||
|
||||
case OpMatches:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
match, err := regexp.MatchString(b.(string), a.(string))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
vm.push(match)
|
||||
|
||||
case OpMatchesConst:
|
||||
a := vm.pop()
|
||||
r := vm.constant().(*regexp.Regexp)
|
||||
vm.push(r.MatchString(a.(string)))
|
||||
|
||||
case OpContains:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(strings.Contains(a.(string), b.(string)))
|
||||
|
||||
case OpStartsWith:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(strings.HasPrefix(a.(string), b.(string)))
|
||||
|
||||
case OpEndsWith:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(strings.HasSuffix(a.(string), b.(string)))
|
||||
|
||||
case OpIndex:
|
||||
b := vm.pop()
|
||||
a := vm.pop()
|
||||
vm.push(fetch(a, b, false))
|
||||
|
||||
case OpSlice:
|
||||
from := vm.pop()
|
||||
to := vm.pop()
|
||||
node := vm.pop()
|
||||
vm.push(slice(node, from, to))
|
||||
|
||||
case OpProperty:
|
||||
a := vm.pop()
|
||||
b := vm.constant()
|
||||
vm.push(fetch(a, b, false))
|
||||
|
||||
case OpPropertyNilSafe:
|
||||
a := vm.pop()
|
||||
b := vm.constant()
|
||||
vm.push(fetch(a, b, true))
|
||||
|
||||
case OpCall:
|
||||
call := vm.constant().(Call)
|
||||
in := make([]reflect.Value, call.Size)
|
||||
for i := call.Size - 1; i >= 0; i-- {
|
||||
param := vm.pop()
|
||||
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(¶m).Elem()
|
||||
} else {
|
||||
in[i] = reflect.ValueOf(param)
|
||||
}
|
||||
}
|
||||
out := FetchFn(env, call.Name).Call(in)
|
||||
if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
|
||||
return nil, out[1].Interface().(error)
|
||||
}
|
||||
vm.push(out[0].Interface())
|
||||
|
||||
case OpCallFast:
|
||||
call := vm.constant().(Call)
|
||||
in := make([]interface{}, call.Size)
|
||||
for i := call.Size - 1; i >= 0; i-- {
|
||||
in[i] = vm.pop()
|
||||
}
|
||||
fn := FetchFn(env, call.Name).Interface()
|
||||
if typed, ok := fn.(func(...interface{}) interface{}); ok {
|
||||
vm.push(typed(in...))
|
||||
} else if typed, ok := fn.(func(...interface{}) (interface{}, error)); ok {
|
||||
res, err := typed(in...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
vm.push(res)
|
||||
}
|
||||
|
||||
case OpMethod:
|
||||
call := vm.constants[vm.arg()].(Call)
|
||||
in := make([]reflect.Value, call.Size)
|
||||
for i := call.Size - 1; i >= 0; i-- {
|
||||
param := vm.pop()
|
||||
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(¶m).Elem()
|
||||
} else {
|
||||
in[i] = reflect.ValueOf(param)
|
||||
}
|
||||
}
|
||||
out := FetchFn(vm.pop(), call.Name).Call(in)
|
||||
if len(out) == 2 && out[1].Type() == errorType && !out[1].IsNil() {
|
||||
return nil, out[1].Interface().(error)
|
||||
}
|
||||
vm.push(out[0].Interface())
|
||||
|
||||
case OpMethodNilSafe:
|
||||
call := vm.constants[vm.arg()].(Call)
|
||||
in := make([]reflect.Value, call.Size)
|
||||
for i := call.Size - 1; i >= 0; i-- {
|
||||
param := vm.pop()
|
||||
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(¶m).Elem()
|
||||
} else {
|
||||
in[i] = reflect.ValueOf(param)
|
||||
}
|
||||
}
|
||||
fn := FetchFnNil(vm.pop(), call.Name)
|
||||
if !fn.IsValid() {
|
||||
vm.push(nil)
|
||||
} else {
|
||||
out := fn.Call(in)
|
||||
vm.push(out[0].Interface())
|
||||
}
|
||||
|
||||
case OpArray:
|
||||
size := vm.pop().(int)
|
||||
array := make([]interface{}, size)
|
||||
for i := size - 1; i >= 0; i-- {
|
||||
array[i] = vm.pop()
|
||||
}
|
||||
vm.push(array)
|
||||
vm.memory += size
|
||||
if vm.memory >= vm.limit {
|
||||
panic("memory budget exceeded")
|
||||
}
|
||||
|
||||
case OpMap:
|
||||
size := vm.pop().(int)
|
||||
m := make(map[string]interface{})
|
||||
for i := size - 1; i >= 0; i-- {
|
||||
value := vm.pop()
|
||||
key := vm.pop()
|
||||
m[key.(string)] = value
|
||||
}
|
||||
vm.push(m)
|
||||
vm.memory += size
|
||||
if vm.memory >= vm.limit {
|
||||
panic("memory budget exceeded")
|
||||
}
|
||||
|
||||
case OpLen:
|
||||
vm.push(length(vm.current()))
|
||||
|
||||
case OpCast:
|
||||
t := vm.arg()
|
||||
switch t {
|
||||
case 0:
|
||||
vm.push(toInt64(vm.pop()))
|
||||
case 1:
|
||||
vm.push(toFloat64(vm.pop()))
|
||||
}
|
||||
|
||||
case OpStore:
|
||||
scope := vm.Scope()
|
||||
key := vm.constant().(string)
|
||||
value := vm.pop()
|
||||
scope[key] = value
|
||||
|
||||
case OpLoad:
|
||||
scope := vm.Scope()
|
||||
key := vm.constant().(string)
|
||||
vm.push(scope[key])
|
||||
|
||||
case OpInc:
|
||||
scope := vm.Scope()
|
||||
key := vm.constant().(string)
|
||||
i := scope[key].(int)
|
||||
i++
|
||||
scope[key] = i
|
||||
|
||||
case OpBegin:
|
||||
scope := make(Scope)
|
||||
vm.scopes = append(vm.scopes, scope)
|
||||
|
||||
case OpEnd:
|
||||
vm.scopes = vm.scopes[:len(vm.scopes)-1]
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("unknown bytecode %#x", op))
|
||||
}
|
||||
|
||||
if vm.debug {
|
||||
vm.curr <- vm.ip
|
||||
}
|
||||
}
|
||||
|
||||
if 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 interface{}) {
|
||||
vm.stack = append(vm.stack, value)
|
||||
}
|
||||
|
||||
func (vm *VM) current() interface{} {
|
||||
return vm.stack[len(vm.stack)-1]
|
||||
}
|
||||
|
||||
func (vm *VM) pop() interface{} {
|
||||
value := vm.stack[len(vm.stack)-1]
|
||||
vm.stack = vm.stack[:len(vm.stack)-1]
|
||||
return value
|
||||
}
|
||||
|
||||
func (vm *VM) arg() uint16 {
|
||||
b0, b1 := vm.bytecode[vm.ip], vm.bytecode[vm.ip+1]
|
||||
vm.ip += 2
|
||||
return uint16(b0) | uint16(b1)<<8
|
||||
}
|
||||
|
||||
func (vm *VM) constant() interface{} {
|
||||
return vm.constants[vm.arg()]
|
||||
}
|
||||
|
||||
func (vm *VM) Stack() []interface{} {
|
||||
return vm.stack
|
||||
}
|
||||
|
||||
func (vm *VM) Scope() Scope {
|
||||
if len(vm.scopes) > 0 {
|
||||
return vm.scopes[len(vm.scopes)-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (vm *VM) Step() {
|
||||
if vm.ip < len(vm.bytecode) {
|
||||
vm.step <- struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
func (vm *VM) Position() chan int {
|
||||
return vm.curr
|
||||
}
|
Loading…
Reference in New Issue