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: .. code-block:: xml 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 ``<`` and ``<=`` 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 .. code-block:: func len(any) (int, error) 2. ``substr``: returns a substring .. code-block:: func substr(string, int, int) (string, error) 3. ``join``: joins a string array or slice with a separator .. code-block:: func join(any, string) (string, error) 4. ``slice``: performs slicing on arrays and slices .. code-block:: func slice(any, int, int) ([]any, error) 5. ``lower``: converts to lowercase .. code-block:: func lower(string) (string, error) 6. ``upper``: converts to uppercase .. code-block:: func upper(string) (string, error) 7. ``trim``, ``trimLeft``, ``trimRight``: trim characters from strings .. code-block:: func trim(string, string) (string, error) 8. ``replace`` and ``replaceAll``: replace substrings .. code-block:: func replace(string, string, string, int) (string, error) func replaceAll(string, string, string) (string, error) 9. ``split``, ``splitN``, ``splitAfter``: split strings .. code-block:: 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: .. code-block:: go 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: .. code-block:: go import "github.com/go-juicedev/juice/eval" Usage: .. code-block:: xml Function Calls -------------- Basic function call: .. code-block:: xml .. code-block:: go func MyFunc() (string, error) { return "eatmoreapple", nil } param := juice.H{ "MyFunc": MyFunc, } Function with parameters: .. code-block:: xml .. code-block:: go func MyFunc(str string) (string, error) { return str, nil } param := juice.H{ "MyFunc": MyFunc, } Referencing parameters: .. code-block:: xml .. code-block:: go param := juice.H{ "MyFunc": MyFunc, "eatmoreapple": "eatmoreapple", } Multiple parameters: .. code-block:: xml .. code-block:: go func MyFunc(str string, x, y int) (string, error) { return str, nil } Methods on Custom Types ----------------------- Method calls: .. code-block:: xml .. code-block:: go 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: .. code-block:: xml .. code-block:: go type A struct { Name string } param := juice.H{ "a": &A{Name: "eatmoreapple"}, } Map Operations -------------- Indexed access: .. code-block:: go param := juice.H{ "a": juice.H{ "Name": "eatmoreapple", }, } .. code-block:: xml .. 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 ---------------- .. code-block:: xml .. code-block:: go param := juice.H{ "a": []string{"eatmoreapple"}, } Slice expressions are also supported: .. code-block:: xml 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.