From a2a430f6c2d973232a4e995133b372ba877558cf Mon Sep 17 00:00:00 2001 From: eunhhu Date: Fri, 25 Sep 2026 12:09:41 +0100 Subject: 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. --- docs/design/008-module-format.md | 63 +++++++++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 17 deletions(-) (limited to 'docs/design') 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)보다 크다. 줄이는 것이 남은 일이다. -- cgit v1.2.3