Expression System

Overview

As you can see from the dynamic SQL examples, Juice supports expressions inside if and when tags. The syntax is broadly similar to Go expressions.

Usage Restrictions

  1. Juice supports only single-line expressions.

  2. When writing a string inside an expression, wrap the test attribute value in single quotes:

    <if test='name == "eatmoreapple"'>
    </if>
    
  3. Keep expressions as simple as possible.

Basic Operators

Comparison Operators

Juice supports these comparison operators:

  1. ==: equal to

  2. !=: not equal to

  3. >: greater than

  4. >=: greater than or equal to

Attention

< and <= conflict with XML syntax. Use &lt; and &lt;= instead.

Logical Operators

Juice supports these logical operators:

  1. and: logical AND

  2. or: logical OR

  3. !: logical NOT

Arithmetic Operators

Juice supports these arithmetic operators:

  1. +: addition

  2. -: subtraction

  3. *: multiplication

  4. /: division

  5. %: remainder

Reserved Keywords

The following keywords are reserved and cannot be used as variable names:

  1. true

  2. false

  3. nil

  4. and

  5. or

Built-In Functions

Juice includes several built-in functions. Built-ins are registered by the github.com/go-juicedev/juice/eval package and can be used directly in dynamic SQL expressions.

  1. len: returns the length of a string, array, slice, map, or channel

    func len(any) (int, error)
    
  2. substr: returns a substring

    func substr(string, int, int) (string, error)
    
  3. join: joins a string array or slice with a separator

    func join(any, string) (string, error)
    
  4. slice: performs slicing on arrays and slices

    func slice(any, int, int) ([]any, error)
    
  5. lower: converts to lowercase

    func lower(string) (string, error)
    
  6. upper: converts to uppercase

    func upper(string) (string, error)
    
  7. trim, trimLeft, trimRight: trim characters from strings

    func trim(string, string) (string, error)
    
  8. replace and replaceAll: replace substrings

    func replace(string, string, string, int) (string, error)
    func replaceAll(string, string, string) (string, error)
    
  9. split, splitN, splitAfter: split strings

    func split(string, string) ([]string, error)
    func splitN(string, string, int) ([]string, error)
    func splitAfter(string, string) ([]string, error)
    

Custom Functions

Registration requirements:

  1. It must be a function.

  2. It must return two values. The first can be any type and the second must be error.

Example:

func add(x, y int) (int, error) {
    return x + y, nil
}

func main() {
    if err := eval.RegisterEvalFunc("add", add); err != nil {
        panic(err)
    }
}

When registering custom functions, import the eval package explicitly:

import "github.com/go-juicedev/juice/eval"

Usage:

<if test='add(1, 2) == 3'>
</if>

Function Calls

Basic function call:

<if test='MyFunc() == "eatmoreapple"'>
</if>
func MyFunc() (string, error) {
    return "eatmoreapple", nil
}

param := juice.H{
    "MyFunc": MyFunc,
}

Function with parameters:

<if test='MyFunc("eatmoreapple") == "eatmoreapple"'>
</if>
func MyFunc(str string) (string, error) {
    return str, nil
}

param := juice.H{
    "MyFunc": MyFunc,
}

Referencing parameters:

<if test='MyFunc(eatmoreapple) == "eatmoreapple"'>
</if>
param := juice.H{
    "MyFunc": MyFunc,
    "eatmoreapple": "eatmoreapple",
}

Multiple parameters:

<if test='MyFunc(eatmoreapple, 1, 2) == "eatmoreapple"'>
</if>
func MyFunc(str string, x, y int) (string, error) {
    return str, nil
}

Methods on Custom Types

Method calls:

<if test='a.MyFunc() == "eatmoreapple"'>
</if>
type A struct {
    Name string
}

func (a *A) MyFunc() (string, error) {
    return a.Name, nil
}

param := juice.H{
    "a": &A{Name: "eatmoreapple"},
}

Attribute Access

Struct fields:

<if test='a.Name == "eatmoreapple"'>
</if>
type A struct {
    Name string
}

param := juice.H{
    "a": &A{Name: "eatmoreapple"},
}

Map Operations

Indexed access:

param := juice.H{
    "a": juice.H{
        "Name": "eatmoreapple",
    },
}
<if test='a["Name"] == "eatmoreapple"'>
</if>

<if test='a.Name == "eatmoreapple"'>
</if>

Attention

Difference between the two forms:

  1. Indexed access such as a["Name"] returns a default value when the key is missing.

  2. Dot access such as a.Name throws an error when the property is missing.

  3. Dot access also supports method calls.

Array Operations

<if test='a[0] == "eatmoreapple"'>
</if>
param := juice.H{
    "a": []string{"eatmoreapple"},
}

Slice expressions are also supported:

<if test='len(a[1:]) > 0'>
</if>

<if test='len(a[start:end]) > 0'>
</if>

Index and slice bounds must evaluate to integer values. Negative single indexes count from the end of the array or slice, for example a[-1] returns the last element.

Tip

Best practices:

  1. Keep expressions simple and readable.

  2. Move complex logic into Go code when possible.

  3. Use built-in functions appropriately.

  4. Pay attention to XML escaping rules.

  5. Test every condition branch.