# Expressions (/docs/products/ussd-flows/developers/expressions)

Anything inside `$(...)` that is more than a plain reference is an expression. Expressions use the [Apache Commons JEXL](https://commons.apache.org/proper/commons-jexl/) syntax. They only compute a value: they can't loop, assign or change anything.

```text
$(rc.display_name)                               a plain reference
$(page.enter_amount.value * 2)                   a number
$(page.enter_amount.value >= 100)                true or false
$(strings.upper(rc.display_name))                text
$(rc.is_member == 'true' ? 'Welcome back' : 'Join today')
```

A `$(...)` is a plain reference when it starts with `page.`, `session.`, `data.`, `rc.` or `sc.` and has only letters, digits, dots and underscores. Anything else is read as an expression. For where you can use `$(...)`, see [Variables](/docs/products/ussd-flows/developers/variables#where--works).

## Operators

| Kind               | Operators                        | Example                                                          |
| ------------------ | -------------------------------- | ---------------------------------------------------------------- |
| Arithmetic         | `+`, `-`, `*`, `/`, `%`          | `$(page.ticket_count.value * 50)`                                |
| Comparison         | `==`, `!=`, `<`, `<=`, `>`, `>=` | `$(page.enter_amount.value >= 100)`                              |
| Logic              | `&&`, `\|\|`, `!`                | `$(rc.is_member == 'true' && !strings.is_blank(sc.chosen_term))` |
| Choice             | `? :`                            | `$(rc.tier == 'gold' ? 'Gold' : 'Standard')`                     |
| Default for `null` | `??`                             | `$(rc.display_name ?? 'friend')`                                 |
| Pattern match      | `=~`, `!~`                       | `$(page.phone.value =~ '232[0-9]{8}')`                           |
| Join text          | `+`                              | `$('Hi ' + rc.display_name)`                                     |

`=~` checks the whole value against the pattern, not part of it. To check how a value starts, use `strings.has_prefix`.

## Built-in Functions

### Numbers

| Function    | Returns                     | Example                                      |
| ----------- | --------------------------- | -------------------------------------------- |
| `max(a, b)` | The larger number           | `$(max(page.enter_amount.value * 1, 10))`    |
| `min(a, b)` | The smaller number          | `$(min(page.enter_amount.value * 1, 10000))` |
| `abs(a)`    | The number without its sign | `$(abs(sc.balance_change * 1))`              |

These take numbers. Values read with `$(...)` are text, so multiply them by 1 first, as above.

### strings

| Function                       | Returns                                                                  | Example                                                |
| ------------------------------ | ------------------------------------------------------------------------ | ------------------------------------------------------ |
| `strings.upper(s)`             | `s` in capitals                                                          | `$(strings.upper(page.code.value))`                    |
| `strings.lower(s)`             | `s` in small letters                                                     | `$(strings.lower(page.code.value))`                    |
| `strings.title(s)`             | Each word capitalised                                                    | `$(strings.title(page.enter_name.value))`              |
| `strings.truncate(s, n)`       | The first `n` characters plus `...`, if `s` is longer than `n`           | `$(strings.truncate(rc.display_name, 20))`             |
| `strings.is_blank(s)`          | `true` if `s` is unset or empty. Spaces count as text.                   | `$(strings.is_blank(rc.display_name))`                 |
| `strings.has_prefix(s, p)`     | `true` if `s` starts with `p`                                            | `$(strings.has_prefix(page.phone.value, '23276'))`     |
| `strings.has_suffix(s, p)`     | `true` if `s` ends with `p`                                              | `$(strings.has_suffix(page.meter_number.value, '01'))` |
| `strings.contains(s, p)`       | `true` if `p` is anywhere in `s`                                         | `$(strings.contains(page.enter_name.value, 'Kamara'))` |
| `strings.split_index(s, d, i)` | The piece at position `i` (from 0) after splitting `s` on `d`, or `null` | `$(strings.split_index(rc.display_name, ' ', 0))`      |

### datetime

| Function                    | Returns                                                     | Example                           |
| --------------------------- | ----------------------------------------------------------- | --------------------------------- |
| `datetime.now()`            | The current time in UTC, as an ISO 8601 timestamp           | `$(datetime.now())`               |
| `datetime.extract(t, part)` | One part of timestamp `t` as a number. Pass `null` for now. | `$(datetime.extract(null, 'HH'))` |

`part` is one of:

- `yyyy` year
- `MM` month, 1 to 12
- `d` day of the month
- `E` day of the week, 1 (Monday) to 7 (Sunday)
- `HH` hour, 0 to 23
- `mm` minute
- `ss` second

Any other value stops the flow with an error.

### subscriber

| Function                     | Returns                                                                                                      | Example                                                |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------ |
| `subscriber.is_one_of(list)` | `true` if the subscriber's number is in `list`, a comma-separated list of numbers, each starting with `+232` | `$(subscriber.is_one_of('+23276000001,+23277000002'))` |

Use it to run a page only for test phones:

```json
{
  "id": "mark_test_phone",
  "component": { "type": "side_effect" },
  "options": {
    "skip": "$(!subscriber.is_one_of('+23276000001,+23277000002'))"
  },
  "on_done": {
    "action": "next",
    "session_context": { "mutations": { "is_test_phone": true } }
  }
}
```

## Text, Numbers and Missing Values

- Every value read with `$(...)` is text, even one you saved as a number or `true`.
- Arithmetic turns number-like text into numbers: `page.enter_amount.value * 2` works.
- `+` joins two pieces of text, so `page.fee.value + page.tax.value` gives `5010` for 50 and 10. Multiply one side by 1 to add them: `page.fee.value * 1 + page.tax.value`.
- To test a saved `true`, compare with text: `rc.is_member == 'true'`.
- An `rc` or `sc` key that was never saved is `null`. Give it a default with `??` before you compare or calculate with it: `(rc.lifetime_spend ?? 0) < 500`.
- Empty text counts as false. Use `strings.is_blank(x)` to test for "unset or empty".

Missing page, data and session values behave differently; see [Missing Values](/docs/products/ussd-flows/developers/variables#missing-values).

## Example: Tiered Menu

A menu that offers more options as a subscriber spends more. Your server, or another page, keeps `rc.lifetime_spend` up to date.

```json
{
  "id": "select_option",
  "component": {
    "type": "menu",
    "label": "Welcome $(rc.display_name ?? 'friend'). Pick an option:",
    "items": [
      {
        "label": "Usual (SLE $(rc.usual_amount ?? 50))",
        "value": "$(rc.usual_amount ?? 50)"
      },
      {
        "label": "Premium (SLE 100)",
        "value": "100",
        "hide": "$((rc.lifetime_spend ?? 0) < 500)"
      },
      {
        "label": "Gold (SLE 250)",
        "value": "250",
        "hide": "$((rc.lifetime_spend ?? 0) < 2000)"
      }
    ]
  },
  "on_done": {
    "action": "next",
    "recurring_context": {
      "mutations": { "usual_amount": "$(page.select_option.value)" }
    }
  }
}
```

A new subscriber sees only "Usual (SLE 50)". Premium appears once lifetime spend reaches SLE 500, and Gold at SLE 2000. The amount chosen becomes the next visit's "Usual".

## Limitations

Expressions can't:

- loop (`for`, `while`)
- create variables (`var x = ...`)
- define functions (`x -> ...`)
- create objects (`new`)
- change any value. Saving happens only through `on_done` mutations.
- index a list with a computed position (`list[i]`)

When you need any of these, do the work on your server through an [exchange](/docs/products/ussd-flows/developers/exchange-api/overview) and read the result with `$(data.KEY)`.
