Files
orbit/weave/engine/evaluate.go

209 lines
4.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package engine 依据已发布规则和持久化快照计算有限推进命令;不调用业务处理器或外部系统。
package engine
import (
"bytes"
"encoding/json"
"errors"
"math/big"
"strings"
"git.yuntuo.hk/k2safe/orbit/internal/jsonwire"
"git.yuntuo.hk/k2safe/orbit/weave/definition"
)
type values map[string]json.RawMessage
func copyJSON(raw json.RawMessage) json.RawMessage { return append(json.RawMessage(nil), raw...) }
func copyValues(in values) values {
out := make(values, len(in)+8)
for k, v := range in {
out[k] = v
}
return out
}
// 表达式来自已验证且由Program持有的不可变定义;每个值仍按运行数据上限检查。
func resolve(v definition.Value, expected definition.Type, env values) (json.RawMessage, error) {
raw, err := evaluate(v, env, 0)
if err != nil {
return nil, err
}
if err := definition.ValidateData(expected, raw); err != nil {
return nil, err
}
return raw, nil
}
func evaluate(v definition.Value, env values, depth int) (json.RawMessage, error) {
if depth > 8 {
return nil, errors.New("expression depth exceeded")
}
var out json.RawMessage
switch v.Kind {
case "LITERAL":
out = copyJSON(v.Literal)
case "REFERENCE":
if v.Reference == nil {
return nil, errors.New("reference required")
}
raw, ok := env[v.Reference.Source]
if !ok {
return nil, errors.New("reference unavailable")
}
for _, key := range v.Reference.Path {
var object map[string]json.RawMessage
if json.Unmarshal(raw, &object) != nil {
return nil, errors.New("reference is not an object")
}
raw, ok = object[key]
if !ok {
return nil, errors.New("reference field missing")
}
}
out = copyJSON(raw)
case "OBJECT":
object := make(map[string]json.RawMessage, len(v.Fields))
size := 0
for k, child := range v.Fields {
raw, err := evaluate(child, env, depth+1)
if err != nil {
return nil, err
}
size += len(raw)
if size > definition.MaxSpecificationBytes {
return nil, errors.New("resolved object exceeds bounds")
}
object[k] = raw
}
raw, err := jsonwire.Marshal(object)
if err != nil {
return nil, err
}
out = raw
case "ARRAY":
array := make([]json.RawMessage, len(v.Items))
size := 0
for i, child := range v.Items {
raw, err := evaluate(child, env, depth+1)
if err != nil {
return nil, err
}
size += len(raw)
if size > definition.MaxSpecificationBytes {
return nil, errors.New("resolved array exceeds bounds")
}
array[i] = raw
}
raw, err := jsonwire.Marshal(array)
if err != nil {
return nil, err
}
out = raw
default:
return nil, errors.New("unknown expression")
}
if len(out) == 0 || len(out) > definition.MaxSpecificationBytes {
return nil, errors.New("resolved value exceeds bounds")
}
return out, nil
}
func compare(p definition.Predicate, env values) (bool, error) {
switch p.Op {
case "AND", "OR", "NOT":
for _, child := range p.Arguments {
ok, err := compare(child, env)
if err != nil {
return false, err
}
if p.Op == "NOT" {
return !ok, nil
}
if p.Op == "AND" && !ok {
return false, nil
}
if p.Op == "OR" && ok {
return true, nil
}
}
return p.Op == "AND", nil
}
if p.Left == nil || p.Right == nil {
return false, errors.New("comparison operands missing")
}
left, err := resolve(*p.Left, definition.Type{Kind: "JSON"}, env)
if err != nil {
return false, err
}
right, err := resolve(*p.Right, definition.Type{Kind: "JSON"}, env)
if err != nil {
return false, err
}
decode := func(raw []byte) (any, error) {
var v any
d := json.NewDecoder(bytes.NewReader(raw))
d.UseNumber()
err := d.Decode(&v)
return v, err
}
a, err := decode(left)
if err != nil {
return false, err
}
b, err := decode(right)
if err != nil {
return false, err
}
var order int
switch v := a.(type) {
case string:
w, ok := b.(string)
if !ok {
return false, errors.New("comparison type mismatch")
}
order = strings.Compare(v, w)
case json.Number:
w, ok := b.(json.Number)
if !ok {
return false, errors.New("comparison type mismatch")
}
x, ok := new(big.Rat).SetString(string(v))
if !ok {
return false, errors.New("invalid exact number")
}
y, ok := new(big.Rat).SetString(string(w))
if !ok {
return false, errors.New("invalid exact number")
}
order = x.Cmp(y)
case bool:
w, ok := b.(bool)
if !ok || p.Op != "EQ" && p.Op != "NE" {
return false, errors.New("comparison type mismatch")
}
if v != w {
order = 1
}
case nil:
if b != nil || p.Op != "EQ" && p.Op != "NE" {
return false, errors.New("comparison type mismatch")
}
default:
return false, errors.New("comparison requires scalars")
}
switch p.Op {
case "EQ":
return order == 0, nil
case "NE":
return order != 0, nil
case "LT":
return order < 0, nil
case "LE":
return order <= 0, nil
case "GT":
return order > 0, nil
case "GE":
return order >= 0, nil
}
return false, errors.New("unknown comparison")
}