1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
|
# Rank PolymoRphic Text editor (RPRT)
A compositional, point-free language for text manipulation.
# 1. Core Concepts
## 1.1 Two Fundamental Types
All functions work with two core types:
1. **Selection** — addresses and regions in buffers
Every selection exists within one or more buffers. Selections have a rank:
- **Rank 0**: Position — a natural number offset within a single buffer
- **Rank 1**: Range — a pair of positions `[start, end)` with capture groups, within a single buffer
- **Rank 2**: Ranges — a list of ranges with capture groups, within a single buffer
- **Rank 3**: Multi-buffer selection — a mapping from buffer identifiers to rank 2 selections
Ranks 0-2 operate within a single buffer (typically the current buffer). Rank 3 is distinguished by spanning multiple buffers simultaneously.
2. **Text** — string data
### Function Types
A **Selection function** is something of signature `Selection × Selection → Selection`.
A **Text function** is something of signature `Selection × Text → Selection`.
Following APL conventions, functions may have nullary, monadic, and dyadic forms. In these cases the number of arguments that they take varies (though not their types).
### Type Coercion
When a Text argument is required, a Selection is implicitly coerced to Text by extracting its contents. This coercion has no side effects.
| Input rank | Coerced Text |
|------------|---------------------------------------------------|
| 0 | Empty string (position has no extent) |
| 1 | Contents of the range |
| 2 | Contents of each range (multiple text values) |
| 3 | Contents per buffer per range (multiple values) |
For ranks 2 and 3, the selection provides multiple text values. Text functions pair these values with selection ranges following the broadcasting rules in §1.2.
**Text constants as functions:** A Text constant (string literal) `"text"` can be used as a constant nullary or monadic Text function that always returns `"text"`, ignoring its argument. This allows Text constants to be composed with other functions using combinators like hooks and trains.
## 1.2 Notation, [Leading axis theory](https://aplwiki.com/wiki/Leading_axis_theory), and broadcasting
**Notation:** In dyadic function application `α F ω`, `α` (alpha) is the left argument, `F` is the function, and `ω` (omega) is the right argument. For Selection functions `ω` may be omitted to obtain the monadic form `α F` of `F`. For Text functions `α` may be omitted to obtain the monadic form `F ω` of `F`.
Selections have a hierarchical structure with two axes, ordered from outermost (leading) to innermost:
1. **Buffer axis**: Distinct buffer identifiers (rank 3 only)
2. **Range axis**: Range index within list of ranges (ranks 2-3)
Selection functions define distinct **rank 0** and **rank 1** semantics. Higher rank selections are processed by **vectorisation**: the rank 1 semantics are applied to each range.
**Monadic rank polymorphism** for `α F`:
| `α` rank | Semantics |
|----------|-----------------------------------------------------------------------------------------------|
| 0 | Apply rank 0 semantics of `F` |
| 1 | Apply rank 1 semantics of `F` |
| 2 | Vectorise: collect `α' F` into ranges, where `α'` varies over each range in `α` |
| 3 | Vectorise: collect `α' F` per buffer, where `α'` varies over the ranges in each buffer of `α` |
**Dyadic selection functions:** The span function `-` (§2.1) is the only dyadic selection function. It broadcasts its rank 0 and rank 1 semantics across higher ranks following the table above. See §2.1 for the complete broadcasting table.
**Text functions:** Text functions `α F τ` (§3) broadcast along the selection's axes when `α` has rank 2+. If the text argument `τ` is itself a Selection that coerces (§1.1) to multiple text values (rank 2+), the text values are paired with the ranges in `α`.
The buffer axis (rank 3) is the **leading axis**: operations on rank 3 selections distribute per-buffer first, then per-range within each buffer.
### Trains
A **train** is a sequence of functions that compose according to specific rules, following [APL train syntax](https://aplwiki.com/wiki/Train).
**2-train (Atop)** `F G`:
- Niladic: `F G` = (F) G`; `F` is evaluated niladically and `G` is evaluated monadically on the result
- Monadic: `ω (F G) = (ω F) G`
- Dyadic: `ω (F G) α = (ω F α) G`
**3-train (Fork)** `F G H`:
- Niladic: `F G H` = `(F) G (H)`; `F`, `H` are evaluated niladically and `G` is evaluated dyadically on the result
- Monadic: `ω (F G H) = (ω F) G (ω H)`
- Dyadic: `ω (F G H) α = (ω F α) G (ω H α)`
**Longer trains**: Parsed left-associatively. A train of n functions is parsed by taking the leftmost 3 functions as a fork (if n is odd and ≥3) or the leftmost 2 functions as atop (if n is even), then recursively parsing the remaining functions as the right part. For example:
- 4 functions `F G H I` → `(F G H) I` (I atop a fork)
- 5 functions `F G H I J` → `(F G H) (I J)` (atop, atop a fork)
### Hooks
RPRT provides two explicit hook combinators from BQN for flexible function composition:
**Before (Left Hook)** `F>G`:
- Niladic: `F>G = (F) G`; `F` is evaluated niladically and `G` is evaluated monadically on the result
- Monadic: `ω (F>G) = (ω F) G ω`
- Dyadic: `ω (F>G) α = (ω F) G α`
**After (Right Hook)** `F<G`:
- Niladic: `F<G = ???` TODO: undefined?
- Monadic: `ω (F<G) = ω F (G ω)`
- Dyadic: `ω (F<G) α = ω F (G α)`
These combinators bind more tightly than trains and enable partial application patterns. Hooks are **right-associative**, the _opposite_ of BQN's modifier associativity: `F<G<H` parses as `F<(G<H)`.
## 1.3 The Editor State Monad
All RPRT expressions evaluate within an implicit `EditorState` monad containing:
- Buffer contents (for all open buffers)
- Current buffer
- Current selection
- File/buffer mappings
Selection and Text functions have signatures `EditorState Selection` and `EditorState Text` respectively.
## 1.5 Evaluation Contexts
**Top-level Selection:** At the top level, all expressions are evaluated niladically (with no arguments). The result is used to set the editor's current selection.
```
F # Applies selection function F niladically and sets the selection to the result
```
**All text operations:** Modify editor content. Note that `F` returns a selection, so at the top level this also changes the current selection.
```
α F τ # F modifies contents, sets selection to changed region.
α F (β G τ) # Inner G modifies contents but doesn't set selection. F modifies contents and sets the new selection.
```
**Buffer switching statement:** The `b` statement changes the current buffer in the EditorState.
```
b "name" # Switch current buffer to "name"
/pattern/ # Searches in "name"
```
The `b` statement is not a function and cannot be composed. It only appears as a top-level statement.
## 1.6 The Grouping Operator `{}`
The grouping operator `{}` provides transactional semantics: all operations within see the same input editor state, and their modifications are merged with position tracking where possible. Any error aborts the entire evaluation.
**Syntax:** `{ op₁ , op₂ , ... , opₙ }`
**Semantics:**
- All operations compute addresses in the original, unmodified editor state
- Changes are validated before application: they must be "in sequence" (non-overlapping and monotonically ordered on position)
- Successive insertions at the same position are concatenated in order
- Changes are applied atomically after all operations complete
- Returns the union of all resulting Selections
- Any validation failure aborts the entire group
# 1.7 Composition and Parsing
**Precedence and Associativity:**:
- Hooks bind tighter than trains
- Hooks are right-associative: `F<G<H` = `F<(G<H)`
- Trains are left-associative: `F G H I` = `(F G) H I`
- Functions and operators apply left-to-right
- Dyadic functions are infix
Note that the associativity of operations here is _reversed_ compared to BQN. Our reasoning for breaking with this convention is purely for convenience of the user: the monadic semantics for search functions is given on a _left_ argument, which matches the usual order in which expressions are made, "first select this, then using that selection do that, then ...".
# 2. Selection Functions
Selection functions produce Selection outputs and take zero (nullary functions), one (monadic functions), or two (dyadic functions) inputs of type Selection.
## 2.1 Core Selection Functions
There are ten core selection functions in RPRT. All ten have nullary and monadic forms, while the function `-` additionally has dyadic semantics---it is the only selection function that may take two arguments.
| Function | Nullary semantics | Output rank |
|------------|------------------------------------------------------------|-------------|
| `e` | Empty selection | 3 |
| `$` | End of buffer | 0 |
| `#n` (n∈ℕ) | Character n in buffer | 0 |
| `n` (n∈ℕ) | Line n in buffer | 1 |
| `/re/` | First match forward in buffer from start | 1 |
| `x/re/` | All matches within buffer | 2 |
| `+n` | Line n in buffer | 1 |
| `B/re/` | All buffers whose name match re, mapping to their contents | 3 |
| `-` | Entire buffer (0-$) | 2 |
In the monadic case, functions have distinct rank 0 and rank 1 semantics. For ranks 2-3, the rank 1 semantics vectorise across ranges (see §1.2 for broadcasting and vectorisation).
| Function | Semantics for rank 0 `α` | Semantics for rank 1 `α` |
|-----------|----------------------------------|------------------------------------------|
| `α e` | Empty selection (rank 3) | Empty selection (rank 3) |
| `α $` | `α` | End position of `α` |
| `α #n` | `α` | Char n within `α` |
| `α n` | `α` | Line n within `α` |
| `α /re/` | `α` | First re within `α` |
| `α x/re/` | `α` | All re within `α` |
| `α +n` | `α` | Line n within `α` |
| `α B/re/` | Buffer matches `re`? `α` : empty | buffer matches `re`? `α` : empty |
| `α -` | Range from `α` to end of buffer | Range from start of `α` to end of buffer |
The span function `-` is the only selection function with dyadic semantics. It creates ranges by spanning from one selection to another.
| Rank of `α` | Rank of `ω` | Rank of `α-ω` | Semantics |
|-------------|-------------|---------------|-------------------------------------------------------|
| 0 | 0 | 1 | Range from `α` to `ω` |
| 0 | 1 | 1 | Range from `α` to end of `ω` |
| 1 | 0 | 1 | Range from start of `α` to `ω` |
| 1 | 1 | 1 | Range from start of `α` to end of `ω` |
| 0/1 | 2 | 2 | `α` to each range in `ω` (broadcast) |
| 2 | 0/1 | 2 | Each range in `α` to `ω` (broadcast) |
| 2 | 2 | 2 | Pairwise ranges (zip shortest) |
| 0 | 3 | 3 | `α` to each range in `ω` (broadcast) |
| 3 | 0 | 3 | Each range in `α` to `ω` (broadcast) |
| 3 | 1/2 | - | Erroneous (TODO: maybe makes sense if indices align?) |
| 1/2 | 3 | - | Erroneous (TODO: maybe makes sense if indices align?) |
### 2.1.1 Examples: Searching Within Buffer vs Current Selection
Because top-level expressions are evaluated niladically (§1.5), searching requires explicit composition to operate on the current selection (see §3.1, Text function `.`):
**Search from buffer start:** `/re/`
Niladic application searches from beginning of buffer
**Search within current selection:** `.>/re/`
In the niladic form this is `(.) /re/` (left hook semantics, §1.2), and `.` niladically is the current selection (§3.1). Thus `.>/re/` applies `/re/` to the current selection.
The After hook `F<G` has niladic semantics `F (G)`, making `.< /re/` evaluate to `/re/ (.)`—applying the search function to the current selection.
## 2.2 Selection Function Operators
Operators take functions as arguments and return new functions. Selection function operators fall into two categories which compose in a restricted manner:
**Search Modifiers** (at most one allowed):
- _(none)_ — Unmodified search semantics (see §2.1)
- `'` — Reverse (§2.2.2)
- `;` — Sequential (§2.2.3)
- `';` — Reverse-Sequential
- `;'` — Sequential-Reverse
**Result Transformations** (at most one allowed):
- _(none)_ — Unmodified result
- `~` — Complement (§2.2.4)
- `?` — Conditional (§2.2.5)
- `~?` — Complement-Conditional
- `?~` — Conditional-Complement
A fully-specified selection function has the form:
```
[result transformation] [search modifier] [base function]
```
where both operators are optional. Order matters: `';F` is not `;'F` and `~?F` is not `?~F`.
### 2.2.1 Search modifiers
#### The Reverse Operator `'`
The reverse operator `'` takes a selection function and reverses its direction.
**Note:** `'F` is not defined when `F` is one of the functions `B-` .
| Function | Nullary | Rank |
|----------|--------------------------------|------|
| `'e` | synonymous with `e` | 2 |
| `'$` | Start of buffer (position 0) | 0 |
| `'#n` | Character n from end of buffer | 0 |
| `'n` | Line n from end of buffer | 1 |
| `'/re/` | First match backward from end | 1 |
| `'x/re/` | All matches, right-to-left | 2 |
| `'+n` | Line -n in buffer | 1 |
In the monadic case, all functions are rank preserving, and all of the above functions are the identity on rank 0 inputs. For ranks 2-3, the rank 1 semantics vectorise (see §1.2).
| Function | Semantics for rank 0 `α` | Semantics for rank 1 `α` |
|------------|--------------------------|----------------------------------|
| `α 'e` | Empty selection (rank 2) | Empty selection (rank 2) |
| `α '$` | `α` | Start position of `α` |
| `α '#n` | `α` | Char n from end of `α` |
| `α 'n` | `α` | Line n from end of `α` |
| `α '/re/` | `α` | First match backward within `α` |
| `α 'x/re/` | `α` | All matches right-to-left in `α` |
| `α '+n` | `α` | Line -n within `α` |
#### The Sequential Operator `;`
The sequential operator `;` transforms a selection function from operating "within" a selection to operating "starting from the end" of that selection.
**Note:** It is erroneous to use `;F` in the nullary case. TODO: or are there interesting semantics?
**Note:** `;F` is not defined when `F` is one of `B-` .
| Function | Semantics for rank 0 `α` | Semantics for rank 1 `α` |
|------------|------------------------------|-------------------------------------|
| `α ;e` | Empty selection (rank 2) | Empty selection (rank 2) |
| `α ;$` | End of buffer | End of buffer (ignores `α`) |
| `α ;#n` | Character n after `α` | Character n after end of `α` |
| `α ;n` | Line n after `α` | Line n after end of `α` |
| `α ;/re/` | First match forward from `α` | First match forward from end of `α` |
| `α ;x/re/` | All matches forward from `α` | All matches forward from end of `α` |
| `α ;+n` | Line n after `α` | Line n after end of `α` |
#### Combined Search Modifiers
The reverse and sequential operators may be composed in either order, producing distinct semantics:
**`;'F` (Sequential-Reverse):**
- Start from the end boundary of the selection
- Search backward (in reverse direction)
**`';F` (Reverse-Sequential):**
- Start from the start boundary of the selection
- Search backward (in reverse direction)
The key distinction is the starting position: `;'F` begins at the end and searches backward, while `';F` begins at the start and searches backward.
### 2.2.2 Result transformations
#### The Complement Operator `~`
The complement operator `~` takes a selection function and produces a new selection function which returns the complement. Naturally this changes rank somewhat, as explained in the below table.
| Rank of output of `F` | Rank of output of `~F` | Semantics |
|-----------------------|------------------------|------------------------------------------------------------------|
| 0 | 2 | The complement of the position its buffer as a selection |
| 1 | 2 | The complement of the range in its buffer as a selection |
| 2 | 2 | The selection corresponding to the ranges between the selections |
| 3 | 3 | The broadcast of `~` to each buffer |
#### The Conditional Operator `?`
The conditional operator `?` takes a selection function and produces a new selection function with the following semantics.
| Expression | Semantics |
|-----------------|--------------------------------------------------|
| Nullary `?F` | exactly as `F` |
| Monadic `α ?F` | if `α F` is non-empty then `α` otherwise empty |
| Dyadic `α ?F β` | if `α F β` is non-empty then `α` otherwise empty |
#### Combined Result Transformations
The complement and conditional operators may be composed in either order, producing distinct semantics:
**`~?F` (Complement-Conditional):**
1. Evaluate function `F` to obtain result `α`
2. Apply conditional: if `α` is non-empty, return input selection; otherwise return empty
3. Apply complement to the conditional result
**`?~F` (Conditional-Complement):**
1. Evaluate function `F` to obtain result `α`
2. Apply complement to `α` to obtain `β`
3. Apply conditional to `β`
The key distinction is when the complement is applied: `~?F` complements the conditional result, while `?~F` applies conditional to the complemented result of `F`.
# 3. Text Functions
## 3.1 Text Operations
Text operations have signature `Selection × Text → Selection`. They modify the editor state (buffer contents) and return the affected Selection.
Text operations follow the broadcasting rules described in §1.2. When the text argument `τ` is itself a Selection that coerces to Text, the text values are paired with ranges following the buffer-first, then range-within-buffer ordering (the leading axis).
| Function | Effect | Return selection |
|----------|--------------------------------------------------------------|----------------------|
| `.` | No-op | Current selection |
| `c` | No-op | Current selection |
| `i` | No-op | Current selection |
| `a` | No-op | Current selection |
| `d` | Delete current selection in | Empty selection |
| `w` | Write the current buffer to its associated file. | Current selection |
| `\|` | Run current selection as shell commands, replace with output | Selection of outputs |
**Note:** `\|` is undefined when the rank of the current selection is 0.
| Function | Effect | Return selection |
|----------|--------------------------------------------------|---------------------------------------------|
| `. τ` | No-op | Current selection in buffers named by `τ` |
| `c τ` | No-op | Replace the current selection by `τ` |
| `i τ` | No-op | Insert `τ` into the current selection |
| `a τ` | No-op | Append `τ` to the current selection |
| `d τ` | Delete current selection in buffers named by `τ` | Empty selection in buffers named by `τ` |
| `w τ` | Write the buffers named by `τ`. | Current selection in buffers named by `τ` |
| `\| τ` | Run `τ` | Return selection of ephemeral buffer output |
**Note:** The text argument `τ` may be a string literal or a Selection (which coerces to Text).
**Note:** `\|` and `d` are undefined when the rank of `τ` is 0.
| Function | Effect | Semantics |
|----------|------------------------------------------------------|-----------------------------------------------------------|
| `α . τ` | No-op | Current selection in buffer(s) named by `τ` (`α` ignored) |
| `α c τ` | No-op | Change (replace) `α` with text `τ` |
| `α i τ` | No-op | Insert text `τ` before `α` |
| `α a τ` | No-op | Append text `τ` after `α` |
| `α d τ` | Delete `α` in buffers named by `τ` | Empty selection in buffers nameb by `τ` |
| `α w τ` | Write (concatenation of) `α` in buffers named by `τ` | `α` |
| `α \| τ` | Pipe `α` through command `τ` | Return selection of ephemeral output buffer |
**Examples:**
```
| "date" # Run date with no input, returns selection to output
/pattern/ c (| "date") # Replace pattern with date command output
α c<|<"sort" # Pipe selection through sort, replace with result (using After hooks)
```
The third example composes two After hooks (§1.2): `|<"sort"` creates a monadic function that pipes its argument through sort, where `"sort"` is treated as a constant Text function (§1.1). Then `α c<|<"sort"` expands to `α c ((|<"sort") α)` = `α c (α | "sort")`, avoiding explicit repetition of the selection. Note that parentheses are not necessary because hooks are right-associative.
## 3.2 Text Function Operators
Text function operators modify the behavior of text functions.
### The Swap Operator
The `swap` operator reverses the arguments of a text function, converting `α F τ` into `τ F α`. For this to typecheck both arguments must be selections.
**Syntax:** `@ F`
**Semantics:** `α (@ F) τ = τ F α`
This is particularly useful for composing text operations where the natural argument order needs to be reversed.
**Idiom for moving content:**
```
α {@i , d} β
```
Using the swap operator `@`, this evaluates in parallel:
* `α @i β` which is `β i α` which means "insert at (the start of) β the content from α"
* `α d β` which means "delete α"
The net result of this is to _move_ the content of α to (the start of) the selection given by β.
|