# How FunctionCallRefiner Handles Chaining in Quarkdown

> Discover how iamgio/quarkdown's FunctionCallRefiner transforms linear function call chains like .foo::bar into nested AST trees, effectively converting syntax into executable code.

- Repository: [Giorgio Garofalo/quarkdown](https://github.com/iamgio/quarkdown)
- Tags: internals
- Published: 2026-04-29

---

**FunctionCallRefiner transforms linear function call chains into nested AST trees by recursively wrapping each resolved call as the initial argument of the subsequent function, converting syntax like `.foo {x}::bar {y}` into the equivalent of `bar(foo(x), y)`.**

The Quarkdown markup language enables powerful functional programming patterns through its unique `::` chaining syntax, allowing developers to pipeline operations without nested parentheses. In the `iamgio/quarkdown` repository, the `FunctionCallRefiner` class—located in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/FunctionCallRefiner.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/FunctionCallRefiner.kt)—handles the critical transformation of these linear chains into a tree structure that the compiler can evaluate.

## The Two-Stage Parsing Architecture

Quarkdown processes function chains through a distinct two-phase pipeline that separates context-free lexing from semantic refinement.

### Stage 1: Lexing into Linked Lists

During the initial lexing phase, the `FunctionCallWalkerParser` constructs a **linked list** of `WalkedFunctionCall` objects defined in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/walker/funcall/WalkedFunctionCall.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/walker/funcall/WalkedFunctionCall.kt). Each node contains the function name, its arguments, an optional body argument, and a `next` reference pointing to the following call.

For the expression `.foo {x}::bar {y}`, the parser creates a structure where:
- The first node represents `.foo {x}` with a `next` pointer to `.bar`
- The second node represents `.bar {y}` with a null `next` reference

This linear representation preserves the textual order but lacks the nested semantic relationship required for execution.

### Stage 2: Refinement into AST Trees

The `FunctionCallRefiner` consumes this linked list and produces a nested tree of `FunctionCallNode` objects (defined in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/FunctionCallNode.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/FunctionCallNode.kt)). Rather than maintaining the flat structure, it recursively nests each call as an argument to the next, effectively transforming `.foo {x}::bar {y}` into the semantic equivalent of `.bar {.foo {x}} {y}`.

## Inside the Refinement Process

The `FunctionCallRefiner.toNode()` method implements the chaining logic through recursive descent and argument wrapping.

First, it instantiates the node for the current call:

```kotlin
val node = FunctionCallNode(context, call.name, extractArguments(),
                             isBlock, sourceText, sourceRange)

```

When the current `WalkedFunctionCall` has a `next` reference, the refiner resolves the current node into an unchecked call:

```kotlin
val call: UncheckedFunctionCall<*> = context.resolveUnchecked(node)

```

It then wraps this resolved call as a `FunctionCallArgument` to become the first argument of the subsequent function:

```kotlin
val initialArguments = listOf(FunctionCallArgument(call))

```

Finally, it recursively instantiates a new `FunctionCallRefiner` for the next element in the chain, passing the wrapped arguments:

```kotlin
val refiner = FunctionCallRefiner(
    context, next, isBlock, sourceText, sourceRange, initialArguments)
return refiner.toNode()

```

This recursive pattern continues until the chain ends, at which point the constructed `FunctionCallNode` propagates back up the call stack, resulting in properly nested function calls.

## Practical Examples of Function Chaining

### Simple Two-Function Pipeline

```markdown
.doSomething {value}
::uppercase

```

This generates an AST equivalent to `uppercase(doSomething("value"))`. The `FunctionCallRefiner` wraps the `.doSomething` call as the first argument of `.uppercase`, ensuring the operations execute in the correct sequence.

### Deeply Nested Chains

```markdown
.format {text}
::trim
::lowercase

```

The refinement process recursively nests each call, producing `lowercase(trim(format("text")))`. Each `::` operator triggers a new level of nesting in the `FunctionCallNode` tree, with each subsequent function receiving the previous entire expression as its initial argument.

### Chaining with Body Arguments

```markdown
.list
    - Item 1
    - Item 2
::ordered

```

Here, the body content (the bullet list) becomes a `DynamicValue` argument to `.list`. The `FunctionCallRefiner` then wraps the entire resolved `.list` call—including its body content—as the first argument to `.ordered`, resulting in the semantic structure `ordered(list("- Item 1\n- Item 2"))`.

## Summary

- **FunctionCallRefiner** converts linear `WalkedFunctionCall` linked lists into nested `FunctionCallNode` trees through recursive refinement.
- The `next` property in `WalkedFunctionCall` links chain elements during the lexing phase.
- Each call is resolved via `context.resolveUnchecked(node)` before being wrapped as a `FunctionCallArgument` for the subsequent function.
- The transformation turns `.foo::bar` syntax into the equivalent of `bar(foo)`, enabling left-to-right function composition.
- Body arguments are preserved as `DynamicValue` objects and participate normally in the nesting process.

## Frequently Asked Questions

### What is the difference between WalkedFunctionCall and FunctionCallNode?

`WalkedFunctionCall` is a context-free, temporary structure created during lexing that links calls via a `next` property in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/walker/funcall/WalkedFunctionCall.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/walker/funcall/WalkedFunctionCall.kt). `FunctionCallNode` is the context-aware AST node produced after refinement in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/FunctionCallNode.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/ast/quarkdown/FunctionCallNode.kt), where chains are unwrapped into nested function calls with proper argument resolution.

### Where does the actual chaining logic live in the source code?

The core transformation logic resides in [`quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/FunctionCallRefiner.kt`](https://github.com/iamgio/quarkdown/blob/main/quarkdown-core/src/main/kotlin/com/quarkdown/core/parser/FunctionCallRefiner.kt). The class's primary method processes the `WalkedFunctionCall` linked list and constructs the nested tree structure through the recursive algorithm described above.

### How does the refiner handle errors in chained calls?

Each call is resolved via `context.resolveUnchecked(node)` before being wrapped as an argument, ensuring that invalid function names or argument mismatches are caught during the refinement phase rather than at execution time. This allows Quarkdown to provide early error detection for undefined functions in a chain.

### Can chaining mix inline arguments and body arguments?

Yes, the refinement process treats body arguments as standard `FunctionCallArgument` instances. When a call with a body (like `.list` with bullet items) chains into another function via `::`, the entire resolved call—including its body content—wraps as the first argument of the subsequent function, maintaining the pipeline semantics.