헤더 행과 컬럼 경로
4. :type 행 — 접힌 타입 표현과 괄호 메타
4.1 타입 표현 — 디테일 행의 소멸
기존 레이아웃은 (타입 칸, 디테일 칸) 한 쌍이었습니다 — 타입 enum + 디테일 Element,
타입 foreign + 디테일 Weapon|Armour. STRUCT DSL이 그 쌍을 타입 표현 하나로 접었고
(설계 §3), 이 레이아웃은 그 접힌 표현을 시트에서도
씁니다. 디테일 행은 없습니다.
| 무엇 | 기존 (타입 + 디테일) | 이 레이아웃 |
|---|---|---|
| enum | enum + Element | Element |
| 참조 | foreign + Weapon|Armour | foreign Weapon|Armour |
| DSL의 struct | (없었음) | Reward |
| 그 외 | int · string[] · vec3f · … | 그대로 |
대안 — 타입과 디테일을 행 둘로 유지. 행 키 레이아웃 §3.3이 엑셀 드롭다운 검증을 위해 택하였던 방향입니다. 채택하지 않습니다 — DSL과 시트가 같은 타입 표현을 쓰는 것이 배우는 문법을 하나로 만들고, 자리 둘에 뜻 하나를 나눠 적는 형태가 §11에서 피하려는 것 자체이기 때문입니다. 드롭다운은 이름 카탈로그 산출로 보완하는 방향이고 미결로 남깁니다(§13).
4.2 괄호 메타 — 첫 (부터는 언제나 메타
타입 칸은 「타입 표현 + 괄호 메타(선택)」입니다. 규칙은 하나입니다 — 셀에서 첫
(가 나오면 거기서부터 메타입니다. 타입 표현 자체에는 괄호가 없습니다.
int
int? (min=0, max=100)
string (text)
string (text=Common)
string (asset=icon)
foreign Item|CharGear
Reward
int[] (size=1..4)
| 메타 키 | 값 | 뜻 | 대응 |
|---|---|---|---|
text | 플래그 또는 그룹 이름 | 번역 수집. (text) · (text=Common) | 기존 text · text(Common) 타입 |
namespace | 이름 | text 그룹의 네임스페이스 | 기존 text(Common,Shared)의 둘째 자리 |
asset | 종류 이름 | 자산 존재 확인. (asset=icon) | 기존 asset(icon) 타입 |
min · max | 수 | 범위. 스칼라 · 원소 | Field.Constraints의 Minimum · Maximum |
allowed | a;b;c | 허용값 | AllowedValues |
regex · size · notDefault | (DSL §6.1과 동일) | 컬럼 제약 | Luban 대조 §5.8의 도입 항목 |
- 키의 사전과 「배열에 붙는가, 원소에 붙는가」는 DSL §6.1의 표가 정본입니다. 이
레이아웃이 키를 따로 정의하지 않습니다 — 같은 키, 같은 뜻, 같은 검증입니다.
text·namespace·asset의 등재는 괄호 메타와 같은 단계입니다(§16의 3). - DSL이 선언한 struct의 컬럼에 적은 제약은 DSL 제약과의 교집합입니다(DSL §6.3). 완화는 없습니다.
- 모르는 키는 오류입니다.
role 키 등재에서 확인해 둔 것 3가지(§16의 3의 입력입니다).
- DSL은
text를 플래그로만 읽습니다 —SchemaMetadata.ApplyRole이Has("text")만 보므로(text=Common)은 파싱을 통과하고Common이 조용히 버려집니다. 등재가 고치는 결함이고, 이 도구가 없애려는 종류 그 자체입니다.- DSL에
namespace키가 없습니다. 시트의 옛 표기는text(Common,Shared)의 둘째 자리로 적었고,key=value형태에는 쉼표 묶음이 필요 없으므로 키를 따로 둡니다 — 양쪽이 같은 형태가 되는 것이 등재의 본 효과입니다.- 기존 role 오류 문구는 옛 표기 전용입니다 — 「괄호를 열고」 · 「쉼표로 끝나고」 ·
text(Achievement,Quests)를 안내합니다.key=value에 그대로 쓰면 작성자가 쓰지 않은 표기를 안내하게 되므로, 규칙은 공유하고 문구는 표기마다 따로 둡니다 —CookingContext.RequiresRoleGroup의 판정을 뽑아 쓰고 메시지는 각자입니다.
기존 표기와의 단절 하나.
text(Common)·asset(icon)처럼 괄호가 타입에 붙는 표기는 이 레이아웃에 없습니다 — 붙여 적으면(Common)이 메타로 읽혀 「모르는 키」로 걸리므로, 그 오류 메시지가(text=Common)형태를 안내해야 합니다. 기존tabbit레이아웃의 시트는 교체(§16의 6) 전까지 기존 파서로 유효하고, 이관에서 이 표기도 함께 고칩니다.
4.3 타입 칸을 적는 자리 — 그룹의 정본은 첫 멤버 컬럼
| 컬럼의 형태 | 타입 칸 |
|---|---|
| 스칼라 컬럼 | 그 컬럼에 적습니다 |
인라인(익명) 레코드 그룹 — pos.x · pos.y | 멤버 컬럼마다 적습니다 |
| DSL struct 그룹 — (나) | 그룹의 첫 멤버 컬럼에 struct 이름 하나. 나머지 멤버 칸은 비웁니다 — 적으면 DSL과의 일치를 검사하고 어긋나면 거부합니다(DSL §7.2) |
원소 번호 그룹 — slots[0].id · slots[1].id | 원소 0 의 컬럼에만 적습니다. 이후 원소의 타입 칸은 비웁니다 — 적으면 일치 검사 |
멀티 로우 그룹 — rewards[].itemId | 위 두 규칙과 같습니다. 타입 칸에 적는 것은 원소의 타입입니다 — 배열임은 이름의 []에 이미 적혀 있으므로 타입에 다시 적지 않습니다 |
:desc · :target · 제약 메타도 같은 규칙입니다 — 그룹의 정본은 첫 멤버 컬럼(원소
0)이고, 반복 기재는 일치 검사입니다.
5. :field 행 — 컬럼 경로 표기
경로의 정본은 대괄호 원소 번호와 점 멤버입니다. 다른 레이아웃의
character[0]["Id"](NamedRangeColumnPath)와 기존
tabbit의 Slot1.Id가 이 표기 하나로 합류합니다.
:field 셀 | 뜻 |
|---|---|
id | 필드 하나 |
pos.x | 레코드 pos의 멤버 x |
slots[0].id | 레코드 배열 slots의 원소 0, 멤버 id — 칸의 수가 정해진 배열 |
tags[0] · tags[1] | 스칼라 배열을 컬럼으로 |
pos.x[0] | 멤버가 배열인 레코드 (다중 중첩의 Pos.X1) |
grid[0][1] | 배열의 배열 — 대괄호가 이어지면 안쪽 레벨은 이름이 없습니다 |
rewards[].itemId | 멀티 로우 — 원소가 컬럼이 아니라 행에서 옵니다(§6) |
costs[] | 스칼라 멀티 로우 |
Price@3 · *code · #old@4 | 와이어 태그 · 보조 인덱스 · Tombstone — 기존 그대로 |
- 원소 번호는 0부터이고 빠짐없이 연속해야 합니다.
[1]부터 시작하거나 건너뛰면 그 셀을 가리켜 오류입니다. - 이름의 숫자 접기(
Slot1·Slot2→ 배열)는 이 레이아웃에 없습니다.FoldSerialFields옵션은 없어졌습니다 — 그 옵션이 있던 이유가 옛 표기에 「이 숫자가 배열이다」를 말할 자리가 없었다는 것뿐이고, 대괄호가 그것을 말하므로 존재 이유가 함께 사라집니다(§16.7).Text1·Text2는 그냥 필드 2개입니다. 배열은 언제나[]대괄호로 선언되고, 「이 이름의 숫자가 배열인가」라는 질문 자체가 없어집니다. - 대괄호가 이어지면 레벨이 하나 더 생기고, 그 레벨은 이름이 없습니다.
grid[0][1]은grid의 원소 0의 원소 1이고, 안쪽에 이름이 없는 것이 그 형태의 내용입니다 — 소비자가 적을 낱말이 없으므로 번호로 가리킵니다(다중 중첩의Grid1.2가 같은 것입니다). 이름 없는 레벨은 대괄호로만 생깁니다. 점 뒤에 이름 없이 대괄호를 적는 것(grid.[1])은 오류입니다 — 한 형태를 적는 방법이 둘이 되지 않게 하기 위해서입니다. - 중첩 깊이는 세지 않습니다 —
stars[0].pos.x는slots[0].id와 같은 규칙 한 단계 더입니다(다중 중첩). - 와이어 태그는 와이어 컬럼당 하나입니다.
slots[0].id@4처럼 원소 0의 멤버 컬럼에 달고, 이후 원소 컬럼에는 달지 않습니다(기존 serial field 규칙과 동형). 멀티 로우 그룹은 멤버 컬럼이 곧 와이어 컬럼이므로rewards[].itemId@4처럼 멤버마다 답니다. *보조 인덱스는[]·[n]컬럼에 붙을 수 없습니다(배열은 인덱스가 될 수 없다는 기존 규칙).
5.1 []의 두 자리 — 이름과 타입
[]가 이름에 있으면 행이 원소이고, 타입에 있으면 셀 안이 원소입니다.
| 기재 | 원소가 오는 곳 | 길이 |
|---|---|---|
:field costs + :type int[] | 셀 안 — 10;20;30 | 셀마다 다름 |
:field costs[] + :type int | 행 — 연장 행마다 하나 | 행 수가 정함 |
:field costs[0] costs[1] + :type int | 컬럼 — 칸의 수가 정해짐 | 컬럼 수. TrimTrailingArrayElements로 뒤를 자를 수 있음 |
셋 다 와이어는 같습니다 — KindVarArray 컬럼
하나(가변 길이 레코드 배열)이고, 생성 코드도
같습니다. 다른 것은 시트에서 읽는 방법뿐입니다.
costs[] + int[]처럼 두 자리에 동시에 적는 것(행마다 셀 배열 — 배열의 배열)은 1차에서
거부합니다(§13).
6. 멀티 로우 — 정밀 규칙
레코드 하나가 여러 행에 걸치는 표기입니다. Luban의 * 멀티 로우가 원형이고, 경계 판정을
바꿨습니다(아래 근거).
6.1 규칙
| # | 규칙 |
|---|---|
| 1 | :field에 [] 컬럼이 하나라도 있으면 그 테이블은 멀티 로우 모드입니다. 없으면 지금처럼 한 행이 한 레코드입니다 |
| 2 | 새 레코드의 시작은 기본 인덱스 칸에 값이 있는 행입니다(§3.5의 첫 키 — 복합이면 성분 중 하나라도). 그 칸이 전부 빈 행은 직전 레코드의 연장 행이고, 복합 키의 성분 일부만 채운 행은 그 빈 성분을 가리켜 오류입니다 |
| 3 | 연장 행에서 값이 허용되는 곳은 [] 컬럼뿐입니다. 그 외 컬럼(스칼라 · [n] · 메타가 아닌 것)에 값이 있으면 그 셀을 가리켜 오류입니다 |
| 4 | 원소의 성립 — 레코드에 속한 각 행(첫 행 포함)에서, 한 [] 그룹의 컬럼 범위에 값이 하나라도 있으면 그 행이 그 그룹의 원소 하나입니다. 전부 비어 있으면 그 행에는 그 그룹의 원소가 없습니다 |
| 5 | [] 그룹이 여럿이면 각자 독립적으로 쌓입니다. 길이가 서로 다를 수 있고, 같은 행에 있다고 원소끼리 짝이 되는 것이 아닙니다 |
| 6 | 원소 안의 빈 칸은 일반 규칙입니다 — 빈 칸과 없음과 OnBlankCell 정책 그대로 |
| 7 | 완전히 빈 행은 엔티티의 종료입니다(§3.2). 레코드 사이에 빈 행을 둘 수 없습니다 — 여백이 필요하면 마커 열 # |
| 8 | 마커 열의 # — 레코드의 첫 행에 있으면 연장 행까지 레코드 전체를 제외하고, 연장 행에 있으면 그 행의 원소들만 제외합니다 |
| 9 | 키 컬럼(§3.5)은 []일 수 없습니다 — 배열은 인덱스가 될 수 없다는 기존 규칙 그대로입니다 |
6.2 경계 판정의 근거 — Luban과 다른 곳
Luban의 연장 판정은 「멀티 로우가 아닌 모든 컬럼이 빈 행」입니다. 그 규칙에서는 연장 행에 스칼라 값을 실수로 적으면 그 행이 새 레코드가 되고, 오류는 「키가 비었다」로 납니다 — 사용자가 한 실수(잘못된 자리에 값)와 메시지(키 누락)가 서로 다른 곳을 가리킵니다.
이 설계는 신호를 인덱스 칸 하나로 좁혔습니다. 인덱스가 비면 연장이고, 연장 행의 스칼라
값은 규칙 3이 「이 값은 첫 행에 있어야 합니다」라고 그 셀을 가리킵니다. 반대 실수 —
연장하려던 행의 인덱스에 값을 적음 — 는 새 레코드가 되지만, 그 행의 스칼라 칸들이 비어
있으므로 기본 정책(OnBlankCell: Error)이 검출합니다. 어느 방향의 실수든 조용히 지나가지
않습니다.
6.3 담지 않는 것
| 무엇 | 판정 |
|---|---|
중첩 멀티 로우 — [] 그룹의 멤버가 또 [] | 1차 거부. Luban은 임의 깊이를 지원하지만, 안쪽 배열은 셀 배열이나 [n] 칸으로 충분히 적습니다. 요구가 실측되면 그때 형태를 정합니다 |
[] 그룹의 옵셔널 — []? | 1차 거부. 원소 0개(빈 배열)로 충분하고, 「배열이 없음」이 필요한 컬럼은 셀 배열 T[]?로 적습니다 |
행마다 셀 배열 — costs[] + int[] | 1차 거부(§5.1) |
6.4 와이어 — 형식 무변경
멀티 로우 그룹의 멤버는 KindVarArray 컬럼 하나입니다 —
가변 길이 레코드 배열이 이미 실어 둔 형태이고, 이
레이아웃은 그 배열에 원소를 공급하는 세 번째 방법(행)일 뿐입니다. 형식도 생성 코드도
움직이지 않습니다. TrimTrailingArrayElements · AllowArrayGaps는 [n] 고정 칸의
질문이므로 멀티 로우에는 해당하지 않습니다 — 원소는 있는 행만큼 생기고, 사이가 빈 원소는
규칙 4가 「원소 없음」으로 읽습니다.
7. 예약 컬럼 — $type · $key · $value
:field 행에서 $로 시작하는 이름은 예약 컬럼입니다. Luban의 $type · $key ·
$value와 같은 이름이고, 이번 구현에서는 셋 다 이름을 인식하고 「아직 지원하지 않는다」로
거부합니다 — 자리를 지금 확보해 두면 뒤의 스펙이 문법을 다시 정하지 않습니다.
경로의 마지막 마디에서 판정합니다 — effect.$type은 effect 그룹의 판별자입니다. 그
자리 말고는 어디서도 $가 이름에 들어가지 않으므로, 식별자 검사의 예외도 그 한 자리입니다.
| 예약 컬럼 | 장래의 역할 | 선행 스펙 |
|---|---|---|
$type | 다형 레코드의 변종 판별 — 행(원소)마다 변종 이름을 적는 컬럼 | 다형과 참조 배열이 그 설계입니다 — 와이어는 형식 무변경이고(§6), 이 컬럼의 표기는 그 문서 §5.2입니다 |
$key | map의 키 컬럼 — prices[] 그룹에서 키가 들어가는 자리 | DSL 5′ (set · map). 셀 표기(1:sword;2:shield)와 컬럼 표기의 확정이 거기입니다 |
$value | 다형 변종의 값을 한 셀로 압축(Luban $value) | 거부 유지 — 다형과 참조 배열 §10이 그 판정을 그대로 두었습니다. (다) sep 표기와 겹치는 두 번째 방법이 됩니다 |
Luban에서 확인해 둔 것 중 이 자리의 설계에 물려받을 판정 2가지입니다.
- 다형 변종 컬럼의 열 집합은 모든 변종의 합집합이고, 그 행의 변종에 없는 컬럼은
비웁니다 — 멀티 로우와 결합하면 원소가 되는 행마다
$type셀이 변종을 정합니다. - 옵셔널이지만 다형이 아닌 레코드에까지 판별 컬럼을 강제하는 것(Luban의
null·{}규칙)은 따라가지 않습니다. tabbit의 레코드 멤버 옵셔널은 별도 설계가 이미 있습니다.
7.1 여러 테이블 중 하나와 다형 — 섞이면 안 되는 두 가지
겉모습이 비슷해서 혼동하기 쉽지만 다른 층의 다른 질문입니다. 이 구분이 흐려지면 같은 요구가 두 문법으로 적히게 되므로, 표로 박아 둡니다.
여러 테이블 중 하나 refs=A;B | 다형 레코드 (:type) | |
|---|---|---|
| 질문 | 이 키 값이 어느 테이블의 행인가 | 이 레코드 자체가 어떤 형태인가 |
| 컬럼이 담는 것 | 키 하나. 컬럼의 형태는 행마다 같습니다 | 행마다 다른 멤버 집합 |
| 답이 정해지는 곳 | 값을 대상들의 인덱스에서 찾아서 | :type 셀에 적힌 이름으로 |
| tabbit의 현재 | 검사만 있습니다. 참조로 올리는 것은 되돌렸습니다 — 셀렉터에는 돌려줄 타입이 없습니다 (설계) | 설계까지 — 다형과 참조 배열. 구현은 없습니다 |
| Luban의 대응물 | ref=의 다중 테이블(검사만) | $type |
「보상이 아이템일 수도 장비일 수도 있다」는 참조의 문제이지만 행에 닿는 참조는 아닙니다 —
int (refs=Item;CharGear)으로 적어 값이 어느 쪽의 id인지 검사하고, 행에 닿아야 하면 테이블마다
컬럼을 둡니다. 「효과 레코드의 필드 구성이 행마다 다르다」가 다형의 문제이고, 그것은 여전히
설계 단계입니다(Luban 대조 §5.2).