철학 6번을 추가했다:
이름은 관례가 아니라 뜻에서 고른다 — 낯섦은 한 번 치르고 끝나지만
부정확함은 읽는 사람마다 매번 치른다.
판정 방법도 같이 적었다. 그 단어로 평범한 문장을 써 보고, 단어가 문장을
도우면 맞는 이름이고 싸우면 틀린 이름이다.
"크래시는 복구하는 것이 아니라 조사하는 것이다" — 돕는다
"패닉은 복구할 수 없다" — 다른 언어에서는 할 수 있어 싸운다
panic의 자연어 뜻은 "갑작스러운 공포"다. 반응하는 쪽의 감정이지 결함에
대한 말이 아니다. 그리고 Go/Rust에서는 붙잡을 수 있어 이름이 거짓말을 한다.
crash는 "계획 없이 갑자기 완전히 망가져 끝남"이고 복구의 함의가 없다 —
크래시는 복구하는 게 아니라 조사하는 것이다.
어휘의 출신도 이유가 됐다. panic+recover는 Go 전통이고 거기엔 감독이 없다.
crash+supervision은 얼랭 전통이며, 우리가 만드는 것이 그쪽이다.
한국어 용어도 세 층으로 정리했다: 실패(Result) / 결함(crash) / 감독.
세 층이 세 가지 다른 기제로 규율된다 — 타입, 없음(발산), capability.
"상황이 나쁨"은 결함이 아니라 실패다. 이 선을 안 그으면 crash가 게으름의
배출구가 된다. "오류"는 컴파일러 진단에만 쓴다.
얼랭 질문에 대한 답도 기록했다: 감독은 가져오고 비구조적 spawn은 안
가져온다. sc.spawn과 sup.spawn(sc, f)로 갈리며 문법 변경이 없다 — 실패를
삼키려면 Supervisor를 받았어야 하고 그것이 시그니처에 보인다.
개명은 문법을 먼저 고치고 대조 장치로 확인했다. 문장 500개, 파일 26개
모두 갈림 0건.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
순서가 요점이다. 문법에 test_decl과 panic_expr을 넣고 파서는 안 고친 채로
대조 장치를 돌렸더니 즉시 잡혔다:
문장 300개 중 파서가 거부한 것 140개
[1] 선언 (fn, struct, enum, capability, const)이(가) 필요합니다 — test 발견
파서를 따라가게 하니 다시 0건. 문법과 구현이 어긋나는 상태가 관측 가능한
것이 되었다는 뜻이다.
panic:
- 키워드다. prelude가 없어 함수로 두면 쓸 때마다 import해야 한다
- effect가 아니다. 경계 검사 하나에 {Panic}이 호출자 전부로 전염되면
effect 절은 신호가 아니라 잡음이 된다
- Never는 어떤 타입 자리에도 놓인다. 없으면 panic을 match 팔에서 못 쓴다
- 언어 수준 recover 없음. 되감기 없음. 자원 해제 여부는 열어둔다
- 0으로 나누기, assert 실패가 이 하나로 모인다
test:
- 파라미터가 없어 capability를 받을 수 없고, 만들 문법도 없다. 그래서
effect-free임이 증명된다 — 관례가 아니라 검사다. 시험해 보니 실제로
"테스트는 effect를 수행할 수 없습니다"로 거부한다
- 일반 코드와 같은 타입/effect/move 검사를 받는다
- interface hash에서 제외 — 테스트를 고쳤다고 downstream이 재검사되면 안 된다
- 격리는 런타임의 일이다. 하나가 죽어도 나머지는 돈다
assert는 std/test.cool에 coollang으로 쓰였다 — panic 위의 설탕임이 코드로
보이고, std에서 본문이 있는 첫 함수가 됐다. 그 바람에 std/런타임 양방향
테스트가 걸렸고(본문 있는 함수에 런타임 구현을 요구했다), 그 구분을 넣었다.
samples/app/config.cool에 첫 테스트 넷.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
설명서와 파서가 어긋나 있었다. 그냥 어긋난 게 아니라, 그 설명서를 안 읽고
"else if가 안 된다"고 마찰 보고서에 잘못 적었다 — 설명서로서 제 역할을 한
번도 못 했다는 뜻이다.
lib/ebnf.ml: EBNF를 데이터로 읽는다. nullable, FIRST, FOLLOW를 계산하고
LL(1) 충돌을 보고한다. 매개변수 프로덕션(name<p>)을 지원한다 — expr_ns와
목록을 복제 없이 적기 위한 것이다.
문법 첫머리의 "설계 제약: LL(1)"은 지금까지 사람의 주장이었다. 기계로 재니
충돌 18건이 나왔다. 세 부류였다:
- 구조적 3건: stmt(assign/expr), primary(ident/struct_lit), pattern —
파서는 왼쪽 인수분해를 손으로 했는데 문법에 안 적혀 있었다
- 후행 콤마 9건: X , { "," , X } , [ "," ]는 콤마를 본 시점에 갈리지 않는다.
우재귀로 다시 적었다
- 줄바꿈 흡수 6건: 어느 쪽이 먹어도 파스 트리가 같다. greedy 규칙을 표기에
명시하고 그렇게 해소되는 것만 따로 분류한다
지금은 진짜 충돌 0건이고, 테스트가 이를 고정한다.
병렬 조사에서 나온 드리프트도 모두 반영했다:
- named_type, name_pattern에 한정 이름(Shapes.Shape, Shapes.Circle)
- name_pattern이 인자 0개와 괄호 없는 한정 생성자를 받는다
- string_lit의 이스케이프
- cap_method의 gen_params (파서가 이미 허용하고 있었다)
- field/variant/field_init/arm 사이의 콤마는 필수다
- stmt 사이의 NEWLINE도 필수다 — 선택적으로 적었더니 그것 하나가 LL(1)
충돌 셋을 만들었다
- { NEWLINE }을 전부 [ NEWLINE ]으로. 렉서가 연속 줄바꿈을 만들 수 없다
expr_ns는 산문 주석이었고 파서 상태 플래그로 구현돼 있었다. 매개변수
프로덕션으로 형식화해 문맥자유가 됐다.
렉서 쪽: Token.next_kind가 모든 토큰을 한 번씩 잇는다. 나열이 빠지면
컴파일러가 지적한다. 문법 문서의 키워드 표와 줄 끝 판정 목록은 이제
lib/lexical_doc.ml이 거기서 생성하고 테스트가 대조한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
세 가지를 자율 결정으로 닫고 타입 검사까지 세웠다.
1. scope 부모 문법: scope 자식 = 부모 { ... }로 확정. 부모를 적지 않으면
자식의 부모가 "가장 가까운 스코프"가 되는데 그것이 정확히 ambient
authority다. 권한 사슬이 main의 루트 TaskScope부터 끊기지 않으려면 모든
자식이 부모를 이름으로 지목해야 한다. 이름 해소가 이 구멍을 잡아준 건이라,
주석 처리했던 중첩 예제를 되살렸다.
2. prelude: 타입 이름은 그 타입에 딸린 함수의 이름공간이다(String.len,
File.close). 정적 메서드를 위한 별도 문법을 두지 않는다.
3. 타입 검사: 이 모듈 안에서 아는 것만 검사한다. 외부 이름은 TUnknown이
되어 무엇과도 맞는다 — 모르는 것을 틀렸다고 말하지 않기 위해서다.
제네릭 해소는 호출 지점의 지역 unification이고 함수 하나를 넘지 않는다.
클로저 파라미터 타입은 기대 타입에서 읽어온다(양방향 검사, 로컬).
단계 소유권을 하나 정정했다. affinity는 타입 동등성의 일부가 아니다. 값이
affine인지는 무엇을 capture했는지로 정해지는 substructural 성질이고
move/affinity 검사가 소유한다. 타입 검사가 이걸 판정하려다 정당한 코드를
거부하는 것을 06에서 확인하고 unify에서 분리했다.
unify 버그 하나: 같은 미지수끼리 unify할 때 occurs check가 자기 자신을
발견해 실패하고 있었다. 02의 fold 호출에서 잡혔다.
09_type_errors.cool 추가 — 외부 타입이 하나도 없어 검사기가 TUnknown으로
빠져나갈 구석이 없는 파일이다. 18개 진단이 전부 잡히고, 첫 오류에서 멈추지
않고 모두 보고한다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
grammar.ebnf의 프로덕션 하나에 함수 하나로 대응한다. LL(1)이므로 선읽기는
항상 한 토큰이고 backtracking이 없다. 모든 노드가 위치를 들고 다닌다.
문법과 얽히는 두 자리를 원칙대로 처리했다:
- NEWLINE 흡수는 문법에 { NEWLINE }으로 적힌 자리에서만 한다. 파서가
"여기선 줄바꿈 무시" 식으로 임의 판단하면 어디서 무시되는지 아무도 모르게
된다.
- struct 리터럴은 if/match/scope 머리에서 금지하고(no_struct), 괄호·인자
목록·블록에 들어가면 다시 허용한다.
문법에 새긴 제한이 실제로 파서에서 죽는 것을 확인했다. 파라미터 위치의
effect 합집합과 match 가드는 검사기가 아니라 파서가 거부하며, 진단이
원인을 직접 말한다. 후행 콤마 누락도 일반적인 "닫는 괄호 필요" 대신
"다중 줄 목록에는 후행 콤마가 필요합니다"로 보고한다.
샘플을 실제로 파싱해 두 가지를 잡았다:
- 02와 05가 own을 타입 위치에 쓰고 있었다. 확정한 규칙은 바인딩 수식어가
이름 앞이므로 샘플이 틀렸다. 수정.
- 05에 구문 오류와 검사기 오류가 섞여 있었다. 파서가 첫 오류에서 멈추면
검사기 케이스에 영영 도달하지 못하므로 08_syntax_errors.cool로 분리.
grammar.ebnf를 구현과 맞췄다: 제네릭 인자에 effect 집합 허용, 마지막 문의
구분자는 "}" 앞에서 생략, 중괄호 목록 안의 NEWLINE 흡수 위치 명시.
cool ast 추가. cool check는 이제 파서까지 돌리되 여전히 통과했다고 말하지
않는다.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
grammar.ebnf의 어휘 절을 구현한다. 렉서는 선읽기를 요구하지 않고,
문법과 얽히는 유일한 부분인 NEWLINE 삽입은 can_end_statement 하나로
판정한다. 모든 토큰이 위치를 들고 다닌다 — 진단 품질이 헌법급이므로
나중에 붙이지 않는다.
샘플을 실제로 렉싱해 함정을 하나 잡았다. effects 절이 줄 끝에 오면 "}"가
값 종료 토큰이라 NEWLINE이 삽입되어 다음 줄의 "->"와 끊긴다(Go ASI와 같은
형태). 렉서에 문맥을 주는 대신 — 렉서 피드백은 철학 2가 배제한다 —
시그니처 머리의 흡수 위치를 프로덕션에 명시적으로 적어 닫았다. 파서가
임의로 건너뛰는 것이 아니라 문법에 적힌 자리에서만 흡수한다.
다중 줄 목록의 후행 콤마 필수도 함께 명시(콤마로 끝난 줄은 NEWLINE을
만들지 않으므로 목록이 자연히 이어진다).
cool check는 어휘 분석을 돌리되 통과했다고 말하지 않는다. 파이프라인의
나머지가 없는 이상 그 파일은 검사된 것이 아니다. 디버깅용 cool tokens 추가.
테스트 30건: 키워드, 두 글자 연산자 우선, NEWLINE 삽입 6가지 경우,
리터럴과 이스케이프, 오류 7종과 오류 위치, 그리고 samples/*.cool 전체가
어휘 분석을 통과하는지.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
열려 있던 문법 항목을 언어 철학에 따라 전부 닫고 EBNF로 고정한다.
- 제네릭 인자를 대괄호로 통일(List[a], map[Int, String](...)). <>는 식
위치에서 비교 연산자와 갈리지 않아 turbofish라는 제2 표기를 부르는데,
effect 규칙이 명시적 인스턴스화 문법을 요구하므로 회피할 수 없다.
대괄호는 전위=리터럴 / 후위=인스턴스화로 위치가 결정해 LL(1)이고 표기가
하나다. 대가로 인덱싱 연산자를 두지 않는다.
- 파라미터 규칙을 callable 한정에서 전 파라미터로 일반화. 기본은 빌림,
own만 소유 이전. capability를 예외로 두지 않는 이유는 capability의 존재가
이미 타입과 effects 절에 드러나기 때문이다 — 표기가 실을 정보는 소유 이동뿐.
- 문 구분은 줄바꿈(Go식 자동 삽입), 블록은 식, return은 조기 탈출 전용.
if/match를 식으로 두면 재대입이 줄어 move 검사가 단순해진다.
- match 가드 없음. 가드는 exhaustiveness를 흐리고 SMT 없이는 _ 분기를
강요한다. 철학 1의 대표 항목을 문법 편의와 바꾸지 않는다.
- ?는 Result 전용 하드코딩. 조기 탈출도 블록 종료이므로 scope의 join/cancel이
? 경로에서도 실행된다. v1 linear 도입 시 이 경로가 암묵 drop 문제와 만난다.
- 수식어 순서는 바인딩(own, mut) 먼저, 타입(affine) 나중.
docs/grammar.ebnf는 LL(1)을 설계 제약으로 명시하고, effect 합집합을
결과 위치 전용 프로덕션으로 새겨 파라미터 위치의 역산을 파서가 거부하게 한다.
samples/는 확정 표기로 전면 갱신.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E