그림 문법
| 항목 | 값 |
|---|---|
| 상태 | 구현 |
| 관련 결정 | 그림 문법과 배치를 직접 맡고 D2 호환을 버린다, 조건과 대기를 빌드 때 계산해 같은 시간표에 담는다 |
요약
그림 문법은 .thinkflow 파일 하나에 문서 하나를 적는 줄 단위 문법이다. 첫 문장은 thinkflow다. 그 뒤에 카드(도형, 사람, 표, API, 클래스, 칸 격자, 차트, 추적)와 선, 값을 선언하고, 필요하면 카드를 어떤 모양으로 보일지 보기(view)로 고른 다음, scene 줄부터 장면마다 움직임을 적는다. 보기를 적지 않은 카드는 기본 보기를 받는다.
- 문서 하나는 모형 하나다. 카드, 카드 안 칸, 연결점, 선과 라벨, 값, 보기, 장면이 모든 카드에 같은 규칙으로 놓이고, 그림 종류 문장은 없다.
- 같은 카드가 여러 보기(그래프, 순서, 차트, 시간)에 함께 놓일 수 있다. 이름이 같아 한 카드의 상태는 하나이고, 장면 하나의 이동과 값 변화는 한 시간표에서 한 번만 계산된다. 보기마다 그려질 뿐이다. 보기는 이름이 없다.
- 장면은 재생 방식(
mode, 생략하면 줄이 있는 장면은 한 번, 빈 장면은 정지)과 속도(speed)를 가진다. 설명 글은 문서 본문이 맡고 그림 안에는 없다. - 사람이 쓰는 글은 모두 따옴표 안에, 이름과 낱말은 모두 따옴표 밖에 둔다. 같은 일을 적는 방법은 하나뿐이다.
동기
D2와 Mermaid는 같은 뜻을 여러 모양으로 적을 수 있다. D2는 오타 난 이름을 새 도형으로 만들고, 점 경로(a.b.c)와 화살표 방향(->, <-)을 섞어 쓸 수 있다. 사람은 그림을 보고 실수를 알아채지만, AI는 그림을 보지 않고 원본을 만든다. 적는 방법이 하나이고 모든 실수가 줄 번호가 붙은 오류로 나와야 AI가 고칠 수 있다.
예시
장면 없이 카드와 선 그리기
thinkflow
person user "고객"
box api "주문 API" {
text "주문을 검증하고 저장합니다"
value pending "처리 중" from=0
}
store db "저장소"
user -> api "주문"
api -> db "저장"
카드와 선만으로 정지 그림을 만든다. 카드의 본문은 선언 안에서 적으며 value에 on=을 쓰지 않는다. 움직임이 필요하면 선언 뒤에 scene "요청"과 이동 줄을 더한다. 장면이 여러 개여도 같은 본문을 다시 쓰지 않는다.
한 문서에 여러 카드와 보기 놓기
thinkflow
title "주문 한 건"
subtitle "고객 요청이 API 칸에서 스키마 칸으로 저장되고, 같은 사건이 호출 순서, 지연 그래프, 실제 시간에 함께 보인다"
person user "고객"
box gw "Gateway" icon=server
box pay "Payment" icon=server
api create "POST /orders" {
body "OrderRequest"
created "201 Order"
}
table orders "orders" {
id bigint pk
status text
}
value depth "대기" on=pay
value p95 "p95(ms)" from=120
chart lag "처리 지연" bar {
x "처리 시간(ms)"
series ms "ms"
row "p50" ms=40
row "p95" ms=p95
}
chart trend "p95 추이" line {
y "latency(ms)"
series p95 "p95"
point x=1 p95=110
point x=2 p95=p95
}
trace t "결제 추적" unit=ms {
span s1 "POST /pay" lane=gw at=0 dur=180
span s2 "charge" lane=pay at=20 dur=140
}
user -> create.body "요청"
create.body -> orders.id "저장"
user -> gw
gw -> pay
view graph right "구조"
view sequence "호출 순서" {
user gw pay
}
view plot "지연" {
trend
}
view time "실제 시간" {
t
}
scene "주문 한 건" mode=loop speed=1.5
user -> create.body "주문"
create.body -> orders.id set="depth+1"
user -> gw "POST /pay"
gw -> pay "charge" set="p95=150"
pay -> gw "ok" dashed
light t.s2
- 사람(
user)은 표준 카드에 사용자 아이콘(icon=user)이 붙은 카드다. 그래프의user -> create.body는 API 카드(create)의 칸body에,create.body -> orders.id는 테이블orders의 열id에 닿는다. value p95는 카드에 놓이지 않은 값(on=없음)이다. 막대 차트lag의 행ms=p95와 선 차트trend의 점p95=p95가 이 값을 읽는다. 값이 바뀌면 차트가 프레임을 바꾼다.- 첫
view graph는 블록이 없어서, 차트 보기에 놓인trend와 추적t를 뺀 모든 카드를 담는다.lag는 차트 보기에 따로 적지 않았으므로 그래프 안의 차트 카드로 놓인다.view sequence는user gw pay를 참여자로 한 순서 보기다. user -> gw "POST /pay"는 그래프의 선 하나와 순서 보기의 메시지 하나로 함께 보인다. 두 투영은 같은 시각에 같은 시간으로 움직이고, 시간은 먼저 놓인 투영(그래프의 선)이 한 번 정한다.gw -> pay의set="p95=150"은 그 이동이pay에 닿는 시각에 한 번만 값을 바꾼다.pay -> gw "ok" dashed는 선언한 선gw -> pay를 거꾸로 지나고, 순서 보기에서는 점선 메시지다.light t.s2는 시간 보기에서 구간s2를 밝힌다. 구간의 가로 위치와 길이는 시작 시각at과dur에 비례한다.- 장면은
mode=loop speed=1.5라서 1.5배 속도로 되풀이한다. 시간표의 ms는 속도로 바뀌지 않는다.
구성 요소 그림에 요청 흐름 입히기
thinkflow
title "Saturn 구성"
person user "개발자"
group system "Saturn" direction=down {
box cli "명령"
box tui "화면" "하나의 대화"
box engine "엔진" "사용자당 하나"
store db "기록 저장소" "sqlite"
}
external codex "Codex CLI"
user -> cli "명령"
user -> tui "입력"
cli -> engine "엔진 시작"
tui -> engine "JSON-RPC"
engine -> db "기록" quiet
engine -> codex "app-server 요청"
scene "대화"
user -> tui "질문"
show tui "이 테스트 왜 깨져?" tag="you"
tui -> engine
engine -> codex "turn" time=3s
engine -> db
- 사용자가
thinkflow render saturn.thinkflow를 실행한다. system안 도형은 위에서 아래로, 바깥 도형은 왼쪽에서 오른쪽으로 놓인다.- 첫 박자에 점이
user에서tui로 가고, 점이 닿을 때tui카드에YOU태그 줄이 나타난다. engine -> db선은quiet라서 그 선을 처음 지나는 박자부터 보인다.
오타 알아채기
- AI가 위 원본의 24번째 줄을
engine -> cdex "turn" time=3s로 적는다. - 명령이
saturn.thinkflow:24: unknown card "cdex". Did you mean "codex"? Declared: cli, codex, db, engine, system, tui, user를 내고 실패한다. - AI가 이름을 고친다.
장면 사이에 값을 이어 가기
thinkflow
title "값 유지와 읽기"
box web "웹"
box api "API"
store db "DB"
value sent "보낸 수" on=web
value saved "저장한 수" on=db
value mirror "복사본" on=api
queue jobs "대기열" slots=4
web -> api
api -> db
on api sent+1
on db saved+1
scene "첫 요청"
web -> api "요청" set="mirror:=sent"
api -> db "저장" set="jobs+1"
scene "이어서" keep="sent, jobs" set="saved=10"
web -> api "요청" set="mirror:=saved"
api -> db "저장" set="jobs+1"
scene "맞바꿈" keep="saved, mirror" set="sent=20"
web -> api "요청"
api -> db "바꿈" set="sent:=saved, saved:=sent"
- 첫 장면은 모든 값이
from에서 시작한다.web -> api의 점이api에 닿으면on줄로sent가 1이 되고, 그 뒤에set=의mirror:=sent가 바뀐sent를 읽어mirror도 1이 된다. - 둘째 장면은
keep때문에sent와jobs가 1인 채로 시작한다.mirror는keep하지 않아from인 0에서 시작하고,saved는 장면set=의 재설정이 정한 10에서 시작한다. - 셋째 장면의
saved는 11,mirror는 10으로 이어지고sent는 재설정으로 20에서 시작한다.api -> db의 점이db에 닿는 순간on줄이saved를 12로 올린 다음set="sent:=saved, saved:=sent"가 두 값을 읽고 맞바꿔sent는 12,saved는 21이 된다. - 장면을 탭으로 바로 골라도 앞 장면을 다시 재생하지 않는다. 시간표가 장면마다 시작 값을 담고 있어서다.
잠금이 풀릴 때까지 기다리기
thinkflow
title "잠금으로 막기"
box a "작업 A"
box b "작업 B"
box lock "잠금"
value holder "쥔 쪽" on=lock from=none
a -> lock
b -> lock
scene "A가 잠금을 쥔다"
a -> lock "요청" set="holder=A"
scene "풀리면 B가 들어간다" keep="holder"
a -> lock "풀기" set="holder=none" & b -> lock "요청" wait="holder='none'" set="holder=B"
- 첫 장면에서 A의 점이
lock에 닿아holder가A가 된다. - 둘째 장면은
keep때문에holder가A인 채로 시작한다. - 같은 박자에 두 이동이 시작하지만 B의
wait조건holder='none'이 거짓이라 B의 점은 출발하지 않는다. - A의 점이
lock에 닿으면holder가none이 되고, 그 시각에 B의 대기가 풀려 B의 점이 출발한다. - B의 점이
lock에 닿으면holder가B가 된다. 이 순서는 빌드 때 한 번 정해져 SVG와 재생기가 같게 보인다.
풀리지 않는 대기 남기기
thinkflow
title "교착"
box t1 "작업 1"
box t2 "작업 2"
box l1 "잠금 가"
box l2 "잠금 나"
value h1 "가를 쥔 쪽" on=l1 from=none
value h2 "나를 쥔 쪽" on=l2 from=none
t1 -> l1
t1 -> l2
t2 -> l2
t2 -> l1
scene "각자 하나씩 쥔다"
t1 -> l1 "쥠" set="h1=t1" & t2 -> l2 "쥠" set="h2=t2"
scene "서로 상대 것을 요청한다" keep="h1, h2" status="t1=wait, t2=wait"
t1 -> l2 "요청" wait="h2='none'" stuck & t2 -> l1 "요청" wait="h1='none'" stuck
- 둘째 장면이 시작하면
h1은t1,h2는t2다. - 두 대기 조건이 모두 거짓이고 그 박자에 예정된 이벤트가 없어서 두 대기의 계산이 그 자리에서 멈춘다. 점이 하나도 남지 않은 박자는 점 없이 멈추는 박자가 된다.
stuck이 있어 경고는 나지 않고, 대기 중인 도형과 조건이 시간표에 남는다.stuck이 없으면 같은 내용이wait-stalled경고로 나온다.
느려진 소비자만 표시하기
thinkflow
title "느려진 소비자"
box prod "생산자"
box hub "브로커"
box fast "빠른 소비자"
box slow "느린 소비자"
prod -> hub
hub -> fast
hub -> slow
scene "평상시" for=6s
track prod -> hub -> fast "주문" every=1s time=2s
track prod -> hub -> slow "주문" at=500ms every=1s time=2s
scene "소비자 하나가 느려진다" for=6s status="slow=warn"
track prod -> hub -> fast "주문" every=1s time=2s
track prod -> hub -> slow "주문" at=500ms every=1s time=4s legs="500ms, -"
- 둘째 장면에서만
slow에WARN알약이 붙는다. 이름과 기호가 있어 색을 구별하지 못해도 읽힌다. slow로 가는 점은 첫 구간 500ms에 지나고, 남은 3.5초가 둘째 구간의 시간이다.- 다음 장면이 시작하면
slow는 선언 상태로 돌아간다.
상세 설계
줄과 낱말
- 한 줄에 문장 하나다. 빈 줄은 무시한다. 들여쓰기는 읽기용이다.
#부터 줄 끝까지는 주석이다. 따옴표 안의#는 글자다.- 따옴표 밖의 공백, 탭, 줄 끝 CR(Windows 줄바꿈), 파일 앞 BOM, 유니코드 공백은 모두 공백으로 읽는다. 편집기 설정과 상관없이 같은 원본이 같은 그림이 되게 하기 위해서다.
- 낱말은 공백 하나 이상으로 나눈다.
->,&,{,}는 앞뒤에 공백이 있어야 하는 낱말이다.a->b는 오류다. {는 블록(그룹, 테이블, API, 클래스, 격자, 차트, 추적, 보기)을 여는 줄의 마지막 낱말이고,}는 혼자 한 줄이다. 안에 아무것도 없는 그룹, 테이블, API, 격자, 추적은 오류다.- 글은 큰따옴표 안에 쓴다. 빈 글(
"", 공백만 있는 글)은 오류다. 이름 없는 도형이나 빈 항목을 막기 위해서다. 따옴표 안의\"는 따옴표,\\는 역슬래시다. 그 밖의 역슬래시와 줄바꿈은 오류다. - 글 안에서 백틱(
`)으로 감싼 부분은 코드다. 마크다운처럼 백틱은 그리지 않고 그 구간만 고정폭 글꼴로 그린다(예:"`session/start` 요청"). 코드 안 한글은 Pretendard로 그린다. 도형 이름, 부제, 그룹 제목, 카드 글, 선 라벨, 노트, 장면 이름, 차트 글에 쓸 수 있다. 백틱 짝이 맞지 않으면 그 줄의 오류다. 글을 그대로 읽는 칸(클래스 멤버, 테이블과 API 칸의 타입, 카드의mono줄, 값 글인from과set의 값)은 이미 전부 코드이거나 사용자 자료라서 백틱도 글자다. 그 칸에 쓴 백틱은 짝이 맞든 홀수 개든 지워지지 않고 그대로 그려지고, 잰 폭도 그 글자를 센다. 원본 검사는 이 칸 밖의 글에서만 백틱 짝을 보므로 홀수 개의 백틱은 그 칸에서 오류가 아니다. 그 밖의 칸에서 백틱 글자 자체를 그릴 방법은 없다. - 선택 사항은
키=값한 낱말이다.=앞뒤 공백은 오류다. 선택 사항의 순서는 자유다. 같은 키를 두 번 쓰면 오류다. - 값이 없는 선택 사항(
quiet,dashed,mono,pk,unique)은 정해진 낱말만 쓴다. 이름 자리의quiet같은 낱말은 이름이다.
이름
- 이름(id)은 영어 소문자로 시작하고 영어 소문자, 숫자,
-만 쓴다.-는 낱말 사이에 하나씩만 온다(a-,a--b는 오류). 테이블 이름은-대신_를 같은 규칙으로 쓴다. 칸(열, 필드) 이름은 영문자로 시작하고 영문자(대소문자), 숫자,_를 쓴다. - 파일 안에서 카드, 그룹, 값 이름은 한 이름 공간이고 서로 겹치지 않는다. 보기는 이름이 없어 이 공간에 들지 않는다. 겹치면
the name "g" is already used (line N)오류(코드syntax)를 겹친 선언의 줄에 내고, 이름을 가리키는 선과 이동, 보기 확인은 하지 않는다. - 카드 안의 이름은 그 카드 안에서만 겹치지 않는다. 테이블과 API의 칸, 클래스의 멤버, 격자의 칸, 추적의 구간, 차트의 계열이다. 서로 다른 차트가 같은 계열 이름을 써도 된다.
- 카드 안 칸을 가리키는 연결점은
카드.칸하나다. 점이 둘 이상인 이름(a.b.c)은write a part as card.part오류다. 연결점이 되는 카드는 테이블, API(열), 칸 격자(item)다. 클래스 멤버는 연결점이 아니라서class members are not ports. Connect "order"오류이고, 칸이 없는 카드에 칸을 쓰면a box has no parts오류다.light는 여기에 추적의 구간(추적.구간)도 가리킨다. - 이름 오류로 버린 선언의 이름을 가리키는 줄에는
unknown card오류를 덧붙이지 않는다. 그 선언이 있던 그룹에도group is empty오류를 덧붙이지 않는다. 원인이 이름 오류 하나이기 때문이다. - 카드(순서 보기의 참여자도 카드다)가 하나도 없으면 오류다.
- 이름 자리에서는 예약어 검사를 하지 않는다.
box data "데이터",box scene "장면",table row "행" {처럼 문장 낱말, 선택 사항 키, 값 없는 선택 사항 낱말(quiet,dashed,mono,pk,unique)을 모두 이름으로 쓸 수 있다. - 문장 종류는 첫 낱말로 정하고, 둘째 낱말이
->인 줄은 첫 낱말이 문장 낱말이어도 선 줄(시간 흐름 안에서는 이동 줄)로 읽는다. 그래서scene -> c는scene이라는 이름에서c로 가는 선이다. - 블록(
{…}) 안의 줄은 그 블록의 읽기가 정한다. 테이블과 API 블록 안 줄의 첫 낱말은 늘 칸 이름이고(state varchar도 열이다), 보기 블록 안 줄은 카드 이름 목록이다.
값 형식
선택 사항의 키와 값 목록은 문법 표가 정본이다. 차트 행의 값은 아래와 같다.
| 키 | 값 |
|---|---|
계열 이름, x, y, min, q1, median, q3, max, 계열.low, 계열.high |
숫자. 빠진 값 -는 문법 표에서 allowsMissing인 종류의 값 자리(막대, 누적, 퍼센트, 선, 계단, 누적분포, 히스토그램, 워터폴, 상자)에서만 된다. x, 계열.low, 계열.high는 안 된다 |
- 시간은
900ms나2s이고 길이는 0보다 크다. 시작 시각인at=은0s도 허용한다. 단위 없는 숫자는 시간이 아니다. 시간 값 하나는 1시간(3600s, 3600000ms)을 넘을 수 없고, 넘으면 그 줄의 오류다(code는time-limit).pace,for,wait,time=,at=,every=,timeout=,legs=의 항목 모두 같다. 장면 하나의 길이와 그림 전체 시간에도 같은 상한이 있다(재생).when이나wait를 쓰는 장면은 시간 값이 0.00001ms의 정수배여야 하고,0.000001ms처럼 더 가는 값은 그 줄의 오류다(code는time-precision, 재생). - 숫자는
-와 소수점만 쓰는 십진수다(-3,91.4). 천 단위 쉼표와 지수 표기는 오류다. 음수를 쓸 수 있는 자리는 차트의 값 축 표가 정한다. - 글 값은 늘 따옴표 안에 쓴다.
tag=you는 오류다.
파일 구조
첫 문장은 늘 thinkflow다. 그 뒤에 세 부분이 이 순서로 온다. 앞 부분으로 돌아가는 줄은 오류다.
| 순서 | 부분 | 줄 |
|---|---|---|
| 1 | 머리 | title, subtitle, pace, aspect, width |
| 2 | 선언 | 카드, 그룹, 선, 값, on 줄, 아이콘 세트, 보기. 이 부분 안에서는 순서가 자유다 |
| 3 | 시간 흐름 | scene과 그 아래 박자 줄 |
- 같은 머리 줄을 두 번 쓰면 오류다. 시간 흐름의 첫 줄은
scene이다. - 그림 종류 문장(
flow right,sequence,state,data,class,chart bar)은 없다. 카드를 선언하고 보기를 고른다. 보기 줄이 없으면 기본 보기가 정해진다(보기).
| 머리 줄 | 뜻 | 기본값 |
|---|---|---|
thinkflow |
시작 선언. 파일의 첫 문장이며 숫자나 옵션을 받지 않는다(시작 선언과 없앤 형태) | 없음. 꼭 적는다 |
title "글" |
문서 제목. SVG <title>, 목록 쪽 머리 제목 |
파일 이름 |
subtitle "글" |
문서 아래 한 줄 설명 | 없음 |
pace 3s |
점이 기준 길이 size.packet.hop-ref의 선을 지나는 시간. 선 길이에 비례해 이동 시간이 정해져 모든 이동이 같은 속도로 보인다(아래 이동 시간). 차트 카드에서는 계열이 자라는 시간이다 |
토큰 duration.hop과 size.packet.hop-ref, 자라는 시간은 duration.reveal |
width wide |
캔버스 폭. wide는 size.figure-canvas-wide(1440)이고 standard는 표준 폭(960)이다. 배치 |
생략하면 표준 폭 |
aspect 1.6 |
그래프 보기의 목표 가로세로 비율. 배치 | 없음(캔버스 폭보다 넓으면 도구가 자동으로 접는다) |
pace는 점의 이동 속도이고, 장면의 speed는 재생 배속이다. 둘은 다르다. pace는 시간표의 ms를 바꾸고 speed는 바꾸지 않는다.
카드 선언
box, person, external, store는 선언 끝에 {를 붙여 본문을 담는다. 본문이 없는 카드의 한 줄 선언도 그대로 쓴다. 원(shape=circle), 갈림길, 상태, 큐는 본문 블록을 받지 않는다.
| 본문 줄 | 뜻 |
|---|---|
text "글" |
show와 같은 글·태그·덧붙임·표시·색 선택 사항을 가진 줄 |
value id "이름" [from=값 또는 ref=값] |
이 카드의 값. on=은 오류이고 이름은 문서 전체에서 유일하다 |
graph "가 -> 나; 가 -> 다" [lit="가"] |
show id graph와 같은 작은 관계 그래프 |
chart id "제목" 종류 { ... } |
독립 차트와 같은 문법을 쓰는 내부 차트 |
본문은 적은 순서대로 놓인다. 블록 밖에서 value ... on=카드로 붙인 값은 본문 앞에 놓인다. 장면마다 선언한 본문에서 시작하고 show는 끝에 줄을 더하며, clear는 값 줄을 남기고 글·관계 그래프를 비운다. 처음 본문이 있는 카드는 장면 첫 줄에서도 clear할 수 있다. 다음 장면에서는 선언한 본문이 복원된다. 내부 차트는 카드 본문의 한 줄이고 별도 노드나 판이 아니다. 차트 이름은 문서 전체에서 유일하며 reveal, light와 값 묶음은 같은 이름을 쓴다. 선과 보기는 차트의 바깥 카드를 가리킨다. 내부 차트를 별도 보기에 넣거나 직접 연결하면 줄 번호 오류다. 내부 차트의 계산·축·표식·효과는 독립 차트와 같다. 본문은 그래프 보기에 그리므로 순서 보기에만 둔 카드의 본문은 해당 줄 오류다.
모든 카드는 같은 틀(테두리, 머리, 줄, 연결점)을 쓰고 모양만 다르다. 줄 하나로 끝나는 카드와 {로 여는 블록 카드가 있다.
| 줄 | 뜻 |
|---|---|
person id "이름" [tone=색] [appearance=모양] [icon=이름] |
사람. 표준 카드에 사용자 아이콘(icon=user)이 붙는다. 다른 아이콘을 골라도 된다 |
box id "이름" ["부제"] [shape=rect|circle|tile] [badge="글"] [icon=이름] [count=N] [tone=색] [appearance=모양] |
구성 요소, 모듈. shape=circle은 합류 연산(⊕)처럼 짧은 이름을 담는 작은 원이고 부제가 없다 |
external id "이름" ["부제"] [tone=색] [appearance=모양] |
외부 프로그램, 외부 서비스. 점선 테두리 |
store id "이름" ["부제"] [tone=색] [appearance=모양] |
파일, 데이터베이스. 원통 |
decision id "질문" |
갈림길. 마름모 |
queue id "이름" slots=N [from=K] |
큐 |
state id "이름", start id, final id |
상태, 처음 점, 끝 겹원 |
table id "이름" { 열 줄 } |
테이블. 열 줄은 이름 타입 [pk] [unique] [nullable] [required] [fk=테이블.열] [ondelete=정책] [from="다중성"] [to="다중성"] |
api id "METHOD /경로" { 칸 줄 } |
API. 제목은 메서드(GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)와 경로(/로 시작하거나 http(s):// 주소)다. 칸 줄은 이름 "타입"이고 칸에는 선택 사항이 없다. 문서화용 카드이고 실행하지 않는다 |
class id "이름" [abstract] { 멤버 줄 }, interface id "이름" { … } |
클래스와 인터페이스. 멤버는 field 이름 "타입", method 이름 "(매개변수): 반환" |
grid id "제목" [rows=N] [cols=N] { item, gap 줄 } |
칸 격자(칸 격자) |
chart id "제목" 종류 ["부제"] { 차트 줄 } |
차트 카드(차트 카드) |
trace id "제목" [unit=ms|us|s] { span 줄 } |
추적 카드(추적 카드) |
group id "이름" [direction=right|down] [border=dashed] [badge="글"] [icon=이름] [tone=색] [appearance=모양] {, } |
그룹. 두 줄 사이에 카드와 그룹을 둔다. 제목 줄에 배지와 아이콘을 달 수 있다 |
icons 이름 "폴더" |
사용자 아이콘 세트 등록. 폴더의 <이름>.svg를 icon=세트:이름으로 쓴다 |
value id "이름" [on=카드] [from=값 | ref=값id] |
값 |
on 카드 id+N, id-N, id=낱말 |
어떤 점이든 그 카드에 닿을 때 값을 바꾼다. 값 선언 뒤에 둔다 |
a[.칸] -> b[.칸] ["라벨"] [quiet] [dashed] [head=end|both|none] [no=N] [relation=종류] [from="다중성"] [to="다중성"] |
선 |
- 카드 크기와 굵기는 적지 않는다. 크기는 글과 카드 내용으로, 모양은 토큰으로 정한다. 색은 기본이 칠하지 않음(중립 면)이고, 고르려면 아래 도형 색의
tone과appearance만 쓴다. 아이콘은icon=으로 고르되 모양과 색은 정하지 않는다(배치). - 선의 양 끝은 선언된 카드나 그룹이고
카드.칸도 된다. 선은 카드 선언보다 앞에 와도 된다. 파일을 다 읽은 뒤 이름을 확인한다. - 선 하나의 끝 종류가 선택 사항을 정한다. 두 끝이 모두 클래스나 인터페이스면 관계 종류(
relation=, 기본association)와 다중성(from=,to=)을 쓸 수 있고, 관계 종류가 점선과 화살촉을 정한다. 두 테이블도 같은 다중성을 쓰며 관계 종류는association만 받는다. 그 밖의 선에 이 선택 사항을 쓰면 오류다. 두 상태를 잇는 선(전이)은 사건 라벨이 필요하다. - 테이블 열의
fk=는 선이 된다. 가리키는 열은pk나unique다. 직접 쓴 선은 설명을 위한 연결이고 외래 키 제약을 만들지 않는다. 같은 두 카드 사이에 직접 쓴 선과 외래 키 선이 함께 있어도 되지만 같은 열 쌍을 중복 선언하면 오류다. 외래 키 열의from=,to=는 각각 이 테이블과 참조 대상 쪽 다중성을 표시한다. 생략한 다중성은 추정하지 않는다. - 같은 방향의 두 끝 사이 선은 하나다.
a -> b가 둘이면 오류다.a -> b와b -> a는 함께 둘 수 있다. 거꾸로 적는<-는 없다. - 자기 자신으로 가는 선과, 그룹과 그 안 모든 하위 카드와 그룹 사이의 선은 오류다. 예외는 상태와 클래스의 자기 연결, 한 격자의 서로 다른 두 칸을 잇는 선(
g.a -> g.b, 라벨 없음), 테이블이나 API 카드의 서로 다른 두 열을 잇는 선(products.parent_id -> products.id, 자기 참조 외래 키fk=도 같다, 라벨 가능)이다. 같은 열끼리, 카드 전체를 잇는 선, 한쪽만 열인 선은 오류다. - 모든 선은 두 끝을 함께 담은 그래프 보기가 하나 이상 있어야 한다. 없으면
edge a -> b is not drawn: no graph view holds both "a" and "b"오류다(외래 키 선은 열 줄에서 알린다). head는 화살촉 자리다.end(기본값)는 도착 끝,both는 양 끝,none은 없는 무방향 선이다. 이동 점은head와 상관없이 적은 방향(a -> b)으로 가고 거꾸로 이동도 선을 따라간다.dashed선은 비동기 흐름이나 선택적 흐름이다. 점선 테두리 카드(external)와 뜻이 다르다.quiet선은 그 선을 처음 지나는 박자부터 그 장면 끝까지만 보인다. 한 번도 지나지 않는quiet선은 그림 검사 11번 경고가 난다.badge,icon,count,border는 어느 카드나 그룹에서든 같은 규칙이다.person의icon을 생략하면user다.
번호, 배지, 아이콘, 복제 개수
no=N(1 이상 정수)은 선 번호다. 라벨 알약 왼쪽에 번호 원이 붙고(라벨이 없으면 번호 원만), 정지 SVG와 문서에서도 순서가 읽힌다. 재생 장면과 독립이고 같은 번호를 여러 선에 써도 된다.badge="글"(8자 이하)은 카드 윗줄과 그룹 제목 줄에 글자 알약을 단다. 흑백에서도 구성 요소의 종류가 글자로 남는다. 배지는 면과 테두리 색을 바꾸지 않는다. 원(shape=circle)은 배지와 아이콘이 오류다.shape=tile(box만,icon=필수)은 아이콘 카드다. 같은 흰 카드에 아이콘을 크게(size.icon.tile) 위에 놓고 이름을 아래에 두며, 최소 너비와 이름 양옆 간격이 작아(size.node.tile-width,size.node.tile-pad) 가로로 퍼진 구성도가 캔버스 폭 안에 든다.- 그룹
border=dashed는 경계 그룹의 점선 테두리다. 그룹 아이콘은 틀 왼쪽 위 모서리에 딱 붙는 정사각 탭(size.group.title)이다. 기본 그룹 면은 투명하다.tone과appearance=filled를 함께 적으면 해당 계열의 옅은 면을 칠하고,appearance=outline은 경계에 색을 쓴다.light가 켠 그룹은 틀의 경계 색만 강조 색이 되고 굵기, 면, 모서리는 그대로다. icon=이름은 기본 세트의 이름이다. 범용 개념(server,db,user)과 기술 브랜드(git,postgresql)가 한 표에 있다. 등록한 세트는icon=세트:이름이다. 이름이 없거나 파일이 없으면 오류이고, 아이콘 없이 배지로 같은 뜻을 낸다.count=N(2 이상,box만)은 같은 역할 복제 개수다. 상자 뒤에 윤곽 두 겹이 겹쳐 보이고 윗줄에(N)알약이 붙는다. 복제는 이름으로 가리킬 수 없고 선은 상자 하나에 닿는다.
도형 색
- 색은
tone=색과appearance=plain|filled|outline두 선택 사항으로 고른다.person,box,external,store,group,show(카드 줄)가 같은 두 키를 쓴다. 둘 다 생략할 수 있고 생략하면 중립 면이다(카드 기본 바탕은color.ui-card,appearance의 기본은plain). appearance가tone을 어디에 쓰는지 정한다.plain: 중립 면 그대로 색 아이콘과 작은 표식에만 색을 쓴다.show에서는tag알약의 색이다.filled: 같은 계열의 옅은 면.outline: 같은 계열의 경계와 중립 면.
filled와outline은 칠할 색이 있어야 해서tone없이 쓰면appearance=filled needs tone오류다.show의tone은plain이면 알약을 칠하므로tag가 있어야 하고(tone colors a tag or, with appearance=filled|outline, the card),appearance=filled|outline이면tag없이 그 내용의 카드 면이나 경계를 칠한다. 차트의 관측값과 기준값을 가르는 채움과 빈 표시(series의role)는 이 규칙과 별개의 차트 역할이다.- 색 이름은 문법 표의
tone값 목록이 정한다: 범주 팔레트 계열 일곱(blue,yellow,red,green,orange,purple,cyan)과 무채색gray. 흐름의 이동 점 색(a -> b와track의tone=)도 같은 목록이다. hex와 따옴표 글은 오류다. 대비 규칙(글자 4.5, 그래픽 3)을 원본이 깨지 못하게 하기 위해서다. 값 없이tone=#ff0000처럼 쓰면#부터 주석이라는 안내가 붙는다. fill=,stroke=,card=는 없다. 쓰면 그 줄이 받는 선택 사항 목록을 알리는 오류다.- 색 단계의 값과 대비는 색 역할이 정한다.
값
-
value id "이름" on=격자.칸 [from=값 | ref=값id]는 해당item의 글을 값으로 바꾼다.item의 원래 글과 값의 이름은 셀 안에 중복해서 그리지 않는다. 한 칸에는 값 하나만 놓을 수 있고gap, 없는 칸, 격자 전체는 대상이 아니다. 값 바꾸기·참조·장면 초기화·keep은 카드 값과 같다. 모든 장면에서 나타날 글의 폭과 줄 수를 미리 재므로 값이 바뀔 때 칸 크기나 연결점은 움직이지 않는다.show와clear는 기존 카드 본문 명령이며 격자 칸에는 쓰지 않는다. -
value id "이름" [on=카드] [from=값]은on카드에이름 값줄을 만든다.from은 처음 값이고 생략하면0이다.on은 카드를 쓰는 도형(box,external,store,person,table,api)이고, 그 카드를 그리는 그래프 보기에 놓여야 한다. 순서 보기는 참여자 머리만 그려 값 줄 자리가 없어서, 순서 보기에만 놓인 카드에on=을 쓰면 줄 번호가 붙은 오류(value "n" is on "s", which no graph view shows)이고 같은 카드를 그래프 보기에도 놓으면 된다.on=이 없는 값은 카드에 보이지 않고 조건(when,wait)과 차트의 묶음이 읽는다. 큐는 같은 이름의 값을 스스로 가진다(큐). 값 이름은 파일 전체에서 카드, 그룹 이름과 겹치지 못한다. -
값 글자의 길이는 제한하지 않는다. 카드의 값 자리 폭은 그 값이 모든 장면에서 가질 글(처음 글, 장면
set=과 이동의set=이 정하는 글, 계산한 글) 가운데 가장 넓은 것의 실제 글꼴 폭이고, 카드가 담을 수 있는 몫(안쪽 폭의 0.6)을 넘는 글은 그 폭에서 글자 단위로 줄을 나눈다. 줄 높이는 글을 나눈 가장 많은 줄이다. 자리는 장면마다 달라지지 않고, 장면 3에만 나오는 가장 긴 글도 장면 1의 자리에 미리 잡힌다. -
시간표를 만들기 전에는 계산한 글(
n+1의 결과)을 알 수 없다. 그래서 배치와 시간표를 되풀이하되 값 자리는 줄어들지 않고 글 집합은 합집합으로만 늘며, 네 번을 넘어도 자리가 바뀌면 값 선언 줄의layout-unstable오류다(value "q" text set does not settle; its width changes hop times). -
선언한 값은 모든 장면의 카드에 장면 시작부터 늘 보인다. 값을 바꾸는 식이 있든 없든 같다. 바뀌는 순간에 그 줄이 짧은 펄스로 밝아진다(재생).
-
value id "이름" on=카드 ref=다른값id는 참조다. 가리키는 값이 바뀌면 참조 값 줄도 같은 순간 같은 값으로 바뀌고 둘 다 밝아진다. 참조의 참조는 된다. 순환과 없는 값을 가리키는 것은 오류이고,from과ref를 함께 쓰는 것도 오류다. 참조 값에 식으로 직접 쓰면 오류다. -
값은 장면이 시작할 때
from으로 돌아간다. 장면의keep에 적은 값만 앞 장면이 끝난 값에서 시작한다(값 유지와 읽기). -
on 카드 식, 식은 어떤 점이든 그 카드에 닿을 때 식을 적용한다. 박자의 이동과 흐름 모두에 적용되고, 출발 카드는 닿는 것이 아니라 뺀다. 이동과 흐름의set=은 그 이동에만 덧붙는다. 식은id+N,id-N,id=N,id=낱말이다.=뒤는 언제나 값 글자이고 다른 값의 이름과 같아도 그 글자다. 공백이 든 낱말은 오류다.+,-뒤 숫자는 차트 숫자(charts.md 값 범위)와 같이 유한하고 절댓값이 1e15 미만이어야 한다. 합의 결과도 같은 범위이고 넘으면 그 식 줄의value-type오류다.on에는@카드를 쓰지 않는다.
큐
queue id "이름" slots=N [from=K]는 이름 아래에 칸 N개를 놓은 둥근 사각형이다. 도형 선호 폭을 넘으면 여러 줄로 나눈다. 찬 칸은 각 행의 왼쪽부터, 위 행에서 아래 행으로 채운다.slots는 1 이상 32 이하 정수이고 꼭 적는다. 0, 33 이상, 소수, 음수는 오류다. 개별 칸을 세어 읽는 표현의 현재 문법 상한은 32이며, 줄 나눔은 이 상한이나 큐의 값을 바꾸지 않는다.from은 처음 찬 칸 수다. 0 이상slots이하 정수이고 생략하면 0(빈 큐)이다.slots보다 크면 오류다. 부제, 배지, 아이콘,tone,appearance, 카드는 없다.- 큐는 자기 이름으로 부르는 값 하나(찬 칸 수)를 스스로 가진다. 따로
value줄을 쓰지 않고, 위 값과 같은 식으로 바꾼다. 식의 값 이름 자리에 큐 이름을 쓴다:on q q+1(점이q에 닿을 때 한 칸 채움),set="q-1@q"(나갈 때 한 칸 비움),set="q=0". 적용 순서, 장면이 시작할 때from으로 돌아가는 것, 출발 도형은 닿는 것이 아니라 빼는 것도 값과 같다. 이 값의 식은 정수만 받는다(q+1.5,q=낱말은 오류). - 큐의 값에는 카드 줄이 없다(
value의on=큐는 오류). 찬 칸 수를 숫자로도 보이려면 다른 도형에 참조를 단다:value len "큐 길이" on=prod ref=q. - 값 범위: 값 자체는 자르지 않고 식대로 계산하지만 그림은 음수를 0칸(빈 큐),
slots를 넘는 값을 가득 찬 칸으로 그린다. 그래서0에서q-1을 하면 값은 -1이고 그림은 빈 큐이며, 이어서q+1을 하면 값이 0이라 여전히 빈 큐다. 이렇게 값이 범위를 벗어나면 그림 검사 14번이 큐마다 아래쪽과 위쪽 한 번씩 경고한다. 정확히 0과slots는 범위 안이라 경고가 없다. - 정지 SVG는 고른 장면의 마지막 찬 칸 수를 그린다.
from칸 수를 그리는 것은 장면이 없는 문서뿐이다. 움직이는 SVG와 재생기는 장면이 시작할 때from(keep한 큐는 앞 장면의 끝 값)을 그리고, 값이 바뀌는 순간 찬 칸 수가 바뀌며 큐와 같은 윤곽의 배경 면이 갱신 펄스 동안 밝아진다(재생). - 연결점은 상자(
box)와 같다.size.node.max-width에서 안쪽 여백을 뺀 폭에 칸을 몇 개 놓을지 계산한다. 칸 폭size.queue.slot-width와 칸 높이size.queue.slot-height는 줄이지 않고, 가로·세로 칸 사이는size.queue.slot-gap을 쓴다. 너비는 이름 폭과 칸 묶음 폭 가운데 큰 쪽이며 최소size.node.min-width이다. 높이는 이름 줄과 칸 행 수로 정한다(배치). 선은 네 면 어디에나 닿는다. - 색은 토큰이다. 도형 면과 1px 외곽선은 다른 도형과 같다(
color.ui-card,color.outline, 대비 3). 찬 칸은 브랜드 파랑color.figure.queue-fill, 빈 칸은 무채색 면color.figure.queue-empty에color.outline외곽선이다. 라이트와 다크 모두 찬 칸은 도형 면과 빈 칸 위에서 대비 3 이상이다. 칸 수는 색만이 아니라 칸 개수로도 읽힌다.
보기
보기(view)는 같은 문서의 카드를 한 가지 방식으로 보이는 판이다. 판은 위에서 아래로 쌓여 한 그림이 되고, 라벨("라벨")이 있으면 판 위에 제목 줄이 생긴다. 보기는 이름이 없다. 어떤 문장도 보기를 가리키지 않기 때문이다. 내부에서는 정해진 순서대로 v1, v2, …를 붙여 시간표와 장면이 보기를 가리키는 열쇠로 쓴다.
view graph [right|down] ["라벨"] [{ 카드 이름들 }]
view sequence ["라벨"] {
참여자 이름들
}
view plot ["라벨"] {
차트 카드 하나
}
view time ["라벨"] {
추적 카드 하나
}
| 방식 | 구성원 | 그리는 것 |
|---|---|---|
graph |
도형, 사람, 표, API, 클래스, 격자, 상태, 차트 카드, 그룹. 추적 카드는 못 든다 | 카드와 선. elkjs 배치(배치) |
sequence |
person, box, external, store, queue, table, api, class·interface. 표, API, 클래스는 공통 카드 머리(아이콘과 이름)만 참여자로 보이고 칸과 멤버는 같은 카드를 담은 그래프 보기가 보인다. 적은 순서가 참여자 순서다 |
참여자 열, 이 보기에 보이는 이동의 메시지, 메모, 활성 구간, 구획 |
plot |
차트 카드 정확히 하나 | 문서 폭의 차트 |
time |
추적 카드 정확히 하나 | 레인과 시간 축 위의 구간 |
- 구성원이 아닌 이름, 방식에 맞지 않는 카드(
a chart card cannot be a sequence participant)는 구성원 이름의 자리에서 오류다. - 카드는 보기 여럿에 함께 놓일 수 있다. 같은 이름의 카드가 보기마다 한 번씩 그려지고 상태는 하나다.
- 그래프 보기의 구성원은 그룹을 적으면 그 안 카드와 그룹 전체를 담는다. 그룹 안 카드를 그룹 없이 적으면
list group "g" instead of "x"오류다. 블록을 생략한 그래프 보기는 추적 카드와plot보기(적은 것과 기본 것)에 놓인 차트를 뺀 모든 카드를 담고(순서 보기에 놓인 카드는 들어간다), 그 차트를 그래프에도 그리려면 블록에 적는다. view main graph처럼 보기 방식 앞에 이름을 쓰면a view is one of graph, sequence, plot, time. Found "main"오류다.- 기본 보기. 적은 보기가 먼저 구성원을 정하고(적은 순서가 판 순서), 어느 보기에도 적히지 않은 카드는 다음 규칙으로 기본 보기를 받는다. 기본 보기는 적은 보기 뒤에 카드 선언 순서로 쌓인다. 같은 카드가 기본 보기와 적은 보기에 겹쳐 그려지는 일은 없다.
- 추적 카드는 카드마다
time보기 하나. - 남은 카드가 차트뿐이고 그래프 보기를 적지 않았으며 그룹 안 차트도 없으면 차트마다
plot보기 하나. 일반 카드와 섞이거나 그래프 보기를 적으면 차트도 그래프 카드다. 연결선 유무는 보기 종류를 바꾸지 않는다. - 나머지 그래프로 그릴 수 있는 카드(그룹 나무는 온전히)는
graph right하나. 블록 없는view graph를 적었으면 그 보기가 맡으므로 만들지 않는다.
- 순서, 차트, 시간 보기에 적은 카드는 “적힌” 카드라 기본 그래프에 들어가지 않는다. 그 카드를 그래프에도 보이려면 그래프 블록에 적는다.
- 보기 줄이 하나도 없으면 위 규칙만으로 보기가 정해진다. 카드가 차트 하나뿐이면 그
plot하나다.
- 추적 카드는 카드마다
- 어떤 보기에도 놓이지 않은 카드는
card "t" is not shown in any view오류다. 위 규칙이 모든 카드를 맡으므로 이 오류는 구성원 오류로 거절된 카드 말고는 나오지 않는다. - 가로 방향(
right)이 기본이고, 그룹마다direction=으로 바꾼다. - 순서 보기 블록의 참여자 순서가 처음 메시지를 보내는 순서와 다르면 순서가 처음 어긋난 참여자를 적은 줄에서 경고한다(
--strict이면 오류). 보내지 않는 참여자는 뒤에 와도 된다.
이동의 투영
장면의 이동 a -> b는 보기마다 한 번씩 보인다. 모든 투영은 한 이동이다.
- 그래프 보기가 두 끝(연결점은 카드로 본다)을 함께 담으면, 그 보기 안의 같은 방향 선을 따라간다. 없으면 반대 방향 선을 거꾸로 따라간다. 한 보기에 후보가 둘 이상이면
there are N edges between "a" and "b". Write the columns or cells오류다. - 순서 보기가 두 끝을 함께 담으면 메시지 하나가 된다. 글이 있어야 하고(
a sequence message needs text), 메시지가 있는 박자에는 이동이 하나뿐이다(&불가). - 투영이 하나도 없으면 오류다. 두 끝을 함께 담은 그래프 보기가 있는데 선이 없으면
there is no edge between "a" and "b". Declare "a -> b" first, 그 밖에는move a -> b is not shown: no graph view holds an edge between them and no sequence view holds both다. - 시간은 보기 순서로 첫 투영(그래프 선이나 메시지)이 정한다. 나머지는 같은
at,ms,cut으로 따라 움직여서 값 변화, 조건, 대기, 예약, 카드 도착은 이동 하나에 한 번만 계산된다. 보기가 하나뿐인 문서는 투영이 하나라 보기 개념이 없던 시간표와 같다. - 흐름(
track)은 경로가 통째로 놓인 그래프 보기마다 점 하나를 지난다. 점은 보기마다 그 보기의 선과 방향을 따르되 출발, 이동 시간, 사라지는 시각, 도형에 닿는 시각은 첫 보기가 정한 하나다. 값 변화와 조건은 흐름 사건 하나에 한 번이다. 순서 보기에는 메시지가 되지 않는다. 그런 그래프 보기가 없으면the track a -> b -> c must lie inside one graph view오류다. - 시간 초과 분기(
else=)는 메시지가 아니라 선을 지나므로 그래프 보기의 선만 고른다. - 순서 보기 전용 문장은 그 카드가 어떤 순서 보기에든 놓여야 한다:
note,activate,deactivate,fragment, 이동의dashed,create,destroy. 아니면"activate api" needs "api" in a sequence view오류다. 구획(fragment,branch)의 메시지 번호는 보기마다 그 보기에 보이는 메시지로 센다. 구획 안의 이동에는when,wait,reserve를 쓸 수 없다. 구획에는 이 옵션의 시간 계약이 없어서 그 줄에서cannot be used inside a fragment오류이고, 구획 밖으로 옮기라고 알린다. - 선과 메시지는 문서 전체에서 이어지는 번호를 받는다. 보기 순서대로, 그래프 보기는 선 선언 순서, 순서 보기는 메시지 순서다. 처음 점과 끝 겹원의 선(상태)은 맨 뒤에 둔다. 시간표의 선 번호(
hop.edge)와 그려진 선(e-번호)이 같은 번호다.
차트 카드
chart lag "처리 지연" bar ["부제"] {
y "ms"
series ms "ms"
row "p50" ms=40
row "now" ms=depth
}
- 차트 종류는 셋째 낱말이다. 블록 안에는 차트의 머리 줄(
x,y,scale,zero,decimals)과 선언 줄(series,rule,missing,data,row,point,cell,sample,bins,total,link)이 온다. 의미는 차트가 정한다.data "경로" at "/포인터"의 경로는 원본 파일 기준이다. - 차트 카드는 어느 보기에든 놓인다. 그래프 보기에서는 카드 틀 안에 좁은 폭(
size.chart.compact-width)으로,plot보기에서는 문서 폭으로 그려진다. 한 차트는 한 모양으로만 그려서,plot보기에 놓인 차트는 그래프에서도 문서 폭이다. reveal 차트.계열,light 차트 "행", 차트의 자라는 움직임은 어느 차트 카드에든 쓴다. 첫 차트만 움직인다는 제한은 없다. 한 장면의reveal에 나온 계열은 그 장면이 시작할 때 숨고reveal박자부터 보인다. 나오지 않은 계열은 처음부터 보인다. 움직임은 차트 카드 id로 가려 같은 차트가 여러 곳에 그려져도 함께 움직인다.- 종류마다 받는 계열 수는 차트가 정한다. 막대, 누적, 선, 계단, 면은 하나 이상이고 퍼센트는 둘 이상이다.
값에 묶기
행의 숫자 자리에 값 이름을 쓰면(ms=depth) 그 값이 바뀔 때 차트가 바뀐다.
- 시작 값은 값의
from(참조면 가리키는 값의from)이고 숫자여야 한다. 아니면chart "lag" reads "depth", which holds "none" at the start. A chart value is a number(value-type)다. 장면 중에 숫자가 아닌 글이 되면 같은 오류를 묶은 행의 줄에서 알린다. 장면 중에 값이 차트의 값 범위를 어겨도(막대의 음수, 로그 눈금의 0 이하, 쌓는 합계나 크기 상한 초과, 신뢰구간low ≤ 값 ≤ high순서) 원본 행과 같은 값 규칙으로value-type오류를 묶은 행의 줄에서 알린다(chart "c" reads v=-5: values cannot be negative). 이 검사는 값 범위를 재는 프레임과 실제 프레임 모두에서 한다. 단 쌓는 합계와 신뢰구간 순서처럼 값 여럿이 함께 정하는 규칙은 실제 프레임에서만 본다. - 상자 차트와 워터폴은 묶을 수 없고, 가로 위치(
x=)도 묶을 수 없다(binding-unsupported). - 모든 프레임은 같은 축을 쓴다. 값 범위는 고정 값과 묶은 값이 가질 모든 값을 합친 범위이고, 그림 영역 길이는 모든 프레임 가운데 가장 짧은 값으로 고정한다. 카드 크기는 모든 프레임 가운데 가장 큰 그림의 크기다. 눈금과 축 이름은 프레임이 바뀌어도 움직이지 않는다.
- 프레임이 바뀌는 표식(막대, 점, 값 글자)에는 바뀌지 않는 id를 붙인다. 막대 등은
data-mark="계열:행", 글은data-mark-text="계열:행"이고 같은 계열과 행에서 둘째부터는~번호가 붙는다. 선 차트의 선(path)은d만 바뀌고 강조하지 않고, 점과 값 글자만 강조한다. - 프레임 구조(태그 순서)가 값에 따라 달라지면 프레임을 바꿔 끼울 수 없어
chart-frames오류다. - 시간표(
timeline.charts)와 장면(scene.chartFrames)의 모양은 구조이 정한다. 프레임은 시각(t0,t1,at)을 바꾸지 않는다. 같은 시각에 이어 쓴 값은 마지막 글의 프레임만 보이고, 한 시각 안에서 제자리로 돌아오면 바뀐 표식이 없다.
추적 카드
trace t "결제 추적" unit=ms {
span s1 "POST /pay" lane=gw at=0 dur=180
span s2 "charge" lane=pay at=20 dur=140
}
unit은ms(기본),us,s다. 구간 줄은span id "이름" lane=카드 at=시작 dur=길이이고 셋 모두 꼭 적는다.at은 0 이상,dur은 0보다 큰 숫자다. 시각은 문서의 시간표(ms)와 상관없는 실제 측정 시간이다.lane은 선언된 카드다. 레인은 구간이 처음 나온 순서로 위에서 아래로 쌓고, 같은 레인에서 시간이 겹치는 구간은 줄을 나눈다. 레인 이름은 그 카드의 이름이다.time보기는 0에서 시작하는 선형 시간 축 위에 구간을 놓는다. 가로 위치는at에, 길이는dur에 비례하고 눈금은 차트 눈금과 같은 규칙이다. 구간은light 추적.구간으로 밝힌다.- 추적은 그래프 보기에 들지 못하고 이동의 끝도 못 된다.
시간 흐름
시간 흐름은 장면(scene)의 목록이다. 장면은 박자(beat)의 목록이거나 흐름(track)의 목록이고, 한 장면이 둘을 섞지는 못한다. 탭 하나가 장면 하나이고 장면은 따로 재생된다. 장면이 하나도 없는 문서(구성도, 스키마)도 된다. 그때는 카드만 그린다.
| 줄 | 뜻 |
|---|---|
scene "이름" [mode=static|once|loop] [speed=1] [for=12s] [keep="값, 값"] [set="식, 식"] [status="카드=종류, 카드=종류"] |
장면 시작. 이름은 글 하나뿐이고 설명 글을 더하면 오류다. mode와 speed는 재생 방식이다. for는 흐름 장면의 길이이고 생략하면 토큰 duration.flow-step이다. keep, set, status는 이 장면의 시작 값과 카드 상태를 정한다(값 유지와 읽기, 장면별 도형 상태) |
a[.칸] -> b[.칸] ["실어 보낼 글"] [time=3s] [tone=purple] [set="식, 식"] [lost=60%] [when="조건"] [wait="조건"] [timeout=2s] [else=카드] [stuck] [reserve="식, 식"] |
이동 박자. 점 하나가 선 하나를 지난다. 순서 보기에 보이는 이동은 dashed, create, destroy를 더할 수 있다 |
track a, b -> c -> d ["글"] [at=0s] [every=2s] [time=6s] [legs="1s, -"] [tone=blue] [set="식"] [lost=60%] [when="조건"] [wait="조건"] [timeout=2s] [else=카드] [stuck] [reserve="식"] |
흐름. 출발지마다 점이 선언된 선들을 멈춤 없이 잇는다. legs는 구간마다 이동 시간을 정한다 |
a -> b "글" & c -> d time=2s |
한 박자 안의 여러 이동 |
show id "글" [tag="태그"] [tone=purple] [appearance=모양] [meta="덧붙임"] [mark="표시"] [mono] |
카드 줄 하나를 바로 앞 박자에 더한다. 카드를 그리는 그래프 보기에 놓인 카드만 쓸 수 있다 |
show id graph "가 -> 나; 가 -> 다" [lit="가, 나"] |
카드에 작은 관계 그래프 줄 하나를 더한다 |
clear id |
바로 앞 박자에서 값 줄을 남기고 카드 본문을 비운다 |
light 이름 ... |
점 없이 카드, 그룹, 칸을 밝히는 박자. 칸은 카드.칸(테이블·API의 칸, 격자의 item, 추적의 구간)이다 |
light 차트 "행", light 차트 x=값, light 차트 "행" "열" |
차트 카드의 행을 밝힌다 |
reveal 차트.계열 ... |
차트 카드의 계열을 드러낸다 |
wait 2s |
멈추는 박자 |
note 참여자 "글", activate 참여자, deactivate 참여자, fragment 종류 "라벨" ... {, branch "조건" { |
순서 보기 전용 문장 |
재생 방식
mode는static,once,loop다. 적지 않으면 줄(이동,track,show,light,reveal,wait등)이 없는 장면은static, 줄이 있는 장면은once다. 적은mode가 우선한다. 줄 없는 장면에mode=once|loop를 적으면 재생할 것이 없어 오류다. 줄이 있는 장면을static으로 두려면mode=static을 적는다. 장면 이름은 숫자로만 쓸 수 없다(--scene n이 숫자를 장면 번호로 읽기 때문이다).static은 장면의 마지막 상태 하나를 보인다. 장면에 이동이 있어도 경고가 없다. 시간표는 모두 계산하고 마지막 상태만 보인다.once는 처음부터 한 번 재생하고 마지막 상태에 머문다.loop는 같은 장면을 되풀이한다.speed는 재생 배속이고 0보다 큰 유한한 숫자(기본 1)다. 시간표의 ms는 배속으로 바뀌지 않는다. 장면의 논리 길이를 배속으로 나눈 값이 1ms보다 짧아지면 재생 길이가 0이 되므로speed=N makes scene "x" shorter than 1ms (max speed for this scene: ms)오류(invalid-speed)다. 숫자가 아니거나 0 이하거나 무한대이면 같은code의 오류다.- 장면은 탭으로만 고른다. 장면이 끝나도 다음 장면으로 넘어가지 않는다.
- 갱신 펄스(값 글자, 값이 바뀐 차트 표식)는 바뀐 시각에서 400ms 동안 80ms 올라가고 80ms 머물고 240ms 내려온다. 길이는 배속과 상관없는 화면 시간이다. 한 대상의 펄스가 겹치면 합하지 않고 가장 센 값을 쓴다. 한 시각에 이어 쓴 값이 처음 글로 돌아오면 펄스가 없다. 장면의 한 바퀴 화면 길이는 마지막 펄스가 끝날 때까지 늘어나서, 반복 경계가 펄스를 자르지 않는다. 논리 시각은 바꾸지 않는다.
- 장면의 시간은 원본이 정한다. 박자 길이는 이동, 자라는 시간, 이벤트 대기,
wait뿐이고 박자는 서로 붙어 이어진다. 박자 앞뒤에 따로 머무는 시간, 첫 박자의 최소 길이, 설명 글 길이나 효과 시간에 따른 늘어남은 없다. 효과 꼬리(마지막 펄스와 선 이탈 뒤 400ms)는 화면 길이에만 더해진다(재생).light,show,clear만 있는 박자의 길이는 0이다(상태가 그 박자 시각에 바뀐다).reveal이 계열을 자라게 하는 박자는 자라는 시간(pace)만큼 걸린다. 길이 0인 박자만 있는 장면은 마지막 모습을 바로 보인다.
이동:
a -> b는 선언된 선a -> b를 따라간다. 없으면b -> a를 거꾸로 따라간다. 둘 다 없으면 오류다. 같은 방향 선은 하나라서 고를 선이 언제나 하나다.- 이동의 글과
time=은 바로 앞a -> b에 붙는다.&로 이은 이동마다 따로 적는다. - 박자의 이동 시간은 그 박자 이동 가운데 가장 긴 시간이다.
- 이동 시간:
time=이 있으면 그 이동의 절대 시간이다. 없으면선 길이 / 토큰 size.packet.hop-ref × pace이다. 길이가 두 배인 선은 시간도 두 배라 점이 같은 속도로 움직인다. 아주 짧은 선이 너무 빨리 끝나지 않게 토큰duration.hop-min보다 짧지 않다. 최대 시간은 없어 긴 선도 같은 속도로 지난다.pace를 기본값(duration.hop)에서 바꾸면 최소도 같은 비율로 바뀐다. 선 길이는 배치가 끝난 선 경로의 길이이고, 점이 따라가는 둥근 모서리 경로(그려지는 선)를 기준으로 잰다. 거꾸로 이동도 같다. 순서 그림 메시지도 같은 규칙이다. 출발과 도착이 느린 곡선(easing.move)은 그대로다. - 이동 글이 그 선의 라벨과 같으면 그림 검사 8번 경고가 난다. 같은 글이 두 번 보이기 때문이다.
- 그룹이 끝인 선을 지나는 점은 그룹 경계의 연결점에서 멈춘다.
카드:
show,clear는 바로 앞 박자에 붙고, 적은 순서대로 적용한다. 장면의 첫 줄이show면 멈추는 박자를 하나 만든다. 장면의 첫 줄이clear면 선언한 본문이 있는 카드만 비울 수 있다. 선언한 본문도 없으면 오류다.- 내용이 없는 카드는 틀도 글자도 그리지 않는다. 읽을 내용이 없는데 점선 틀이 도형마다 남으면 정보 없는 표시가 반복돼 화면이 어수선하기 때문이다. 틀은 카드에 내용이 들어오는 박자에 내용과 함께 나타나고
clear로 비우면 함께 사라진다. 카드 자리는 배치에 그대로 잡혀 도형 크기는 변하지 않고, 도형의 이름 묶음은 카드가 비어 있어도 찬 뒤에도 같은 자리에 있다. 머리가 내려오거나 올라가지 않으며, 카드 내용이 들어올 자리는 비어 있는 동안에도 남는다. 내용이 찬 카드의 틀은 중립 면과 경계이고 파랑 윤곽은 바깥 도형이 맡는다. 카드에 읽을 글이 없으므로 대체 글도 두지 않는다. 정지 SVG는 고른 장면의 마지막 카드 내용을 그린다. 장면이 없으면 선언한 본문과 값이 남는다. - 도착 규칙: 그 박자에 점이 도착하는 도형의 카드는 그 도형에 도착하는 이동 가운데 가장 늦은 도착 때 바뀐다. 나머지 도형의 카드는 박자 시작에 바뀐다.
- 태그는 대문자로 그린다.
tone이 있는 줄은 그 색이다.tone이 없는 줄은,tone없이 처음 나온 태그 순서대로 범주 팔레트 순서(blue,yellow,red,green,orange,purple,cyan, 다시 처음부터)로 붙인 색을 그림 전체에서 같은 태그에 쓴다. 상태 색이라고 건너뛰는 자리는 없다.gray는tone으로 고를 때만 쓴다. mark는 8자 이하다. 카드 오른쪽 끝에 들어갈 자리가 정해져 있기 때문이다.- 관계 그래프 글은
;로 관계를 나누고, 관계는이름 -> 이름또는 이름 하나다. 이름은 앞뒤 공백을 빼고;,,,->를 쓰지 않는다. 관계가 돌아 제자리로 오거나,lit의 이름이 그래프에 없으면 오류다. 이름 수나 깊이, 이름 길이의 한도는 없다. 그래프 줄은 이름을 자르지 않고, 필요하면 카드를size.node.max-width보다 넓힌다(표처럼 구조 내용이 카드를 넓히는 것과 같고, 넓어진 카드에도 같은 바깥 폭 맞춤이 적용된다. 배치). - 카드를 쓰는 도형은
box,external,store,person,table,api다.decision, 격자, 그룹에show를 쓰면 오류다.show와clear의 대상은 카드를 그리는 그래프 보기에 놓여야 한다. 순서 보기에만 놓인 카드에 쓰면show "s" needs a card, and no graph view shows "s"오류이고, 같은 카드를 그래프 보기에도 놓으면 된다. 상태 알약(status)은 순서 보기 머리에도 그려서 이 제한이 없다.
장면:
- 박자 줄도 흐름 줄도 하나도 없는 장면은
mode를 적지 않으면static이다. 줄이 없어도 처음 구성 그대로의 정지 모습이고 표시 길이가 0이다(자리를 채우는wait나 가짜light를 넣지 않는다). 줄 없는 장면에mode=once또는mode=loop를 적으면 오류이고, 메시지가mode=static을 알린다. - 한 장면 안에서 밝힌 도형과 카드 내용은 남는다. 선은 점이 지나는 동안만 켜지고, 남는 선은
quiet선의 처음 지난 뒤뿐이다. 다음 장면이 시작하면 모두 꺼진다. 차트 카드의 장면 규칙은 차트 카드에 있다.
흐름 장면:
- 흐름은 박자와 따로 돈다. 한 장면에
track줄을 두면 그 장면은 길이가for인 구간 하나이고, 흐름끼리 서로 기다리지 않는다. 한 장면에 박자 줄(이동,show,clear,light,wait)과track줄을 섞으면 오류다. 한 문서에는 두 종류의 장면이 함께 있어도 된다. track a, b, c -> x -> y는 출발지마다 흐름 하나로 펼친다.at을 적지 않으면 출발지 i의 첫 출발은every × i / n(n은 출발지 수)이라 출발이 엇갈린다.tone을 적지 않으면 출발지 이름마다 범주 팔레트 순서로 색이 하나씩 배정되고, 같은 출발지 이름은 그림 전체에서 같은 색이다. 적으면 그 값이다.- 구간마다 이동과 같은 규칙으로 선을 고른다(같은 방향 선, 없으면 거꾸로, 둘 다 없으면 오류). 점은 구간 사이에서 멈추지 않고 이어 붙인 경로를 지나며, 도형 안을 지나는 동안은 보이지 않는다. 이동 시간은 구간 시간(선 길이 비례)의 합이고
time=은 경로 전체의 시간이다. 구간마다 시간을 정하려면legs=를 쓴다(사라짐과 구간 시간). 출발과 도착이 느린 곡선(easing.move)은 경로 전체에 한 번 건다. at은 처음 출발 시각이다(0s도 된다).every가 있으면 장면 끝 전까지 그 간격으로 되풀이해 출발한다.at + every가at과 같아지는 것처럼 출발 시각이 엄격히 늘지 않는 시간 정밀도는 지원하지 않고 그track줄의 오류다(code는time-precision.every=를 늘리거나at=을 줄인다). 장면 끝까지 도착하지 못하는 점은 장면 끝에서 서서히 사라지게 그리고(duration.cut-fade), 그 점이 닿지 못한 도형의 값은 바뀌지 않는다. 출발 수와 잘린 점 수는 배치가 정하는 이동 시간에 따라 달라지므로 횟수가 중요한 그림은time=을 적는다.when,wait,lost가 없는 흐름에서 점이 하나도 그려지지 않으면 그림 검사 14번 오류다. 이 셋이 있는 흐름은 조건과 사라짐 때문에 점이 없을 수 있어서 오류가 아니다.for는track이 없는 장면에 쓰면 오류다.- 글은 이동 글 상자처럼 점과 함께 간다. 흐름이 지나는 선은 점이 올라 있는 동안만 밝고 점이 떠나면 꺼진다. 도형은 켜 두지 않고, 점이 닿을 때마다 공통 펄스를 한 번 그린다.
점 색과 값 바꾸기:
tone=은 점과 글 상자의 색이다. tone 이름은 그 색을 그린다. 값 목록은 카드 태그의tone과 같아서(범주 팔레트 계열 일곱과gray) 문법 표의 값 목록 한 곳이 이름을 정한다. 생략한 이동은 지금 색(state.active)이고, 생략한 흐름은 위 자동 배정 색이다. 점과 글 상자의 면, 그 위 글자, 글 상자 윤곽은 계열마다 팔레트의 면, 글자, 윤곽 단계(src/tone.js)이고gray는 윤곽이 없다(색 역할).set="식, 식"은 이동이나 흐름의 점이 도형에 닿을 때 값을 바꾼다.@도형을 붙이면 점이 그 도형에 닿을 때, 없으면 이동이나 흐름의 도착 도형에 닿을 때 적용한다.@도형은 경로 위 도형이어야 하고, 경로가 그 도형을 두 번 지나면 어느 쪽인지 알 수 없어 오류다(방문 순번은 없다. 그 도형의 변화는on줄로 쓴다). 낱말을 담는 값에+,-를 쓰면 오류다.- 같은 순간에 적용할 식의 순서: 시각이 앞선 것, 같은 시각이면
on줄이 모두set=보다 앞, 같은 종류면 이동과 흐름의 선언 순서, 같은 줄이면 적은 순서다. - 값이 바뀌는 순간 그 카드 줄과 그 값을 참조하는 줄의 배경 면(테두리 없음)이 갱신 펄스 동안 밝아지고, 값 글자는 새 글로 바뀐다. 글이 그대로면 바뀐 것이 아니다.
- 박자 장면에서도
a -> b set=와on은 같은 규칙으로 동작해, 장면별 설명과 상태 변화를 함께 쓴다.
값 유지와 읽기
- 장면의
keep="값, 값"은 목록의 값이 장면 시작 때 앞 장면이 끝난 값에서 시작하게 한다. 목록은 쉼표로 나눈 값 이름(value의 id나 큐 이름)이다. 목록에 없는 값은 지금처럼from으로 돌아간다. 값 이름은 안정적인 식별자라서 줄 순서와 카드 자리가 바뀌어도 같은 값을 가리킨다. 없는 이름, 참조 값(ref), 같은 이름의 중복은 그 줄의 오류다. 참조 값은 가리키는 값을 따르기 때문이다. 앞 장면이 없는 첫 장면의keep도 오류다. - 장면의
set="식, 식"은 장면이 시작할 때 값을 정하는 재설정이다. 식 꼴은 이동의set=과 같고@도형만 쓸 수 없다.keep한 값에 적으면 재설정이 앞 장면의 값보다 우선하고,keep하지 않은 값에 적으면from대신 그 식의 결과에서 시작한다. keep의 항목은 값 이름 꼴이 아니면 그 줄의 오류다.keep이 넘기는 것은 값 글자뿐이다. 카드 내용, 밝힌 도형과 선, 도형 상태, 값 줄의 밝힘은 넘기지 않는다.- 읽기 식
대상:=원천은 값대상을 이벤트를 처리하는 시점의 값원천으로 바꾼다. 이동과 흐름의set=,on줄, 장면의set=에 쓴다.원천은 값 이름이고 참조 값과 큐도 된다.대상은 참조 값일 수 없다. 큐는 정수만 받아서, 정수가 아닌 글을 읽어 큐에 넣으면 실행 때value-type오류다. 읽기와 쓰기의 순서는 재생이 정한다. - 읽기 식의
원천이 선언하지 않은 값이면 그 줄의 오류다. 장면set=의 읽기는keep을 반영한 시작 값을 읽는다. 낱말을 담은 값을 읽는 값도 낱말을 담는 값으로 세므로, 그 값에+,-를 쓰면 오류다.
조건과 대기
조건은 따옴표 글 안에 쓰고, 문법은 아래와 같다.
조건 = 항 { "||" 항 }
항 = 요소 { "&&" 요소 }
요소 = "!" 요소 | "(" 조건 ")" | 피연산자 비교 피연산자
비교 = "=" | "!=" | "<" | "<=" | ">" | ">="
피연산자 = 값 이름 | 숫자 | '낱말'
- 값 이름은 선언된
value나 큐이고, 숫자는 값 형식의 숫자이며, 낱말은 작은따옴표 안에 쓴 공백 없는 글자('none')다. 값 이름과 같은 글자여도 따옴표가 있으면 낱말이다. 토큰 사이 공백은 있어도 없어도 된다. 조건은 200자 이하다. 평가 비용에 상한을 두기 위해서다. - 함수 호출, 산술, 대입, 임의의 JavaScript는 문법에 없다.
- 비교의 한쪽 이상은 값 이름이어야 한다(
1=1은 오류).<,<=,>,>=에 낱말을 쓰면 그 줄의 오류다. - 없는 값 이름과 조건 문법 오류는 줄 번호가 붙은 오류(
syntax)이고, 이름 오타에는 비슷한 이름을 제안한다. 값의 현재 글이 숫자인지 낱말인지는 평가할 때 정해진다. 숫자와 낱말을 비교하거나<계열에 낱말이 오면 그 조건 줄의 오류(value-type)다. 조용히 거짓이 되지 않는다.
| 선택 사항 | 쓰는 줄 | 뜻 |
|---|---|---|
when="조건" |
이동, 흐름 | 실행 직전에 한 번 평가한다. 거짓이면 그 이동(흐름이면 그 출발)을 건너뛴다 |
wait="조건" |
이동, 흐름 | 조건이 참이 될 때까지 출발을 미룬다 |
timeout=시간 |
wait와 함께 |
대기를 시작한 뒤 이 시간이 지나도 풀리지 않으면 대기를 끝낸다 |
else=도형 |
timeout과 함께 |
시간 초과로 끝난 점이 출발 도형에서 이 도형으로 가는 선을 지난다. 없으면 그 점은 만들지 않는다 |
stuck |
wait와 함께 |
이 대기가 끝나지 않는 것이 의도임을 알린다. wait-stalled 경고를 내지 않고 결과에는 그대로 남는다 |
reserve="식, 식" |
이동, 흐름 | 조건을 통과해 점이 출발하는 같은 사건 안에서 값을 한 번에 바꾼다. 하나라도 실패하면 아무것도 바꾸지 않는다 |
- 박자 이동과 흐름에 쓴다.
&로 이은 이동은 각자 조건을 가진다. - 한 줄에
wait와when이 함께 있으면wait가 풀린 뒤에when을 평가한다. - 자동 재시도는 없다. 건너뛴 이동은 다시 시도하지 않고, 기다리려면
wait를 쓴다. timeout,else,stuck을wait없이 쓰거나else를timeout없이 쓰면 오류다.else이동은 이동 규칙대로 선을 고르고(같은 방향 선, 없으면 거꾸로, 둘 다 없으면 오류) 글과tone을 이어받는다. 시간은 선 길이로 정하고set,lost,when,wait,legs는 이어받지 않는다. 흐름 줄이면 출발지마다 그 출발지에서else도형으로 가는 선을 고른다.- 평가 시점, 해제 순서, 대기가 끝나는 때는 재생이 정한다.
예약
wait와 set=를 함께 쓰면 확인은 출발 때, 갱신은 점이 닿을 때 일어나서 같은 시각의 두 요청이 둘 다 통과한다. 확인과 갱신을 한 사건으로 하려면 reserve=를 쓴다.
thinkflow
title "잠금은 하나만 쥔다"
box a "작업 A"
box b "작업 B"
box lock "잠금"
value holder "쥔 쪽" on=lock from=none
a -> lock
b -> lock
scene "같은 시각에 요청한다" for=8s
track a -> lock "요청" at=0s time=1s wait="holder='none'" reserve="holder=A"
track b -> lock "요청" at=0s time=1s wait="holder='none'" reserve="holder=B"
track a -> lock "풀기" at=3s time=1s when="holder='A'" set="holder=none"
- 식은
set=와 같은 꼴이고(id+N,id-N,id=값,id:=원천) 점이 출발할 때 적용하므로@도형은 쓸 수 없다. 틀린 식은set=와 같은 오류다. when이나wait가 없어도 쓸 수 있다. 조건이 없으면 출발할 때 값을 바꾸기만 한다. 해제를 출발 때 하려면 이 꼴로 쓴다.reserve=와set=는 따로 쓴다.set=는 점이 닿을 때,reserve=는 점이 출발할 때 값을 바꾼다. 한 줄에 둘을 함께 써도 된다.- 값 종류가 맞지 않는 식(큐에 낱말을 읽어 넣기, 낱말에 합하기)은 실행 때
value-type오류이고, 같은 목록의 앞 식도 적용하지 않는다. 선언만 보고 알 수 있는 오류는set=처럼 읽을 때syntax로 알린다. - 원자성, 선언 순서, 시간 초과와 사라짐과 해제의 규칙은 재생이 정한다.
사라짐과 구간 시간
lost=퍼센트는 이동과 흐름 줄에서 점이 경로 전체 길이의 그 비율에서 사라지게 한다. 값은 숫자와%(0%,60%,62.5%,100%)이고 0 이상 100 이하다.%가 없거나 범위 밖이면 오류다. 사라지는 지점의 효과는 재생이 정한다.legs="시간, 시간, -"은track줄에서 경로의 선마다 이동 시간을 정한다. 항목 수는 경로의 선 수와 같고 둘 이상이어야 한다. 선이 하나인 흐름은time=으로 적는다. 같은 일을 적는 방법을 하나로 두기 위해서다. 항목은 0보다 큰 시간이거나 시간을 정하지 않은 구간을 뜻하는-다.time=과 함께 쓸 수 있고, 시간을 정하는 순서는 재생이 정한다.- 항목 수가 다르면
syntax, 지정한 시간의 합이time=을 넘거나 시간이 남지 않는데 정하지 않은 구간이 있으면leg-time오류다. 박자 이동은 선이 하나라서legs를 쓸 수 없다.
장면별 도형 상태
- 장면의
status="도형=종류, 도형=종류"는 그 장면 동안만 도형에 상태 알약을 붙인다. 박자 장면과 흐름 장면 모두에 쓴다.
| 종류 | 알약 글자 | 기호 | 색 역할 |
|---|---|---|---|
ok |
OK |
체크 | state.success |
warn |
WARN |
느낌표가 든 삼각형 | state.warning |
fail |
FAIL |
가위표 | state.error |
wait |
WAIT |
점 셋 | outline |
- 알약은 글자와 기호를 늘 함께 그린다. 색을 구별하지 못해도 상태가 읽히게 하기 위해서다. 알약 면은 도형 면(
ui-card)이고 기호와 테두리는 종류의 색, 글자는ink다. 글자는ink4.5 이상이다. 기호와 테두리는ok,fail,wait가 도형 면과 그림 바탕, 그룹 면 위에서 그래픽 대비 3을 지키고 다크의warn도 지킨다(색 역할).wait는 구분을 위해 중립색을 쓰는데border는ui-card위 대비가 라이트 1.27, 다크 1.08이라 기준에 못 미쳐 대비 3을 보장하는outline을 쓴다. - 라이트의
warn은 예외다. 승인한 노랑 계열 경계(state.warning)는 바꾸지 않고, 그 테두리는 어느 면 위에서도 대비 3에 못 미친다. 같은 색상의 테두리, 읽히는 글자WARN, 중복 기호(삼각형 안 느낌표)가 색 대신 상태를 전한다. 모든 그래픽이 대비 3이라고 주장하지 않는다. - 알약은 도형 이름과 글자, 다른 도형, 선 라벨, 그룹 제목을 가리지 않는다. 가리면 그림 검사 2번이 상태를 적은 장면 줄의 오류로 알린다. 이동 글 상자도 상태 알약을 가리지 않는 자리를 고른다.
- 알약은 도형 오른쪽 위 모서리에 걸쳐 그려서 도형 크기와 배치를 바꾸지 않는다.
- 도형 상자는 그 도형이 어느 장면에서든 보일 가장 큰 카드(
show,value)에 맞춰 늘 같은 크기이므로, 카드를 한 장면에만 넣은 도형은 카드 없는 같은 종류의 도형보다 커 보인다. 대기 중 같은 일시 표시를 카드로 적으면 이 크기 차이가 생기므로, 크기를 같게 두려면 상태 알약(status)으로 적는다. - 도형은
box,external,store,person,queue,decision이다. 그룹, 격자, 없는 이름, 한 장면 안에서 같은 도형의 중복, 모르는 종류는 오류다. - 상태는 그 장면에서만 적용하고 다음 장면은 도형의 선언 상태로 돌아간다.
keep은 상태를 넘기지 않는다. 정지 장면(mode=static)은 마지막 박자의 상태 알약을 그린다.
글 줄 나누기
- 도형 이름, 부제, 카드 글은 도형 너비 상한(토큰
size.node.max-width)에서 띄어쓰기 자리로 줄을 나눈다. 띄어쓰기 없는 긴 낱말은 글자 단위로 나눈다. 그래서 글이 도형을 넘는 일은 원본 오류가 아니다.
오류와 경고
- 형식:
{파일}:{줄}: {무엇이 틀렸나}. {고치는 방법}. 경고는{파일}:{줄}: warning: {메시지}다. 영어로 쓴다(code-style 메시지 규칙). 진단 하나의 모양은{ severity, code, line, column, message }이다.column은 줄 안 1부터 센 자리다. - 이름 오류에는 선언된 이름 목록을 알파벳순으로 붙이고, 편집 거리가 2 이하인 이름이 있으면
Did you mean "{이름}"?을 붙인다. - 오류가 하나라도 있으면 파일을 쓰지 않는다. 문법 오류, 글꼴에 없는 글자,
data읽기 오류는 한 번에 모두 알린다. 파일이 비었거나 첫 문장이thinkflow가 아니면 거기서 멈춘다. 다음 줄을 읽을 규칙이 없기 때문이다. - 그림 검사는 배치가 끝나야 돌므로, 원본 오류가 없을 때만 그 오류를 알린다.
- 생성 예산을 넘으면
budget-exceeded오류다. 필요한 양, 지금 한도, 올리는 방법(--budget 이름=값, Action 입력budget)을 적고 파일을 쓰기 전에 끝난다. 격자 예산과 이동 글 상자 계획의 색인 예산(chip-index)의 이름과 기본 한도는 칸 격자가, 이벤트 예산(events,chain)은 재생이 정한다.--budget값이 틀리면 그림을 만들기 전에 사용법 오류(종료 2)다. - 경고는 파일을 쓰고 표준 오류에 남긴다.
--strict면 경고도 실패다. --json은 진단마다 한 줄을 표준 출력에 쓴다.code는syntax(문법),check-1~check-14(그림 검사 번호),time-limit,time-precision,value-type(값 종류, 차트 묶음 포함),leg-time,wait-stalled,budget-exceeded,invalid-speed,layout-unstable(값 글 집합이 수렴하지 않음),chart-frames(프레임 구조가 값마다 다름),binding-unsupported,missing-preamble,invalid-preamble,unsupported-extension,layout,layout-width,layout-timing,io,internal이다. 줄 하나는{ "file", "line", "message", "severity", "code", "column" }여섯 필드이고severity는error나warning이다. 이 밖의 필드와 옛 이름의 별칭은 없다.
시작 선언과 없앤 형태
첫 문장이 thinkflow가 아니면 읽지 않는다. 읽는 어댑터, 별칭, 옮기는 명령은 없고, 오류는 줄과 자리를 가지며 결과 파일을 쓰지 않는다.
| 입력 | code |
자리 | 메시지 |
|---|---|---|---|
첫 문장이 thinkflow가 아니다 |
missing-preamble |
그 문장의 첫 글자 | the first line must be "thinkflow" |
thinkflow 뒤에 숫자나 옵션이 있다 |
invalid-preamble |
추가한 낱말 | write the first line as: thinkflow |
| 시작 선언만 있고 본문이 없다 | invalid-preamble |
그 문장의 첫 글자 | the file has no figure. Declare cards after thinkflow |
.thinkflow가 아닌 파일(render, check의 입력) |
unsupported-extension |
파일, 1줄 1자리 | only .thinkflow files are read |
문법 표에 없는 낱말(flow, step, say, 머리 speed 등) |
syntax |
첫 글자 | unknown statement "step" |
글이 둘인 scene "이름" "설명" |
syntax |
둘째 글 | write a scene as: scene "name" [mode=...] ... |
이름을 쓴 보기 view main graph |
syntax |
그 줄 | a view is one of graph, sequence, plot, time. Found "main". A view has no name: write view graph ... |
도형과 그룹의 fill=, stroke=, show의 card= |
syntax |
그 줄 | a box takes count=, shape=, badge=, icon=, tone=, appearance=. Found "fill" |
숫자로만 된 장면 이름 scene "2" |
syntax |
그 줄 | scene "2" is only digits, which --scene reads as a scene number. Add a word to the name, such as "step 2" |
머리 자리의 x, y, scale, zero, decimals |
syntax |
첫 글자 | "x" belongs inside a chart block |
- 값 목록에 없는 색 이름(
brand,amber,teal,navy,pink,sky포함)은 일반 값 목록 오류다. - 옮기는 명령과 폐기 안내 옵션은 없다. 모르는 명령과 옵션은 일반 사용법 오류(종료 2)다.
- 마크다운 이미지 줄과 SVG에 이 도구가 남기는 소유 표시는
<!-- thinkflow -->와<!-- thinkflow md v2 ... -->뿐이다. 다른 표시가 붙은 파일과 줄,```muto같은 다른 울타리는 사용자의 것이라 읽거나 가져가거나 덮어쓰지 않는다. - 명령
render와md의--scene n|이름은 SVG로 만들 장면을 고른다(생략하면 첫 장면, 번호는 1부터). 숫자만으로 된 장면 이름은 문법이 받지 않아 숫자는 늘 번호다. 장면 수를 넘는 번호, 없는 이름, 장면이 없는 원본에 준--scene은 오류다.
문법 표
다음 표는 문법 표(src/source/grammar.js)에서 만든다. 낱말, 선택 사항, 값 목록, 기본값은 이 표 한 곳에만 있고, 파서, 검증, 오류 메시지, 이 절이 모두 그 표를 읽는다. 손으로 고치지 않고 npm run grammar로 다시 쓰며, 어긋나면 테스트가 실패한다. 선택 사항의 앞 이름은 쓰이는 문장이다(graph는 show id graph, column은 테이블과 API의 칸 줄).
<!-- grammar-table:start -->
| 부분 | 자리 | 낱말 |
|---|---|---|
| 선언 | 테이블 블록 안 | pk (열, ...), unique (열, ...), fk (열, ...) -> 테이블 (열, ...) |
| 시작 선언 | 문서 줄 | thinkflow |
| 머리 | 문서 줄 | title, subtitle, pace, aspect, width |
| 선언 | 문서 줄 | person, box, external, store, decision, queue, state, group, grid, icons, value, on node id+N, start, final, table, api id "METHOD /url" {, class, interface, chart id "title" type ["subtitle"] {, trace id "title" [unit=ms] {, `view graph |
| 선언 | 격자 블록 안 | item, gap |
| 선언 | 카드 본문 안 | text, graph |
| 선언 | 클래스 블록 안 | field, method |
| 선언 | 추적 블록 안 | span |
| 선언 | 차트 블록 안 | x, y, scale, zero, decimals, series, rule, missing, data, row, point, cell, sample, `bins minimum maximum count [measure=count |
| 시간 흐름 | 문서 줄 | `scene “label” [mode=static |
| 선택 사항 | 값 |
|---|---|
fragment.choose |
글 |
fragment.times |
양의 정수 |
fragment.run |
on, off |
group.direction |
right, down |
group.border |
solid, dashed |
group.badge |
글, 최대 8자 |
group.icon |
이름 또는 세트:이름 |
group.tone |
blue, yellow, red, green, orange, purple, cyan, gray |
group.appearance |
plain, filled, outline |
node.badge |
글, 최대 8자 |
node.icon |
이름 또는 세트:이름 |
node.tone |
blue, yellow, red, green, orange, purple, cyan, gray |
node.appearance |
plain, filled, outline |
box.count |
2 이상 정수 |
queue.slots |
1 이상 32 이하 정수 |
queue.from |
0 이상 slots 이하 정수 |
edge.no |
양의 정수 |
scene.mode |
static, once, loop |
scene.speed |
0보다 큰 숫자 |
scene.for |
시간 |
scene.status |
ok, warn, fail, wait |
scene.keep |
값 이름 목록 |
scene.set |
글 |
hop.time |
시간 |
hop.tone |
blue, yellow, red, green, orange, purple, cyan, gray |
hop.set |
글 |
hop.lost |
0 이상 100 이하 퍼센트 |
hop.when |
조건 글 |
hop.wait |
조건 글 |
hop.timeout |
시간 |
hop.else |
도형 이름 |
hop.stuck |
값 없음(낱말만) |
hop.reserve |
값 식 목록 |
track.at |
시간(0 가능) |
track.every |
시간 |
track.time |
시간 |
track.tone |
blue, yellow, red, green, orange, purple, cyan, gray |
track.set |
글 |
track.lost |
0 이상 100 이하 퍼센트 |
track.when |
조건 글 |
track.wait |
조건 글 |
track.timeout |
시간 |
track.else |
도형 이름 |
track.stuck |
값 없음(낱말만) |
track.reserve |
값 식 목록 |
track.legs |
시간 또는 -의 목록 |
value.on |
도형 이름 또는 격자.칸 |
value.from |
숫자 또는 낱말 |
value.ref |
값 이름 |
hop.dashed |
값 없음(낱말만) |
hop.create |
값 없음(낱말만) |
hop.destroy |
값 없음(낱말만) |
edge.quiet |
값 없음(낱말만) |
edge.dashed |
값 없음(낱말만) |
edge.head |
end, both, none |
box.shape |
rect, circle, tile |
show.tag |
글 |
show.tone |
blue, yellow, red, green, orange, purple, cyan, gray |
show.appearance |
plain, filled, outline |
show.meta |
글 |
show.mark |
글, 최대 8자 |
show.mono |
값 없음(낱말만) |
graph.lit |
글 |
bins.measure |
count, probability, density |
series.role |
main, compare, reference |
series.key |
글 |
point.series |
계열 이름 |
trace.unit |
ms, us, s |
span.lane |
도형 이름 |
span.at |
0 이상 숫자 |
span.dur |
0보다 큰 숫자 |
grid.rows |
양의 정수 |
grid.cols |
양의 정수 |
item.key |
글 |
item.row |
0 이상 정수 |
item.col |
0 이상 정수 |
item.rows |
양의 정수 |
item.cols |
양의 정수 |
gap.count |
양의 정수 |
light.x |
숫자 |
column.pk |
값 없음(낱말만) |
column.unique |
값 없음(낱말만) |
column.nullable |
값 없음(낱말만) |
column.required |
값 없음(낱말만) |
foreignKey.ondelete |
restrict, cascade, set-null, no-action |
column.fk |
테이블.열 |
classifier.abstract |
값 없음(낱말만) |
member.visibility |
public, private, protected, package |
member.static |
값 없음(낱말만) |
member.abstract |
값 없음(낱말만) |
relation.relation |
association, dependency, inheritance, realization, aggregation, composition |
multiplicity.from |
출발 쪽 다중성 |
multiplicity.to |
도착 쪽 다중성 |
| 값 목록 | 쓰는 곳 | 값 | 기본값 |
|---|---|---|---|
fragmentType |
fragment 값 |
alt, loop, par, opt |
없음 |
histogramMeasure |
bins.measure |
count, probability, density |
count |
fragmentRun |
fragment.run |
on, off |
없음 |
direction |
group.direction |
right, down |
right |
viewStrategy |
graph, sequence, plot, time |
없음 | |
sceneMode |
scene.mode |
static, once, loop |
줄이 있는 장면은 once, 줄이 없는 장면은 static |
traceUnit |
trace.unit |
ms, us, s |
ms |
scale |
scale 값 |
linear, log |
linear |
zero |
zero 값 |
on, off |
on |
chartType |
bar, stacked, percent, dumbbell, box, scatter, line, step, area, ecdf, difference, heatmap, pie, donut, histogram, waterfall |
없음 | |
tone |
group.tone, node.tone, hop.tone, track.tone, show.tone |
blue, yellow, red, green, orange, purple, cyan, gray |
없음 |
appearance |
group.appearance, node.appearance, show.appearance |
plain, filled, outline |
plain |
role |
series.role |
main, compare, reference |
계열이 하나나 둘이면 선언 순서대로 main, compare(dumbbell은 compare, main), 셋 이상이면 없음 |
head |
edge.head |
end, both, none |
end |
shape |
box.shape |
rect, circle, tile |
rect |
width |
width 값 |
standard, wide |
standard |
border |
group.border |
solid, dashed |
solid |
status |
scene.status |
ok, warn, fail, wait |
없음 |
deleteAction |
foreignKey.ondelete |
restrict, cascade, set-null, no-action |
없음 |
relation |
relation.relation |
association, dependency, inheritance, realization, aggregation, composition |
association |
visibility |
member.visibility |
public, private, protected, package |
없음 |
| <!-- grammar-table:end --> |
요구사항
| 요구사항 | 검증 계획 |
|---|---|
첫 문장이 thinkflow가 아니거나 .thinkflow가 아닌 파일이면 읽지 않고, 지원하지 않는 낱말(step, say, 머리 speed)과 글이 둘인 장면 줄은 일반 문법 오류다. 모든 오류는 code, 줄, 자리를 알리며 결과 파일을 쓰지 않는다. 읽는 어댑터와 옛 이름 전용 경로는 없다. |
test/grammar.test.js가 buildFigure의 진단(code, 줄, 자리)을 표의 줄마다 확인한다. 명령줄의 동작은 test/exports.test.js |
| 한 문서가 사람, API 칸, 스키마 칸, 값, 값에 묶인 차트 카드를 한 모형으로 읽고, 카드 종류마다 다른 연결점(API 칸, 테이블 열, 격자 칸, 추적 구간)을 쓴다. 클래스 멤버는 연결점이 아니다. | test/cards.test.js |
| 보기는 이름이 없고, 보기를 적지 않은 카드는 기본 보기를 받는다(추적은 시간 보기, 남은 카드가 차트뿐이고 명시한 그래프 보기와 그룹이 없으면 차트 보기, 나머지는 그래프). 적은 보기와 겹쳐 그려지지 않고, 이름을 쓴 옛 꼴은 줄 번호가 붙은 오류다. 구성원, 그릴 수 있는 선, 투영 없는 이동, 순서 보기 전용 문장도 줄 번호와 함께 알린다. | test/authoring.test.js(기본 보기), test/cards.test.js(보기 규칙) |
장면의 mode를 적지 않으면 줄이 있는 장면은 once, 없는 장면은 static이고 적은 값이 우선한다. 도형, 그룹, show 줄의 색은 tone과 appearance이고 옛 fill, stroke, card는 오류다. |
test/authoring.test.js |
이동은 보기마다 한 번씩 보이고 같은 시각과 시간을 쓰며, 값은 첫 투영의 도착에 한 번만 바뀐다. 정지 장면에 이동이 있어도 경고가 없다. speed는 양의 유한한 숫자이고 재생 길이가 0이 되면 invalid-speed다. 순서 보기에만 놓인 카드의 값 on=, show, clear는 줄 번호가 붙은 오류다. |
test/cards.test.js, test/values.test.js, test/exports.test.js |
| 같은 원본에서 같은 시각을 낸다(예약, 값 유지, 대기와 조건, 사라짐, 흐름). 같은 시각에 해제와 시간 초과가 겹치면 해제가 이긴다. 논리 시각에는 설명 글 길이, 박자 뒤 머묾, 효과 시간이 없다. | test/values.test.js(공개 출력의 값 줄)와 test/exports.test.js(결정성) |
| 값 글자 자리는 모든 장면의 글의 실제 폭이고 긴 글은 줄을 나눈다. 8자 제한이 없다. | test/values.test.js |
| 값에 묶인 차트는 고정된 축, 바뀌지 않는 표식 id, 시간표의 프레임 구간을 갖고, 숫자가 아닌 값, 값 범위를 어긴 값, 묶을 수 없는 차트는 줄 번호와 함께 알린다. | test/charts.test.js |
장면의 mode와 speed가 SVG 재생을 정하고(loop는 되풀이, once는 한 번 뒤 마지막 상태에 머묾, static은 움직임 없음), --scene으로 장면을 고른다. 모든 보기 판이 공유 재생기가 찾을 수 있는 id로 그려진다. |
test/exports.test.js |
예제 원본과 문서의 ```thinkflow 블록이 현재 문법으로 읽힌다. |
예제는 test/examples.test.js, ```thinkflow 블록은 thinkflow md --check가 읽는다. 설계 문서의 ```text 예시 원본은 자동 시험이 읽지 않는다(검증 요구사항, 미완료) |
| 표의 차트 종류와 계열 수 범위는 문법 표가 정하고 이 문법은 별도 제한을 두지 않는다. | test/charts.test.js |
| 사용자가 눈으로 보는 화면(탭, 시계, 전체 화면, 복사와 내려받기 조작)은 이 시험이 대신하지 않는다. | 검증 요구사항, 미완료. 실제 Chrome에서 사람이 직접 보는 검수로 따로 확인한다 |
단점
- D2 원본을 그대로 쓸 수 없다. 이전 문법의 원본은 현재 문법으로 다시 써야 한다. 옮기는 명령은 두지 않았다(어댑터는 옛 문법을 영원히 남긴다).
- 도형 크기와 모양, 임의의 색을 원본에서 정할 수 없다. 색은 이름(
tone)과 표현 방식(appearance)만 고른다. 특별한 강조가 필요한 그림은 표현할 수 없다.
대안
- D2 문법 유지: 배치 모델이 어긋나 버렸다. 결정 기록
- 들여쓰기로 그룹 표시: 복사해 붙일 때 들여쓰기가 깨지면 뜻이 바뀌어 버렸다.
- 따옴표 없는 글 값 허용(
tag=you): 같은 값을 두 방식으로 적게 되어 버렸다.