Go Functions

Register Go functions that can be called from Scriptling scripts.

Native API

The native API gives you direct control with maximum performance.

Basic Function

import (
    "context"
    "github.com/paularlott/scriptling/object"
)

p.RegisterFunc("add", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    if len(args) != 2 {
        return &object.Error{Message: "add requires 2 arguments"}
    }

    a, err := args[0].AsInt()
    if err != nil {
        return &object.Error{Message: "first argument must be a number"}
    }

    b, err := args[1].AsInt()
    if err != nil {
        return &object.Error{Message: "second argument must be a number"}
    }

    return &object.Integer{Value: a + b}
})

With Keyword Arguments

p.RegisterFunc("format_greeting", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    if len(args) < 1 {
        return &object.Error{Message: "format_greeting requires name argument"}
    }

    name, err := args[0].AsString()
    if err != nil {
        return &object.Error{Message: "name must be a string"}
    }

    // Extract keyword arguments with defaults
    prefix, _ := kwargs.GetString("prefix", "Hello")
    suffix, _ := kwargs.GetString("suffix", "!")
    uppercase, _ := kwargs.GetBool("uppercase", false)

    result := prefix + ", " + name + suffix
    if uppercase {
        result = strings.ToUpper(result)
    }

    return &object.String{Value: result}
})

Use from Scriptling:

greeting = format_greeting("Alice")
# "Hello, Alice!"

greeting = format_greeting("Bob", prefix="Hi", suffix="!!!")
# "Hi, Bob!!!"

greeting = format_greeting("Charlie", uppercase=True)
# "HELLO, CHARLIE!"

With Help Text

p.RegisterFunc("calculate", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    if len(args) < 2 {
        return &object.Error{Message: "calculate requires x and y arguments"}
    }

    x, _ := args[0].AsFloat()
    y, _ := args[1].AsFloat()
    op, _ := kwargs.GetString("operation", "add")

    var result float64
    switch op {
    case "add":
        result = x + y
    case "subtract":
        result = x - y
    case "multiply":
        result = x * y
    case "divide":
        if y == 0 {
            return &object.Error{Message: "division by zero"}
        }
        result = x / y
    default:
        return &object.Error{Message: "unknown operation: " + op}
    }

    return &object.Float{Value: result}
}, `calculate(x, y, operation="add") - Perform mathematical operation

Parameters:
  x - First number
  y - Second number
  operation - Operation to perform ("add", "subtract", "multiply", "divide")

Returns:
  The calculated result

Examples:
  calculate(10, 5)                    # 15
  calculate(10, 5, operation="subtract")  # 5
  calculate(10, 5, operation="multiply")  # 50`)

Variadic Functions

p.RegisterFunc("sum_all", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    total := int64(0)
    for _, arg := range args {
        val, err := arg.AsInt()
        if err != nil {
            return &object.Error{Message: "all arguments must be numbers"}
        }
        total += val
    }
    return &object.Integer{Value: total}
}, `sum_all(*args) - Sum all arguments

Example:
  sum_all(1, 2, 3, 4, 5)  # 15`)

Return Types

Integers

return &object.Integer{Value: 42}
return object.NewInteger(42)

Floats

return &object.Float{Value: 3.14}
return object.NewFloat(3.14)

Strings

return &object.String{Value: "hello"}

Booleans

return &object.Boolean{Value: true}
return object.True
return object.False

Lists

return &object.List{Elements: []object.Object{
    &object.Integer{Value: 1},
    &object.String{Value: "two"},
}}

Dictionaries

return &object.Dict{Pairs: map[string]object.Object{
    "name":  &object.String{Value: "Alice"},
    "count": &object.Integer{Value: 42},
}}

None/Null

return object.None

Errors

return &object.Error{Message: "something went wrong"}

Context Usage

Cancellation

p.RegisterFunc("long_operation", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    for i := 0; i < 1000000; i++ {
        // Check for cancellation
        select {
        case <-ctx.Done():
            return &object.Error{Message: "operation cancelled"}
        default:
            // Continue processing
        }
    }
    return &object.Integer{Value: 1}
})

Timeout Handling

p.RegisterFunc("fetch_with_timeout", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    url, _ := args[0].AsString()
    timeoutSec, _ := kwargs.GetInt("timeout", 30)

    // Create child context with timeout
    childCtx, cancel := context.WithTimeout(ctx, time.Duration(timeoutSec)*time.Second)
    defer cancel()

    // Use childCtx for operations
    result := fetchData(childCtx, url)

    return result
})

Registering Script Functions

From Go Code

// Register a function defined in Scriptling syntax
p.RegisterScriptFunc("my_func", `
def my_func(x):
    return x * 2
my_func
`)

// Now callable from scripts or Go
result, _ := p.CallFunction("my_func", 21)
// Returns: 42

From File

content, _ := os.ReadFile("helpers.py")
p.RegisterScriptFunc("helper", string(content))

Best Practices

1. Always Validate Arguments

p.RegisterFunc("divide", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    if len(args) != 2 {
        return &object.Error{Message: "divide requires 2 arguments: (a, b)"}
    }

    a, err := args[0].AsFloat()
    if err != nil {
        return &object.Error{Message: "first argument must be a number"}
    }

    b, err := args[1].AsFloat()
    if err != nil {
        return &object.Error{Message: "second argument must be a number"}
    }

    if b == 0 {
        return &object.Error{Message: "division by zero"}
    }

    return &object.Float{Value: a / b}
})

2. Provide Help Text

p.RegisterFunc("process", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    // implementation
}, `process(data, options={}) - Process data with options

Parameters:
  data - Input data to process
  options - Optional configuration dictionary
    - format: Output format ("json", "xml")
    - validate: Validate input (default: True)

Returns:
  Processed result as dictionary`)

3. Handle Context

p.RegisterFunc("fetch", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    url, _ := args[0].AsString()

    // Create HTTP request with context
    req, err := http.NewRequestWithContext(ctx, "GET", url, nil)
    if err != nil {
        return &object.Error{Message: err.Error()}
    }

    // ... rest of implementation
})

4. Use Kwargs for Optional Parameters

p.RegisterFunc("connect", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
    host, _ := args[0].AsString()

    // Optional parameters with defaults
    port, _ := kwargs.GetInt("port", 8080)
    timeout, _ := kwargs.GetInt("timeout", 30)
    useTLS, _ := kwargs.GetBool("tls", false)

    // implementation
})

Complete Example

package main

import (
    "context"
    "fmt"
    "github.com/paularlott/scriptling"
    "github.com/paularlott/scriptling/object"
    "github.com/paularlott/scriptling/stdlib"
)

func main() {
    p := scriptling.New()
    stdlib.RegisterAll(p)

    // Register custom functions
    p.RegisterFunc("greet", func(ctx context.Context, kwargs object.Kwargs, args ...object.Object) object.Object {
        if len(args) < 1 {
            return &object.Error{Message: "greet requires a name"}
        }

        name, _ := args[0].AsString()
        formal, _ := kwargs.GetBool("formal", false)

        var greeting string
        if formal {
            greeting = "Good day, " + name
        } else {
            greeting = "Hi, " + name + "!"
        }

        return &object.String{Value: greeting}
    }, `greet(name, formal=False) - Generate a greeting

Parameters:
  name - Person's name
  formal - Use formal greeting (default: False)

Examples:
  greet("Alice")              # "Hi, Alice!"
  greet("Bob", formal=True)   # "Good day, Bob"`)

    // Use from script
    p.Eval(`
message1 = greet("Alice")
message2 = greet("Bob", formal=True)
print(message1)
print(message2)
`)
}