aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design')
-rw-r--r--docs/design/008-module-format.md61
1 files changed, 45 insertions, 16 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index f5dd0bb..8ccd6af 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -1,6 +1,6 @@
-# Pure 모듈 형식 v3
+# Pure 모듈 형식 v4
-2026-09-25. 구현 `prototypes/connections/module.{h,c}`, `module-text.c`, 트리 저장소 연결 `module-store.c`.
+2026-09-25. 구현 `prototypes/connections/module.{h,c}`, `module-text.c`, `module-list.c`, 트리 저장소 연결 `module-store.c`.
Pure가 실행하는 앱 정의의 형식이다. JVM이 class 파일 형식을 소유하듯 Pure가 이 형식과
검증 규칙을 소유한다. 생산자는 [aup](/srv/git/aup.git) 같은 툴체인이다. Pure는 소스·문법을 모르며,
@@ -8,7 +8,8 @@ Pure가 실행하는 앱 정의의 형식이다. JVM이 class 파일 형식을 �
v1(스칼라)은 워크벤치 앱과 같은 의미였다: f64 상태, 키 입력, 원자적 이벤트 핸들러, 순수한 보기.
v2는 타입 있는 바이트 값, 필드가 있는 **메시지 입력**, 키별 **영속 컬렉션**, 응답을 더했다.
-v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더한다. 이전 판은 받지 않는다(생산자는 aup 하나다).
+v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더했다.
+v4는 **레코드**, 레코드의 **리스트** 상태, 리스트의 **인덱스** 상태를 더한다. 이전 판은 받지 않는다(생산자는 aup 하나다).
## 바이트 배치
@@ -16,16 +17,18 @@ v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더한
| 위치 | 내용 |
|---|---|
-| 0 | `PUREMOD3` (8 B) |
+| 0 | `PUREMOD4` (8 B) |
| 8 | u16 상태, u16 입력, u16 보기, u16 핸들러 |
| 16 | u32 명령 수, u32 문자열 바이트 수 |
-| 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 상태 |
+| 24 | u16 필드, u16 컬렉션, u16 상수, u16 레코드 |
+| 32 | u32 상수 블롭 바이트 수, u16 레코드 필드, u16 0 |
+| 40 | 상태 × 16 B: u32 이름, u32 타입(0 수, 1 text, 2 커서, 3 리스트, 4 인덱스), 8 B 값: 수는 f64 초기값, text는 u32 초기 상수, 커서는 u32 대상 text 상태, 리스트는 u32 레코드, 인덱스는 u32 대상 리스트 상태 |
| | 컬렉션 × 12 B: u32 이름, u32 1(바이트), u32 1(키별) |
| | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지, 3 text), u8 키별 여부, u16 0, u32 키 이름(키 입력) 또는 0, u16 첫 필드, u16 필드 수 |
| | 필드 × 8 B: u32 이름, u32 타입(1 u32le, 2 바이트 꼬리) |
| | 상수 × 8 B: u32 블롭 오프셋, u32 길이 (UTF-8) |
+| | 레코드 × 8 B: u32 이름, u16 첫 레코드 필드, u16 필드 수(1–16) |
+| | 레코드 필드 × 16 B: u32 이름, u32 타입(0 수, 1 text), 8 B 기본값: 수는 f64, text는 u32 상수 |
| | 보기 × 16 B: u32 이름, u32 타입(0 수, 1 text), u32 시작 명령, u32 명령 수 |
| | 핸들러 × 12 B: u32 이벤트, u32 시작 명령, u32 명령 수 |
| | 명령 × 16 B: u8 opcode, 0 3바이트, u32 인자, f64 값 |
@@ -76,20 +79,35 @@ v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더한
| 47 | TEXT_FROM | b → t | UTF-8이 아니면 이벤트가 실패(encoding) |
| 48 | BYTES_FROM | t → b | 같은 바이트 |
| 49 | CUR_COUNT | c → n | 단위 `인자`로 커서 앞의 글자 수, 또는 1부터 센 줄 번호 |
-
-수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 핸들러가 한 일이 취소된다.
+| 50 | LIST_COUNT | → n | 리스트 상태 `인자`의 항목 수 |
+| 51 | LIST_APPEND | f… → | 레코드 필드 값을 필드 순서대로 받아 끝에 붙인다. 핸들러 전용 |
+| 52 | LIST_INSERT | i f… → | 인덱스의 항목 앞에 넣는다(빈 리스트면 처음) |
+| 53 | LIST_REMOVE | i → | 인덱스의 항목을 지운다. 없으면 `not_found` |
+| 54 | ITEM_GET | i → f | 항목의 필드. `인자`의 아래 16비트는 리스트 상태, 위는 필드 번호. 없으면 `not_found` |
+| 55 | ITEM_SET | i f → | 항목의 필드를 바꾼다. 핸들러 전용 |
+| 56 | INDEX_LOAD | → i | 인덱스 상태 `인자` |
+| 57 | INDEX_STORE | i → | 인덱스 상태 `인자`. 핸들러 전용. 같은 이벤트에서 지우기 전에 얻은, 끝을 넘은 인덱스는 `not_found` |
+| 58 | INDEX_MOVE | i n → i | n만큼. 정수가 아니면 `limit`. 처음과 마지막 항목에서 멈춘다 |
+| 59–60 | INDEX_FIRST INDEX_LAST | → i | 리스트 상태 `인자`의 첫·마지막 항목(빈 리스트면 0) |
+| 61 | INDEX_NUM | i → n | 0부터 센 위치 |
+
+`f`는 레코드 필드의 타입(수 또는 text)이다. 수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든
+핸들러가 한 일이 취소된다.
## 로드할 때 검증하는 것
- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(상태 32, 입력 32, 보기 24, 핸들러 64, 명령 512,
필드 32, 컬렉션 1, 문자열 32 KiB)와 정확한 파일 크기.
-- 이름은 ASCII 식별자 1–31바이트이고, 상태·컬렉션·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지 안에서 유일하다.
+- 이름은 ASCII 식별자 1–31바이트이고, 상태·레코드·컬렉션·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지나
+ 레코드 안에서 유일하다. 레코드(최대 8)의 필드 범위는 차례로 이어지고 모두 합쳐 64개 이하다.
키 입력의 키는 정해진 목록(`Space Enter` 화살표 `Backspace Delete Home End Tab Escape PageUp PageDown KeyA–Z Digit0–9`)
안에 있고 겹치지 않는다. 메시지 입력은 키 이름이 없고 필드 범위가 차례로 이어진다. text 입력은 필드가 없다.
-- 상수는 블롭 안에 있고 유효한 UTF-8이다. text 상태의 초기 상수와 커서의 대상 text가 존재한다.
+- 상수는 블롭 안에 있고 유효한 UTF-8이다. text 상태의 초기 상수, 커서의 대상 text, 리스트의 레코드, 인덱스의
+ 대상 리스트, 레코드 필드의 기본값(유한한 수 또는 있는 상수)이 존재한다.
- 메시지·text 핸들러의 이벤트는 도착(`1 + 2i`)뿐이다.
-- 각 블록에서 명령마다 피연산자 타입(수, 바이트, text, 커서)과 스택 높이(0–64)를 확인한다. 커서 타입은 어느
- text의 커서인지 담고, 다른 text의 커서로 넣기·지우기·저장하는 모듈은 거절한다. 명령이 놓일 수 있는 문맥(보기,
+- 각 블록에서 명령마다 피연산자 타입(수, 바이트, text, 커서, 인덱스)과 스택 높이(0–64)를 확인한다. 커서 타입은 어느
+ text의 커서인지, 인덱스 타입은 어느 리스트의 인덱스인지 담는다. 다른 text의 커서나 다른 리스트의 인덱스를 쓰는
+ 모듈은 거절한다. 레코드 명령은 레코드 필드 수만큼 필드 타입대로 꺼낸다. 명령이 놓일 수 있는 문맥(보기,
핸들러, 메시지 핸들러, 키별 메시지 핸들러, text 입력 핸들러)도 확인한다. 분기는 블록 안에서 앞으로만 가고, 분기와
합류는 스택이 빈 곳에서만 일어난다. 그래서 타입이 합쳐지는 경로가 없다. 모든 명령에 도달할 수 있어야 하고,
핸들러는 값을 남기지 않으며 보기는 수 하나를 남긴다.
@@ -98,11 +116,17 @@ v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더한
## 실행
-`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 열린 이벤트 하나를 가진다(736 B, `PureModule`은 상수 블롭을
-포함해 23,792 B). text 상태는 저장소 파일이 없는 draft라 1 MiB 텍스트도 조각 트리로 편집한다. 커서는 바이트
+`PureApp`은 모듈 참조, 상태 값, text 상태 draft, 리스트, 열린 이벤트 하나를 가진다(1,016 B, `PureModule`은 상수
+블롭을 포함해 26,704 B). text 상태는 저장소 파일이 없는 draft라 1 MiB 텍스트도 조각 트리로 편집한다. 커서는 바이트
위치이며 항상 글자 경계에 있다. 넣기는 그 위치 이후의 커서를 뒤로(삽입 위치의 커서 포함) 밀고, 지우기는 범위
안의 커서를 범위 처음으로 모은다. 모든 이벤트에서 text draft를 잡아 두고, 실패하면 이벤트 전 버전으로 돌린다.
+리스트는 런타임이 소유하는 항목 배열이고, 항목은 필드마다 수 또는 text 바이트를 가진다(최대 65,536 항목). 인덱스는
+항상 그 리스트의 항목을 가리킨다(빈 리스트면 0). 넣기는 넣은 자리 이후를 가리키던 인덱스를 한 칸 밀어 같은 항목을
+따라가게 하고, 지우기는 뒤의 인덱스를 당기며 지운 항목을 가리키던 인덱스는 다음 항목(없으면 마지막)으로 간다.
+이벤트 동안 리스트 변경은 되돌리기 기록에 쌓이고, 실패하면 거꾸로 되돌리며 성공하면 지운 항목과 바뀐 text를 해제한다.
+항목의 text 필드를 읽으면 이벤트 공간에 복사한다.
+
키·frame·text 이벤트는 그 핸들러를 실행한다. 메시지 이벤트는 다음 순서로 처리한다.
1. 입력 종류, 키 형식, 기준 리비전(`-1`은 없음)을 확인하고, 페이로드가 고정 부분보다 짧으면 `limit`.
@@ -111,10 +135,15 @@ v3은 **text 상태와 커서**, text 상수, text 입력, text 보기를 더한
4. 문서를 바꿨다면 기준 리비전이 있어야 하고, 작업 버전을 커밋한다. 문서에서 읽은 응답은 커밋 전에 복사한다.
커밋이 실패하면 상태를 되돌린다. 결과 불명(`uncertain`)이면 상태만 되돌리고 그렇게 보고한다.
+모듈을 바꾸면(`pure_app_replace`) 상태는 이름과 종류가 같을 때 남는다. 레코드가 같으면 리스트를 그대로 쓰고,
+레코드가 바뀌었으면 항목마다 이름과 타입이 같은 필드를 옮기고 새 필드는 기본값으로 채운다. 커서와 인덱스는 대상
+text·리스트가 남을 때만 위치를 유지한다.
+
응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드와 문서의 익스텐트를 복사하지 않는다. 문서를 고친 이벤트의
문서 응답과 text 상태의 응답은 확정 전에 복사한다. text 보기는 `pure_app_view_text`로 빌려준다.
데이터 호스트는 `PureData` 인터페이스이며, `pure_tree_data`가 트리 저장소의 draft로 구현한다.
`pure-edit-module`은 기존 문서 CLI(`pure-edit`)와 같은 명령·출력·종료 코드를 모듈로 제공한다.
아직 런타임 코어(`runtime.c`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다. 실행기 코드는
-`module.c` 21.8 KB, `module-text.c` 3.6 KB(`-O2` text)로 v1(7.4 KB)보다 크다. 줄이는 것이 남은 일이다.
+`module.c` 28.3 KB, `module-text.c` 3.6 KB, `module-list.c` 3.2 KB(`-O2` text)로 v1(7.4 KB)보다 크다. 줄이는 것이
+남은 일이다. 항목 text를 읽을 때마다 복사하는 비용도 남아 있다.