From da0d7514b058f1baa99a8bcc8f9e23b43cac12de Mon Sep 17 00:00:00 2001 From: eunhhu Date: Sun, 18 Oct 2026 20:54:10 +0100 Subject: Module format v7, 027's 4d: every input a message: a module's inputs are messages, outputs and arguments (kinds 1 to 3), a message bound to what makes it (PureBinding: a key's press or its release, a text input, a timer, a frame, a pointing at a table), each binding's fields fixed (none; one text; one number, dt; one u32, the item's position, the table its view), one frame input at most; a handler's event its input's number; no DT or TEXT_INPUT (97 opcodes now 95, FIELD reads dt and the text), pure_app_input, frame, tick, text and point, pure_module_position, PureEdge or an app's ticks: every event is pure_app_message, saved after it unless deferred; pure-run sends each command's message to the input bound so (a release or a frame no input takes still an event, clearing the fault, so the workbench parity holds) and defers a stored app's saves past frames as before; pure-serve posts a press with its key's release, refuses timers, frames and pointings (no_input) and ticks timers as messages; holes closed: the tier found a handler's input by the old numbering, reading a message's number field as another input's (aup's numbers caught it), and serve's replies_borrow did too; module-probe presses as messages; unit tests: the test modules' builder (expand) writes the old shapes as bound messages, pointing over a pointing input and its table, stored frames deferred as a host defers them, a press's handler refused a FIELD; counts: b35 against b34 with batches BFS +0.03%, the rest within 0.001%, without batches BFS +0.12%, join +0.01%; the bank's queries (s34 on the 4c aup's module, s35 on 4d's, six each in turns) field -0.23%, found -0.14%, large -0.12%, in_credit +0.11%, the rest within 0.06%; 008 and 027 --- docs/design/008-module-format.md | 196 ++++++++++++++++++++------------------- 1 file changed, 100 insertions(+), 96 deletions(-) (limited to 'docs/design/008-module-format.md') diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md index 3028011..61e053b 100644 --- a/docs/design/008-module-format.md +++ b/docs/design/008-module-format.md @@ -32,7 +32,9 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 항목의 바이트 5는 이제 0)와 기준 리비전. 키 있는 리스트가 그 일을 한다(바이트를 담는 레코드 필드, 앱이 가진 리비전). 머리가 38바이트로 줄었고(컬렉션 수가 빠졌다), LEN부터의 명령 번호가 당겨졌다(아래 표). 4c로 **커서와 인덱스가 앵커 하나**가 되었다: 상태 종류 2가 text나 리스트 안의 자리이고(bool은 4, i64는 5), 명령 일곱(ANCHOR_*)이 커서와 인덱스의 열셋을 대신한다. 로드가 그것을 -대상에 맞는 런타임의 명령으로 바꿔 쓴다(module-run.h). 4단계가 끝날 때까지 v7은 걸음마다 바뀐다(1.0 전, 생산자는 aup 하나다). +대상에 맞는 런타임의 명령으로 바꿔 쓴다(module-run.h). 4d로 **장치의 사건이 메시지**가 되었다: 입력의 종류는 메시지, 출력, +인자 셋이고, 키의 누름과 뗌, text 입력, 타이머, frame, 가리킴은 메시지 입력의 묶음(무엇이 그 메시지를 만드는가)이다. 핸들러의 +이벤트는 그 입력의 번호이고, DT와 TEXT_INPUT은 필드 읽기(FIELD)가 대신한다(아래 표의 번호가 당겨졌다). 4단계가 끝날 때까지 v7은 걸음마다 바뀐다(1.0 전, 생산자는 aup 하나다). **레코드 안의 리스트**(027의 2b). - 레코드 필드의 타입 4(LIST)는 항목들의 레코드를 기본값 자리(i64)에 둔다. @@ -79,7 +81,7 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 | 16 | u32 명령 수, u32 문자열 바이트 수 | | 24 | u16 필드, u16 상수, u16 레코드, u32 상수 블롭 바이트 수, u16 레코드 필드, u16 함수 | | 38 | 상태 × 16 B: u32 이름, u32 타입(비트 0–7: 0 수, 1 text, 2 앵커(text나 리스트 안의 자리, 027의 4c), 3 리스트, 4 bool, 5 i64. 비트 8: 저장하지 않는 상태(transient, [018](018-lifetimes-and-computation.md)), 그 위는 0. 메시지의 행 목록은 transient가 아니고, transient text나 리스트를 가리키는 앵커는 transient다), 8 B 값: 수와 bool은 f64 초기값(bool은 0이나 1), i64는 초기값(부호 있는 little-endian), text는 u32 초기 상수, 앵커는 u32 대상(text나 리스트 상태)과 u32 플래그(text 안이면 1이 마크: 그 자리에 넣은 글 앞에 남는다. 리스트 안이면 0), 리스트는 u32 레코드와 u32 키(0이면 없음, 아니면 키 필드 + 1, 256이면 메시지의 행 목록: 이름은 그 필드의 경로 `입력.필드`이고 키가 없다) | -| | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text, 4 타이머, 5 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 6 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 키별 여부, u16 0, u32 키 이름(키 입력)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자) 또는 0, u16 첫 필드, u16 필드 수 | +| | 입력 × 16 B: u32 이름, u8 종류(1 메시지, 2 출력: 앱이 보내는 메시지, 모양은 메시지와 같고 핸들러가 없다, 3 인자: 한 보기가 받는 인자, 모양은 메시지와 같고 핸들러가 없으며 이름은 `보기.args`), u8 묶음(메시지의 것, 그 메시지를 만드는 것: 0 없음(호스트나 다른 앱이 보낸다), 1 키의 누름, 2 키의 뗌, 3 text 입력, 4 타이머, 5 frame, 6 가리킴), u16 0, u32 키 이름(누름·뗌)·주기(타이머, 1 ms–24시간을 ms로)·보기 번호(인자: 그 보기, 가리킴: 그 표) 또는 0, u16 첫 필드, u16 필드 수 | | | 필드 × 8 B: u32 이름(식별자, 또는 그룹 안의 필드면 점으로 이은 경로 `user.name`), u32 타입(1 u32le, 2 바이트, 3 수(binary64), 4 text, 5 행: 비트 8–23에 그 행이 채우는 행 목록 상태, 6 bool: 1바이트 0이나 1, 7 i64: 8바이트 부호 있는 little-endian). 페이로드는 접두부와 가변 필드의 바이트다. 접두부에는 선언 순서로 고정 필드의 값과 가변 필드(바이트·text)의 u32 길이가 들어가는데, 마지막 가변 필드는 길이 없이 나머지를 차지한다. 가변 필드의 바이트는 접두부 뒤에 선언 순서로 이어진다(가변 필드가 하나면 예전의 "바이트 꼬리"와 같다) | | | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) | | | 레코드 × 8 B: u32 이름, u16 첫 레코드 필드, u16 필드 수(1–16) | @@ -92,9 +94,9 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 | | 문자열: `[u8 길이][바이트]`의 연속 | | | 상수 블롭 (최대 8 KiB) | -파일 크기는 위 합과 정확히 같아야 한다. 이벤트 번호는 `frame`이 0, 입력 `i`의 누름·도착이 `1 + 2i`, -키의 뗌이 `2 + 2i`다. 메시지 필드는 입력 순서대로 이어지고, u32le 필드가 차례로 놓인 뒤 바이트 꼬리가 -최대 하나 온다. 메시지 페이로드가 고정 부분보다 짧으면 이벤트는 `limit`으로 실패하고, 남는 바이트는 무시한다. +파일 크기는 위 합과 정확히 같아야 한다. 핸들러의 이벤트는 그것이 받는 메시지 입력의 번호다(027의 4d. 전에는 `frame`이 0, +입력 `i`의 누름·도착이 `1 + 2i`, 키의 뗌이 `2 + 2i`였다). 메시지 필드는 입력 순서대로 이어진다. 메시지 페이로드가 고정 부분보다 +짧으면 이벤트는 `limit`으로 실패하고, 남는 바이트는 무시한다. ## 명령 @@ -102,83 +104,81 @@ v7은 027의 4단계(패턴 모으기)의 판이다. 4b로 **키별 문서**를 |---:|---|---|---| | 0 | CONST | → n 또는 bool | 유한한 값. `인자`가 1이면 bool(값은 +0이나 1) | | 1 | LOAD | → n 또는 bool | 수나 bool 상태 `인자` | -| 2 | DT | → n | frame 1/60, 그 밖 0. 핸들러 전용 | -| 3–6 | ADD SUB MUL DIV | n n → n | DIV에서 0이면 실패 | -| 7 | NEG | n → n | | -| 8–13 | LT LE GT GE EQ NE | n n → bool | EQ와 NE는 bool 둘도 받는다 | -| 14–16 | AND OR NOT | bool bool → bool | NOT은 bool → bool. 양쪽 모두 계산 | -| 17–18 | MIN MAX | n n → n | IEEE 754-2019: −0 < +0 | -| 19 | CLAMP | n n n → n | lo > hi이면 실패 | -| 20 | STORE | n 또는 bool → | 수나 bool 상태 `인자`(그 타입의 값). 핸들러 전용 | -| 21 | JZ | bool → | 거짓이면 `인자`로 | -| 22 | JMP | | `인자`로 | -| 23 | FIELD | → n, bool, b 또는 t | 메시지 필드 `인자`(u32·수는 n, bool은 bool, 바이트는 b, text는 t). 메시지 핸들러와 그 안의 집계, 그리고 인자를 받는 보기(그 표의 열과 필터, 그 안의 집계)에서 인자를 읽는다. 도착할 때 길이가 페이로드를 넘거나 bool의 바이트가 0이나 1이 아니거나 text가 UTF-8이 아니거나 수가 유한하지 않으면 메시지가 `limit`·`encoding`·`non_finite`로 실패한다 | -| 24 | LEN | b → n | 바이트 값의 길이 | -| 25 | REPLY | b → | 이 메시지의 응답 | -| 26 | REQUIRE | bool → | 거짓이면 이벤트가 `limit`으로 실패 | -| 27 | TEXT_CONST | → t | 상수 `인자` | -| 28 | TEXT_LOAD | → t | text 상태 `인자`의 현재 값(복사 없는 버전) | -| 29 | TEXT_INPUT | → t | text 입력의 내용. text 입력 핸들러 전용 | -| 30 | NUM_TEXT | n → t | 가장 짧은 왕복 10진수. 1e15 미만 정수는 소수점·지수 없이, −0은 `0`. `인자`의 비트 0–1이 1이면 bool → t: `true`·`false`, 2면 i64. 그 위는 형식([027](027-computation-core.md)): 비트 8–15 폭(왼쪽을 채움), 16 부호 뒤를 0으로 채움, 17–19 꼴(PureFormat: 10진, 16진, 대문자 16진, 2진, 8진, 고정 소수), 20–24 고정 소수의 자릿수 + 1. 정수의 꼴에 정수가 아닌 수는 `limit` | -| 31 | CONCAT | t t → t 또는 b b → b | `인자` 1이면 바이트끼리 | -| 32–34 | CHARS LINES TEXT_BYTES | t → n | 글자(Unicode scalar) 수, 줄 수(줄바꿈 + 1), 바이트 수 | -| 35 | ANCHOR_LOAD | → a | 앵커 상태 `인자`(text 안이면 c, 리스트 안이면 i) | -| 36–37 | ANCHOR_START ANCHOR_END | → a | `인자`가 text면 그 처음·끝(c), 리스트면 첫·마지막 항목(i, 빈 리스트면 0) | -| 38 | ANCHOR_MOVE | a n → a | `인자`의 비트 0–15는 앵커가 든 text나 리스트, 16–23은 단위(text: 0 글자, 1 줄. 리스트: 0 항목). n만큼, 정수가 아니면 `limit`. 끝에서 멈춘다. 줄 이동은 열을 유지 | -| 39 | ANCHOR_STORE | a → | 앵커 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` | -| 40 | TEXT_INSERT | c t → | text `인자`의 커서 위치에 넣는다 | -| 41 | TEXT_REMOVE | c c → | 두 커서 사이를 지운다 | -| 42 | TEXT_LINE | c → t | 커서가 있는 줄(줄바꿈 제외) | -| 43 | TEXT_SET | t → | text 전체를 바꾸고 그 text의 커서를 처음으로 | -| 44 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) | -| 45 | BYTES_FROM | t → b | 같은 바이트 | -| 46 | ANCHOR_COUNT | a → n | 앵커 앞의 단위 수(`인자`는 MOVE와 같다): 글자 수, 0부터 센 줄, 0부터 센 항목의 자리 | -| 47 | LIST_COUNT | → n | 리스트 상태 `인자`의 항목 수 | -| 48 | LIST_APPEND | f… → | 레코드 필드 값을 필드 순서대로 받아 끝에 붙인다. 키 있는 리스트에서는 키 순서의 자리에 넣고, 그 키가 이미 있으면 `exists`. 핸들러 전용 | -| 49 | LIST_INSERT | i f… → | 인덱스의 항목 앞에 넣는다(빈 리스트면 처음). 키 있는 리스트에는 쓸 수 없다 | -| 50 | LIST_REMOVE | i → | 인덱스의 항목을 지운다. 없으면 `not_found` | -| 51 | ITEM_GET | i → f | 항목의 필드. `인자`의 아래 16비트는 리스트 상태, 위는 필드 번호. 없으면 `not_found` | -| 52 | ITEM_SET | i f → | 항목의 필드를 바꾼다. 키 있는 리스트의 키 필드는 바꿀 수 없다. 핸들러 전용 | -| 53 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 | -| 54 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 | -| 55 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 | -| 56 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` | -| 57 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 | -| 58 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` | -| 59 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 | -| 60 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 | -| 61 | FLOOR | n → n | 내림 | -| 62 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` | -| 63 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 | -| 64 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 | -| 65 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 | -| 66 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 | -| 67 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 | -| 68–70 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` | -| 71–72 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` | -| 73 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` | -| 74 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) | -| 75–76 | I64_MIN I64_MAX | i64 i64 → i64 | | -| 77 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` | -| 78 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` | -| 79 | LOOP | n n 또는 i i → | 세는 반복([018](018-lifetimes-and-computation.md)). `인자`의 비트 0–7은 지역 칸, 비트 8은 i64의 반복, 16–31은 뒤따르는 블록의 길이 L. 낮음과 높음을 꺼내 그 사이의 값마다(낮음부터 높음 앞까지 1씩) 칸에 값을 두고 L개 명령을 돈다. 수는 2^53 안의 정수여야 하고 아니면 `limit`이다. 블록은 둘레 블록의 코드로 돈다(걷기 안이면 `ROW`도 그대로). 실행량은 돌 때마다 L + 1을 센다. 핸들러 전용 | -| 80 | BREAK | | 그것이 든 가장 안쪽의 LOOP나 EACH를 끝낸다(그 블록 안, 조건 안에서도). 스택이 빈 자리에서만. 핸들러 전용 | -| 81 | I64_BITS | i64 i64 → i64 | 끝없는 2의 보수 위의 정수 연산([027](027-computation-core.md)). `인자` 0 and, 1 or, 2 xor, 3 왼쪽 시프트(a·2^b, 범위를 넘으면 `overflow`), 4 오른쪽 시프트(floor(a / 2^b)). 시프트 수가 0–63 밖이면 `limit` | -| 82 | BYTE_AT | b n 또는 b i64 → i64 | 바이트 값의 그 자리 바이트(0–255). `인자` 0이면 자리가 수, 1이면 i64. 정수가 아니거나 값 밖이면 `limit` | -| 83 | BYTES_OF | → b | 리스트 상태 `인자`(레코드가 i64 필드 하나)의 항목들을 한 바이트씩. 0–255 밖의 값은 `limit` | -| 84 | ANCHOR_AT | n → a | 처음부터 n 단위 뒤의 앵커(`인자`는 MOVE와 같다). 정수가 아니면 `limit`, 그 자리가 없으면 `not_found` | -| 85 | BYTES_SLICE | b n n 또는 b i64 i64 → b | a부터 c 앞까지의 바이트. `인자` 1이면 자리가 i64. 둘 다 정수이고 0 ≤ a ≤ c ≤ 길이가 아니면 `limit` | -| 86 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` | -| 87 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 | -| 88 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 | -| 89 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" | -| 90 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 | -| 91 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 | -| 92 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` | -| 93 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" | -| 94 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 | -| 95 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` | -| 96 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` | +| 2–5 | ADD SUB MUL DIV | n n → n | DIV에서 0이면 실패 | +| 6 | NEG | n → n | | +| 7–12 | LT LE GT GE EQ NE | n n → bool | EQ와 NE는 bool 둘도 받는다 | +| 13–15 | AND OR NOT | bool bool → bool | NOT은 bool → bool. 양쪽 모두 계산 | +| 16–17 | MIN MAX | n n → n | IEEE 754-2019: −0 < +0 | +| 18 | CLAMP | n n n → n | lo > hi이면 실패 | +| 19 | STORE | n 또는 bool → | 수나 bool 상태 `인자`(그 타입의 값). 핸들러 전용 | +| 20 | JZ | bool → | 거짓이면 `인자`로 | +| 21 | JMP | | `인자`로 | +| 22 | FIELD | → n, bool, b 또는 t | 메시지 필드 `인자`(u32·수는 n, bool은 bool, 바이트는 b, text는 t). 메시지 핸들러와 그 안의 집계, 그리고 인자를 받는 보기(그 표의 열과 필터, 그 안의 집계)에서 인자를 읽는다. 도착할 때 길이가 페이로드를 넘거나 bool의 바이트가 0이나 1이 아니거나 text가 UTF-8이 아니거나 수가 유한하지 않으면 메시지가 `limit`·`encoding`·`non_finite`로 실패한다 | +| 23 | LEN | b → n | 바이트 값의 길이 | +| 24 | REPLY | b → | 이 메시지의 응답 | +| 25 | REQUIRE | bool → | 거짓이면 이벤트가 `limit`으로 실패 | +| 26 | TEXT_CONST | → t | 상수 `인자` | +| 27 | TEXT_LOAD | → t | text 상태 `인자`의 현재 값(복사 없는 버전) | +| 28 | NUM_TEXT | n → t | 가장 짧은 왕복 10진수. 1e15 미만 정수는 소수점·지수 없이, −0은 `0`. `인자`의 비트 0–1이 1이면 bool → t: `true`·`false`, 2면 i64. 그 위는 형식([027](027-computation-core.md)): 비트 8–15 폭(왼쪽을 채움), 16 부호 뒤를 0으로 채움, 17–19 꼴(PureFormat: 10진, 16진, 대문자 16진, 2진, 8진, 고정 소수), 20–24 고정 소수의 자릿수 + 1. 정수의 꼴에 정수가 아닌 수는 `limit` | +| 29 | CONCAT | t t → t 또는 b b → b | `인자` 1이면 바이트끼리 | +| 30–32 | CHARS LINES TEXT_BYTES | t → n | 글자(Unicode scalar) 수, 줄 수(줄바꿈 + 1), 바이트 수 | +| 33 | ANCHOR_LOAD | → a | 앵커 상태 `인자`(text 안이면 c, 리스트 안이면 i) | +| 34–35 | ANCHOR_START ANCHOR_END | → a | `인자`가 text면 그 처음·끝(c), 리스트면 첫·마지막 항목(i, 빈 리스트면 0) | +| 36 | ANCHOR_MOVE | a n → a | `인자`의 비트 0–15는 앵커가 든 text나 리스트, 16–23은 단위(text: 0 글자, 1 줄. 리스트: 0 항목). n만큼, 정수가 아니면 `limit`. 끝에서 멈춘다. 줄 이동은 열을 유지 | +| 37 | ANCHOR_STORE | a → | 앵커 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` | +| 38 | TEXT_INSERT | c t → | text `인자`의 커서 위치에 넣는다 | +| 39 | TEXT_REMOVE | c c → | 두 커서 사이를 지운다 | +| 40 | TEXT_LINE | c → t | 커서가 있는 줄(줄바꿈 제외) | +| 41 | TEXT_SET | t → | text 전체를 바꾸고 그 text의 커서를 처음으로 | +| 42 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) | +| 43 | BYTES_FROM | t → b | 같은 바이트 | +| 44 | ANCHOR_COUNT | a → n | 앵커 앞의 단위 수(`인자`는 MOVE와 같다): 글자 수, 0부터 센 줄, 0부터 센 항목의 자리 | +| 45 | LIST_COUNT | → n | 리스트 상태 `인자`의 항목 수 | +| 46 | LIST_APPEND | f… → | 레코드 필드 값을 필드 순서대로 받아 끝에 붙인다. 키 있는 리스트에서는 키 순서의 자리에 넣고, 그 키가 이미 있으면 `exists`. 핸들러 전용 | +| 47 | LIST_INSERT | i f… → | 인덱스의 항목 앞에 넣는다(빈 리스트면 처음). 키 있는 리스트에는 쓸 수 없다 | +| 48 | LIST_REMOVE | i → | 인덱스의 항목을 지운다. 없으면 `not_found` | +| 49 | ITEM_GET | i → f | 항목의 필드. `인자`의 아래 16비트는 리스트 상태, 위는 필드 번호. 없으면 `not_found` | +| 50 | ITEM_SET | i f → | 항목의 필드를 바꾼다. 키 있는 리스트의 키 필드는 바꿀 수 없다. 핸들러 전용 | +| 51 | ROW | → i | 열이 계산하는 행. `인자`는 그 표의 리스트 상태. 열 전용 | +| 52 | PICK | … → … v | `인자`개 아래의 값을 복사해 올린다(0이면 맨 위). 타입과 대상도 그대로 | +| 53 | SLIDE | … v → v | 맨 위 값 아래의 `인자`개(1 이상)를 버린다 | +| 54 | FOLD | → n 또는 i | 집계. `인자`의 비트 0–7은 종류(0 count, 1 sum, 2 max, 3 min, 4 first, 5 distinct: 포함된 항목의 서로 다른 값의 수, −0은 0), 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 항목마다 그 리스트의 열처럼 돈다(`ROW`가 항목). count는 블록이 참을 남긴 항목을 세고, sum·max·min·distinct는 블록이 남긴 수와 포함 여부(bool)로 계산한다. first는 블록이 참을 남긴 첫 항목의 인덱스(그 리스트의 인덱스)이고 거기서 멈춘다. 포함된 항목이 없으면 count·sum은 0, max·min·first는 `not_found` | +| 55 | TEXT_COMPARE | t t → bool | 바이트 순서로 비교한다. `인자`는 관계(0 LT, 1 LE, 2 GT, 3 GE, 4 EQ, 5 NE, 수 비교 명령의 순서). 앞이 같으면 짧은 쪽이 앞이다 | +| 56 | LIST_FIND | k → i | 키 있는 리스트 `인자`에서 키가 k인 항목의 인덱스. 반씩 나눠 찾는다. 없으면 `not_found` | +| 57 | LIST_HAS | k → bool | 그 키의 항목이 있으면 참 | +| 58 | NOW | → n | 이벤트가 보는 지금(1970년부터 ms, UTC). 호스트가 정하고 스크립트는 아무 때나 재현한다. 핸들러 전용: 보기는 상태의 순수 함수로 남는다 | +| 59 | FLOOR | n → n | 내림 | +| 60 | MOD | n n → n | 나눗셈을 내림한 나머지. 결과는 나누는 수의 부호를 따른다(0도). 0으로 나누면 `divide` | +| 61 | SEND | f… → | 출력 `인자`의 필드 값을 필드 순서대로 받아(u32·수는 n, bool은 bool, 바이트는 b, text는 t) 메시지 페이로드(접두부와 가변 필드)로 만들고 이벤트의 일부로 보낸다. u32 필드의 값이 0–2³²−1의 정수가 아니면 `limit`. 핸들러 전용 | +| 62 | OUTER_ROW | → i | 열 안의 집계 블록에서 그 열의 행(집계의 항목은 ROW). 걷기 안의 집계에서는 걷는 항목 | +| 63 | EACH | | 걷기. `인자`의 비트 8–15는 리스트 상태, 16–31은 뒤따르는 블록의 길이 L. 그 L개 명령이 리스트가 시작할 때 가진 항목마다 차례로 핸들러의 코드로 돈다(`ROW`가 항목). 블록은 상태, 그 리스트의 항목(필드 바꾸기, 지우기), 다른 리스트를 바꿀 수 있지만 걷는 리스트에 넣을 수는 없다. 걷는 항목이나 그 앞의 항목을 지우면 걷기가 따라가 다음 항목을 건너뛰지 않는다. 걷기와 LOOP는 서로 안에 8겹까지 둔다. 안쪽의 코드는 바깥 걷기가 도는 리스트에 넣거나 지우지 못한다. 핸들러 전용 | +| 64 | LOCAL_LOAD | → n, bool 또는 i | 이벤트의 지역 칸. `인자`의 비트 0–7은 칸(64개), 8–15는 담는 것(0 수, 1 인덱스, 2 bool), 16–31은 인덱스의 리스트. 칸은 이벤트가 시작할 때 0이고 저장되지 않는다. 핸들러와 그 안의 걷기, 그 안의 집계에서 읽는다 | +| 65 | LOCAL_STORE | n, bool, i64 또는 i → | 그 칸에 넣는다. 핸들러와 그 안의 걷기에서. 담는 것 3은 i64. 한 핸들러는 한 칸을 한 가지(수, bool, i64, 또는 한 리스트의 인덱스)로만 쓴다 | +| 66–68 | I64_ADD I64_SUB I64_MUL | i64 i64 → i64 | 범위를 넘으면 `overflow` | +| 69–70 | I64_DIV I64_MOD | i64 i64 → i64 | 내림한 몫과 나머지(나머지는 나누는 수의 부호). 0으로 나누면 `divide`, 가장 작은 i64 / −1은 `overflow` | +| 71 | I64_NEG | i64 → i64 | 가장 작은 i64는 `overflow` | +| 72 | I64_COMPARE | i64 i64 → bool | `인자`는 관계(TEXT_COMPARE와 같다) | +| 73–74 | I64_MIN I64_MAX | i64 i64 → i64 | | +| 75 | I64_FROM | n → i64 | 범위 안의 정수만. 아니면 `limit` | +| 76 | NUM_FROM | i64 → n | \|i\| ≤ 2^53일 때만(정확하다). 아니면 `limit` | +| 77 | LOOP | n n 또는 i i → | 세는 반복([018](018-lifetimes-and-computation.md)). `인자`의 비트 0–7은 지역 칸, 비트 8은 i64의 반복, 16–31은 뒤따르는 블록의 길이 L. 낮음과 높음을 꺼내 그 사이의 값마다(낮음부터 높음 앞까지 1씩) 칸에 값을 두고 L개 명령을 돈다. 수는 2^53 안의 정수여야 하고 아니면 `limit`이다. 블록은 둘레 블록의 코드로 돈다(걷기 안이면 `ROW`도 그대로). 실행량은 돌 때마다 L + 1을 센다. 핸들러 전용 | +| 78 | BREAK | | 그것이 든 가장 안쪽의 LOOP나 EACH를 끝낸다(그 블록 안, 조건 안에서도). 스택이 빈 자리에서만. 핸들러 전용 | +| 79 | I64_BITS | i64 i64 → i64 | 끝없는 2의 보수 위의 정수 연산([027](027-computation-core.md)). `인자` 0 and, 1 or, 2 xor, 3 왼쪽 시프트(a·2^b, 범위를 넘으면 `overflow`), 4 오른쪽 시프트(floor(a / 2^b)). 시프트 수가 0–63 밖이면 `limit` | +| 80 | BYTE_AT | b n 또는 b i64 → i64 | 바이트 값의 그 자리 바이트(0–255). `인자` 0이면 자리가 수, 1이면 i64. 정수가 아니거나 값 밖이면 `limit` | +| 81 | BYTES_OF | → b | 리스트 상태 `인자`(레코드가 i64 필드 하나)의 항목들을 한 바이트씩. 0–255 밖의 값은 `limit` | +| 82 | ANCHOR_AT | n → a | 처음부터 n 단위 뒤의 앵커(`인자`는 MOVE와 같다). 정수가 아니면 `limit`, 그 자리가 없으면 `not_found` | +| 83 | BYTES_SLICE | b n n 또는 b i64 i64 → b | a부터 c 앞까지의 바이트. `인자` 1이면 자리가 i64. 둘 다 정수이고 0 ≤ a ≤ c ≤ 길이가 아니면 `limit` | +| 84 | BYTE | i64 → b | 한 바이트. 0–255 밖이면 `limit` | +| 85 | BYTES_LOAD | → b 또는 t | 이름으로 담은 값 `인자`(0–15 바이트, 16–31 text). 이벤트와 보기의 본문이 시작할 때 비어 있다 | +| 86 | BYTES_STORE | b 또는 t → | 이름으로 담은 값 `인자`의 비트 0–4를 그 값으로(비트 8이면 뒤에 덧붙여). 담은 바이트는 이벤트의 몫에 세고, 넘으면 `memory`. text 칸에는 text만 | +| 87 | LIST_VALUE | → l | 이벤트의 리스트 상태 `인자`의 값(항목의 복사, 빈 리스트는 0). 아래 "레코드 안의 리스트" | +| 88 | LIST_BIND | l → | 묶이는 리스트 상태 `인자`(레지스터)가 그 리스트를 읽게 한다 | +| 89 | APPEND_IN | i f… → | 리스트 상태 `인자`의 비트 0–15(이벤트의 리스트)에서 인덱스의 항목, 그 필드(비트 16–31, 리스트 필드)에 담긴 리스트에 항목을 덧붙인다. 그 리스트를 다른 값이 나눠 가지면 먼저 제 것으로 복사한다 | +| 90 | NUM_PARSE | t → n | JSON의 수로 쓴 text의 가장 가까운 수. 그런 text가 아니거나 유한하지 않으면 `limit` | +| 91 | CALL | a… → r… | 함수 `인자`를 부른다. 매개변수를 꺼내고 결과를 놓는다. 위 "함수" | +| 92 | RETURN | … r… → | 함수 `인자`(자신)를 끝낸다. 맨 위의 결과가 부른 쪽으로 간다 | +| 93 | CHAR_AT | t q → q | text의 바이트 자리 q(i64)에서 시작하는 글자의 코드 포인트(i64). 글자가 시작하지 않는 자리나 밖이면 `limit` | +| 94 | CHAR | q → t | 코드 포인트 하나의 text. 0–0x10FFFF 밖이면 `limit`, 서로게이트면 `encoding` | bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이트의 정수다. CONST의 인자 2는 i64 상수(값의 8바이트), NUM_TEXT의 인자 2는 i64의 10진 글자다. LOAD·STORE·지역 칸·항목·FIELD·SEND는 i64를 수처럼 옮긴다. FOLD의 종류에 64(PURE_FOLD_INTEGER)를 @@ -194,16 +194,18 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이 - 이름은 ASCII 식별자 1–31바이트이고, 상태·레코드·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지나 레코드 안에서 유일하다. 메시지 필드의 이름은 식별자를 점으로 이은 경로일 수 있고(그룹 `user` 안의 `user.name`), 한 이름이 필드이면서 그룹일 수는 없다(`user`와 `user.name`). 레코드(최대 16)의 필드 범위는 차례로 이어지고 모두 합쳐 128개 이하다. - 키 입력의 키는 정해진 목록(`Space Enter` 화살표 `Backspace Delete Home End Tab Escape PageUp PageDown KeyA–Z Digit0–9`) - 안에 있고 겹치지 않는다. 메시지 입력은 키 이름이 없고 필드 범위가 차례로 이어진다. text 입력은 필드가 없다. + 누름과 뗌의 키는 정해진 목록(`Space Enter` 화살표 `Backspace Delete Home End Tab Escape PageUp PageDown KeyA–Z Digit0–9`) + 안에 있고 같은 묶음에서 겹치지 않는다. 입력의 필드 범위는 차례로 이어진다. 묶음마다 필드가 정해져 있다: 누름·뗌·타이머는 + 없고, text 입력은 text 하나(들어온 글), frame은 수 하나(지난 frame부터의 초, 호스트는 1/60), 가리킴은 u32 하나(그 표의 리스트 안 + 항목 위치)다. frame에 묶인 입력은 하나까지이고, 가리킴의 보기는 표다. - 상수는 블롭 안에 있고 유효한 UTF-8이다. text 상태의 초기 상수, 커서의 대상 text, 리스트의 레코드, 인덱스의 대상 리스트, 레코드 필드의 기본값(유한한 수 또는 있는 상수)이 존재한다. bool의 상수, 초기값, 기본값은 +0이나 1이다. -- 메시지·text 핸들러의 이벤트는 도착(`1 + 2i`)뿐이다. +- 핸들러의 이벤트는 메시지 입력이다(출력과 인자에는 핸들러가 없다). - 각 블록에서 명령마다 피연산자 타입(수, bool, 바이트, text, 커서, 인덱스)과 스택 높이(0–64)를 확인한다. bool과 수는 서로의 자리에 쓸 수 없다. 커서 타입은 어느 text의 커서인지, 인덱스 타입은 어느 리스트의 인덱스인지 담는다. 다른 text의 커서나 다른 리스트의 인덱스를 쓰는 모듈은 거절한다. 레코드 명령은 레코드 필드 수만큼 필드 타입대로 꺼낸다. 명령이 놓일 수 있는 문맥(보기, - 열, 핸들러, 메시지 핸들러, 키별 메시지 핸들러, text 입력 핸들러)도 확인한다. 분기는 블록 안에서 앞으로만 간다. + 열, 핸들러, 메시지 핸들러, 함수)도 확인한다. 분기는 블록 안에서 앞으로만 간다. 합류 지점에는 모든 경로가 같은 스택(높이, 타입, 커서·인덱스의 대상)을 가져와야 한다. 그래서 조건식처럼 값이 합류를 건너가도 타입이 섞이지 않는다. 앞에 남은, 값을 가진 합류는 한 번에 8개까지다. 모든 명령에 도달할 수 있어야 하고, 핸들러는 값을 남기지 않으며 보기와 열은 자기 타입의 값 하나를 남긴다. @@ -227,7 +229,7 @@ bool은 실행 중에 수의 칸에 든 0이나 1이고, i64는 같은 8바이 - 보기의 본문(018의 3단계): 표 밖의 보기는 계산할 수 있다. 그 코드는 지역 칸, LOOP, BREAK, EACH를 쓰고 이벤트의 리스트를 바꿀 수 있지만(LIST_APPEND·LIST_INSERT·LIST_REMOVE·ITEM_SET) 다른 상태는 바꾸지 못한다(`view changes a state`). 반복의 블록은 값을 남기지 않는 보기의 문장으로, 걷기의 블록은 그 항목을 읽는 보기의 문장으로 검증한다. 상태 쓰기, SEND, REPLY, REQUIRE, - DT, NOW는 보기 어디에도 없다. 로드할 때 보기마다 계산하는지(지역 칸이나 반복을 쓰는지)와 바꾸는 이벤트의 리스트를 적는다. + NOW는 보기 어디에도 없다. 로드할 때 보기마다 계산하는지(지역 칸이나 반복을 쓰는지)와 바꾸는 이벤트의 리스트를 적는다. - 이벤트의 리스트: 플래그 256(PURE_LIST_ROWS)을 가진 리스트는 이벤트가 끝날 때마다 비워지고 저장되지 않으며 되돌리기 기록을 쌓지 않는다. 이름은 경로다. `입력.필드`이면 그 메시지의 행 필드가 채우고(키 없이), `let.이름`이면 코드만 채운다(aup의 `let 이름: list of 레코드`, 키를 둘 수 있다: 플래그 256 + 키 필드 + 1). 저장하지 않는 상태(비트 8)일 수 없다. @@ -335,8 +337,9 @@ text, 끝이 맞지 않는 바이트는 메시지를 `limit`으로 실패시킨 키 있는 리스트 앱의 메모리·저장 대조(400단계 × 3, 재시작 포함), 검증 거절 8가지, 손상 모듈 20,000개(받아들인 모듈의 키 있는 리스트는 늘 엄격한 키 순서다). aup는 무작위 3,600단계에서 text 키와 수 키 리스트가 파이썬 모델과 같음을 확인한다. -키·frame·text 이벤트와 타이머의 틱(`pure_app_tick`)은 그 핸들러를 실행한다. 틱은 키 이벤트처럼 저장되는 이벤트다. -호스트가 앱을 돌리는 동안 타이머의 주기마다 한 번 넣고, 놓친 틱을 몰아서 넣지는 않는다. 이벤트가 보는 시각은 +장치의 사건(키의 누름과 뗌, text 입력, 타이머의 틱, frame, 가리킴)은 그것에 묶인 입력의 메시지다(027의 4d). 호스트가 +`pure_app_message`로 그 입력에 보내고, 다른 메시지처럼 핸들러를 실행해 저장한다. 받는 입력이 없는 사건(핸들러 없는 키의 뗌, +frame 핸들러 없는 앱의 frame)은 보내지 않는다. 호스트는 앱을 돌리는 동안 타이머의 주기마다 한 번 넣고, 놓친 틱을 몰아서 넣지는 않는다. 이벤트가 보는 시각은 `PureApp.now`이고 호스트가 정한다(`pure-run`의 `time MS`, `tick NAME`). 메시지 이벤트는 다음 순서로 처리한다. 1. 입력 종류를 확인하고, 페이로드가 고정 부분보다 짧으면 `limit`. 행을 행 목록에 채운다. @@ -352,10 +355,11 @@ text·리스트가 남을 때만 위치를 유지한다. 응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드를 복사하지 않는다. text 상태의 응답은 확정 전에 복사한다. text 보기는 `pure_app_view_text`로 빌려준다. -호스트는 인덱스 상태를 한 항목으로 옮길 수 있다(`pure_app_point`, 사용자가 행을 가리키는 것). 핸들러 없는 이벤트이며 -키 이벤트처럼 저장되고, 마지막 항목을 넘으면 `not_found`다. 어느 인덱스를 옮길지는 보기가 알려 준다. -`pure_module_position`은 수 보기가 인덱스 상태의 위치 그대로(`view tasks_focus = at.position`)이면 그 상태를 돌려준다. -그래서 인터페이스(Vega)는 앱 코드 없이 목록의 클릭을 포커스로 옮긴다. +사용자가 표의 행을 가리키는 것은 그 표에 묶인 입력(가리킴)의 메시지다. 필드 하나가 행이 보이는 항목의 리스트 안 위치이고(걸러진 +표면 호스트가 행을 항목으로 바꾼다, `pure_app_row_item`), 그 핸들러가 인덱스를 옮긴다. 마지막 항목을 넘으면 `not_found`다. aup는 +수 보기가 인덱스 상태의 위치 그대로(`view tasks_focus = at.position`)인 표마다 입력 `tasks.point`와 그 핸들러(FIELD 0, +ANCHOR_AT, ANCHOR_STORE)를 만든다. 그래서 인터페이스(Vega)는 앱 코드 없이 목록의 클릭을 포커스로 옮긴다. 4d 전에는 핸들러 없는 +이벤트(`pure_app_point`)였고, 옮길 인덱스를 보기에서 찾았다(`pure_module_position`). 표 보기는 리스트 항목마다 행 하나를 가진다(`pure_app_rows`). 걸러진 표는 필터 블록이 참을 남긴 항목마다 행 하나를 가진다(항목 순서 그대로). 필터는 상태가 바뀐 뒤 처음 행을 물을 때 항목마다 한 번 돌고, 그 결과(행 → 항목)를 @@ -415,8 +419,8 @@ text·리스트가 남을 때만 위치를 유지한다. 마지막 저장 이후의 이벤트가 **모두** 되돌려진다(값, text draft, 리스트 되돌리기 기록과 저널, 이벤트·tick 수). 그래서 "성공을 알린 이벤트는 저장됐고, 실패를 알린 이벤트는 흔적이 없다"가 성립한다. 결과 불명(`uncertain`)이나 다른 기록자와의 충돌 뒤에는 앱이 저장소와 어긋나 이후 저장이 충돌로 실패한다. 다시 시작하면 저장소의 상태로 이어진다. -- **기본은 이벤트마다 저장.** 키·text·메시지 이벤트는 저장까지 마치고 돌아온다(묶음 크기 1). 초당 60번 오는 - frame 이벤트는 다음 이벤트나 `pure_app_save`에 함께 들어간다. `pure_app_stop`만 하면 저장하지 않은 변경은 사라진다. +- **기본은 이벤트마다 저장.** 메시지 이벤트는 저장까지 마치고 돌아온다(묶음 크기 1). 초당 60번 오는 frame은 + 호스트가 지연 저장으로 보내(Vega의 `vega_frame`, `pure-run`) 다음 이벤트나 `pure_app_save`에 함께 들어간다. `pure_app_stop`만 하면 저장하지 않은 변경은 사라진다. - **지연 저장(그룹 커밋).** `pure_app_defer(app, true)`면 이벤트는 바로 적용되고, 호스트가 입력을 몰아 처리한 뒤 `pure_app_save` 한 번으로 저장한다. 이벤트는 그 뒤 저장이 성공해야 확정이고, 그 전에는 응답을 내보내지 않는 것이 호스트의 몫이다. @@ -476,7 +480,7 @@ text·리스트가 남을 때만 위치를 유지한다. | `GET /views/이름` | 보기의 값. 수는 가장 짧은 왕복 10진수, bool은 `true`·`false`, text는 UTF-8 그대로(`text/plain`). text 상태를 그대로 보이는 보기(`view page = note`)는 저장된 익스텐트를 빌려준다([079](../research/079-lent-views.md)) | | `GET /views/표?from=N&count=M` | 표의 행: `{"rows":전체,"from":N,"items":[{열: 값}…]}`(`application/json`, bool 열은 `true`·`false`). 요청한 행의 칸만 계산한다. 값이 없는 칸은 `null` | | `GET /views/이름?인자=값…` | 인자를 받는 보기(표라면 `from`·`count`와 함께). 값은 `%XX`를 풀어 읽고, 고정 필드는 메시지의 쿼리 필드와 같다. 빠진 고정 인자, 모르는 이름, 두 번 준 인자, 타입이 다른 값은 400 `invalid`. 등록한 질의도 같다(`GET /queries/이름?인자=값`) | -| `POST /inputs/이름` | 이벤트. text 입력은 본문이 text, 키 입력은 누름(down 뒤 up). 메시지는 고정 필드(u32, 수, bool)를 쿼리(`?points=3`, `?done=true`, 그룹 안이면 `?to.zip=7`)로, 가변 필드 하나를 본문으로 받거나, `Content-Type: application/json` 본문의 객체로 필드를 받는다(문자열은 바이트·text, 수는 u32·수, `true`·`false`는 bool, 그룹은 안의 객체). 응답 본문은 핸들러의 `reply`(메시지 필드) | +| `POST /inputs/이름` | 이벤트. text 입력은 본문이 text, 키의 누름은 누름 뒤 같은 키의 뗌(받는 입력이 있으면). 타이머·frame·가리킴과 출력·인자에는 `no_input`. 메시지는 고정 필드(u32, 수, bool)를 쿼리(`?points=3`, `?done=true`, 그룹 안이면 `?to.zip=7`)로, 가변 필드 하나를 본문으로 받거나, `Content-Type: application/json` 본문의 객체로 필드를 받는다(문자열은 바이트·text, 수는 u32·수, `true`·`false`는 bool, 그룹은 안의 객체). 응답 본문은 핸들러의 `reply`(메시지 필드) | | `POST /inputs/이름` + `X-Pure-Source`·`X-Pure-Sequence` | 다른 앱의 출력에서 전해진 메시지(`pure_app_deliver`): 번호마다 한 번 적용하고, 이미 들은 번호는 200. 답에 `X-Pure-Batch: 256`을 싣고, `X-Pure-Count: N`이면 본문은 N개 객체의 JSON 배열(연속 번호, 한 묶음에서 차례로 적용) | - **실패는 실패 종류로 답한다.** 이벤트가 실패하면 `X-Pure-Error`에 종류를 싣는다. `not_found` 404, `exists` 409, -- cgit v1.2.3