mirror of
https://github.com/romychs/Ocean-240.2-Emulator.git
synced 2026-04-21 11:03:21 +03:00
335 lines
12 KiB
Go
335 lines
12 KiB
Go
// Package gval provides a generic expression language.
|
|
// All functions, infix and prefix operators can be replaced by composing languages into a new one.
|
|
//
|
|
// The package contains concrete expression languages for common application in text, arithmetic, decimal arithmetic, propositional logic and so on.
|
|
// They can be used as basis for a custom expression language or to evaluate expressions directly.
|
|
package gval
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"reflect"
|
|
"text/scanner"
|
|
"time"
|
|
|
|
"github.com/shopspring/decimal"
|
|
)
|
|
|
|
// Evaluate given parameter with given expression in gval full language
|
|
func Evaluate(expression string, parameter interface{}, opts ...Language) (interface{}, error) {
|
|
return EvaluateWithContext(context.Background(), expression, parameter, opts...)
|
|
}
|
|
|
|
// Evaluate given parameter with given expression in gval full language using a context
|
|
func EvaluateWithContext(c context.Context, expression string, parameter interface{}, opts ...Language) (interface{}, error) {
|
|
l := full
|
|
if len(opts) > 0 {
|
|
l = NewLanguage(append([]Language{l}, opts...)...)
|
|
}
|
|
return l.EvaluateWithContext(c, expression, parameter)
|
|
}
|
|
|
|
// Full is the union of Arithmetic, Bitmask, Text, PropositionalLogic, TernaryOperator, and Json
|
|
//
|
|
// Operator in: a in b is true iff value a is an element of array b
|
|
// Operator ??: a ?? b returns a if a is not false or nil, otherwise n
|
|
//
|
|
// Function Date: Date(a) parses string a. a must match RFC3339, ISO8601, ruby date, or unix date
|
|
func Full(extensions ...Language) Language {
|
|
if len(extensions) == 0 {
|
|
return full
|
|
}
|
|
return NewLanguage(append([]Language{full}, extensions...)...)
|
|
}
|
|
|
|
// TernaryOperator contains following Operator
|
|
//
|
|
// ?: a ? b : c returns b if bool a is true, otherwise b
|
|
func TernaryOperator() Language {
|
|
return ternaryOperator
|
|
}
|
|
|
|
// Arithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-)
|
|
// and numerical order (<=,<,>,>=)
|
|
//
|
|
// Arithmetic operators expect float64 operands.
|
|
// Called with unfitting input, they try to convert the input to float64.
|
|
// They can parse strings and convert any type of int or float.
|
|
func Arithmetic() Language {
|
|
return arithmetic
|
|
}
|
|
|
|
// DecimalArithmetic contains base, plus(+), minus(-), divide(/), power(**), negative(-)
|
|
// and numerical order (<=,<,>,>=)
|
|
//
|
|
// DecimalArithmetic operators expect decimal.Decimal operands (github.com/shopspring/decimal)
|
|
// and are used to calculate money/decimal rather than floating point calculations.
|
|
// Called with unfitting input, they try to convert the input to decimal.Decimal.
|
|
// They can parse strings and convert any type of int or float.
|
|
func DecimalArithmetic() Language {
|
|
return decimalArithmetic
|
|
}
|
|
|
|
// Bitmask contains base, bitwise and(&), bitwise or(|) and bitwise not(^).
|
|
//
|
|
// Bitmask operators expect float64 operands.
|
|
// Called with unfitting input they try to convert the input to float64.
|
|
// They can parse strings and convert any type of int or float.
|
|
func Bitmask() Language {
|
|
return bitmask
|
|
}
|
|
|
|
// Text contains base, lexical order on strings (<=,<,>,>=),
|
|
// regex match (=~) and regex not match (!~)
|
|
func Text() Language {
|
|
return text
|
|
}
|
|
|
|
// PropositionalLogic contains base, not(!), and (&&), or (||) and Base.
|
|
//
|
|
// Propositional operator expect bool operands.
|
|
// Called with unfitting input they try to convert the input to bool.
|
|
// Numbers other than 0 and the strings "TRUE" and "true" are interpreted as true.
|
|
// 0 and the strings "FALSE" and "false" are interpreted as false.
|
|
func PropositionalLogic() Language {
|
|
return propositionalLogic
|
|
}
|
|
|
|
// JSON contains json objects ({string:expression,...})
|
|
// and json arrays ([expression, ...])
|
|
func JSON() Language {
|
|
return ljson
|
|
}
|
|
|
|
// Parentheses contains support for parentheses.
|
|
func Parentheses() Language {
|
|
return parentheses
|
|
}
|
|
|
|
// Ident contains support for variables and functions.
|
|
func Ident() Language {
|
|
return ident
|
|
}
|
|
|
|
// Base contains equal (==) and not equal (!=), perentheses and general support for variables, constants and functions
|
|
// It contains true, false, (floating point) number, string ("" or “) and char (”) constants
|
|
func Base() Language {
|
|
return base
|
|
}
|
|
|
|
var full = NewLanguage(arithmetic, bitmask, text, propositionalLogic, ljson,
|
|
|
|
InfixOperator("in", inArray),
|
|
|
|
InfixShortCircuit("??", func(a interface{}) (interface{}, bool) {
|
|
v := reflect.ValueOf(a)
|
|
return a, a != nil && !v.IsZero()
|
|
}),
|
|
InfixOperator("??", func(a, b interface{}) (interface{}, error) {
|
|
if v := reflect.ValueOf(a); a == nil || v.IsZero() {
|
|
return b, nil
|
|
}
|
|
return a, nil
|
|
}),
|
|
|
|
ternaryOperator,
|
|
|
|
Function("date", func(arguments ...interface{}) (interface{}, error) {
|
|
if len(arguments) != 1 {
|
|
return nil, fmt.Errorf("date() expects exactly one string argument")
|
|
}
|
|
s, ok := arguments[0].(string)
|
|
if !ok {
|
|
return nil, fmt.Errorf("date() expects exactly one string argument")
|
|
}
|
|
for _, format := range [...]string{
|
|
time.ANSIC,
|
|
time.UnixDate,
|
|
time.RubyDate,
|
|
time.Kitchen,
|
|
time.RFC3339,
|
|
time.RFC3339Nano,
|
|
"2006-01-02", // RFC 3339
|
|
"2006-01-02 15:04", // RFC 3339 with minutes
|
|
"2006-01-02 15:04:05", // RFC 3339 with seconds
|
|
"2006-01-02 15:04:05-07:00", // RFC 3339 with seconds and timezone
|
|
"2006-01-02T15Z0700", // ISO8601 with hour
|
|
"2006-01-02T15:04Z0700", // ISO8601 with minutes
|
|
"2006-01-02T15:04:05Z0700", // ISO8601 with seconds
|
|
"2006-01-02T15:04:05.999999999Z0700", // ISO8601 with nanoseconds
|
|
} {
|
|
ret, err := time.ParseInLocation(format, s, time.Local)
|
|
if err == nil {
|
|
return ret, nil
|
|
}
|
|
}
|
|
return nil, fmt.Errorf("date() could not parse %s", s)
|
|
}),
|
|
)
|
|
|
|
var ternaryOperator = PostfixOperator("?", parseIf)
|
|
|
|
var ljson = NewLanguage(
|
|
PrefixExtension('[', parseJSONArray),
|
|
PrefixExtension('{', parseJSONObject),
|
|
)
|
|
|
|
var arithmetic = NewLanguage(
|
|
InfixNumberOperator("+", func(a, b uint) (interface{}, error) { return a + b, nil }),
|
|
InfixNumberOperator("-", func(a, b uint) (interface{}, error) { return a - b, nil }),
|
|
InfixNumberOperator("*", func(a, b uint) (interface{}, error) { return a * b, nil }),
|
|
InfixNumberOperator("/", func(a, b uint) (interface{}, error) { return a / b, nil }),
|
|
InfixNumberOperator("%", func(a, b uint) (interface{}, error) { return a % b, nil }),
|
|
|
|
InfixNumberOperator(">", func(a, b uint) (interface{}, error) { return a > b, nil }),
|
|
InfixNumberOperator(">=", func(a, b uint) (interface{}, error) { return a >= b, nil }),
|
|
InfixNumberOperator("<", func(a, b uint) (interface{}, error) { return a < b, nil }),
|
|
InfixNumberOperator("<=", func(a, b uint) (interface{}, error) { return a <= b, nil }),
|
|
|
|
InfixNumberOperator("==", func(a, b uint) (interface{}, error) { return a == b, nil }),
|
|
InfixNumberOperator("!=", func(a, b uint) (interface{}, error) { return a != b, nil }),
|
|
|
|
base,
|
|
)
|
|
|
|
var decimalArithmetic = NewLanguage(
|
|
InfixDecimalOperator("+", func(a, b decimal.Decimal) (interface{}, error) { return a.Add(b), nil }),
|
|
InfixDecimalOperator("-", func(a, b decimal.Decimal) (interface{}, error) { return a.Sub(b), nil }),
|
|
InfixDecimalOperator("*", func(a, b decimal.Decimal) (interface{}, error) { return a.Mul(b), nil }),
|
|
InfixDecimalOperator("/", func(a, b decimal.Decimal) (interface{}, error) { return a.Div(b), nil }),
|
|
InfixDecimalOperator("%", func(a, b decimal.Decimal) (interface{}, error) { return a.Mod(b), nil }),
|
|
InfixDecimalOperator("**", func(a, b decimal.Decimal) (interface{}, error) { return a.Pow(b), nil }),
|
|
|
|
InfixDecimalOperator(">", func(a, b decimal.Decimal) (interface{}, error) { return a.GreaterThan(b), nil }),
|
|
InfixDecimalOperator(">=", func(a, b decimal.Decimal) (interface{}, error) { return a.GreaterThanOrEqual(b), nil }),
|
|
InfixDecimalOperator("<", func(a, b decimal.Decimal) (interface{}, error) { return a.LessThan(b), nil }),
|
|
InfixDecimalOperator("<=", func(a, b decimal.Decimal) (interface{}, error) { return a.LessThanOrEqual(b), nil }),
|
|
|
|
InfixDecimalOperator("==", func(a, b decimal.Decimal) (interface{}, error) { return a.Equal(b), nil }),
|
|
InfixDecimalOperator("!=", func(a, b decimal.Decimal) (interface{}, error) { return !a.Equal(b), nil }),
|
|
base,
|
|
//Base is before these overrides so that the Base options are overridden
|
|
PrefixExtension(scanner.Int, parseDecimal),
|
|
PrefixExtension(scanner.Float, parseDecimal),
|
|
PrefixOperator("-", func(c context.Context, v interface{}) (interface{}, error) {
|
|
i, ok := convertToUint(v)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected %v(%T) expected number", v, v)
|
|
}
|
|
return -i, nil
|
|
}),
|
|
)
|
|
|
|
var bitmask = NewLanguage(
|
|
InfixNumberOperator("^", func(a, b uint) (interface{}, error) { return uint(int64(a) ^ int64(b)), nil }),
|
|
InfixNumberOperator("&", func(a, b uint) (interface{}, error) { return uint(int64(a) & int64(b)), nil }),
|
|
InfixNumberOperator("|", func(a, b uint) (interface{}, error) { return uint(int64(a) | int64(b)), nil }),
|
|
InfixNumberOperator("<<", func(a, b uint) (interface{}, error) { return uint(int64(a) << uint64(b)), nil }),
|
|
InfixNumberOperator(">>", func(a, b uint) (interface{}, error) { return uint(int64(a) >> uint64(b)), nil }),
|
|
|
|
PrefixOperator("~", func(c context.Context, v interface{}) (interface{}, error) {
|
|
i, ok := convertToUint(v)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected %T expected number", v)
|
|
}
|
|
return float64(^int64(i)), nil
|
|
}),
|
|
)
|
|
|
|
var text = NewLanguage(
|
|
InfixTextOperator("+", func(a, b string) (interface{}, error) { return fmt.Sprintf("%v%v", a, b), nil }),
|
|
|
|
InfixTextOperator("<", func(a, b string) (interface{}, error) { return a < b, nil }),
|
|
InfixTextOperator("<=", func(a, b string) (interface{}, error) { return a <= b, nil }),
|
|
InfixTextOperator(">", func(a, b string) (interface{}, error) { return a > b, nil }),
|
|
InfixTextOperator(">=", func(a, b string) (interface{}, error) { return a >= b, nil }),
|
|
|
|
InfixEvalOperator("=~", regEx),
|
|
InfixEvalOperator("!~", notRegEx),
|
|
base,
|
|
)
|
|
|
|
var propositionalLogic = NewLanguage(
|
|
PrefixOperator("!", func(c context.Context, v interface{}) (interface{}, error) {
|
|
b, ok := convertToBool(v)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected %T expected bool", v)
|
|
}
|
|
return !b, nil
|
|
}),
|
|
|
|
InfixShortCircuit("&&", func(a interface{}) (interface{}, bool) { return false, a == false }),
|
|
InfixBoolOperator("&&", func(a, b bool) (interface{}, error) { return a && b, nil }),
|
|
InfixShortCircuit("||", func(a interface{}) (interface{}, bool) { return true, a == true }),
|
|
InfixBoolOperator("||", func(a, b bool) (interface{}, error) { return a || b, nil }),
|
|
|
|
InfixBoolOperator("==", func(a, b bool) (interface{}, error) { return a == b, nil }),
|
|
InfixBoolOperator("!=", func(a, b bool) (interface{}, error) { return a != b, nil }),
|
|
|
|
base,
|
|
)
|
|
|
|
var parentheses = NewLanguage(
|
|
PrefixExtension('(', parseParentheses),
|
|
)
|
|
|
|
var ident = NewLanguage(
|
|
PrefixMetaPrefix(scanner.Ident, parseIdent),
|
|
)
|
|
|
|
var base = NewLanguage(
|
|
PrefixExtension(scanner.Int, parseNumber),
|
|
PrefixExtension(scanner.Float, parseNumber),
|
|
PrefixOperator("-", func(c context.Context, v interface{}) (interface{}, error) {
|
|
i, ok := convertToUint(v)
|
|
if !ok {
|
|
return nil, fmt.Errorf("unexpected %v(%T) expected number", v, v)
|
|
}
|
|
return -i, nil
|
|
}),
|
|
|
|
PrefixExtension(scanner.String, parseString),
|
|
PrefixExtension(scanner.Char, parseString),
|
|
PrefixExtension(scanner.RawString, parseString),
|
|
|
|
Constant("true", true),
|
|
Constant("false", false),
|
|
|
|
InfixOperator("==", func(a, b interface{}) (interface{}, error) { return reflect.DeepEqual(a, b), nil }),
|
|
InfixOperator("!=", func(a, b interface{}) (interface{}, error) { return !reflect.DeepEqual(a, b), nil }),
|
|
parentheses,
|
|
|
|
Precedence("??", 0),
|
|
|
|
Precedence("||", 20),
|
|
Precedence("&&", 21),
|
|
|
|
Precedence("==", 40),
|
|
Precedence("!=", 40),
|
|
Precedence(">", 40),
|
|
Precedence(">=", 40),
|
|
Precedence("<", 40),
|
|
Precedence("<=", 40),
|
|
Precedence("=~", 40),
|
|
Precedence("!~", 40),
|
|
Precedence("in", 40),
|
|
|
|
Precedence("^", 60),
|
|
Precedence("&", 60),
|
|
Precedence("|", 60),
|
|
|
|
Precedence("<<", 90),
|
|
Precedence(">>", 90),
|
|
|
|
Precedence("+", 120),
|
|
Precedence("-", 120),
|
|
|
|
Precedence("*", 150),
|
|
Precedence("/", 150),
|
|
Precedence("%", 150),
|
|
|
|
Precedence("**", 200),
|
|
|
|
ident,
|
|
)
|