Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
86 commits
Select commit Hold shift + click to select a range
7ae0a55
wire management: add link name editing and auto-wire functionality
skmp Mar 25, 2026
6a2d5ab
named style
skmp Mar 25, 2026
76852d2
Migrate v1 arguments: strip $ from variable references and convert @N…
skmp Mar 25, 2026
a937964
first steps for graphbuilder
skmp Mar 25, 2026
b1dea6f
argument parsing changes: add split_args and parse_args_v2 functions,…
skmp Mar 25, 2026
9ff34c6
Initials steps for graphbuilder
skmp Mar 25, 2026
4772975
more graphbuilder work
skmp Mar 25, 2026
12d455c
Cleanups
skmp Mar 25, 2026
022c4a4
more graphbuilder work
skmp Mar 25, 2026
d0c9eb1
work on nets and nodes
skmp Mar 25, 2026
2e7b6c5
args2 to graphbuilder
skmp Mar 25, 2026
28bcd4c
more net work
skmp Mar 25, 2026
a7a7e00
Editor2Pane!
skmp Mar 25, 2026
925b808
Folding
skmp Mar 25, 2026
099e443
attolang.md update
skmp Mar 26, 2026
bf4d01f
towards editor2
skmp Mar 26, 2026
fb3d96d
not perfect, but progress is progress
skmp Mar 26, 2026
4120050
towards lambda link rendering
skmp Mar 26, 2026
3d66499
lambda grab rendering
skmp Mar 26, 2026
25cd373
Cleanup node_types2.h
skmp Mar 26, 2026
1855ab5
node drawing: add side-bang and lambda grab only for flow nodes
skmp Mar 26, 2026
db492f7
better output mappings
skmp Mar 26, 2026
21a2eab
better imports
skmp Mar 26, 2026
d2482e2
Add special handling for label and error nodes in editor rendering
skmp Mar 26, 2026
fe361ca
Add initial value support to decl_var input ports
skmp Mar 26, 2026
a2a0654
editor2: pin hovers
skmp Mar 26, 2026
7a73090
editor2: move styles out
skmp Mar 26, 2026
3d7904a
editor2: PinMapping
skmp Mar 26, 2026
0522f0f
optionals are indeed optionals now
skmp Mar 26, 2026
1768a5c
Split input_ports_optional
skmp Mar 26, 2026
789589e
hover on + pin.
skmp Mar 26, 2026
f4fb025
optionally scaled tooltips
skmp Mar 26, 2026
596bbcf
Increase default font size a bit
skmp Mar 26, 2026
44d53aa
Renumber auto strings
skmp Mar 26, 2026
f2b387e
next_id
skmp Mar 26, 2026
56f211b
Polymorphy for Nodes
skmp Mar 26, 2026
9a7b2bd
more editor2 cleanups
skmp Mar 26, 2026
85bc2fc
editor2: non overlap
skmp Mar 26, 2026
733c6fa
Add Liberation Mono font support and license files
skmp Mar 26, 2026
d480da9
graphbuilder -> graph_builder
skmp Mar 26, 2026
09d3d1f
ditry tracking
skmp Mar 26, 2026
ab32c54
Refactor dirty tracking methods in GraphBuilder
skmp Mar 26, 2026
43c99c2
editor2: ditry state visualization
skmp Mar 26, 2026
bf8c9cd
graph rename
skmp Mar 26, 2026
9104082
wire hovers
skmp Mar 26, 2026
87e083e
node selection.
skmp Mar 26, 2026
297bf7b
hover_item_ initial work
skmp Mar 26, 2026
31178ae
ArgNet2 w/ builders
skmp Mar 26, 2026
1812167
towards first class pins
skmp Mar 26, 2026
231d04c
graph_builder rework
skmp Mar 26, 2026
55ea34c
$empty, $unconnected
skmp Mar 26, 2026
fff59f8
$empty/$unconnected always
skmp Mar 26, 2026
5d98972
refactor: consolidate FlowNodeBuilder and NetBuilder pointer type def…
skmp Mar 26, 2026
3e9d3b1
alias fix from last commit
skmp Mar 26, 2026
1c68687
feat: add find_or_null_node method to GraphBuilder and update usages
skmp Mar 26, 2026
d6ce2bd
add remap and pin index methods to FlowArg2 and update GraphBuilder a…
skmp Mar 26, 2026
a0d3c33
FlowArg2::name
skmp Mar 26, 2026
d55cf36
wire -> net in grahp_builder
skmp Mar 26, 2026
5444e94
Wire has entry now
skmp Mar 26, 2026
cd8b1d6
cosmetics
skmp Mar 26, 2026
9771f1e
wire hover functionality to highlight all wires sharing the same entr…
skmp Mar 26, 2026
06177b0
mostly unified highlights
skmp Mar 26, 2026
c86850c
refine hover detection logic for pins, nodes, and wires; adjust thres…
skmp Mar 26, 2026
ed25319
multi select
skmp Mar 26, 2026
0915017
output_va_args groundwork
skmp Mar 26, 2026
b484ada
output handling for flow and banged expressions; separate fixed outpu…
skmp Mar 26, 2026
62c1a1d
fix non-bang nodes to have next post bangs
skmp Mar 26, 2026
3389cc3
add output ports and num_outputs to node types; adjust flow node hand…
skmp Mar 26, 2026
a4c6917
update FlowArg2 name handling and add fq_name method; update editor t…
skmp Mar 26, 2026
6c97640
enhance hover functionality to include +diamond pins in editor intera…
skmp Mar 26, 2026
626cf8b
refactor node ID and net name handling; introduce sentinel entries an…
skmp Mar 26, 2026
4a7d6c6
update node types to increase num_outputs from 1 to 2 for multiple nodes
skmp Mar 26, 2026
753c051
as_Node/Net -> as_node/net, dead code deletion
skmp Mar 26, 2026
1ae4778
Observer Pattern
skmp Mar 26, 2026
243dc4f
Refactor node rendering and editor structure
skmp Mar 26, 2026
0f5ca5c
Extract tooltip rendering functionality and style management for edit…
skmp Mar 26, 2026
21e950d
Refactor editor structure to introduce IEditorPane interface
skmp Mar 26, 2026
f296f08
editor->window.cpp/h
skmp Mar 26, 2026
9de4f08
Remove / #if LEGACY_EDITOR editor1
skmp Mar 26, 2026
a53d8a5
shift/alt + scroll = x/y pan
skmp Mar 26, 2026
7dd3656
further decoulping refactors
skmp Mar 26, 2026
722a591
move editor1 to legacy folder
skmp Mar 26, 2026
5838b34
nets_editor concept
skmp Mar 26, 2026
84dc2da
Add scroll pan speed to Editor2Style and update canvas panning logic
skmp Mar 26, 2026
2aaa7f4
fix: shorten node ids
skmp Mar 26, 2026
27617c5
in atto:0 import remove unconnected nets and replace with $unconnected
skmp Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ add_library(attolang STATIC
src/atto/graph_index.cpp
src/atto/shadow.cpp
src/atto/symbol_table.cpp
src/atto/graph_builder.cpp
)
target_include_directories(attolang PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/src
Expand Down Expand Up @@ -56,13 +57,35 @@ if(ATTOLANG_BUILD_EDITOR)
set(ATTO_NEEDS_IMGUI ON)
include(${CMAKE_CURRENT_SOURCE_DIR}/cmake/AttoDeps.cmake)

# Embed Liberation Mono font as C array
set(FONT_TTF "${CMAKE_CURRENT_SOURCE_DIR}/src/attoflow/fonts/LiberationMono-Regular.ttf")
set(FONT_HDR "${CMAKE_CURRENT_BINARY_DIR}/generated/LiberationMono_Regular.h")
file(READ "${FONT_TTF}" FONT_HEX HEX)
string(LENGTH "${FONT_HEX}" FONT_HEX_LEN)
math(EXPR FONT_SIZE "${FONT_HEX_LEN} / 2")
string(REGEX REPLACE "([0-9a-f][0-9a-f])" "0x\\1," FONT_BYTES "${FONT_HEX}")
file(WRITE "${FONT_HDR}"
"// Liberation Mono Regular - SIL Open Font License (auto-generated)\n"
"#pragma once\n"
"static const unsigned int LiberationMono_Regular_size = ${FONT_SIZE};\n"
"static const unsigned char LiberationMono_Regular_data[] = {\n"
"${FONT_BYTES}\n};\n"
)

add_executable(attoflow
src/attoflow/main.cpp
src/attoflow/editor.cpp
src/attoflow/window.cpp
src/attoflow/editor2.cpp
src/attoflow/nets_editor.cpp
src/attoflow/visual_editor.cpp
src/attoflow/node_renderer.cpp
src/attoflow/tooltip_renderer.cpp
src/attoflow/editor_style.cpp
)
target_include_directories(attoflow PRIVATE
${CMAKE_CURRENT_SOURCE_DIR}/src
${CMAKE_CURRENT_SOURCE_DIR}/src/attoflow
${CMAKE_CURRENT_BINARY_DIR}/generated
)
if(WIN32)
target_link_libraries(attoflow PRIVATE attolang SDL3::SDL3 imgui::imgui)
Expand Down
256 changes: 210 additions & 46 deletions docs/attolang.md
Original file line number Diff line number Diff line change
Expand Up @@ -457,27 +457,31 @@ Nodes with input or output bangs:

## Inline Expressions

All non-declaration nodes support **inline expressions** in their arguments. Each space-separated arg token replaces the corresponding descriptor input. If an arg is an inline expression (a literal, symbol, or complex expression), that input slot is "filled" and does not require a pin connection. Only `$N` references within inline expressions create actual input pins.
All non-declaration nodes support **inline expressions** in their arguments. Each arg maps 1:1 to a descriptor input port. An arg can be:

- **Net reference** (`$net-name`): connects to a named net — produces a visible input pin
- **Expression** (`sin($0)+1`): inline expression — displayed in node text, no pin
- **Literal** (`42`, `"hello"`, `true`): inline constant — displayed in node text, no pin
- **Symbol** (`oscs`, `sin`): bare identifier resolved via symbol table — displayed in text, no pin

Only net references produce visible input pins. All other arg types fill the slot inline.

### Rules

1. Each arg token (space-separated, respecting parentheses and quotes) maps to a descriptor input left-to-right
2. The number of arg tokens must not exceed the node's descriptor input count (error otherwise)
3. `$N` references within inline args create input pins; symbol references (bare names) do not
4. Pin indices must be contiguous starting from 0 — gaps (e.g. `$0` and `$2` without `$1`) are errors
5. Descriptor inputs beyond the number of inline args remain as pin connections
1. Each arg maps to a descriptor input left-to-right
2. The number of args must not exceed the node's descriptor input count (plus va-args if applicable)
3. `$N` references within expressions create **remap pins** (mapped via the `remaps` array)
4. Remap indices must be contiguous starting from 0 — gaps produce errors
5. `$name` (non-numeric, starting with `$`) references a named net and produces a visible pin
6. Bare names (no `$` prefix) are symbols resolved via the symbol table, not pins

### Examples (store! has 2 descriptor inputs: target, value)
### Examples (store! has 3 descriptor inputs: bang_in, target, value)

| Node text | Pins | Explanation |
|-----------|------|-------------|
| `store!` | target, value | No inline args — both inputs are pins |
| `store! oscs` | value | target filled by symbol `oscs` (resolves via symbol table to `&T`) |
| `store! oscs 42` | (none) | Both filled inline |
| `store! oscs $0` | $0 | target = symbol, value = pin $0 |
| `store! $1 $0` | $0, $1 | Both inline but reference pins |
| `store! $0 $1 $2` | error | Too many args (store! takes 2) |
| `store! $0 $2` | error | Missing pin $1 |
| Args | Visible pins | Explanation |
|------|-------------|-------------|
| `["$bang-src", "$var-ref", "$val-net"]` | 3 pins | All net refs — all visible |
| `["$bang-src", "oscs", "$0"]` | 2 pins (bang + remap $0) | target filled by symbol `oscs` |
| `["$bang-src", "oscs", "42"]` | 1 pin (bang only) | Both target and value filled inline |

## Expression Language

Expand Down Expand Up @@ -627,53 +631,213 @@ When a lambda's data dependency traces back to a node in the caller scope, that
Bang pins represent `() -> void` callable connections for control flow:

- **BangTrigger** (top square): The node's callable entry point. When invoked, the node executes. Typed as `() -> void`. Can be used as a value source — connecting a BangTrigger to a data Input passes the `() -> void` callable as a value (e.g., to store it in a variable).
- **BangNext** (bottom square): The node's continuation. After execution, the node calls whatever is connected here. Typed as `() -> void`. Links go FROM BangNext TO BangTrigger.
- **Post-bang** (side): Fires after the node's inline expressions are evaluated. Same semantics as BangNext.
- **BangNext** (bottom square): The node's continuation output. After execution, the node calls whatever is connected here. Typed as `() -> void`. Links go FROM BangNext TO BangTrigger. The first output pin on bang nodes is always a `BangNext` named `next` — this replaces the old `post_bang` pseudo-pin and is rendered at the same visual position.

**Link direction:** BangNext → BangTrigger. The "next" pin calls the "trigger" pin.

**Multiple connections:** BangTrigger pins accept multiple incoming connections if the owning node has no captured data inputs (pure `() -> void` callable). Lambda pins accept multiple connections if the lambda root has no captures. Otherwise, inference reports an error.

**Bidirectional BangTrigger:** A BangTrigger pin can be both a link destination (receiving bang chain flow from BangNext) and a link source (providing its `() -> void` value to a data Input pin).

## File Format (.atto)
## File Format (instrument@atto:0)

TOML-like format:
TOML-like format with named nets instead of explicit pin-to-pin connections.

```
version = "attoprog@0"

[viewport]
x = -500.0
y = -200.0
zoom = 1.5
# version instrument@atto:0

[[node]]
guid = "a3f7c1b2e9d04856"
id = "$gen-expr"
type = "expr"
args = ["$0+$1"]
position = [100, 200]
connections = ["a3f7c1b2e9d04856.out0->b4c8d9e0f1a23456.0"]
args = ["sin($0.p)*$1/32.f"]
remaps = ["$iter-item", "$iter-amp"]
position = [1866.25, 1443.11]

[[node]]
id = "$store-p"
type = "store!"
args = ["$0.p"]
remaps = ["$iter-item"]
position = [2133.84, 1421.55]
Comment on lines +642 to +661
Copy link

Copilot AI Mar 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doc section describes instrument@atto:0 nodes using remaps but omits the inputs/outputs arrays that the current serializer/loader uses for wiring (and tests assert are present). Either update the documentation examples/field descriptions to match the implemented format, or update the serializer/loader to the documented schema so readers aren’t misled about what’s required in a valid .atto file.

Copilot uses AI. Check for mistakes.
```

### Viewport Section
### Version Header

First line: `# version instrument@atto:0` (comment-style).

Legacy formats (`nanoprog@0`, `nanoprog@1`, `attoprog@0`, `attoprog@1`) are loaded via a legacy parser and auto-migrated. Saving always writes `instrument@atto:0`.

### Node IDs and Net Names

Node IDs and net names share the same namespace:
- Format: `$[a-zA-Z_-][a-zA-Z0-9_-]*`
- Auto-generated on import: `$a-0`, `$a-1`, ... `$a-f`, `$a-10`, ... (compact hex, migrated from old `$auto-<guid>`)
- `$0`, `$1`, ... `$N` are reserved for expression pin inputs (remaps)
- `$unconnected` is a reserved sentinel net for unconnected pins
- `$empty` is a reserved sentinel node for unassigned pin ownership
- The `$` prefix is stored in the file

### Sentinel Entries

| Sentinel | Type | Purpose |
|---|---|---|
| `$unconnected` | Net | Default wire for unconnected pins |
| `$empty` | Node | Default node owner for pins not yet assigned to a node |

Both are pre-registered by `GraphBuilder::ensure_sentinels()`. Direct `find()` or `find_or_create_net()` calls with these names throw — use `gb->unconnected_net()` / `gb->empty_node()` instead.

### Node Structure

```toml
[[node]]
id = "$a-5" # compact hex identifier
type = "store!" # node type name
args = ["oscs", "$0"] # inline arguments (expressions, literals, net refs)
remaps = ["$a-3-out0"] # $N → net mapping for expression pin inputs
position = [100, 200] # canvas coordinates
```

The optional `[viewport]` section stores the editor's camera state. It must appear after `version` and before any `[[node]]` entries.
### Node Kinds

| Field | Type | Description |
|--------|-------|--------------------------------|
| `x` | float | Horizontal scroll offset |
| `y` | float | Vertical scroll offset |
| `zoom` | float | Zoom level (1.0 = default) |
| Kind | Bang input | Bang output | Side-bang | Description |
|---|---|---|---|---|
| `Flow` | No | Yes (side-bang, right) | Yes | Dataflow node — all flow nodes have a side-bang |
| `Banged` | Yes (top) | Yes (bottom) | No | Imperative node with bang trigger |
| `Event` | No | Yes (bottom) | No | Event source |
| `Declaration` | Yes (top) | Yes (bottom) | No | Compile-time declaration |
| `Special` | No | No | No | Label or Error |

Flow nodes always have `outputs[0]` as the side-bang (BangNext). It is rendered on the right side, not at the bottom.

### Arguments (`args`)

Each entry in the `args` array is a singular expression (space-delimited in the source, already split in the file). Arguments map 1:1 to the node's descriptor input ports.

An argument can be:
- **Net reference** (`$name`): connects to a named net — produces a visible input pin
- **Expression** (`sin($0)+1`): inline expression with `$N` pin refs — displayed in node text, not a pin
- **Number** (`42`, `3.14f`): inline constant — displayed in node text
- **String** (`"hello"`): inline string literal — displayed in node text

Only net reference entries produce visible input pins. Inline values are displayed in the node's label text.

### Remaps (`remaps`)

The `remaps` array maps `$N` expression pin inputs to named nets:

```toml
remaps = ["$a-2-out0", "$a-2-out1"]
```

- `remaps[0]` = net for `$0`, `remaps[1]` = net for `$1`, etc.
- `$unconnected` for unconnected expression inputs
- Remaps are always net references

### Pin Model

Pins are graph entities (`FlowArg2` hierarchy: `ArgNet2`, `ArgNumber2`, `ArgString2`, `ArgExpr2`). Each pin has:
- **`node()`** — owning FlowNodeBuilder (always valid, `$empty` if unassigned)
- **`wire()`** / **`net()`** — associated NetBuilder (always valid, `$unconnected` if unassigned)
- **`port()`** — PortDesc2 descriptor (null for remaps)
- **`name()`** — computed: `"port_name"` or `"va_name[idx]"` or `"remaps[idx]"`
- **`is_remap()`** — true if port is null (remap pin)

#### Input pins (top of node, left to right)

Only net reference (`$name`) arguments produce visible pins. The visible pin count is:

| Section | Visible pins | Source |
|---|---|---|
| **Base args** | Only net refs in `args` | 1:1 with descriptor input ports |
| **Input va-args** | Only net refs in va-args | Named `va_name[0]`, `va_name[1]`, ... |
| **+diamond** | Add button (if node has input va-args) | Rendered as ◇ with + |
| **Remaps** | All entries | `$0`, `$1`, ... from expressions |

### Connection Format
#### Output pins (bottom of node)

Connections use pin IDs: `"<guid>.<pin_name>-><guid>.<pin_name>"`
Fixed descriptor output ports are rendered at the bottom, EXCEPT for flow nodes where `outputs[0]` (side-bang) is rendered on the right side. Output va-args follow fixed outputs.

| Section | Pins | Source |
|---|---|---|
| **Fixed outputs** | Descriptor output ports (skip side-bang for flow) | `outputs[skip_sb..]` |
| **Output va-args** | Dynamic outputs | `outputs_va_args[]` |

Expr/expr! have output va-args sized to match expression count. Event! has output va-args for spillover outputs.

#### Pin kinds

| Kind | Visual | Description |
|---|---|---|
| `BangTrigger` | Square (top) | Trigger input |
| `Data` | Circle | Data value |
| `Lambda` | Down-pointing triangle | Lambda capture (accepts node refs) |
| `BangNext` | Square (bottom/right) | Bang continuation output |
| `Va-args` | Diamond (◇) | Variable-length input/output |
| `Optional` | Diamond with ? | Optional input (trailing) |

#### Special pins

| Pin | Position | Visual | Description |
|---|---|---|---|
| **Lambda grab** | Left center | Left-pointing triangle (purple) | Capture this node as lambda |
| **Side-bang** | Right center | Square (yellow) | Post-bang output (flow nodes only) |

### Lambda Captures via Node ID

When a `$id` in an argument resolves to a **node** (not a net), it is a lambda capture. The wire renders from the source node's lambda grab (left side) to the destination's lambda pin.

- Node reference → lambda capture (wire from grab)
- Net reference → data wire (wire from output pin)
- `Lambda` pins accept only node refs
- `Data` pins can accept either

### Input Va-args

Some node types accept a variable number of additional inputs. The va-args template is defined on `NodeType2::input_ports_va_args`.

| Node | Va-args template | Description |
|---|---|---|
| `new` | `field` | Constructor fields (`field[0]`, `field[1]`, ...) |
| `call` / `call!` | `arg` | Function arguments (`arg[0]`, `arg[1]`, ...) |
| `lock` / `lock!` | `param` | Lambda parameters (`param[0]`, `param[1]`, ...) |

### Output Va-args

Some node types have dynamic output counts. The template is defined on `NodeType2::output_ports_va_args`.

| Node | Va-args template | Description |
|---|---|---|
| `expr` | `expr` | One output per expression (`expr[0]`, `expr[1]`, ...) |
| `expr!` | `expr` | Same, after the fixed `next` output |
| `event!` | `args` | Event argument outputs |

### Optional Ports

Optional ports are always trailing in the descriptor. They are split into separate `input_optional_ports` / `num_inputs_optional` on NodeType2. If not connected, they are omitted from `parsed_args` (shorter array). The editor shows absent optionals as ◇ with ? inside.

Currently only `decl_var` has an optional port (`initial`).

### Viewport (Meta File)

Viewport state is stored in `.atto/<filename>.yaml`, not in the `.atto` file:

```yaml
# Editor metadata for main.atto
viewport_x: -1504.32
viewport_y: -551.573
viewport_zoom: 4.17725
```

The `.atto/` directory is gitignored. Node positions remain in the `.atto` file.

### Labels and Errors

```toml
[[node]]
id = "$lbl-types"
type = "label"
args = ["Types"]
position = [766, 335]
```

Pin names:
- Data/lambda inputs: `0`, `1`, `2`, ... or named (e.g. `gen`, `stop`)
- Bang inputs: `bang_in0`, `bang_in1`, ...
- Data outputs: `out0`, `out1`, ...
- Bang outputs: `bang0`, `bang1`, ...
- Lambda grab: `as_lambda`
- Post-bang: `post_bang`
Labels have exactly 1 argument (the display text). Error nodes are the same — they display the original args when parsing failed.
Loading
Loading