aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design/008-module-format.md
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-25 12:09:41 +0100
committereunhhu <[email protected]>2026-09-25 12:09:41 +0100
commita2a430f6c2d973232a4e995133b372ba877558cf (patch)
treef1e22521ba03ff4683737131eec0af05e6e0cbd4 /docs/design/008-module-format.md
parent08056bb65bb9d1cfa4ccb1cd0b9cb67554d560a3 (diff)
downloadpure-a2a430f6c2d973232a4e995133b372ba877558cf.tar.gz
pure-a2a430f6c2d973232a4e995133b372ba877558cf.zip
Add text states and cursors to Pure modules
Module format v3 adds text states, cursors, UTF-8 constants, text inputs and text views. A text state is a runtime-owned draft without a store, so a large text edits as an extent tree. Cursors are byte positions the runtime keeps on character boundaries: insertion pushes cursors at or after the point, removal gathers cursors inside the range, and a failed event returns every text draft and cursor to where it was. The verifier types a cursor by the text it belongs to and rejects editing one text with another's cursor. Operations cover constants, concatenation, shortest round-trip number text, character, line and byte counts, character and column-keeping line moves, insert, remove, line views, whole-text replacement and UTF-8 checked conversion from bytes. Replies of edited documents or texts are copied before they are finished. pure-run gains text input and text output. test-module covers typing Korean and emoji, moves that stop at the ends, line moves, views, rollback, invalid UTF-8 and replacement, text-specific verifier rules, and 20,000 damaged text modules whose cursors always stay on character boundaries under ASan/UBSan.
Diffstat (limited to 'docs/design/008-module-format.md')
-rw-r--r--docs/design/008-module-format.md63
1 files changed, 46 insertions, 17 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index d364bd6..4286ceb 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -1,14 +1,14 @@
-# Pure 모듈 형식 v2
+# Pure 모듈 형식 v3
-2026-09-25. 구현 `prototypes/connections/module.{h,c}`, 트리 저장소 연결 `module-store.c`.
+2026-09-25. 구현 `prototypes/connections/module.{h,c}`, `module-text.c`, 트리 저장소 연결 `module-store.c`.
Pure가 실행하는 앱 정의의 형식이다. JVM이 class 파일 형식을 소유하듯 Pure가 이 형식과
검증 규칙을 소유한다. 생산자는 [aup](/srv/git/aup.git) 같은 툴체인이다. Pure는 소스·문법을 모르며,
로드할 때 모든 규칙을 확인한 모듈만 실행한다.
v1(스칼라)은 워크벤치 앱과 같은 의미였다: f64 상태, 키 입력, 원자적 이벤트 핸들러, 순수한 보기.
-v2는 여기에 타입 있는 바이트 값, 필드가 있는 **메시지 입력**, 키별 **영속 컬렉션**, 응답을 더한다.
-v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸다).
+v2는 타입 있는 바이트 값, 필드가 있는 **메시지 입력**, 키별 **영속 컬렉션**, 응답을 더했다.
+v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더한다. 이전 판은 받지 않는다(생산자는 aup 하나다).
## 바이트 배치
@@ -16,18 +16,21 @@ v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸
| 위치 | 내용 |
|---|---|
-| 0 | `PUREMOD2` (8 B) |
+| 0 | `PUREMOD3` (8 B) |
| 8 | u16 상태, u16 입력, u16 보기, u16 핸들러 |
| 16 | u32 명령 수, u32 문자열 바이트 수 |
-| 24 | u16 필드, u16 컬렉션, u32 0 |
-| 32 | 상태 × 16 B: u32 이름, u32 0(수), f64 초기값 |
+| 24 | u16 필드, u16 컬렉션, u16 상수, u16 0 |
+| 32 | u32 상수 블롭 바이트 수, u32 0 |
+| 40 | 상태 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 커서), 8 B 값: 수는 f64 초기값, text는 u32 초기 상수, 커서는 u32 대상 text 상태 |
| | 컬렉션 × 12 B: u32 이름, u32 1(바이트), u32 1(키별) |
-| | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지), u8 키별 여부, u16 0, u32 키 이름(키 입력) 또는 0, u16 첫 필드, u16 필드 수 |
+| | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text), u8 키별 여부, u16 0, u32 키 이름(키 입력) 또는 0, u16 첫 필드, u16 필드 수 |
| | 필드 × 8 B: u32 이름, u32 타입(1 u32le, 2 바이트 꼬리) |
-| | 보기 × 12 B: u32 이름, u32 시작 명령, u32 명령 수 |
+| | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) |
+| | 보기 × 16 B: u32 이름, u32 타입(0 수, 1 text), u32 시작 명령, u32 명령 수 |
| | 핸들러 × 12 B: u32 이벤트, u32 시작 명령, u32 명령 수 |
| | 명령 × 16 B: u8 opcode, 0 3바이트, u32 인자, f64 값 |
| | 문자열: `[u8 길이][바이트]`의 연속 |
+| | 상수 블롭 (최대 8 KiB) |
파일 크기는 위 합과 정확히 같아야 한다. 이벤트 번호는 `frame`이 0, 입력 `i`의 누름·도착이 `1 + 2i`,
키의 뗌이 `2 + 2i`다. 메시지 필드는 입력 순서대로 이어지고, u32le 필드가 차례로 놓인 뒤 바이트 꼬리가
@@ -56,6 +59,23 @@ v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸
| 27 | REPLACE | n n b → | 문서의 `[a, b)`를 값으로 바꾼다. 정수가 아니거나 범위 밖이면 `limit` |
| 28 | REPLY | b → | 이 메시지의 응답 |
| 29 | REQUIRE | n → | 0이면 이벤트가 `limit`으로 실패 |
+| 30 | TEXT_CONST | → t | 상수 `인자` |
+| 31 | TEXT_LOAD | → t | text 상태 `인자`의 현재 값(복사 없는 버전) |
+| 32 | TEXT_INPUT | → t | text 입력의 내용. text 입력 핸들러 전용 |
+| 33 | NUM_TEXT | n → t | 가장 짧은 왕복 10진수. 1e15 미만 정수는 소수점·지수 없이, −0은 `0` |
+| 34 | CONCAT | t t → t | |
+| 35–37 | CHARS LINES TEXT_BYTES | t → n | 글자(Unicode scalar) 수, 줄 수(줄바꿈 + 1), 바이트 수 |
+| 38 | CUR_LOAD | → c | 커서 상태 `인자` |
+| 39–40 | CUR_START CUR_END | → c | text 상태 `인자`의 처음·끝 |
+| 41 | CUR_MOVE | c n → c | 단위 `인자`(0 글자, 1 줄)로 n만큼. 끝에서 멈춘다. 줄 이동은 열을 유지 |
+| 42 | CUR_STORE | c → | 커서 상태 `인자`. 핸들러 전용 |
+| 43 | TEXT_INSERT | c t → | text `인자`의 커서 위치에 넣는다 |
+| 44 | TEXT_REMOVE | c c → | 두 커서 사이를 지운다 |
+| 45 | TEXT_LINE | c → t | 커서가 있는 줄(줄바꿈 제외) |
+| 46 | TEXT_SET | t → | text 전체를 바꾸고 그 text의 커서를 처음으로 |
+| 47 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) |
+| 48 | BYTES_FROM | t → b | 같은 바이트 |
+| 49 | CUR_COUNT | c → n | 단위 `인자`로 커서 앞의 글자 수, 또는 1부터 센 줄 번호 |
수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 핸들러가 한 일이 취소된다.
@@ -64,10 +84,13 @@ v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸
- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(상태 32, 입력 8, 보기 24, 핸들러 64, 명령 512,
필드 32, 컬렉션 1, 문자열 32 KiB)와 정확한 파일 크기.
- 이름은 ASCII 식별자 1–31바이트이고, 상태·컬렉션·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지 안에서 유일하다.
- 키 입력의 키는 정해진 목록 안에 있고 겹치지 않는다. 메시지 입력은 키 이름이 없고 필드 범위가 차례로 이어진다.
-- 메시지 핸들러의 이벤트는 도착(`1 + 2i`)뿐이다.
-- 각 블록에서 명령마다 피연산자 타입(수 또는 바이트)과 스택 높이(0–64)를 확인한다. 명령이 놓일 수 있는
- 문맥(보기, 핸들러, 메시지 핸들러, 키별 메시지 핸들러)도 확인한다. 분기는 블록 안에서 앞으로만 가고, 분기와
+ 키 입력의 키는 정해진 목록(`Space Enter` 화살표 `Backspace Delete Home End Tab Escape PageUp PageDown KeyA–Z Digit0–9`)
+ 안에 있고 겹치지 않는다. 메시지 입력은 키 이름이 없고 필드 범위가 차례로 이어진다. text 입력은 필드가 없다.
+- 상수는 블롭 안에 있고 유효한 UTF-8이다. text 상태의 초기 상수와 커서의 대상 text가 존재한다.
+- 메시지·text 핸들러의 이벤트는 도착(`1 + 2i`)뿐이다.
+- 각 블록에서 명령마다 피연산자 타입(수, 바이트, text, 커서)과 스택 높이(0–64)를 확인한다. 커서 타입은 어느
+ text의 커서인지 담고, 다른 text의 커서로 넣기·지우기·저장하는 모듈은 거절한다. 명령이 놓일 수 있는 문맥(보기,
+ 핸들러, 메시지 핸들러, 키별 메시지 핸들러, text 입력 핸들러)도 확인한다. 분기는 블록 안에서 앞으로만 가고, 분기와
합류는 스택이 빈 곳에서만 일어난다. 그래서 타입이 합쳐지는 경로가 없다. 모든 명령에 도달할 수 있어야 하고,
핸들러는 값을 남기지 않으며 보기는 수 하나를 남긴다.
@@ -75,8 +98,12 @@ v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸
## 실행
-`PureApp`은 모듈 참조, 상태 값, 열린 메시지 이벤트 하나를 가진다(432 B, `PureModule`은 13,136 B).
-키·frame 이벤트는 v1과 같다. 메시지 이벤트는 다음 순서로 처리한다.
+`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 열린 이벤트 하나를 가진다(736 B, `PureModule`은 상수 블롭을
+포함해 21,968 B). text 상태는 저장소 파일이 없는 draft라 1 MiB 텍스트도 조각 트리로 편집한다. 커서는 바이트
+위치이며 항상 글자 경계에 있다. 넣기는 그 위치 이후의 커서를 뒤로(삽입 위치의 커서 포함) 밀고, 지우기는 범위
+안의 커서를 범위 처음으로 모은다. 모든 이벤트에서 text draft를 잡아 두고, 실패하면 이벤트 전 버전으로 돌린다.
+
+키·frame·text 이벤트는 그 핸들러를 실행한다. 메시지 이벤트는 다음 순서로 처리한다.
1. 입력 종류, 키 형식, 기준 리비전(`-1`은 없음)을 확인하고, 페이로드가 고정 부분보다 짧으면 `limit`.
2. 키별 메시지면 데이터 호스트에서 그 키의 작업 버전을 연다. 기준 리비전이 있고 현재와 다르면 `conflict`.
@@ -84,8 +111,10 @@ v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸
4. 문서를 바꿨다면 기준 리비전이 있어야 하고, 작업 버전을 커밋한다. 문서에서 읽은 응답은 커밋 전에 복사한다.
커밋이 실패하면 상태를 되돌린다. 결과 불명(`uncertain`)이면 상태만 되돌리고 그렇게 보고한다.
-응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드와 문서의 익스텐트를 복사하지 않는다.
+응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드와 문서의 익스텐트를 복사하지 않는다. 문서를 고친 이벤트의
+문서 응답과 text 상태의 응답은 확정 전에 복사한다. text 보기는 `pure_app_view_text`로 빌려준다.
데이터 호스트는 `PureData` 인터페이스이며, `pure_tree_data`가 트리 저장소의 draft로 구현한다.
`pure-edit-module`은 기존 문서 CLI(`pure-edit`)와 같은 명령·출력·종료 코드를 모듈로 제공한다.
-아직 런타임 코어(`runtime.c`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다.
+아직 런타임 코어(`runtime.c`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다. 실행기 코드는
+`module.c` 21.8 KB, `module-text.c` 3.6 KB(`-O2` text)로 v1(7.4 KB)보다 크다. 줄이는 것이 남은 일이다.