Icecream.ink

Learn by editing

Runnable examples

Each example includes its model, icedoc and actual rendered output. Open any example in the playground to change it.

Keep author notes out of output

Inline and block comments are ignored. A comment-only line also removes its newline.

Model JSON

{
  "Name": "Ada"
}

Icedoc

<{#} comment
This greeting is maintained by the document author.
<{Missing | unknown}> is opaque here.
{#}>
<{! This whole line disappears. !}>
Hello <{Name}>!<{! This note is not printed. !}>

Rendered output

Hello Ada!
Try keep author notes out of output

Show Icecream notation literally

Backslashes escape delimiter markers. Quoted expression strings retain their existing escape rules.

Model JSON

{}

Icedoc

Write \<{Name}> to insert a name.
\<{#} if(Enabled == true)
\{#}>

Rendered output

Write <{Name}> to insert a name.
<{#} if(Enabled == true)
{#}>
Try show icecream notation literally

Check required model properties

Strict mode is enabled. Remove Name to see a located error; Note is explicitly null and can use default.

Model JSON

{
  "Name": "Ada",
  "Note": null
}

Icedoc

Hello <{Name}>. Note: <{Note | default('None')}>

Rendered output

Hello Ada. Note: None
Try check required model properties

Calculate an invoice

Compute line totals, add supplied amounts and subtract payments. Currency formatting follows decimal arithmetic.

Model JSON

{
  "Items": [
    {
      "Name": "Inspection",
      "Quantity": 2,
      "UnitPrice": 75,
      "Amount": 150
    },
    {
      "Name": "Report",
      "Quantity": 1,
      "UnitPrice": 50,
      "Amount": 50
    }
  ],
  "Paid": 80
}

Icedoc

<{#} forEach(Items:i)
<{#loop.index + 1}>. <{i.Name}>: <{i.Quantity * i.UnitPrice | currency('en-AU')}>
{#}>
Total: <{Items | sum('Amount') | currency('en-AU')}>
Balance: <{(Items | sum('Amount')) - Paid | currency('en-AU')}>
<{#} if((Items | sum('Amount')) - Paid > 0)
Payment outstanding
{#}>

Rendered output

1. Inspection: $150.00
2. Report: $50.00
Total: $200.00
Balance: $120.00
Payment outstanding
Try calculate an invoice

Summarise measurements

sum, avg, min and max operate on numeric collections. avg is an ordinary, unweighted mean.

Model JSON

{
  "Scores": [
    60,
    75,
    90
  ],
  "Completed": 3,
  "Total": 4
}

Icedoc

Total: <{Scores | sum}>
Average: <{Scores | avg | number('0.0')}>
Range: <{Scores | min}>–<{Scores | max}>
Spread: <{(Scores | max) - (Scores | min)}>
Progress: <{Completed / Total * 100 | number('0.0')}>%

Rendered output

Total: 225
Average: 75.0
Range: 60–90
Spread: 30
Progress: 75.0%
Try summarise measurements

Handle empty summaries

An empty sum is zero. Other empty aggregates return null and can use default. Invalid items still raise errors.

Model JSON

{
  "Scores": []
}

Icedoc

Total: <{Scores | sum}>
Average: <{Scores | avg | default('N/A')}>
Lowest: <{Scores | min | default('N/A')}>
Highest: <{Scores | max | default('N/A')}>

Rendered output

Total: 0
Average: N/A
Lowest: N/A
Highest: N/A
Try handle empty summaries

Format a collection

Join items, count them and select typed endpoints. Quoted arguments may contain commas and pipes.

Model JSON

{
  "Tags": [
    "ice",
    "cream",
    "ink"
  ]
}

Icedoc

<{Tags | count}> tags: <{Tags | join(', ')}>. First: <{Tags | first | upper}>. Last: <{Tags | last}>. <{'a,b' | replace(',', ' | ')}>

Rendered output

3 tags: ice, cream, ink. First: ICE. Last: ink. a | b
Try format a collection

Encode and shorten text

HTML and URL encoding are explicit. Truncation includes the suffix in its length limit.

Model JSON

{
  "Name": "A\u0026B",
  "Summary": "A longer description"
}

Icedoc

<{Name | html}> / <{Name | urlEncode}> / <{Summary | truncate(8)}>

Rendered output

A&amp;B / A%26B / A longe…
Try encode and shorten text

Check quantities and emptiness

Use numeric comparisons, filtered operands and empty/notEmpty predicates. Zero and false are not empty.

Model JSON

{
  "Items": [
    1,
    2
  ],
  "Total": 125
}

Icedoc

<{#} if(notEmpty(Items) && Items | count >= 2 && Total > 100)
Ready
<{#} elseIf(empty(Items))
No items
<{#} else
Needs review
{#}>

Rendered output

Ready
Try check quantities and emptiness

Inspect each loop position

The index starts at zero. First and last are booleans; nested loops can inspect #loop.parent.

Model JSON

{
  "Items": [
    "Ada",
    "Lin"
  ]
}

Icedoc

<{#} forEach(Items:i)
<{#loop.index}>: <{i}> (first=<{#loop.first}>, last=<{#loop.last}>)
{#}>

Rendered output

0: Ada (first=True, last=False)
1: Lin (first=False, last=True)
Try inspect each loop position

Normalize names and plurals

Naming filters share acronym and separator handling; plural includes common irregular words.

Model JSON

{
  "Name": "HTTPServer_name",
  "Entity": "Person"
}

Icedoc

<{Name | camel}> / <{Name | pascal}> / <{Name | snake}> / <{Name | kebab}> / <{Entity | plural}>

Rendered output

httpServerName / HttpServerName / http_server_name / http-server-name / People
Try normalize names and plurals

Hello projection

Insert model properties and chain filters. Missing properties normally produce empty text.

Model JSON

{
  "Name": "Ada",
  "Domain": "finance"
}

Icedoc

Hello <{Name}>. Welcome to <{Domain | upper}>.

Rendered output

Hello Ada. Welcome to FINANCE.
Try hello projection

Repeat a block

A loop binds the item to i. It leaves #this and #parent unchanged. Nested loops restore shadowed item variables and expose #loop metadata.

Model JSON

{
  "Name": "Quote",
  "Items": [
    {
      "Name": "Inspection",
      "Quantity": 1
    },
    {
      "Name": "Repairs",
      "Quantity": 2
    }
  ]
}

Icedoc

<{#} forEach(Items:i)
<{#this.Name}>: <{i.Name}> x<{i.Quantity}>
{#}>

Rendered output

Quote: Inspection x1
Quote: Repairs x2
Try repeat a block

Choose a branch

Compare values with == or !=; numeric ordering is also supported. Combine comparisons with &&. One end line closes the whole chain.

Model JSON

{
  "Status": "Pending",
  "Enabled": true
}

Icedoc

<{#} if(Status == 'Approved' && Enabled == true)
Approved
<{#} elseIf(Status == 'Pending')
Pending review
<{#} else
Other
{#}>

Rendered output

Pending review
Try choose a branch

Capture reusable text

set stores the rendered body without emitting it. The capture retains its final newline and normalizes line endings to LF.

Model JSON

{
  "Name": "Ada"
}

Icedoc

<{#} set(#greeting)
Hello <{Name}>.
{#}>
<{#greeting | trim}> Welcome back.

Rendered output

Hello Ada. Welcome back.
Try capture reusable text

Embed multiline text in JSON

jsonString escapes string contents. Keep the surrounding JSON quotes in the document.

Model JSON

{}

Icedoc

<{#} set(#note)
Hello
"World"
{#}>
{"note":"<{#note | jsonString}>"}

Rendered output

{"note":"Hello\n\"World\"\n"}
Try embed multiline text in json

Compose an AI prompt

gpt.prompt is an application convention. Icecream emits this JSON; the host chooses a model, calls a service and validates its response.

Model JSON

{
  "Topic": "rainwater collection"
}

Icedoc

<{#} set(#system)
Explain clearly. Return a JSON object.
{#}>
<{#} set(#user)
Explain <{Topic}>.
{#}>
{
  "type": "gpt.prompt",
  "system": "<{#system | jsonString}>",
  "user": "<{#user | jsonString}>"
}

Rendered output

{
  "type": "gpt.prompt",
  "system": "Explain clearly. Return a JSON object.\n",
  "user": "Explain rainwater collection.\n"
}
Try compose an ai prompt

Describe an action request

sys.call and CreateQuote are literal text, not built-in commands. The host supplies authorization, schema validation and execution.

Model JSON

{
  "CustomerId": "customer-123",
  "Scope": "Inspect roof\nProvide report"
}

Icedoc

{"type":"sys.call","name":"CreateQuote","args":{"customerId":"<{CustomerId | jsonString}>","scope":"<{Scope | jsonString}>"}}

Rendered output

{"type":"sys.call","name":"CreateQuote","args":{"customerId":"customer-123","scope":"Inspect roof\nProvide report"}}
Try describe an action request

Compose documents and scopes

QuoteLine receives each item as #this. Its #parent is the caller's model and #root remains the original model. The standalone include opens and closes on the same line; the following end line closes the loop.

Model JSON

{
  "Company": "Example Co",
  "Customer": "Ada",
  "Items": [
    {
      "Name": "Inspection",
      "Quantity": 1
    }
  ]
}

Included document: QuoteLine

<{#root.Company}> / <{#parent.Customer}> / <{#this.Name}> x<{Quantity}>

Icedoc

<{#} forEach(Items:i)
<{#} include(QuoteLine, i) {#}>
{#}>

Rendered output

Example Co / Ada / Inspection x1
Try compose documents and scopes

Serialize structured data

jsonc serializes this JsonObject as compact JSON. Capability names and schemas are interpreted by the receiving application.

Model JSON

{
  "type": "capability.spec",
  "name": "quote.generate",
  "inputs": {
    "scope": "string"
  },
  "outputs": {
    "quoteHtml": "string"
  }
}

Icedoc

<{#root | jsonc}>

Rendered output

{"type":"capability.spec","name":"quote.generate","inputs":{"scope":"string"},"outputs":{"quoteHtml":"string"}}
Try serialize structured data

Output panes show text, never execute HTML or commands. Directive lines disappear; body newlines remain. See the whitespace rules for exact behaviour.