Phograph is a visual, object-oriented, dataflow programming language descended from Prograph. This reference covers its core concepts, data types, primitives, and control flow.
Data flows from top to bottom through a directed acyclic graph of operation nodes. A node executes when all its input data is available (data-driven firing rule). Execution order depends solely on data dependencies, not spatial position.
Phograph has two kinds of wires:
A node with an execution-in pin fires only after the upstream execution wire has triggered, even if all data inputs are already available. Execution wires are optional — pure-dataflow graphs need none.
A Project contains one or more Sections. Each section has three compartments:
A method has one or more cases. Each case is a separate dataflow graph with an input bar (parameters) and an output bar (return values). When a method is called, case 1 runs first. If a control annotation triggers “next case,” case 2 runs, and so on.
Every method has a defined arity — a fixed number of inputs and outputs. Inputs can be marked optional (with a default value) or variadic (collects into a list).
Phograph has thirteen data types:
| Type | Description |
|---|---|
| Integer | 64-bit signed whole numbers |
| Real | 64-bit IEEE 754 floating point |
| String | UTF-8 text |
| Boolean | true / false |
| List | Ordered, mixed-type collection (1-indexed) |
| Dict | Key-value map (keys must be hashable) |
| Data | Raw binary byte buffer |
| Date | Point in time with nanosecond precision |
| Object | Instance of a user-defined class |
| External | Opaque reference to a platform resource |
| Error | Error with message, code, and details |
| Enum | Enum value with optional associated data |
| Null | The single “nothing” value |
Phograph is dynamically typed. Types are checked at runtime. Optional type annotations can be added to pins for documentation and validation.
Lists are 1-indexed (the first element is at position 1), following the original Prograph convention.
| Type | Appearance | Description |
|---|---|---|
| Primitive | Rectangle with bottom line | Built-in operation |
| Universal method | Rectangle with name | Call to a standalone method |
| Local method | Rectangle with side lines | Embedded method body evaluated inline within the parent case |
| Instance generator | Octagon | Creates a new object |
| Constant | Horizontal line with value | Supplies a fixed value |
| Get | Special icon | Reads an attribute from an object |
| Set | Special icon | Writes an attribute on an object |
| Persistent | Oval | Stores/retrieves values that persist across method invocations |
| Evaluation | Rectangle with expression | Inline expression evaluation (arithmetic, boolean, ternary) |
| Inject | Rectangle with inject pin | Dynamic dispatch by name string or method-ref |
| Loop | Rectangle with loop icon | Iteratively calls a method, feeding outputs back as inputs |
Nodes have input pins at the top and output pins at the bottom. Pin shapes indicate type compatibility: circles for scalars, hexagons for booleans, squares for collections, diamonds for optionals, pentagons for objects, triangles for execution flow.
Pins have additional properties:
is_execution)
— enforce ordering for side-effecting operations without carrying data.
A node with an execution-in pin fires only after the upstream execution wire
has triggered.In addition to control-flow annotations (see Control Flow), nodes support structural annotations that change how they process data:
| Annotation | Description |
|---|---|
ListMap | Applies the node operation to each element of a list input, producing a list of results |
Partition | Splits a list into two lists (pass/fail) based on a boolean result from the node |
Try | Catches errors on a designated output pin instead of propagating failure |
Cases in a method support pattern matching guards that control which case fires based on the input values. Guard types:
Integer, String, a class name).| Primitive | Inputs | Output | Description |
|---|---|---|---|
+ | a, b | sum | Addition |
- | a, b | difference | Subtraction |
* | a, b | product | Multiplication |
/ | a, b | quotient | Division |
mod | a, b | remainder | Modulo |
abs | n | |n| | Absolute value |
round | n | rounded | Round to nearest integer |
floor | n | floor | Floor |
ceiling | n | ceiling | Ceiling |
sqrt | n | root | Square root |
power | base, exp | result | Exponentiation |
min | a, b | minimum | Minimum of two |
max | a, b | maximum | Maximum of two |
clamp | val, lo, hi | clamped | Clamp to range |
pi | — | 3.14… | Pi constant |
sin | rad | sine | Sine |
cos | rad | cosine | Cosine |
tan | rad | tangent | Tangent |
ln | n | log | Natural logarithm |
log10 | n | log | Base-10 logarithm |
rand | — | real | Random number [0, 1) |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
= | a, b | boolean | Equality |
!= | a, b | boolean | Not equal |
< | a, b | boolean | Less than |
> | a, b | boolean | Greater than |
<= | a, b | boolean | Less than or equal |
>= | a, b | boolean | Greater than or equal |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
and | a, b | boolean | Logical AND |
or | a, b | boolean | Logical OR |
not | a | boolean | Logical NOT |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
concat | a, b | string | Join two strings |
length | string | integer | Number of characters |
to-string | any | string | Convert to text |
split | string, sep | list | Split by separator |
trim | string | string | Strip whitespace |
replace | str, old, new | string | Replace all occurrences |
uppercase | string | string | Convert to uppercase |
lowercase | string | string | Convert to lowercase |
string-contains? | str, sub | boolean | Test if contains substring |
string-starts-with? | str, prefix | boolean | Test prefix |
string-ends-with? | str, suffix | boolean | Test suffix |
char-at | str, index | string | Character at position (1-indexed) |
string-repeat | string, count | string | Repeat string N times |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
get-nth | list, index | element | Get element (1-indexed) |
set-nth | list, idx, val | list | Set element (returns new list) |
append | list, element | list | Add element to end |
length | list | integer | Number of elements |
first | list | element | First element |
rest | list | list | All but first element |
sort | list | list | Sort ascending |
reverse | list | list | Reverse order |
empty? | list | boolean | True if empty |
contains? | list, elem | boolean | Test membership |
concat | list1, list2 | list | Concatenate two lists |
zip | list1, list2 | list | Combine element-wise into list of pairs |
unique | list | list | Remove duplicates |
enumerate | list | list | List of [index, value] pairs (1-indexed) |
These primitives take a method-ref as a callback argument
(see Method References).
| Primitive | Inputs | Output | Description |
|---|---|---|---|
map | list, method-ref | list | Apply function to each element |
filter | list, method-ref | list | Keep elements where callback returns true |
reduce | list, method-ref, initial | value | Fold list with accumulator |
flat-map | list, method-ref | list | Map then flatten one level |
any? | list, method-ref | boolean | True if callback returns true for any element |
all? | list, method-ref | boolean | True if callback returns true for all elements |
find | list, method-ref | value | First element where callback returns true (null if none) |
sort-by | list, method-ref | list | Sort by key extracted by callback |
group-by | list, method-ref | dict | Group elements by key extracted by callback |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
dict-create | — | dict | Empty dict |
dict-get | dict, key | value | Get value (fails if absent) |
dict-set | dict, key, val | dict | Set key (returns new dict) |
dict-remove | dict, key | dict | Remove key |
dict-has? | dict, key | boolean | Check if key exists |
dict-keys | dict | list | All keys |
dict-values | dict | list | All values |
dict-size | dict | integer | Number of entries |
dict-merge | d1, d2 | dict | Merge (d2 wins on conflict) |
dict-set! | dict, key, val | dict | Set key in-place (mutating) |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
log | value(s) | — | Print to console |
inspect | value | value | Print and pass through |
breakpoint | — | — | Pause if debugger attached |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
type-of | value | string | Type name |
integer? | value | boolean | Is integer? |
real? | value | boolean | Is real? |
string? | value | boolean | Is string? |
list? | value | boolean | Is list? |
null? | value | boolean | Is null? |
error? | value | boolean | Is error? |
class-of | object | string | Class name of an object |
instance-of? | obj, class_name | boolean | Is instance of named class? |
responds-to? | obj, method_name | boolean | Does object have named method? |
conforms-to? | obj, protocol_name | boolean | Does object conform to named protocol? |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
enum-create | type, variant, data_list | enum | Create an enum value with type name, variant name, and associated data |
enum-type | enum | string | Get the enum type name |
enum-variant | enum | string | Get the variant name |
enum-data | enum | list | Extract associated data as a list |
Method references are first-class values that point to a method. They can be
passed to higher-order primitives like map, filter,
and reduce, or called dynamically with call.
| Primitive | Inputs | Output | Description |
|---|---|---|---|
method-ref | name_string | ref | Create a reference to a universal method by name |
method-ref-class | class, method | ref | Reference to a specific class method |
method-ref-bound | object, method | ref | Reference bound to a specific object instance |
call | ref, arg | result | Invoke a method-ref with an argument |
is-method-ref? | value | boolean | Test if value is a method reference |
method-ref-name | ref | string | Get the method name from a reference |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
http-get | url | body_string | HTTP GET request, returns response body as string |
http-post | url, body, content_type | response_string | HTTP POST request with body and content type |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
json-parse | string | value | Parse JSON string into a Phograph value (dict, list, etc.) |
json-encode | value | string | Encode a Phograph value as a JSON string |
| Primitive | Inputs | Output | Description |
|---|---|---|---|
date-now | — | date | Current date/time |
date-create | Y, M, D, h, m, s | date | Create from year, month, day, hour, minute, second |
date-components | date | list | Extract [year, month, day, hour, minute, second] |
date-add | date, seconds | date | Add seconds to a date |
date-diff | date1, date2 | seconds | Difference between two dates in seconds |
date-format | date, format_string | string | Format date as string (e.g., "yyyy-MM-dd") |
date-parse | string, format_string | date | Parse string into date using format |
date-weekday | date | integer | Day of week (0=Sunday through 6=Saturday) |
date-compare | date1, date2 | integer | Compare dates: <0 if before, 0 if equal, >0 if after |
date-from-timestamp | number | date | Create date from Unix timestamp (seconds since epoch) |
date-to-timestamp | date | number | Convert date to Unix timestamp |
Convenience primitives for creating durations in seconds, suitable for use
with date-add:
| Primitive | Inputs | Output | Description |
|---|---|---|---|
seconds | n | n | Identity (n seconds) |
minutes | n | n * 60 | Convert minutes to seconds |
hours | n | n * 3600 | Convert hours to seconds |
days | n | n * 86400 | Convert days to seconds |
Observables let you watch for changes to object attributes and react automatically. They are the foundation for reactive data binding in front panels and between objects.
| Primitive | Inputs | Output | Description |
|---|---|---|---|
observe | object, attr_name | observer_id | Watch a specific attribute for changes |
unobserve | object, observer_id | null | Stop watching a specific observer |
observe-any | object | observer_id | Watch all attributes on an object for any change |
bind | src_obj, src_attr, tgt_obj, tgt_attr | observer_id | Bind source attribute to target — changes propagate automatically |
unbind | object, observer_id | null | Remove a binding |
Every node execution has one of three outcomes:
An annotation on a node tells the engine what to do on success or failure. The most important annotations:
| Annotation | Meaning |
|---|---|
nextCaseOnFailure | If this node fails, try the next case |
nextCaseOnSuccess | If this node succeeds, try the next case |
continueOnFailure | If this node fails, skip it and continue |
terminateOnSuccess | If this node succeeds, exit the loop |
terminateOnFailure | If this node fails, exit the loop |
failOnFailure | Propagate failure to the caller |
For simple conditionals within a single case: if(condition,
then-value, else-value) outputs whichever value matches the condition.
Created by wrapping a method call. The output of each iteration feeds back
as input for the next. A terminate annotation ends the loop.
Executes repeatedly with no counter until a terminate or
finish annotation fires inside.
Mark an input with an ellipsis: the operation runs once per list element. Mark the output too: results are gathered into a list. This turns any scalar operation into a map over a list — no explicit loop needed.
A scalar operation that receives a List on an input automatically executes once per element, producing a List of results. Multiple list inputs are zipped. This makes most explicit loops unnecessary.
A class has attributes (data) and methods
(behavior). Attributes can be instance-level or class-level. Methods receive
the object as their first input (self).
An octagonal node creates a new object. If the class has an init
method, it runs automatically. Extra inputs on the generator are passed to
init.
Get reads an attribute: 1 input (object), 2 outputs (object pass-through, value). Set writes an attribute: 2 inputs (object, value), 1 output (modified object).
Single inheritance. A child class inherits all attributes and methods from
its parent. Methods can be overridden. Use /methodName for
dynamic dispatch based on the object’s class.
| Syntax | Description |
|---|---|
/MethodName | Dynamic dispatch — determined by the first input’s class |
//MethodName | Context dispatch — within the same class |
ClassName/MethodName | Explicit — call a specific class’s method |
A protocol defines a set of method signatures that a class must implement to conform. Protocols enable polymorphism without inheritance — any class can conform to any protocol by providing the required methods.
Use conforms-to? to test at runtime whether an object’s
class conforms to a given protocol. Protocol conformance is declared on the
class definition and checked by the runtime.
An actor class is declared with the actor keyword
in its class definition. Actors provide concurrency safety: all method calls on
an actor are serialized, ensuring that only one method executes at a time on a
given actor instance. This eliminates data races without manual locking.
Actor methods are implicitly asynchronous — calls from outside the actor return a Future that resolves when the method completes. Calls within the same actor execute synchronously.
A front panel is a SwiftUI interface automatically generated from a class’s attributes. Each attribute with a front-panel annotation becomes an interactive control (slider, text field, toggle, color picker, etc.) bound to the attribute’s value via the observable system.
When the user changes a control, the bound attribute updates. When code changes
the attribute, the control updates. This two-way binding is established
automatically using bind.
An Error is a value (not an exception) with a message, code, and optional details dict.
| Primitive | Description |
|---|---|
error-create | Create an error value |
error-message | Get message string |
error-code | Get code string |
error? | Test if a value is an error |
fail-with | Fail the current method with an error |
The try annotation catches failures and converts them to Error values on a dedicated error output pin (visually a dashed red wire).
Error cluster wiring: fallible operations can chain an error-in/error-out pin. If any operation fails, subsequent operations skip and the error flows through to the end.
Async operations return Futures. A Future resolves later;
downstream operations automatically wait for the value (no explicit
await needed — it falls out of the firing rule).
| Primitive | Description |
|---|---|
future-value | Block until resolved |
future-resolved? | Non-blocking check |
future-then | Chain a callback |
future-all | Wait for all futures |
future-any | Wait for first future |
dispatch | Run method on background thread |
Channels enable producer-consumer patterns:
channel-create, channel-send,
channel-receive.