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.