diff options
Diffstat (limited to 'docs')
| -rw-r--r-- | docs/design/008-module-format.md | 99 |
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`)의 작업·예산 모델과 연결하지 않았다. 호스트가 직접 호출한다. |
