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
|
#ifndef PURE_MODULE_H
#define PURE_MODULE_H
#include "flow.h"
#include "result.h"
#include "module-list.h"
#include "state.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
/* Pure module format v5 (docs/design/008-module-format.md). A toolchain such
* as aup produces it; Pure verifies it completely before anything executes:
* operand types (including which text a cursor and which list an index
* belongs to), stack heights and the stack at every join, forward-only
* branches, names, records, tables, constants and effects. */
enum {
PURE_MODULE_STATES = 32, PURE_MODULE_INPUTS = 32, PURE_MODULE_VIEWS = 64,
PURE_MODULE_HANDLERS = 64, PURE_MODULE_CODE = 65535, PURE_MODULE_STACK = 64,
PURE_MODULE_NAME = 32, PURE_MODULE_STRINGS = 32768, PURE_MODULE_HEADER = 40,
PURE_MODULE_FIELDS = 64, PURE_MODULE_COLLECTIONS = 1, PURE_MODULE_CONSTANTS = 4096,
PURE_MODULE_CONSTANT_BYTES = 1 << 20, PURE_MODULE_RECORDS = 8, PURE_MODULE_RECORD_FIELDS = 64,
PURE_RECORD_FIELDS = 16, PURE_MODULE_JOINS = 8, PURE_APP_PENDING = 256
};
typedef enum {
PURE_OP_CONST, PURE_OP_LOAD, PURE_OP_DT, PURE_OP_ADD, PURE_OP_SUB, PURE_OP_MUL,
PURE_OP_DIV, PURE_OP_NEG, PURE_OP_LT, PURE_OP_LE, PURE_OP_GT, PURE_OP_GE,
PURE_OP_EQ, PURE_OP_NE, PURE_OP_AND, PURE_OP_OR, PURE_OP_NOT, PURE_OP_MIN,
PURE_OP_MAX, PURE_OP_CLAMP, PURE_OP_STORE, PURE_OP_JZ, PURE_OP_JMP,
PURE_OP_FIELD, PURE_OP_DOC, PURE_OP_DOC_SIZE, PURE_OP_LEN, PURE_OP_REPLACE,
PURE_OP_REPLY, PURE_OP_REQUIRE,
PURE_OP_TEXT_CONST, PURE_OP_TEXT_LOAD, PURE_OP_TEXT_INPUT, PURE_OP_NUM_TEXT,
PURE_OP_CONCAT, PURE_OP_CHARS, PURE_OP_LINES, PURE_OP_TEXT_BYTES,
PURE_OP_CUR_LOAD, PURE_OP_CUR_START, PURE_OP_CUR_END, PURE_OP_CUR_MOVE,
PURE_OP_CUR_STORE, PURE_OP_TEXT_INSERT, PURE_OP_TEXT_REMOVE, PURE_OP_TEXT_LINE,
PURE_OP_TEXT_SET, PURE_OP_TEXT_FROM, PURE_OP_BYTES_FROM, PURE_OP_CUR_COUNT,
PURE_OP_LIST_COUNT, PURE_OP_LIST_APPEND, PURE_OP_LIST_INSERT, PURE_OP_LIST_REMOVE,
PURE_OP_ITEM_GET, PURE_OP_ITEM_SET, PURE_OP_INDEX_LOAD, PURE_OP_INDEX_STORE,
PURE_OP_INDEX_MOVE, PURE_OP_INDEX_FIRST, PURE_OP_INDEX_LAST, PURE_OP_INDEX_NUM, PURE_OP_ROW,
PURE_OP_PICK, PURE_OP_SLIDE, PURE_OP_FOLD, PURE_OP_TEXT_COMPARE, PURE_OP_LIST_FIND, PURE_OP_LIST_HAS,
PURE_OP_NOW, PURE_OP_FLOOR, PURE_OP_MOD, PURE_OP_SEND, PURE_OP_OUTER_ROW,
PURE_OP_COUNT
} PureOpcode;
/* FOLD's argument: the kind (bits 0-7), the list state (8-15) and the length
* of the block that follows (16-31), run once per item as a column of that
* list. count counts the items whose block leaves a non-zero number; sum,
* max and min take a value and an include flag from their block; first gives
* the index of the first item whose block leaves a non-zero number. */
typedef enum { PURE_FOLD_COUNT, PURE_FOLD_SUM, PURE_FOLD_MAX, PURE_FOLD_MIN, PURE_FOLD_FIRST } PureFold;
/* TEXT_COMPARE's argument: the relation, in the order of the number
* comparisons (LT LE GT GE EQ NE); texts order by their bytes. */
typedef enum { PURE_LESS, PURE_LESS_EQUAL, PURE_GREATER, PURE_GREATER_EQUAL, PURE_EQUAL, PURE_NOT_EQUAL } PureRelation;
/* A timer ticks every `period` milliseconds while a host runs the app. An
* output is a message the app sends (SEND, in a handler; it has fields as a
* message has and no handler): what receives it is the host's wiring. */
typedef enum { PURE_KIND_KEY = 1, PURE_KIND_MESSAGE = 2, PURE_KIND_TEXT = 3, PURE_KIND_TIMER = 4,
PURE_KIND_OUTPUT = 5 } PureInputKind;
enum { PURE_TIMER_LONGEST = 86400000, PURE_SOURCE = 64 };
/* A message field: a u32le or a binary64 (fixed, at its offset in the
* payload's prefix), or bytes or text of any length. The prefix also holds
* each variable field's u32le length but the last one's (its offset is
* PURE_FIELD_REST), which takes what follows the others; the variable
* fields' bytes follow the prefix in declaration order. A text is UTF-8 and
* a number finite, or the message fails. */
typedef enum { PURE_FIELD_U32 = 1, PURE_FIELD_BYTES = 2, PURE_FIELD_NUMBER = 3, PURE_FIELD_TEXT = 4 } PureFieldType;
enum { PURE_FIELD_REST = UINT32_MAX };
typedef enum {
PURE_STATE_NUM = 0, PURE_STATE_TEXT = 1, PURE_STATE_CURSOR = 2, PURE_STATE_LIST = 3, PURE_STATE_INDEX = 4
} PureStateType;
/* A cursor that stays before text inserted at it (a mark); other cursors go
* after it (a caret). */
enum { PURE_CURSOR_STAYS = 1 };
/* A table view has one row per item of a list, or per item its filter
* keeps; its columns are number or text views evaluated for one row at a
* time. */
typedef enum { PURE_VIEW_NUM = 0, PURE_VIEW_TEXT = 1, PURE_VIEW_TABLE = 2 } PureViewType;
typedef enum { PURE_UNIT_CHAR = 0, PURE_UNIT_LINE = 1 } PureUnit;
typedef enum { PURE_EDGE_DOWN, PURE_EDGE_UP } PureEdge;
/* EXISTS: an item with that key is already in the keyed list (the event
* reports PURE_CONFLICT). */
typedef enum {
PURE_FAULT_NONE, PURE_FAULT_DIVIDE, PURE_FAULT_NON_FINITE, PURE_FAULT_CLAMP,
PURE_FAULT_LIMIT, PURE_FAULT_NOT_FOUND, PURE_FAULT_ENCODING, PURE_FAULT_EXISTS
} PureFault;
/* plan is the runtime's, set at load: a fold whose block has a form for
* every instruction that works on a leaf of its list at once runs so. */
typedef struct { double value; uint32_t arg; uint8_t op, plan; } PureOp;
typedef struct { uint32_t start, length; } PureBlock;
typedef struct { uint32_t event; PureBlock code; } PureHandler;
typedef struct { char name[PURE_MODULE_NAME]; uint8_t type; uint32_t offset; } PureField;
typedef struct {
uint8_t kind;
bool keyed;
uint16_t field_start, field_count;
uint32_t prefix; /* fixed payload bytes before the variable fields */
uint32_t period; /* a timer's, in milliseconds */
} PureInputShape;
/* A record type: fields numbered from first, each a number or text. */
typedef struct { uint16_t first, count; } PureRecord;
typedef struct {
uint16_t states, inputs, views, handlers, fields, collections, constants, records, record_fields;
uint32_t code_count;
char state_names[PURE_MODULE_STATES][PURE_MODULE_NAME];
char input_names[PURE_MODULE_INPUTS][PURE_MODULE_NAME];
char view_names[PURE_MODULE_VIEWS][PURE_MODULE_NAME];
char collection_names[PURE_MODULE_COLLECTIONS][PURE_MODULE_NAME];
char input_keys[PURE_MODULE_INPUTS][PURE_MODULE_NAME];
uint8_t state_type[PURE_MODULE_STATES];
uint16_t state_ref[PURE_MODULE_STATES]; /* text: constant; cursor: text; list: record; index: list */
uint8_t state_flags[PURE_MODULE_STATES]; /* cursor: PURE_CURSOR_STAYS */
/* A keyed list: its key field + 1 (0: none). Its items stay in key order
* (numbers by value, texts by bytes), each key once, and keys do not change. */
uint8_t state_key[PURE_MODULE_STATES];
PureInputShape input[PURE_MODULE_INPUTS];
PureField field[PURE_MODULE_FIELDS];
double initial[PURE_MODULE_STATES];
uint8_t view_type[PURE_MODULE_VIEWS];
uint16_t view_table[PURE_MODULE_VIEWS]; /* a column's table view + 1, or 0 */
uint16_t view_list[PURE_MODULE_VIEWS]; /* the list of a table or column */
PureBlock view_code[PURE_MODULE_VIEWS];
uint8_t view_plan[PURE_MODULE_VIEWS]; /* a table's filter runs a leaf of its list at once (as a fold may) */
PureHandler handler[PURE_MODULE_HANDLERS];
/* The code and the text constants, sized to the module in one block
* (storage) that load takes and pure_module_release gives back. */
PureOp *code;
uint32_t *constant_offset, *constant_length;
unsigned char *constant_data;
void *storage;
char record_names[PURE_MODULE_RECORDS][PURE_MODULE_NAME];
PureRecord record[PURE_MODULE_RECORDS];
char record_field_names[PURE_MODULE_RECORD_FIELDS][PURE_MODULE_NAME];
uint8_t record_field_text[PURE_MODULE_RECORD_FIELDS];
double record_field_default[PURE_MODULE_RECORD_FIELDS]; /* number, or text constant index */
} PureModule;
/* Decode and verify into out, which must be zeroed (a static one is) or hold
* a loaded module: what it held is given back first. On failure out holds
* nothing and error names the rule. */
PureResult pure_module_load(const void *bytes, size_t size, PureModule *out,
char *error, size_t error_size);
/* Give back a loaded module's code and constants (no app may still run it). */
void pure_module_release(PureModule *);
/* Read a module file whole and load it (NOT_FOUND when it cannot be read). */
PureResult pure_module_read(const char *path, PureModule *out, char *error, size_t error_size);
/* Index of a name, or -1. Views are those outside tables. */
int pure_module_state(const PureModule *, const char *name);
int pure_module_input(const PureModule *, const char *name);
int pure_module_view(const PureModule *, const char *name);
int pure_module_column(const PureModule *, uint32_t table, const char *name);
/* The index state a number view shows as its position (a view that is just
* `at.position`), or -1: what a host moves with pure_app_point. */
int pure_module_position(const PureModule *, uint32_t view);
/* The text state a text view shows as it is (a view that is just the
* state), or -1: a host may lend its stored extents instead of copying. */
int pure_module_text_state(const PureModule *, uint32_t view);
/* Keyed collections live in a data host (for example the tree store). One
* event opens at most one document, the message's key, at its current
* version; edits stay in that working version until the event commits it.
* A version's bytes field is the value's size. */
typedef struct {
void *context;
PureResult (*open)(void *context, uint32_t collection, const char *key,
void **document, int64_t *revision);
PureVersion (*version)(void *context, void *document);
PureResult (*read)(void *context, void *document, PureVersion, size_t offset, size_t length,
bool (*visit)(void *, PureSpan), void *visit_context);
PureResult (*splice)(void *context, void *document, size_t offset, size_t remove, PureSpan insert);
PureResult (*commit)(void *context, void *document, int64_t *revision);
void (*close)(void *context, void *document);
/* Optional: the host keeps the app's states. state opens a runtime-owned
* value by name as a draft (a missing one is empty at revision 0); save
* publishes the given documents and state drafts together, all or
* nothing, and names each document's new revision. */
PureResult (*state)(void *context, const char *name, PureDraft **out);
PureResult (*save)(void *context, void *const *documents, size_t document_count, PureDraft *const *states,
size_t count, int64_t *revisions);
} PureData;
/* A byte or text value inside one event or view: a range of the payload, a
* document or text version, a module constant, a copy or the event's arena. */
typedef struct { uint8_t source; uint16_t owner; PureVersion version; uint64_t offset, length; } PureBytes;
typedef struct { unsigned char *bytes; size_t used, capacity; } PureArena;
/* One app instance: its states and at most one open event. Text states are
* runtime-owned drafts; cursors are positions the runtime keeps on character
* boundaries; lists are runtime-owned and an index always names an item
* (or 0 in an empty list). Every event is atomic: on failure nothing changes.
*
* Saving. When the data host keeps states the app starts from the stored
* ones. A save publishes, in one commit, the documents edited by messages and
* the states changed since the last save; if it fails, every event since the
* last save is undone. By default each key, text and message event saves
* before it returns (a frame waits for the next one). Deferred, events apply
* at once and a host saves a burst with one commit: an event is durable once
* a later save succeeds, and each key's document joins a burst once (a second
* message for it, or one past PURE_APP_PENDING keys, saves the burst first). */
typedef struct {
const PureModule *module;
PureData data;
double values[PURE_MODULE_STATES]; /* numbers, cursor byte positions and list indices */
PureDraft *texts[PURE_MODULE_STATES];
PureList *lists[PURE_MODULE_STATES];
PureUndo undo; /* list changes of the current event */
PureDraft *list_drafts[PURE_MODULE_STATES]; /* stored lists, when kept */
PureDraft *stored_values; /* numbers, cursors and indices, when kept */
PureVersion saved[PURE_MODULE_STATES]; /* text and list versions at the last save */
/* Events since the last save: what a failed save returns to, and the
* documents messages opened (held until the save). */
bool deferred, open;
double durable_values[PURE_MODULE_STATES];
PureVersion durable_texts[PURE_MODULE_STATES], durable_lists[PURE_MODULE_STATES];
uint64_t durable_ticks, durable_events;
struct PurePending { void *document; char key[65]; bool edited; } *pending; /* grows to PURE_APP_PENDING */
unsigned pending_count, pending_capacity;
/* The pending documents by key, so a message finds its key in one look:
* open addressing over the key's hash, a slot holding one more than the
* document's place in pending (0: none). */
uint16_t pending_index[2 * PURE_APP_PENDING];
bool document_pending; /* the current event's document is among them */
PureFault fault;
uint32_t fault_pc;
uint64_t ticks, events;
/* The time events see as now, in milliseconds since 1970 (UTC). The host
* sets it, so a script can replay any time; views never see it. */
double now;
uint64_t changes; /* counts every change of states, never back: what a filter's rows are fresh for */
struct PureRows { uint32_t table; uint64_t changes; size_t *items, count, capacity; } *rows; /* a filtered table's items */
unsigned row_tables;
/* Current message or text event, kept until pure_app_finish. */
void *document;
PureSpan payload;
bool has_reply;
PureBytes reply;
unsigned char *copied;
int64_t revision;
PureArena arena, view_arena;
/* Links (runtime-owned, saved with the states): messages sent on outputs
* and not trimmed, in the order sent (the first sent_durable are saved;
* a trimmed one goes at the next save), each output's last number (and
* as saved), those of outputs a module dropped, and the last number
* delivered from each source (and as saved). */
struct PureSent { uint32_t output; uint64_t seq; unsigned char *payload; size_t size; bool trimmed; } *sent;
size_t sent_count, sent_capacity, sent_durable;
uint64_t last_sent[PURE_MODULE_INPUTS], saved_sent[PURE_MODULE_INPUTS];
struct PureCarried { char name[PURE_MODULE_NAME]; uint64_t last; } *carried;
unsigned carried_count;
struct PureHeard { char source[PURE_SOURCE + 1]; uint64_t seq, saved; } *heard;
unsigned heard_count, heard_capacity;
PureDraft *stored_links;
bool links_changed; /* since the last save */
bool duplicate; /* the last delivery was heard before and not applied */
struct { const char *source; uint64_t seq; bool pending; } delivering;
struct PureSnapshot *snapshot; /* the newest snapshot made, or NULL */
PureRetired retired; /* list items let go since it was made */
bool reading; /* a reader: its states are a snapshot's */
} PureApp;
PureResult pure_app_start(PureApp *, const PureModule *, PureData data);
/* Publish every change since the last save in one commit, or undo them all. */
PureResult pure_app_save(PureApp *);
/* Defer saving to pure_app_save (false: save after each event). Needs a data
* host that saves documents together (PureData.save). */
bool pure_app_defer(PureApp *, bool deferred);
/* Whether key's document is in the unsaved burst: a message for it would
* save the burst first. */
bool pure_app_pending(const PureApp *, const char *key);
/* Leaves unsaved frame changes unsaved: call pure_app_save first to keep them. */
void pure_app_stop(PureApp *);
PureResult pure_app_frame(PureApp *);
/* A timer input's tick: an event saved like a key's. */
PureResult pure_app_tick(PureApp *, uint32_t input);
PureResult pure_app_input(PureApp *, uint32_t input, PureEdge);
/* Point an index state at the item at position (0 first), as a user pointing
* at a row: an event with no handler, saved like a key event. NOT_FOUND past
* the last item, INVALID for a state that is not an index. */
PureResult pure_app_point(PureApp *, uint32_t state, size_t position);
/* Deliver committed text (for example from an input method). Not UTF-8: invalid. */
PureResult pure_app_text(PureApp *, uint32_t input, PureSpan text);
/* Deliver a message. base is the revision the sender edited, or -1 for none;
* a handler that edits the document needs one. payload stays alive until
* pure_app_finish. On return app->revision is the document's revision
* (deferred: the revision the next save gives it). */
PureResult pure_app_message(PureApp *, uint32_t input, const char *key, int64_t base, PureSpan payload);
/* The same, with the variable fields where the host found them: field f
* starts at placed[2f] in payload and has placed[2f + 1] bytes (entries of
* fixed fields are not read, nor are the variable fields' lengths in the
* prefix). A host that parsed a request in place passes the fields' bytes
* where they are. */
PureResult pure_app_message_fields(PureApp *, uint32_t input, const char *key, int64_t base, PureSpan payload,
const uint32_t *placed);
/* Visit the reply of the last message in order, without copying, until the
* next event (deferred: copy it before a later save too). */
PureResult pure_app_reply(const PureApp *, bool (*visit)(void *, PureSpan), void *context);
void pure_app_finish(PureApp *);
/* A number view; false for a text view or a failed evaluation. */
bool pure_app_view(PureApp *, uint32_t view, double *out);
/* A text view, lent in order until the next view evaluation. */
PureResult pure_app_view_text(PureApp *, uint32_t view, bool (*visit)(void *, PureSpan), void *context);
/* The rows of a table view (its list's items, or those its filter keeps),
* or 0 for another view. A host evaluates only the cells it shows. */
size_t pure_app_rows(PureApp *, uint32_t table);
/* The item a row of a table shows, and the row that shows an item (false:
* its filter leaves the item out). Without a filter both are the identity. */
bool pure_app_row_item(PureApp *, uint32_t table, size_t row, size_t *item);
bool pure_app_item_row(PureApp *, uint32_t table, size_t item, size_t *row);
bool pure_app_cell(PureApp *, uint32_t column, size_t row, double *out);
PureResult pure_app_cell_text(PureApp *, uint32_t column, size_t row, bool (*visit)(void *, PureSpan), void *context);
/* Switch to another verified module. States keep their value by name (text
* states keep their content, cursors their position in that text); new states
* start at their initial value. The module must outlive the app. */
PureResult pure_app_replace(PureApp *, const PureModule *);
/* Connections between apps (docs/research/078-app-connections.md). A host
* sees only saved sends, delivers each at least once, in order per output,
* and trims what its receiver has saved; a receiver applies each once. */
typedef bool (*PureSentVisit)(void *context, uint32_t output, uint64_t seq, PureSpan payload);
void pure_app_outbox(const PureApp *, PureSentVisit visit, void *context);
/* The same, newest first (a host looking only for what is new stops early). */
void pure_app_outbox_newest(const PureApp *, PureSentVisit visit, void *context);
/* Forget output's saved sends numbered up to through (saved with the next save). */
void pure_app_trim(PureApp *, uint32_t output, uint64_t through);
/* Deliver a message event from source (a sender's name, 1 to 64 printable
* ASCII bytes) numbered seq (from 1, growing per source). A number heard
* before is not applied again: it succeeds with app->duplicate set. The
* number counts as heard with the event's changes, and also when the app
* refuses the message (a fault: it was delivered, and refused); not when a
* save fails. placed is as for pure_app_message_fields, or NULL. */
PureResult pure_app_deliver(PureApp *, uint32_t input, const char *source, uint64_t seq, PureSpan payload,
const uint32_t *placed);
/* Readers on other threads. Right after a save an app can make a snapshot of
* its saved states, for evaluating views anywhere while it keeps applying
* events: numbers are copied; text states share their stored versions and
* lists their items (a list unchanged since the last snapshot shares that
* snapshot's copy), so the cost does not grow with the text. NULL when a
* change waits for a save, when a text state is not all stored (for example
* without a data host that keeps states), or when memory runs out. The caller
* gets a reference; the app keeps its own until the next snapshot. */
typedef struct PureSnapshot PureSnapshot;
PureSnapshot *pure_app_snapshot(PureApp *);
void pure_snapshot_hold(PureSnapshot *);
/* On any thread; the last reference frees it. */
void pure_snapshot_release(PureSnapshot *);
/* Show a snapshot's states in reader, a zeroed app or a reader before: its
* views read them while the snapshot is held, with the reader's own arena and
* row cache (one thread at a time). It applies no events. pure_app_stop frees
* the reader and leaves the snapshot alone. */
void pure_app_read(PureApp *reader, const PureSnapshot *);
#endif
|