iowo/docs/design/iowo-design.typ
2024-01-18 22:38:19 +01:00

188 lines
5.1 KiB
Text

#import "../template.typ": conf
#import "util/graphics.typ"
#import graphics: arrow
#show: conf.with(
title: [iOwO design],
subtitle: [don't worry, we're just dreaming],
)
= Evaluation stages
#graphics.stages-overview
iOwO operates in stages.
This has a number of benefits and implications:
- One can just work on one aspect of the stages without having to know about the rest.
- Bugs are easier to trace down to one stage.
- Stages are also replacable, pluggable and usable somewhere else.
- For example,
one could write a Just-In-Time compiler as a new executor to replace the runtime stage,
while preserving the source #arrow() graph IR step.
However, this also makes the architecture somewhat more complicated. So here we try our best to describe how each stage looks like. If you have any feedback, feel free to drop it on #link("https://forge.katzen.cafe/katzen-cafe/iowo/issues")[the issues in the repository]!
== Source <source>
```iowo
open base.png >|
open stencil.png >|
mask
|> show
|> (invert | show)
```
Functional and pipeline-based.
However, in contrast to classic shell commands,
functions can have multiple outputs and multiple inputs.
=== Functions
`open`, `invert`, `mask`, `show` and friends.
The foundation of actually "doing" something.
- Can have any, even infinite, amount of inputs and outputs.
- Their amounts may or may not be equal.
- Inputs and outputs must have their types explicitly declared.
- An function with
- at least one output is called a streamer.
- at least one input is called a consumer.
- _both_ at least one input and at least one output is _both_ a streamer and a consumer,
and culminatively called a modifier.
- May also contain spaces in its name.
==== Inputs <input>
- Based on position.
- Inputs can be provided both through the pipeline and ad-hoc.
- Ad-hoc inputs are called arguments.
- So all of these are equivalent:
```iowo
add 1 2 -- all inputs as arguments
[ 1 2 ] | add -- all inputs through the pipeline
1 | add 2 -- one input through the pipeline, one as argument
```
==== Outputs
- Also based on position.
=== Pipelines <pipeline>
- Exchange data between streamers and consumers.
==== Simple forwarding
In the simplest case,
where inputs map to outputs bijectively#footnote[one to one],
pipelines are just pipes and forward unchanged:
```iowo
open owo.png | invert | save owo.png
```
==== Splitting <splitting>
To handle each output of a streamer individually, they can be _split_:
```iowo
mask
|> show -- would show the masked part
|> invert -- would invert the unmasked part
```
==== Combination <combination>
To throw multiple streamers into the inputs of a consumer,
they can be _combined_:
```iowo
open base.png >|
open stencil.png >|
mask
```
However, since lists are automatically spliced into inputs,
this is equivalent to the above:
```iowo
[
open base.png,
open stencil.png,
]
| mask
```
=== Comments
Done with any of `--` or `//`.
=== Data types
==== Lists
- Signified by `[]` braces.
- If thrown into a pipeline, it automatically works like a streamer.
- Can be delimited by commas.
- Must be delimited by commas if a contained streamer takes an input.
- May have a trailing comma.
- Outputs of streamers are spliced into the contained list.
- In effect, they are automatically flattened.
== Graph IR
#graphics.graph-example
The parsed representation of the source, and also what the runtime operates on.
In a way, this is the AST, except that it's not a tree.
It is represented in iOwO using adjacencies, where essentially
the vertices#footnote[Nodes or functions in this case.]
and their edges#footnote[Connections or pipes in this case.]
are stored separately.
=== Optimizer
Merges and simplifies functions in the graph IR.
== Runtime <runtime>
Runs through all functions in the graph IR.
It does not have any significantly other representation,
and despite its name there's _no_ bytecode involved.
Different runtimes are called executors.
Executors operate on instructions instead of functions.
=== Scheduler
Looks at the graph IR and decides when the VM should execute what.
=== VM <vm>
= Open questions
- @input
- At which position are arguments injected into function inputs?
- How could that be controlled if set to e.g. the end by default?
- Not all inputs are order-independent, e.g. `div`
- Should inputs and outputs really be positional?
- Could make more complex functions hard to read
- But keyworded could also make code very noisy
- Maybe a middle ground, such that at most 1 input is allowed to be positional?
- @pipeline
- We need some way to reshuffle and reorder outputs and inputs in a pipeline
- @splitting
- How would one split different outputs into a list?
- @runtime
- What is the difference between an instruction and a function?
- Should outputs that are not thrown into a consumer
be automatically displayed in some kind of debug view?
- Or should that be done instead using a debug `show` function or the like?
- Should consumers be called sinks instead?
- Shorter
- More ambiguous if only looking at the first char though