aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design/008-module-format.md
diff options
context:
space:
mode:
authoreunhhu <[email protected]>2026-09-25 11:48:51 +0100
committereunhhu <[email protected]>2026-09-25 11:48:51 +0100
commitefba82d75e4357c7e82b027b04a5ec9937f8443b (patch)
tree9c874527466faf836e6fb4a03dd25c9c7160410a /docs/design/008-module-format.md
parent12410be7ec2a030e8c3adcf318d87d5ac2954e1d (diff)
downloadpure-efba82d75e4357c7e82b027b04a5ec9937f8443b.tar.gz
pure-efba82d75e4357c7e82b027b04a5ec9937f8443b.zip
Add typed messages and keyed documents to Pure modules
Module format v2 adds byte values, message inputs with u32 and bytes-tail fields, one keyed byte collection, replies and require. The verifier checks every operand's type and the context each operation may run in, and allows branches and joins only at an empty stack, so typed values never merge. A message event opens its key's working version through a PureData host, conflicts early on a stale base revision, runs its handlers, and commits only if they succeed; a failed event leaves documents and states unchanged. Replies lend payload or document extents, and a document reply is copied before commit. pure_tree_data implements the host on tree store drafts, and pure-edit-module offers the document CLI contract of pure-edit from a module. test-module adds document semantics on an in-memory host (not found, create, conflict before bounds, limits, short payloads, busy commits, stable replies, require, state rollback), typed verifier rules, and 20,000 damaged message modules run safely under ASan/UBSan.
Diffstat (limited to 'docs/design/008-module-format.md')
-rw-r--r--docs/design/008-module-format.md99
1 files changed, 59 insertions, 40 deletions
diff --git a/docs/design/008-module-format.md b/docs/design/008-module-format.md
index 616125c..d364bd6 100644
--- a/docs/design/008-module-format.md
+++ b/docs/design/008-module-format.md
@@ -1,13 +1,14 @@
-# Pure 모듈 형식 v1
+# Pure 모듈 형식 v2
-2026-09-25. 상태: scalar 모듈 확정, 구현 `prototypes/connections/module.{h,c}`.
+2026-09-25. 구현 `prototypes/connections/module.{h,c}`, 트리 저장소 연결 `module-store.c`.
Pure가 실행하는 앱 정의의 형식이다. JVM이 class 파일 형식을 소유하듯 Pure가 이 형식과
검증 규칙을 소유한다. 생산자는 [aup](/srv/git/aup.git) 같은 툴체인이다. Pure는 소스·문법을 모르며,
로드할 때 모든 규칙을 확인한 모듈만 실행한다.
-v1은 워크벤치 scalar 앱과 같은 의미를 담는다. f64 상태, 키 입력, 원자적 이벤트 핸들러,
-순수한 보기다. 바이트 값과 영속 문서는 v2에서 추가한다.
+v1(스칼라)은 워크벤치 앱과 같은 의미였다: f64 상태, 키 입력, 원자적 이벤트 핸들러, 순수한 보기.
+v2는 여기에 타입 있는 바이트 값, 필드가 있는 **메시지 입력**, 키별 **영속 컬렉션**, 응답을 더한다.
+v1 모듈은 더 이상 받지 않는다(생산자는 aup 하나이고 v2를 낸다).
## 바이트 배치
@@ -15,58 +16,76 @@ v1은 워크벤치 scalar 앱과 같은 의미를 담는다. f64 상태, 키 입
| 위치 | 내용 |
|---|---|
-| 0 | `PUREMOD1` (8 B) |
-| 8 | u16 상태 수, u16 입력 수, u16 보기 수, u16 핸들러 수 |
+| 0 | `PUREMOD2` (8 B) |
+| 8 | u16 상태, u16 입력, u16 보기, u16 핸들러 |
| 16 | u32 명령 수, u32 문자열 바이트 수 |
-| 24 | u32 0, u32 0 (예약) |
-| 32 | 상태 × 16 B: u32 이름, u32 0, f64 초기값 |
-| | 입력 × 12 B: u32 이름, u32 종류(1 = 키), u32 키 이름 |
+| 24 | u16 필드, u16 컬렉션, u32 0 |
+| 32 | 상태 × 16 B: u32 이름, u32 0(수), f64 초기값 |
+| | 컬렉션 × 12 B: u32 이름, u32 1(바이트), u32 1(키별) |
+| | 입력 × 16 B: u32 이름, u8 종류(1 키, 2 메시지), u8 키별 여부, u16 0, u32 키 이름(키 입력) 또는 0, u16 첫 필드, u16 필드 수 |
+| | 필드 × 8 B: u32 이름, u32 타입(1 u32le, 2 바이트 꼬리) |
| | 보기 × 12 B: u32 이름, u32 시작 명령, u32 명령 수 |
| | 핸들러 × 12 B: u32 이벤트, u32 시작 명령, u32 명령 수 |
| | 명령 × 16 B: u8 opcode, 0 3바이트, u32 인자, f64 값 |
-| | 문자열: `[u8 길이][바이트]`의 연속. 이름은 이 영역의 오프셋이다 |
+| | 문자열: `[u8 길이][바이트]`의 연속 |
-파일 크기는 위 합과 정확히 같아야 한다. 이벤트 번호는 `frame`이 0, 입력 `i`의 누름이 `1 + 2i`,
-뗌이 `2 + 2i`다. 핸들러는 파일 순서대로 실행한다.
+파일 크기는 위 합과 정확히 같아야 한다. 이벤트 번호는 `frame`이 0, 입력 `i`의 누름·도착이 `1 + 2i`,
+키의 뗌이 `2 + 2i`다. 메시지 필드는 입력 순서대로 이어지고, u32le 필드가 차례로 놓인 뒤 바이트 꼬리가
+최대 하나 온다. 메시지 페이로드가 고정 부분보다 짧으면 이벤트는 `limit`으로 실패하고, 남는 바이트는 무시한다.
## 명령
| opcode | 이름 | 스택 | 의미 |
|---:|---|---|---|
-| 0 | CONST | → v | 값(유한)을 넣는다 |
-| 1 | LOAD | → v | 상태 `인자`를 읽는다 |
-| 2 | DT | → v | 이벤트 시간(frame 1/60, 입력 0). 핸들러 전용 |
-| 3–6 | ADD SUB MUL DIV | a b → v | DIV에서 b = 0이면 실패 |
-| 7 | NEG | a → v | |
-| 8–13 | LT LE GT GE EQ NE | a b → 0/1 | |
+| 0 | CONST | → n | 유한한 값 |
+| 1 | LOAD | → n | 상태 `인자` |
+| 2 | DT | → n | frame 1/60, 그 밖 0. 핸들러 전용 |
+| 3–6 | ADD SUB MUL DIV | n n → n | DIV에서 0이면 실패 |
+| 7 | NEG | n → n | |
+| 8–13 | LT LE GT GE EQ NE | n n → n | 0 또는 1 |
| 14–16 | AND OR NOT | | 0이 아니면 참, 양쪽 모두 계산 |
-| 17–18 | MIN MAX | a b → v | IEEE 754-2019: −0 < +0 |
-| 19 | CLAMP | x lo hi → v | lo > hi이면 실패 |
-| 20 | STORE | v → | 상태 `인자`에 쓴다. 핸들러 전용 |
-| 21 | JZ | c → | c = 0이면 `인자`로. 핸들러 전용 |
+| 17–18 | MIN MAX | n n → n | IEEE 754-2019: −0 < +0 |
+| 19 | CLAMP | n n n → n | lo > hi이면 실패 |
+| 20 | STORE | n → | 상태 `인자`. 핸들러 전용 |
+| 21 | JZ | n → | 0이면 `인자`로. 핸들러 전용 |
| 22 | JMP | | `인자`로. 핸들러 전용 |
+| 23 | FIELD | → n 또는 b | 메시지 필드 `인자`. 메시지 핸들러 전용 |
+| 24 | DOC | → b | 컬렉션 `인자`의 메시지 키 문서. 없으면 `not_found`. 키별 메시지 핸들러 전용 |
+| 25 | DOC_SIZE | → n | 그 문서의 바이트 수(없으면 0) |
+| 26 | LEN | b → n | 바이트 값의 길이 |
+| 27 | REPLACE | n n b → | 문서의 `[a, b)`를 값으로 바꾼다. 정수가 아니거나 범위 밖이면 `limit` |
+| 28 | REPLY | b → | 이 메시지의 응답 |
+| 29 | REQUIRE | n → | 0이면 이벤트가 `limit`으로 실패 |
-연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 핸들러가 한 일이 취소된다.
+수 연산 결과가 유한하지 않으면 실패한다. 실패하면 그 이벤트의 모든 핸들러가 한 일이 취소된다.
## 로드할 때 검증하는 것
- 머리말, 예약 바이트, 명령 패딩이 0인지. 한도(상태 32, 입력 8, 보기 24, 핸들러 64, 명령 512,
- 문자열 32 KiB)와 정확한 파일 크기.
-- 이름은 ASCII 식별자 1–31바이트. 상태·입력·보기 이름은 한 이름공간에서 겹치지 않는다.
- 키는 `Space Enter ArrowLeft ArrowRight ArrowUp ArrowDown KeyA–KeyZ Digit0–Digit9` 중 하나이고 겹치지 않는다.
-- 초기값과 CONST 값은 유한하다. 인자·값을 쓰지 않는 명령은 둘 다 0이다(표현이 하나뿐).
-- 보기·핸들러의 명령 범위가 모듈 안에 있고, 핸들러 이벤트가 존재한다.
-- 각 블록에서: 모든 명령이 도달 가능하고, 분기는 블록 안에서 앞으로만 간다.
- 모든 경로가 합류점에서 같은 스택 높이를 갖고, 높이는 0–64다. 핸들러는 0개, 보기는 1개의 값을 남긴다.
- 보기에는 DT, STORE, 분기가 없다.
-
-분기가 앞으로만 가므로 한 번의 순차 검사로 모든 들어오는 경로를 본다. 검증된 모듈을 실행할 때는
-스택 검사가 필요 없고, 이벤트 하나의 실행량은 핸들러 명령 수 이하다.
-
-## 실행 인스턴스
-
-`PureApp`은 모듈 참조와 상태 값(최대 32 × f64)만 가진다(288 B). 이벤트는 상태 사본에서 실행하고
-모두 성공했을 때만 반영한다. `pure_app_replace`는 같은 이름의 상태 값을 유지하고 새 상태에 초기값을 준다.
-불러오기·실행·교체 모두 할당하지 않는다. `PureModule`은 고정 용량 11,728 B이다.
+ 필드 32, 컬렉션 1, 문자열 32 KiB)와 정확한 파일 크기.
+- 이름은 ASCII 식별자 1–31바이트이고, 상태·컬렉션·입력·보기가 한 이름공간을 쓴다. 필드 이름은 메시지 안에서 유일하다.
+ 키 입력의 키는 정해진 목록 안에 있고 겹치지 않는다. 메시지 입력은 키 이름이 없고 필드 범위가 차례로 이어진다.
+- 메시지 핸들러의 이벤트는 도착(`1 + 2i`)뿐이다.
+- 각 블록에서 명령마다 피연산자 타입(수 또는 바이트)과 스택 높이(0–64)를 확인한다. 명령이 놓일 수 있는
+ 문맥(보기, 핸들러, 메시지 핸들러, 키별 메시지 핸들러)도 확인한다. 분기는 블록 안에서 앞으로만 가고, 분기와
+ 합류는 스택이 빈 곳에서만 일어난다. 그래서 타입이 합쳐지는 경로가 없다. 모든 명령에 도달할 수 있어야 하고,
+ 핸들러는 값을 남기지 않으며 보기는 수 하나를 남긴다.
+
+검증된 모듈은 실행 중 스택·타입 검사가 필요 없고, 이벤트 하나의 명령 수는 핸들러 길이 이하다.
+
+## 실행
+
+`PureApp`은 모듈 참조, 상태 값, 열린 메시지 이벤트 하나를 가진다(432 B, `PureModule`은 13,136 B).
+키·frame 이벤트는 v1과 같다. 메시지 이벤트는 다음 순서로 처리한다.
+
+1. 입력 종류, 키 형식, 기준 리비전(`-1`은 없음)을 확인하고, 페이로드가 고정 부분보다 짧으면 `limit`.
+2. 키별 메시지면 데이터 호스트에서 그 키의 작업 버전을 연다. 기준 리비전이 있고 현재와 다르면 `conflict`.
+3. 핸들러를 소스 순서대로 실행한다. 실패하면 작업 버전을 버리고 상태도 되돌린다.
+4. 문서를 바꿨다면 기준 리비전이 있어야 하고, 작업 버전을 커밋한다. 문서에서 읽은 응답은 커밋 전에 복사한다.
+ 커밋이 실패하면 상태를 되돌린다. 결과 불명(`uncertain`)이면 상태만 되돌리고 그렇게 보고한다.
+
+응답은 `pure_app_reply`로 순서대로 빌려준다. 페이로드와 문서의 익스텐트를 복사하지 않는다.
+데이터 호스트는 `PureData` 인터페이스이며, `pure_tree_data`가 트리 저장소의 draft로 구현한다.
+`pure-edit-module`은 기존 문서 CLI(`pure-edit`)와 같은 명령·출력·종료 코드를 모듈로 제공한다.
아직 런타임 코어(`runtime.c`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다.