aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design/028-bytes-state.md
blob: 7e760fa594c2810d6c09873ec32b278f98dd08a1 (plain)
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
# 028 — 바이트 하나인 상태

작성일: 2026-10-19. 상태: 만들었다(아래 "만든 것"). [140](../research/140-spec-suite.md)의 "027 뒤의 결정점"에서 써서 닿은 명세가 둘(inflate,
HTTP/1.1)이 된 틈이다. 쌓임을 막는 규칙(둘 이상의 명세)을 넘으므로 핵에 들인다. 같은 결정점의 다른 하나(바이트의 `==`)는
TEXT_COMPARE의 인자 비트로 이미 만들었다(008).

## 쉬운 말로

- **지금.** 상태의 종류는 수, text, 앵커, 리스트, bool, i64다. 바이트 하나를 상태로 두려면 항목 하나의 리스트로 우회한다.
  inflate는 풀어낸 바이트를, HTTP/1.1은 패킷 사이에 남은 바이트를 그렇게 둔다. 이벤트마다 항목을 지우고 다시 붙인다.
- **이 설계.** 상태의 타입에 bytes를 더한다. 표현은 text와 하나(draft)이고 타입만 다르다(027의 의심: "표현은 하나이고 타입만
  둘이다"). 새 명령은 없다. TEXT_LOAD와 TEXT_SET이 상태의 타입대로 바이트를 내고 받는다.
- **옮기기.** 이름이 같은 text와 bytes는 정확할 때만 서로 옮긴다(5단계의 규칙): text는 언제나 bytes가 되고, bytes는 UTF-8일
  때만 text가 된다.

## 꼴

- **형식.** 상태 타입 6이 bytes다. 8바이트 값은 text처럼 u32 초기 상수다. 상수는 UTF-8을 묻는 표에 있으므로 초기값은 UTF-8의
  바이트다(보통 빈 것). 판은 v7 그대로다. v6에 i64를 더할 때처럼 1.0 전이라 판을 올리지 않는다.
- **런타임.** text 상태와 같은 draft(`app->texts`)에 담고, 모듈의 `state_bytes[i]`가 그 타입을 적는다. 상태의 종류(PureStateType)는
  text 그대로다. text를 다루는 길(교체, 저장, 메모리 셈, 스냅숏)이 모두 그대로 쓰인다.
- **검증.** bytes 상태의 TEXT_LOAD는 바이트를 놓고, TEXT_SET은 바이트를 받는다. 앵커는 bytes 상태를 가리키지 못한다. 그래서 커서를
  받는 TEXT_INSERT, TEXT_REMOVE, TEXT_LINE도 쓰이지 않는다.
- **저장.** 파일은 text와 같은 `text-이름` 하나다. bytes 상태를 열 때는 UTF-8을 묻지 않는다. 그 상태가 bytes라는 것은 `values`
  기록(`PVS1`)에 적는다: 수·bool·i64·앵커의 항목 옆에, bytes 상태마다 종류 7(저장의 표시)과 이름의 항목(값 8바이트는 0)이다.
  `values`는 저장할 때마다 새로 짠 것과 견줘 다르면 다시 쓰이므로, bytes 상태를 처음 저장하는 커밋이 그 표시도 함께 쓴다.
- **손상과 옮김을 가르기.** text 상태의 파일이 UTF-8이 아닐 때, `values`가 그 이름을 bytes로 적고 있으면 타입이 바뀐 것이다(옮김의
  규칙으로 초기값). 적고 있지 않으면 손상이다(시작을 `store_error`로 멈춘다, 조용히 버리지 않는다).
- **교체와 시작.** 실행 중의 교체는 앞 모듈의 타입을 알므로 기록 없이 옮긴다(text → bytes는 그대로, UTF-8인 bytes → text는
  그대로, 아니면 초기값). 저장소에서 시작할 때는 위의 기록으로 가른다. 파일이 하나라 A(text) → B(bytes) → A를 오가도 낡은 값이
  되살아나지 않는다: A는 B가 쓴 바이트를 같은 규칙으로 읽는다.
  - 레코드 필드는 지금 bytes → text가 기본값이다(008). 메시지 필드는 UTF-8이면 옮긴다. 셋을 "정확할 때"로 하나 맞추는 것은
    따로 한다.
- **보기와 경계.** bytes 상태를 읽는 보기는 바이트 보기다(027의 4e). HTTP의 GET은 `application/octet-stream`이다. pure-run은
  16진 문자열로 보여 준다.
- **메모리.** text처럼 그 바이트를 상태의 예산에 센다.
- **Vega.** text 상태를 직접 읽지 않고 보기를 읽으므로 바뀌지 않는다.

## aup

- `state data: bytes`는 빈 바이트로 시작한다. `state data: bytes = "..."`는 그 문자열의 UTF-8로 시작한다.
- 이름은 바이트 값으로 읽힌다(`data.bytes`, `data[i]`, `data[a .. b]`, 비교, 잇기). `data = …`가 바이트를 쓴다. 문자열은 바이트를
  만나므로 `data = "x"`도 된다.
- `cursor in data`, `mark in data`는 거절한다.

## 검증

- Pure 단위 시험: bytes 상태의 읽기와 쓰기(0x00, 0xff), 거절(앵커, text를 받는 TEXT_SET, bytes를 받는 text의 TEXT_SET), 저장소에서
  다시 시작(UTF-8이 아닌 바이트가 남는다), 교체(text → bytes, UTF-8인 bytes → text, 아닌 것은 초기값), 저장소에서 타입이 바뀐 시작.
  text → bytes → text를 두 번의 다시 시작으로(사이에 UTF-8이 아닌 저장), 그리고 실행 중의 교체로. 기록이 없는데 UTF-8이 아닌 text
  파일은 여전히 `store_error`다.
- 모듈의 모양을 견주는 곳(교체의 `was == now`, 질의 모듈과 교체를 받아들이는 serve의 길)이 bytes의 표시도 견주는지 본다.
- aup 검사: 진단(커서와 마크), 저장소에서 여러 번 다시 시작하는 앱.
- 명령 수: 041의 다섯 일, 은행 질의. 실행기의 걸음을 건드리지 않아야 한다(TEXT_LOAD와 TEXT_SET은 같은 걸음이다).
- 140의 우회 걷어내기: inflate의 `inflated`, HTTP의 `pending`. 줄 수와 시간을 다시 잰다.

## 대안과 왜 아닌가

- **새 런타임 종류(PURE_STATE_BYTES).** text를 다루는 곳(교체, 저장, 스냅숏, 메모리 셈)마다 갈래가 는다. 표현이 같으므로 표시
  하나가 압축이다.
- **저장소의 이름을 둘로(`bytes-이름`).** 손상과 옮김을 이름으로 가를 수 있지만, 타입이 바뀔 때 다른 이름의 파일이 남는다.
  저장소에는 draft를 지우는 길이 없으므로, A(text) → B(bytes) → A에서 A가 낡은 `text-이름`을 되살린다. 그래서 파일은 하나로 두고
  타입을 `values`에 적는다.
- **바이트를 담는 리스트 항목(지금의 우회).** 뜻은 같지만 앱이 "항목 하나"라는 꼴을 지어야 한다. 그 꼴이 명세의 개념(바이트)이 아니다.

## 따로 남긴 것

- **리스트 값의 나눔.** let의 리스트를 값으로 넘기면 Pure의 LIST_VALUE가 항목을 하나씩 복사한다. 복사 없이 나누려면 이벤트
  도중의 리스트가 쓰기 전 복사(copy-on-write)를 해야 한다. 지금의 `pure_list_share`는 이벤트 사이의, 다른 스레드의 읽는 쪽을 위한
  것이다. 명세가 그 비용에 닿으면 정한다.
- **한 번에 비우기.** 블록 안의 let 리스트와 `xs = [...]`는 항목을 하나씩 지운다. 명령 하나로 비울지는 그 비용이 보이면 정한다.

## 착수 기준

- **바꿀 결정.** bytes를 상태의 타입으로 들인다.
- **최소 확인.** 위 검증 모두.
- **중단 기준.** 실행기만의 길이나 묶음 실행기의 명령 수가 1%를 넘게 바뀌면 멈추고 어디를 건드렸는지 찾는다.

## 만든 것 (2026-10-19)

- **Pure.** 상태 타입 6을 text 상태로 읽고 `state_flags`에 `PURE_TEXT_BYTES`를 둔다. 표시가 상태의 모양에 들어가므로, 질의 모듈과
  교체를 받아들이는 serve의 견주기(`same_states`)가 그것까지 견준다. 검증: bytes 상태의 TEXT_LOAD는 바이트를 놓고 TEXT_SET은
  바이트를 받는다. 커서의 명령(CUR_START, CUR_END, TEXT_INSERT, TEXT_REMOVE, TEXT_LINE, 앵커의 MOVE·COUNT·AT)은 거절하고("anchor in
  bytes"), 앵커가 bytes 상태를 가리키면 로드가 거절한다. 저장: `values`에 종류 7과 이름을 적는다. text 상태의 파일이 UTF-8이
  아닐 때 그 표시가 있으면 초기값, 없으면 `store_error`다. 교체: bytes → text는 draft가 UTF-8일 때만 남기고, 아니면 초기값으로
  저장소의 draft를 다시 연다. pure-run은 16진으로 보인다.
- **aup.** `state x: bytes`(`= "..."`), 이름은 바이트 값, `x = …`는 바이트(문자열은 바이트를 만난다), 커서와 text 연산은 진단.
- **검증.**
  - 단위 시험 `bytes_states`: 저장소에서 다시 시작하는 일곱 번, 실행 중의 교체 셋, 거절 넷. 0xff와 0x00이 다시 시작을 지나 남는다.
    bytes → text → bytes → text를 오가며 UTF-8이 아닌 것은 초기값, UTF-8인 것은 그대로다. 표시 없이 UTF-8이 아닌 text 파일은 기존
    시험대로 여전히 `store_error`다.
  - aup의 `bytes_state`: 300걸음을 저장소로 15번 다시 시작하며 파이썬과 같다. 같은 이름의 text로 저장소에서 시작하고 실행 중에
    교체한다. 진단 셋.
  - Pure, Vega, aup의 전체 검사가 지난다.
  - 명령 수(b47 대 b46): 묶음의 BFS +0.12%, 나머지 ±0.001% 안이다. 실행기의 걸음은 그대로다(로드와 저장만 바뀌었다).
  - 은행 질의(s45와 s47을 한 번에 번갈아 여섯 번의 중앙값, 바이트의 비교와 함께): in_credit −0.31%, per_branch −0.18%, ranks
    −0.11%, 나머지 ±0.01% 안이다. 일곱 질의의 답은 바이트까지 같다.