aboutsummaryrefslogtreecommitdiffstats
path: root/docs/design
diff options
context:
space:
mode:
Diffstat (limited to 'docs/design')
-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`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다.