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
|
# 027 — 계산의 핵: 상태는 값 하나, 값은 몇 가지로 짓는다
작성일: 2026-10-13. 상태: 설계와 단계. [026](026-app-contract.md)이 얼릴 계약 안에서 계산의 언어를 다시 세운다. 잴 도구는
[140](../research/140-spec-suite.md)의 명세 묶음이다. 1.0 전이라 호환은 챙기지 않는다. 사용자의 말: "진행해" (2026-10-13).
1단계를 만들었다([140](../research/140-spec-suite.md)의 뒷절).
- aup로 쓴 SHA-256이 공식 벡터를 세 실행 길에서 모두 지난다.
- 줄 수는 막대에 못 미쳤다(aup 52 대 파이썬 28). 남은 차이는 형식 지정, 바이트 잇기와 자르기, 값으로서의 리스트다. 이것들을 2단계에 더했다.
- 시간은 C의 176배, 순수 파이썬의 0.43배다.
## 쉬운 말로
- **지금의 꼴.** aup와 Pure는 패턴마다 종류와 명령을 둔다. 커서, 인덱스, 문서, 표 보기, 메시지의 행이 각각 따로다. 그래서 세상의
명세를 담지 못하고(140), 새 패턴마다 형식과 세 실행 길이 함께 자란다.
- **이 설계의 꼴.** 그것을 "값 몇 가지와, 그 값들이 서로를 담는 법"으로 바꾼다.
- 상태는 타입 있는 값 하나다.
- 값은 수(정수, 실수, bool), 바이트(text는 UTF-8인 바이트), 레코드, 갈래, 리스트(키가 있으면 맵)다.
- **계산.** 함수(재귀 포함)와 접기다. 끝남은 문법이 아니라 예산이 보증한다. 이더리움의 가스와 같은 생각이다. 가스가 다하면 그
호출의 상태 변경이 모두 되돌아가는데, 이것이 026의 원자성과 같다.
- **빠름.** 뜻이 아니라 구현에서 온다. 열로 담기, B+트리, 묶음 실행, 같은 답일 때만 차례 바꾸기가 그 수단이다.
- **단계.** 다섯이다.
- 1단계(바이트, 비트, 수의 리스트)로 SHA-256을 쓴다.
- 2단계(중첩, 갈래, 재귀, 글자)로 JSON을 쓴다.
- 3단계에서 중첩된 상태를 저장한다.
- 4단계에서 패턴을 모은다.
- 5단계에서 수의 모델을 정한다.
- **근거.**
- 140의 탐침
- 지금의 구현: 값의 칸 `Slot`, 열로 담은 B+트리 `PureList`, aup의 타입 여덟
- 선행 기술: 아래 "선행" 절
- **다음 결정.** 1단계를 만든다.
## 핵
### 값
| 값 | 뜻 | 지금 | 바뀌는 것 |
|---|---|---|---|
| `bool` | 참, 거짓 | 같음 | — |
| `i64` | 정확한 정수, −2^63 … 2^63−1, 넘치면 `overflow` | 같음 | 비트 연산(1단계). 이름과 리터럴의 기본은 5단계 |
| `number` | 유한한 binary64 | 같음 | 5단계(수의 모델) |
| `bytes` | 바이트의 열, 불변 | 길이와 text 변환뿐 | 자리로 읽기, 자르기, 잇기, 짓기(1단계) |
| `text` | UTF-8로 검사한 바이트 | 같은 표현 | 글자로 읽기는 그 위의 견해(2단계) |
| 레코드 | 이름 있는 필드의 곱 | 필드는 수, text, bool, i64 | 필드에 아무 값(2단계) |
| 갈래 | 태그 붙은 경우의 합 | 없음 | 새로. `match`가 모든 경우를 다룬다(2단계) |
| `list of T [by k]` | 차례 있는 열. `by k`면 k의 차례이고 키마다 하나다 | 레코드만, 상태와 이벤트와 메시지의 행으로 따로 | 아무 T(1단계 수, 2단계 전부). 키 없는 리스트의 키는 자리다 |
| 앵커 | 편집을 따라가는 위치 | 커서와 인덱스(상태의 종류 둘) | 하나로(4단계) |
### 계산
- **그대로 두는 것.** 식, `let`, `if`, `for`(세기와 걷기), `break`.
- **함수.**
- 자신을 부를 수 있다(2단계).
- 인라인은 구현의 선택이다. 작은 fn은 지금처럼 인라인하고, 재귀하는 fn은 부른다.
- 매개변수에 타입을 적지 않는 것은 그대로다. 부르는 타입마다 한 벌을 만든다(단형화).
- **`match`.** 갈래의 모든 경우를 다룬다. 빠진 경우는 컴파일 오류다(2단계).
- **끝을 모르는 반복.** `while`을 둔다(2단계). 세기의 상한은 뜻에서 필요하지 않다. 묶음 실행이 시작할 때의 횟수를 쓰는 것은 구현이
상한을 알아볼 수 있을 때다.
- **접기.** 뜻은 차례대로의 왼쪽 접기다. 구현은 같은 답과 같은 실패를 낼 때만 차례를 바꾼다. 묶음 실행기의 차례 없는 모으기가 이미
이 규칙을 따른다.
- **비트 연산.** 정수를 무한한 2의 보수로 보고 정한다. 파이썬의 정수와 같은 뜻이다.
- `&`, `|`, `^`, `~`: 비트마다의 연산이다.
- `x << n`은 x·2^n이다. i64를 넘으면 `overflow`다.
- `x >> n`은 floor(x / 2^n)이다.
- n은 0부터 63까지다. 밖이면 `limit`이다.
- 어느 기계에서나 같은 뜻이고, C의 정의되지 않은 동작이 없다.
- **끝남과 결정성.**
- 모든 걸음은 실행량을 쓰고, 부름의 깊이는 프레임의 바이트로 메모리 몫에 센다(2c: 프레임의 바이트는 구현과 떨어진 표다). 넘으면
`work`나 `memory`로 끝나고, 이벤트는 통째로 되돌아간다.
- 결정성의 단위는 모듈이다. 같은 모듈과 같은 이벤트열이면 실패의 자리까지 같다.
- 소스는 컴파일러의 판에 묶인다. EVM의 바이트코드와 가스표가 그렇다. 이것이 026의 의심 6(예산의 단위)에 대한 답이다.
### 효과
026 그대로다. 상태에 쓰기, 보내기, 답하기, `require`.
### 상태는 값 하나
- 앱의 상태는 레코드 값 하나다. 선언한 상태들이 그 필드이고, 수명(저장, transient)은 필드의 성질이다.
- 저장은 바뀐 부분만 쓴다. 지금 리스트가 이미지와 로그로 하는 그대로다. 중첩된 리스트도 제 이미지와 로그를 가진다(경로로 이름을
붙인다, 3단계).
- 값은 불변이라 스냅숏은 나누기만 한다. 지금 리스트의 공유 사본이 그 구현이다.
### 뜻과 표현
| 뜻 | 표현(구현이 고른다) |
|---|---|
| 레코드의 리스트 | 열로 담은 B+트리, 값만큼 좁힌 열, HASHED(지금) |
| 수의 리스트 | 필드 하나인 레코드의 리스트(1단계), 나중에 배열 |
| 바이트 짓기(`out = out + byte(x)`의 반복) | 왼쪽이 이벤트 공간의 끝에 있으면 제자리 덧붙이기 |
| 표 보기 | 물을 때 칸 계산 |
| 접기 | 묶음 실행, 차례 없는 모으기(같은 답일 때만) |
| 작은 fn | 인라인 |
## 026의 의심에 대한 답
| 의심 | 답 | 단계 |
|---|---|---|
| 1. 입력의 종류 | 입력은 메시지 하나다. 키, 글자, 틱은 표면과 호스트가 만드는 메시지다 | 4 |
| 2. 보기의 종류 | 보기는 아무 값을 낸다 | 4 |
| 3. 커서와 인덱스 | 앵커 하나로 모은다. 다만 키가 있는 리스트에서 인덱스는 키와 같을 수 있다. 4단계에서 앵커가 text에만 필요한지 본다 | 4 |
| 4. 키별 문서 | 없앤다. 키 있는 리스트가 한다 | 4 |
| 5. 수의 모델 | 정할 것: 정수 리터럴의 기본, `/`와 `//`, 돈을 위한 십진수 | 5 |
| 6. 예산의 단위 | 모듈로 정한다(위) | 이 문서 |
| 7. 고정 한도 | 뜻에는 예산(실행량, 메모리)과 모듈의 크기만 남긴다. 상태 32, 입력 32, 레코드 16, 필드 16, 컬렉션 1, 중첩 8, aup 소스 32 KB는 뺀다 | 4 |
| 8. 교체의 세부 | 수의 모델 뒤에 다시 본다 | 5 |
| 9. 메시지의 행 | 메시지의 필드가 아무 값을 가지면 따로 둘 까닭이 없다 | 2 |
## 단계
### 1단계: 바이트, 비트, 수의 리스트 (SHA-256)
- **Pure.**
- 바이트를 자리로 읽기(`b[i]` → i64, 밖이면 `limit`), 자르기, 잇기, 한 바이트로 짓기
- i64의 비트 연산 여섯
- 이벤트의 지역 칸이 바이트를 담는다
- **aup.**
- `b[i]`, `b[a .. c]`, `b + c`, `byte(n)`
- i64의 `& | ^ ~ << >>`, 16진 리터럴 `0x…`
- `list of i64`와 `list of number`(안에서는 필드 하나인 레코드), 리스트 리터럴 `[ … ]`
- 키 없는 리스트를 자리로 읽고 쓰기 `xs[i]`
- **막대.**
- aup로 쓴 SHA-256이 NIST의 짧은 메시지 벡터와 긴 메시지 벡터를 모두 지난다(Pure의 검사 안에서).
- 줄 수가 같은 일을 하는 파이썬 참조 이하다.
- C 대비 시간은 재고 기록하되 막대는 두지 않는다. 바이트와 비트의 일은 실행기로 돈다. 빠르게 하는 것은 핵이 선 뒤다.
- 실행기만의 길에서 다섯 일(041)의 명령 수가 ±0.1%, 은행 질의의 명령 수가 ±1% 안이다. 새 명령은 기존 길을 바꾸지 않는다.
- **검증.** 새 명령마다 경계(밖의 자리, 음수 시프트, 넘침)를 단위 시험으로 본다. 무작위 입력의 다이제스트는 파이썬 `hashlib`와 같아야
한다.
### 2단계: 중첩, 갈래, 재귀, 글자 (JSON)
크기 때문에 셋으로 나눈다(2026-10-13). 각각 재는 일이 있다.
- **2a(만듦):** 형식 지정, 바이트 잇기와 자르기, 바이트와 text를 담는 `let`, `while`, `&&`와 `||`의 단락 평가. JSON을 받거나
거절하는 검사기로 잰다. 결과는 [140](../research/140-spec-suite.md)의 2a절이다.
- JSONTestSuite에서 틀린 것이 없다.
- 줄 수는 파이썬 이하다(80 대 90).
- 시간은 C의 116배, 파이썬의 1.2배다.
- 쓰다가 드러난 뜻의 틈 하나를 메웠다. `&&`가 양쪽을 다 계산해서 `i < n && d[i]`가 실패했다. 이제 단락 평가이고, 오른쪽이
실패할 수 없을 때는 지금처럼 AND 하나를 낸다. 그래서 TPC-C를 포함한 기존 모듈 35개가 바이트까지 같다.
- **2b(만듦):** 레코드 안의 레코드와 리스트, 갈래와 `match`, 값으로서의 리스트, 수 읽기. 결과는 140의 2b절이다.
- **꼴.** 레코드와 갈래는 aup가 경로 이름의 열로 펼쳐, 런타임에는 레코드 값이 없다. 리스트만 런타임의 묶음 값이다.
- **칸.** 칸에는 이벤트가 만든 리스트의 번호(i64)를 담는다. 리스트의 내부, 묶음 실행기, JIT는 그대로다. JIT의 덧붙이기 빠른 길만
리스트 필드를 비킨다.
- **값의 뜻.** 나눠 가진 리스트는 바뀌기 전에 복사되므로 값의 뜻이 지켜진다. 읽기는 레지스터에 묶어 기존의 걷기, 접기, 자리
읽기를 그대로 쓴다.
- **JSON 트리.** JSONTestSuite에서 틀린 것이 없고, 무작위 문서 300개의 다이제스트가 파이썬과 같다.
- **막대.** 줄 수는 못 미쳤다(167 대 133: 타입 선언). 시간은 C(cJSON)의 40배이고, 순수 파이썬보다도 1.3배 느리다.
- **남은 것.** 메시지 필드의 아무 값(JSON 경계에서 바로 트리로)과 저장되는 리스트의 리스트는 3·4단계로 남긴다.
- **2c(만듦):** 재귀(부르기와 돌아가기, 함수마다의 검증), 값으로서의 레코드, 글자로 읽기. 결과는 140의 2c절이다.
- **꼴.** 함수는 CALL이 도는 블록이다. 매개변수를 스택에 두고 시작해 RETURN이 결과를 돌려준다. 부름마다 제 스택, 지역 칸, 그리고
함수의 코드가 이름을 대는 이벤트의 리스트, 레지스터, 이름으로 담은 값을 가진다. 부른 쪽의 것은 돌아올 때 그대로다.
- **끝남.** 부름의 깊이는 프레임마다의 바이트를 이벤트의 몫에 세어 묶는다(넘으면 `memory`). 그 바이트는 구현의 크기가 아니라
표다(`module.h`의 `PURE_CALL_*`, 가스표처럼). 그래서 같은 모듈은 어느 기계, 어느 빌드에서나 같은 깊이에서 실패한다.
- **값으로서의 레코드.** aup는 레코드와 갈래의 값을 필드들로 스택에 놓고, 이름 하나(필드마다 let)에 담고, 함수에 주고받는다.
항목은 함수에 값으로 넘어간다. 함수는 제 리스트를 가지므로 항목의 자리는 뜻이 없다.
- **글자.** `for ch in t`가 text를 코드 포인트로 돈다(CHAR_AT). `char(n)`이 코드 포인트를 text로 짓는다(CHAR).
- **JSON 트리.** 재귀 하강 파서와 깊이 우선 걷기로 다시 썼다. JSONTestSuite와 무작위 문서 300개가 파이썬과 같다. 10만 겹은
프레임이 몫을 다 써서 거절되고, 3만 겹의 올바른 배열은 받는다(파이썬의 `json`은 그 깊이를 못 읽는다).
- **막대.** 줄 수는 못 미쳤다(142 대 105). 시간은 cJSON의 46배, 같은 설계의 순수 파이썬의 1.6배다.
- **막은 구멍.** 반복 아래에 값을 둔 모듈이 검증을 지나 실행기를 죽였다(반복의 블록이 그 실행의 스택 바닥에서 시작해 64칸을 넘었다).
이제 LOOP와 EACH는 제 피연산자만 있는 스택에서 연다. aup가 만든 모듈에는 그런 꼴이 없었다.
- **남은 것.** 작은 블록 fn의 인라인(JSON 트리의 부름 대부분이 `skip` 같은 작은 fn이다), 보기에서의 부름, 레코드 값의 `if`
합류, 저장 상태의 중첩(3단계).
- 레코드의 필드와 리스트의 원소가 아무 값을 가진다.
- 갈래와 `match`를 둔다.
- 재귀하는 fn과 `while`을 둔다. Pure에는 부르기와 돌아가기, 함수마다의 검증이 생긴다.
- `for c in t`로 글자(코드 포인트)를 읽는다.
- 메시지의 필드가 아무 값을 가진다. JSON의 배열과 객체가 곧 리스트와 레코드다.
- 1단계의 SHA-256이 길었던 까닭을 여기서 푼다.
- 형식 지정(`{x:08x}`, `{price:.2}`)
- 바이트 값을 잇고 자르기(`b + c`, `b[a .. c]`)와 바이트를 담는 `let`
- 값으로서의 리스트: 블록 안에서 짓고 다시 짓는다
- fn을 펼칠 때 리터럴 인자를 접기: 구현의 일이다. 140이 블록당 명령의 39%를 fn 호출의 몫으로 쟀다
- 먼저 이벤트와 보기의 값(메모리 안)에서 한다. 상태는 3단계다.
- **막대.**
- JSON 파서가 JSONTestSuite의 `y_`를 모두 받고 `n_`을 모두 거절한다. `i_`는 정한 쪽을 적는다.
- 줄 수가 파이썬 참조 이하다.
- 시간을 기록한다.
- TPC-C와 은행 질의가 그대로다(026의 중단 기준).
### 3단계: 중첩된 상태의 저장
- 중첩된 리스트의 이미지와 로그를 경로로 이름 붙인다.
- 스냅숏의 나누기와 교체의 이름 잇기가 깊이에서도 그대로 성립해야 한다.
- **막대.** 메모리 앱과 수시로 재시작하는 저장 앱이 매 단계 비트까지 같다(지금의 대조를 중첩으로 넓힌다). 커밋의 모든 단계에서
죽여도 전부 아니면 없음이다.
**만듦(2026-10-18).** 결과는 140의 3단계절이다.
- **고른 표현.** 계획(중첩 리스트마다 경로로 이름 붙인 이미지와 로그)과 다르게 했다.
- 이벤트를 넘어 사는 리스트(저장, transient)의 칸이 리스트 값을 그 항목들의 저장 형태로 담는다(바이트처럼 담고, 이미지 머리는 종류 5로 적는다).
- 읽으면 이벤트의 리스트 하나로 풀리고(ITEM_GET), 쓰면 그것에서 지어진다.
- **그래서 따라오는 것.** 리스트 모듈은 그 칸을 text의 바이트처럼 다룬다. 그래서 이미 있는 기계가 깊이에서도 그대로다.
- 이미지, 로그, 되돌리기, 읽는 쪽과의 나눔이 그렇다.
- 커밋의 원자성과 죽음의 회복도 그렇다. 새 저장소 형식과 새 복구 길이 없다.
- **이름 잇기.** 이미지의 머리가 레코드 그래프를 적는다(레코드가 리스트를 거쳐 자신을 담을 수 있어서 트리가 아니라 번호로 가리키는
표다). 시작과 교체는 그래프를 따라 이름으로 옮긴다.
- **비용.** 중첩 리스트를 바꿀 때마다 그 값 전체를 풀고 짓고 쓴다. 바뀐 것이 아니라 그 값의 크기에 비례한다.
- 큰 중첩 리스트를 덧붙여 짓는 것은 제곱이다.
- 구조를 나누는 표현(노드를 나누는 트리, 경로로 이름 붙인 로그)은 뜻을 바꾸지 않고 나중에 고를 수 있는 구현이다.
- **남은 것.** 메시지의 행이 리스트를 담는 것(JSON 경계에서 바로 트리로)은 4단계다. 접기 안에서 리스트 필드의 `.count`를 읽는 것도
아직 안 된다(레지스터를 묶어야 해서).
### 4단계: 패턴 모으기
- 앵커, 메시지 하나인 입력, 아무 값인 보기를 둔다.
- 키별 문서와 고정 한도를 없애고, 메시지의 행을 중첩으로 옮긴다.
- **막대.**
- Pure, aup, Vega의 모든 검사를 옮긴 뒤 초록이다.
- 명령과 상태 종류의 수, 코어의 줄 수가 줄었다(세어 적는다).
- TPC-C와 은행 질의가 그대로다.
**계획(2026-10-18).** 여섯 걸음으로 나눈다.
- 걸음마다 지킬 것: Pure, aup, Vega의 검사가 초록이다. 기존 일의 명령 수(041의 다섯 일, 은행 질의)가 막대(1%) 안이다. 걸음마다
커밋한다.
- 시작의 수(3단계 뒤, 9f17dd6):
- 명령 106개
- 상태 종류 7개: 수, text, 커서, 리스트, 인덱스, bool, i64
- 입력 종류 6개: 키, 메시지, text, 타이머, 출력, 인자. 여기에 프레임 이벤트와 가리킴(`pure_app_point`)이 따로 있다.
- 보기 종류 5개: 수, text, 표, bool, i64
- 메시지 필드의 타입 7개, 레코드 필드의 타입 5개
- 코어(`core/`의 시험이 아닌 파일) 22,398줄, serve 5,086줄
- **4a. 메시지의 리스트가 중첩을 받는다.**
- 메시지의 행은 이미 이벤트의 리스트로 들어온다(let의 리스트와 같은 것). 그 레코드가 리스트를 담게 한다.
- 페이로드는 3단계의 저장 형태 그대로다. JSON 경계는 객체 안의 배열을 그 형태로 짓는다.
- 3단계가 남긴 것이다. JSON 경계에서 바로 트리로 들어온다.
- **만듦(2026-10-18).**
- Pure: 메시지가 도착할 때 리스트를 담은 행을 3단계의 해독기로 읽는다. 행마다의 리스트는 이벤트의 리스트가 된다.
- serve: JSON 경계가 객체 안의 배열을 그 레코드의 객체들로 짓는다(리스트 64겹까지).
- aup: 메시지의 행이 리스트를 담는 것을 받는다. 메시지의 행 안의 리스트에 덧붙이는 것은 거절한다(행은 읽기만 한다).
- 찾아 막은 구멍: 3단계의 해독기가 4바이트가 남지 않은 길이를 넘겨 읽었다(`size - at - 4`가 넘쳐 돌았다). 저장한 것만 읽던
3단계에서는 드러나지 않았고, 밖에서 오는 메시지가 드러냈다.
- 시험:
- 단위 시험 `nested_rows`: 세 번의 이벤트, 온전하지 않은 행 넷이 아무것도 바꾸지 않는다.
- Pure의 검사 `server_trees`: 무작위 숲 300개를 JSON으로 보내 같은 차례로 도는 파이썬 모델과 견준다. 거절 셋(리스트
필드가 배열이 아님, 없는 필드, 65겹)과 재시작도 본다.
- 명령 수(b30 대 b29): 다섯 일이 묶음 BFS +0.12%(늘 오가는 범위) 말고는 ±0.001% 안이다. 은행 질의(s29 대 s30, 각 두
번)는 −0.00%에서 +0.23%다.
- **4b. 키별 문서를 없앤다(026의 의심 4).**
- 레코드 필드가 바이트를 받는다. 편집기는 `list of Doc by key`와 앱이 가진 리비전으로 쓴다.
- Pure에서 컬렉션, DOC·DOC_SIZE·REPLACE, 키 있는 메시지와 기준 리비전, PureData의 문서 콜백, 문서별 묶음 저장이 빠진다. serve의
문서 모드(키마다 앱, ETag와 If-Match)도 빠진다.
- 옛 편집기(editor.pure)와의 대조는 같은 뜻의 파이썬 모델과의 대조로 바꾼다.
- 두 커밋으로 나눈다.
- 4b-1: 레코드 필드의 바이트(Pure, aup). 새 편집기를 키 있는 리스트로 쓰고 모델과 견준다. 옛 문서의 기계는 아직 둔다.
- 4b-2: 키별 문서를 지운다. 문서에 기대던 검사는 같은 것을 보는 다른 앱으로 옮긴다.
- 파이프라이닝과 묶음 저장(kill -9 포함)은 새 편집기의 입력과 보기로 본다.
- 빌려 읽기와 그 예산은 text 상태의 보기로 본다. text 상태도 저장된 조각을 빌려준다.
- HTTP 벤치마크의 문서 묶음은 그 계약(axum, drogon과 같은 `/objects`)이 사라지므로 걷는다. 앱 묶음은 남는다.
- 잃는 것: 키마다 따로 도는 앱(문서만 가진 모듈이 스레드마다 앱 하나)과 큰 문서의 조각 편집. 키 있는 리스트를 키로 나눠
돌리는 것과 큰 바이트 값의 조각 표현은 뜻을 바꾸지 않고 나중에 고를 수 있는 구현이다.
- **4b-1 만듦(2026-10-18).**
- 레코드 필드의 타입 5가 바이트다. text처럼 담고 UTF-8을 묻지 않는다. 함수 서명의 바이트(전의 PURE_SIGNATURE_BYTES)도 이
타입이 되었다. 바이트 필드는 키가 되지 않는다. 교체하면 text 필드는 바이트 필드로 값을 지닌 채 옮겨진다.
- aup: `body: bytes`(기본값은 문자열의 바이트), `x.body.bytes`. 새 편집기 `examples/documents.aup`는 키 있는 리스트와 앱의
리비전으로 쓴다.
- 시험: 단위 시험 `bytes_fields`(UTF-8이 아닌 바이트, text에서 바이트로의 교체, 거절 셋), aup의 `documents_model`(무작위
보기와 편집 160걸음, 씨앗 넷, 메모리와 여섯 번 다시 시작하는 저장소에서 모델과 1,280번 같음), `server_trees`의 바이트 필드.
- 명령 수: 덧붙이기가 필드마다 타입을 두 번 묻던 것을, 로드가 만든 레코드마다의 비트 마스크(`record_held`) 하나로 바꿨다.
그래서 b32가 b30보다 적다(묶음 없는 BFS −1.05%, join −0.25%, 묶음 BFS −0.36%, 나머지 ±0.04% 안). 은행 질의(s30 대 s32)는
−0.01%에서 +0.30%다.
- **4b-2 만듦(2026-10-18).** 모듈 형식 v7.
- Pure: 컬렉션 표, DOC·DOC_SIZE·REPLACE(명령 106개 → 103개), 키 있는 메시지와 기준 리비전, PureData의 문서 콜백 여섯,
문서별 묶음 저장(키마다 한 번, 256개까지)이 빠졌다. `pure_app_message`는 입력과 페이로드만 받는다. 머리는 38바이트다.
`pure-edit-module`도 빠졌다.
- serve: `/objects` 경로, ETag와 `If-Match`·`If-None-Match`, 412, 문서만 가진 모듈의 스레드마다 앱이 빠졌다. 상태도 출력도
없는 모듈만 스레드마다 돈다.
- aup: `bytes by key`, `message by key`, `이름[key]`, `.replace(..)`가 빠졌고 옛 편집기 예제 둘도 빠졌다.
- 찾아 막은 구멍: 어느 블록에도 없는 명령(돌지 않는 코드)은 검증되지 않는데, 로드의 뒤 패스들이 모든 명령의 인자를 읽었다.
손상한 모듈의 시험이 머리가 바뀌어 다른 손상을 고르자 범위 밖을 읽고 죽었다. 이제 로드가 그런 명령을 CONST 0으로 지운다.
- 옮긴 검사: 옛 문서 서버와의 대조(`server_parity`)는 키 있는 리스트의 문서 앱(`DOCS_SOURCE`)과 파이썬 모델의 대조
(`server_documents`, 연결 스레드 하나와 셋, 재시작)가 되었다. 파이프라이닝과 묶음 저장(kill -9)은 그 앱의 입력과 보기로, 빌려
읽기의 예산은 text 보기로 본다(문서의 빌려 읽기는 `server_lent_views`가 이미 text 보기로 본다). 시계 검사는 그 앱으로 편집한다.
aup의 옛 편집기 대조(`editor_parity`)는 `documents_model`이 대신한다.
- HTTP 벤치마크의 문서 묶음은 옛 문서 서버와 기준선만 돈다.
- 잃은 것: 저장소의 그룹 커밋(여러 기록자가 한 저장소에 커밋을 모으는 것)을 pure-serve가 더 쓰지 않는다. 앱마다 소유자 하나가
저장하기 때문이다. 그 길은 옛 문서 서버와 저장소의 시험에 남는다.
- 줄 수: 코어 22,398 → 22,204(4a와 4b-1의 더함을 넣고), serve.c 5,086 → 4,978, aup의 compiler/ 5,923 → 5,829(compile.c는 4b-1 뒤
5,689 → 5,587).
- 명령 수(b33 대 b32): 묶음은 BFS −0.33%, 나머지 ±0.002% 안이다. 실행기만의 길은 BFS +0.63%, knapsack +0.52%, join +0.38%다
(`step`에서 경우 셋이 빠져 코드 생성이 움직였다). 은행 질의(s32는 v6 모듈, s33은 v7 모듈로, 각 두 번)는 field +0.68%,
found +0.44%, large +0.42%, 나머지 +0.13% 안이다. 모두 막대(1%) 안이지만 4단계 끝에 다시 센다.
- **4c. 커서와 인덱스를 앵커 하나로(의심 3).**
- 상태 종류 "앵커"가 text나 리스트 하나를 가리킨다.
- 뜻은 그대로다: 편집을 따라가고, 글자의 경계에 서고, 지운 항목에서는 이웃으로 간다.
- 같은 일을 하는 커서와 인덱스의 명령은 하나로 합친다.
- **설계(2026-10-18).**
- 둘은 같은 것이다. 열(text의 글자, 리스트의 항목) 안의 틈 하나이고, 편집을 따라 움직인다.
- 커서는 틈에 선다. 그 자리에 넣으면 뒤로 가고(마크는 남는다), 지운 범위 안이면 그 처음으로 간다.
- 인덱스는 항목 앞의 틈에 서서 그 항목을 가리킨다. 그 자리에 넣으면 뒤로 간다(항목을 따라간다). 지운 항목에서는 다음
항목을 가리킨다. 끝의 틈에는 서지 않는다(빈 리스트가 아니면 마지막 항목으로).
- 형식의 앵커 명령은 일곱이다. 모두 인자에 대상(text나 리스트 상태)을 적는다.
- LOAD, STORE: 앵커 상태
- START, END: text의 처음과 끝, 리스트의 첫 항목과 마지막 항목
- MOVE(단위): 글자, 줄, 항목으로 n만큼. 끝에서 멈춘다.
- COUNT(단위): 앵커 앞의 단위 수. 줄은 0부터 센다(aup의 `at.line`이 1을 더한다).
- AT(단위): 처음부터 n 단위 뒤의 앵커. 없으면 `not_found`
- 지금의 열셋(CUR_* 여섯, INDEX_* 일곱)이 일곱이 되고, 상태 종류가 일곱에서 여섯이 된다(bool과 i64의 번호가 당겨진다).
- 구현은 대상의 종류로 나뉜다. 로드가 형식의 명령을 대상에 맞는 내부 명령(지금의 CUR_*와 INDEX_*의 걸음)으로 바꿔
쓰고, 검증은 그것에 대해 지금처럼 한다. 그래서 실행기, 묶음 실행기, JIT의 길은 그대로다. 026의 원칙(뜻은 일반적으로,
특수화는 구현 아래로)이다.
- **만듦(2026-10-18).**
- 형식: 상태 종류 2가 앵커다(bool 4, i64 5). 명령은 103개에서 97개가 되었다. 앵커 명령 일곱의 MOVE·COUNT·AT는 인자에
대상과 단위를 적는다(`PURE_ANCHOR_UNIT`).
- 로드: 앵커 상태는 대상이 text면 커서, 리스트면 인덱스로 둔다(리스트 안의 마크는 거절). 앵커 명령은 런타임의 명령
(`PURE_OP_CUR_*`, `PURE_OP_INDEX_*`: module-run.h, 모듈 명령 다음 번호)으로 바뀐다. 모듈이 그 번호를 담으면 거절한다.
검증기는 MOVE·COUNT·AT가 적은 대상과 피연산자의 주인이 같은지 본다. 3단계의 내부 명령(`PURE_OP_KEPT_*`)도 그 표로 옮겼다.
- 새 것: text의 AT(처음부터 n 글자나 n 줄 뒤의 커서, 끝을 넘으면 `not_found`). COUNT의 줄은 0부터 센다(aup의 `at.line`이
1을 더한다).
- aup는 컴파일러 안의 커서와 인덱스를 그대로 두고, 명령 번호와 상태 번호를 형식대로 낸다.
- 시험: 단위 시험 `anchors`(text의 AT와 COUNT, 리스트의 AT, 거절 넷). 시험의 모듈 짓기는 런타임의 명령 이름을 형식의 앵커
명령으로 옮겨 쓴다.
- 명령 수(b34 대 b33): 묶음 matmul −1.36%, knapsack −0.08%, BFS +0.03%, 실행기만은 ±0.04% 안이다. 은행 질의(s33은 4b-2의 aup
모듈, s34는 4c의 것, 각 여섯 번의 중앙값)는 in_credit +0.28%, per_branch +0.23%, ranks +0.10%, 나머지 0.00%다. 이 셋은 같은
빌드에서도 1–2% 오간다.
- **4d. 입력은 메시지 하나(의심 1).**
- 키, text, 타이머, 프레임, 가리킴은 표면과 호스트가 만드는 메시지다.
- 모듈의 입력은 메시지(필드들)와 표면이 읽는 묶음(키 이름, text, 주기, 프레임)이다. 묶음을 해석하는 것은 표면이고, Pure는 메시지만
받는다.
- `dt`는 프레임 메시지의 필드가 된다. 키의 누름과 뗌은 각각의 메시지다.
- **설계(2026-10-18).**
- 입력 항목은 메시지, 출력, 인자의 셋이다. 메시지는 묶음(binding)을 하나 가질 수 있다. 묶음은 표면과 호스트에게 무엇이
그 메시지를 만드는지 알려 준다(묶음의 필드는 정해져 있다):
- 키 누름 `이름`, 키 뗌 `이름`: 그때마다 메시지 하나, 필드 없음(누름은 이름 붙은 동작이다: 접근성이 그것을 읽는다)
- text: 들어온 text마다, 필드 `text: text`
- 주기 `ms`: 틱마다, 필드 없음(시각은 늘 그렇듯 `now`)
- 프레임: 화면의 한 장마다, 필드 `dt: number`
- 가리킴 `보기`: 표의 행을 가리킬 때, 필드 `position: u32`
- Pure의 코어는 묶음을 해석하지 않는다. 묶음의 필드가 맞는지만 검증한다. 핸들러의 이벤트는 입력의 번호다(누름과 뗌의 두
이벤트, 프레임의 이벤트 0이 없다).
- 빠지는 것: 입력 종류 키·text·타이머, 프레임 이벤트(이벤트 0), `pure_app_input`·`tick`·`text`·`frame`·`point`, 명령 DT와
TEXT_INPUT(필드 읽기가 한다). 호스트는 묶음대로 메시지를 짓는다.
- 저장: 프레임 메시지도 다른 메시지처럼 저장한다. 초당 60번 저장하지 않으려면 호스트가 묶음 저장을 쓴다(Vega는 프레임이 아닌
입력 뒤에 저장한다). Pure는 어느 메시지가 프레임인지 모른다.
- aup: 겉은 그대로다. `input up = key ArrowUp`의 `on up.down { … }`은 누름 메시지의 핸들러로, `on up.up { … }`은 숨은 뗌 입력의
핸들러로,
`on frame`과 `dt`는 숨은 프레임 입력과 그 필드로, `input typed = text`의 `typed`는 그 필드로 낸다. `view X_focus =
at.position`은 숨은 가리킴 입력과 `at`을 옮기는 핸들러를 낸다(그래서 앱이 바뀌지 않는다).
- Vega: 키, text, 타이머, 프레임, 가리킴을 묶음을 보고 메시지로 보낸다. serve: 키와 text의 POST를 메시지로, 타이머를 주기 묶음으로.
- **4e. 보기는 아무 값(의심 2).**
- 보기의 타입은 레코드 필드의 타입이다. 표는 레코드의 리스트를 내는 보기이고, 열은 그 레코드의 필드다.
- 게으른 칸 계산과 한 번 훑는 집계는 그 구현으로 남는다. 항목이 리스트를 담을 수 있고, 보기의 본문이 지은 리스트도 낼 수 있다.
- **4f. 고정 한도를 뺀다(의심 7).**
- 상태, 입력, 레코드, 필드, 중첩의 수를 모듈에 맞춰 잡는다. 비트 하나씩 쓰던 집합은 길이를 가진다.
- aup 소스의 32 KB도 뺀다.
- 앞의 걸음들이 표를 바꾸므로 마지막에 한다.
- **차례의 까닭.**
- 4a는 작고, 3단계의 끝을 맺는다.
- 4b는 가장 많이 지운다. 키 있는 메시지와 기준 리비전이 사라지면 4d가 단순해진다.
- 4c는 가리킴(4d)이 무엇을 옮기는지 정한다.
- 4e는 표면(Vega)이 읽는 쪽을 바꾸므로, 입력 쪽(4d)과 따로 잰다.
### 5단계: 수의 모델
- 정수 리터럴의 기본, `/`와 `//`, 돈의 십진수, 묶음 실행기의 2^53 검사를 정한다.
- **막대.** TPC-C가 SQLite와 행마다 같은 상태를 남기는 것이 그대로다.
**순서의 까닭.**
- 1과 2가 묶음의 명세 대부분을 연다. 가장 값이 크다.
- 3은 중첩을 상태까지 넓힌다.
- 4는 쌓인 것을 줄인다. 2 뒤에 해야 모을 곳(중첩, 갈래)이 생긴다.
- 5는 모든 앱의 수를 건드리므로 마지막이다.
## 선행
- **EVM의 가스.** 끝없는 반복과 재귀를 허락하고, 끝남과 결정성을 가스로 얻는다. 가스가 다하면 그 호출의 상태 변경이 되돌아간다. 가스는
명령마다 명세의 표로 정한다. 모든 노드가 같은 가스를 센다. Pure의 예산, 원자성, "모듈이 단위"와 같은 꼴이다.
- **파이썬의 정수 비트 연산.** 무한한 2의 보수 위의 뜻이다. Pure는 i64 안에서 같은 뜻을 쓰고, 넘으면 실패한다.
- **Elm.** 모델, 업데이트, 뷰와 레코드, 커스텀 타입(갈래)이다. aup 앱의 모양과 같다. 영속, 트랜잭션, 전달은 없다.
- **영속 자료 구조**(Clojure의 벡터와 맵). 구조를 나눠 스냅숏을 싸게 한다. Pure의 공유 리스트 사본이 그 한 예다(B+트리).
## 의심 (이 설계 안의)
- **바이트와 text를 따로 둘 것인가?** 표현은 하나이고 타입만 둘이다. 불변식(UTF-8)이 보기와 경계에 필요해서 타입은 남긴다.
- **앵커가 필요한가?** 항목에 키가 있으면 인덱스는 키다. text의 커서는 편집에 따라 옮겨야 하므로 남는다. 4단계에서 정한다.
- **`number`(binary64)는 앱 런타임에 필요한가?** 물리와 그래픽에는 필요하다. 돈에는 맞지 않는다(5단계).
- **세는 `for`가 필요한가?** 뜻으로는 `while`과 예산이면 된다. 다만 세기는 읽기 쉽고, 상한을 구현에 알려 준다. 남긴다.
## 1단계의 착수 기준
- **바꿀 결정.** 바이트, 비트, 수의 리스트로 SHA-256을 얇게 쓸 수 있으면 받아들이고, 2단계로 간다.
- **남은 질문.**
- 새 명령이 실행기, 묶음 실행기, JIT의 기존 길을 바꾸지 않는가?
- 한 블록에 드는 명령 수와 시간은 얼마인가?
- **최소 확인.** aup로 쓴 SHA-256, NIST 벡터, 파이썬 참조의 줄 수, C 대비 시간, 다섯 일과 은행 질의의 명령 수.
- **중단 기준.** 기존 일의 명령 수가 막대를 넘으면 멈추고, 새 명령이 기존 길을 건드린 곳을 찾는다. SHA-256이 파이썬보다 길면, 무엇이
길게 했는지 적고 그것을 1단계에서 고칠지 정한다.
|