expr-lang / expr

Expression language and expression evaluation for Go
https://expr-lang.org
MIT License
5.84k stars 378 forks source link

Implement `int*` and `uint*` builtin functinos #645

Open antonmedv opened 1 month ago

antonmedv commented 1 month ago

We already have int() and float() builtins. Let's add others as well: uint8, byte, int64, float64, etc.

And we can change compiler to compile int for IntegerNode, and remove this code:


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(node.Value)
    case reflect.Int8:
        if node.Value > math.MaxInt8 || node.Value < math.MinInt8 {
            panic(fmt.Sprintf("constant %d overflows int8", node.Value))
        }
        c.emitPush(int8(node.Value))
    case reflect.Int16:
        if node.Value > math.MaxInt16 || node.Value < math.MinInt16 {
            panic(fmt.Sprintf("constant %d overflows int16", node.Value))
        }
        c.emitPush(int16(node.Value))
    case reflect.Int32:
        if node.Value > math.MaxInt32 || node.Value < math.MinInt32 {
            panic(fmt.Sprintf("constant %d overflows int32", node.Value))
        }
        c.emitPush(int32(node.Value))
    case reflect.Int64:
        if node.Value > math.MaxInt64 || node.Value < math.MinInt64 {
            panic(fmt.Sprintf("constant %d overflows int64", node.Value))
        }
        c.emitPush(int64(node.Value))
    case reflect.Uint:
        if node.Value < 0 {
            panic(fmt.Sprintf("constant %d overflows uint", node.Value))
        }
        c.emitPush(uint(node.Value))
    case reflect.Uint8:
        if node.Value > math.MaxUint8 || node.Value < 0 {
            panic(fmt.Sprintf("constant %d overflows uint8", node.Value))
        }
        c.emitPush(uint8(node.Value))
    case reflect.Uint16:
        if node.Value > math.MaxUint16 || node.Value < 0 {
            panic(fmt.Sprintf("constant %d overflows uint16", node.Value))
        }
        c.emitPush(uint16(node.Value))
    case reflect.Uint32:
        if node.Value > math.MaxUint32 || node.Value < 0 {
            panic(fmt.Sprintf("constant %d overflows uint32", node.Value))
        }
        c.emitPush(uint32(node.Value))
    case reflect.Uint64:
        if node.Value < 0 {
            panic(fmt.Sprintf("constant %d overflows uint64", node.Value))
        }
        c.emitPush(uint64(node.Value))
    default:
        c.emitPush(node.Value)
    }
}

And for function calls we can delete this code:


func traverseAndReplaceIntegerNodesWithIntegerNodes(node *ast.Node, newType reflect.Type) {
    switch (*node).(type) {
    case *ast.IntegerNode:
        (*node).SetType(newType)
    case *ast.UnaryNode:
        (*node).SetType(newType)
        unaryNode := (*node).(*ast.UnaryNode)
        traverseAndReplaceIntegerNodesWithIntegerNodes(&unaryNode.Node, newType)
    case *ast.BinaryNode:
        // TODO: Binary node return type is dependent on the type of the operands. We can't just change the type of the node.
        binaryNode := (*node).(*ast.BinaryNode)
        switch binaryNode.Operator {
        case "+", "-", "*":
            traverseAndReplaceIntegerNodesWithIntegerNodes(&binaryNode.Left, newType)
            traverseAndReplaceIntegerNodesWithIntegerNodes(&binaryNode.Right, newType)
        }
    }
}

And replace it with implicit added calls to int*.

NOTE We should check for overflows on conversion in checkers!

jippi commented 1 month ago

If you haven't looking into go-cty, then I can 10/10 recommend it for a pseudo-type system like this - it's really flexible and powerful :)

antonmedv commented 1 month ago

Although cty looks interesting, I believe we better stick with native Go types. Also, I'm not sure how cty can help in case of:

func(123)

In func takes uint32 param.