저장소의 .cool 파일로만 대조하면 사람이 쓴 코드만 훑는다. 문법이 약속했는데 파서가 못 읽는 구석은 아무도 안 밟으면 드러나지 않는다. lib/ebnf_gen.ml이 문법에서 문장을 만든다. 텍스트가 아니라 토큰 열을 만드는 이유는, 렉서의 줄바꿈 삽입을 거치면 문법이 허용해도 렉서가 만들 수 없는 문장이 생기는데 그건 파서의 잘못이 아니기 때문이다. 검사하려는 것은 문법과 파서 사이지 렉서가 아니다. 커버리지를 같이 잰다. 안 밟은 규칙은 시험되지 않은 규칙이므로, 통과했다는 말에 값이 없다. 현재 프로덕션 95개 전부를 밟고 거부 0건이다. 이 퍼저가 잡은 갈림 하나: 대입 왼쪽 제약. 문법은 expr_stmt = expr, ["=" expr] 로 적었는데 파서는 파싱 중에 "변수나 필드만"을 강제하고 있었다. 구문으로 가르면 ident 하나로 대입과 식이 갈리지 않아 LL(1)이 깨지므로, 제약을 이름 해소로 옮겼다. 파서는 이제 순수하게 구문만 본다. 만드는 과정에서 퍼저 자체의 함정도 하나 지났다. 처음엔 연료를 총 확장 횟수로 셌더니 선언 머리에서 다 써 버려 식과 문에 도달하지 못했고, 커버리지를 재기 전까지는 "3000개 통과"가 아무 뜻도 아니었다. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
coollang
되돌리기 비싼 결정부터 확정하는 프로그래밍 언어. 현재 v0 — 설계 검증판.
$ coolc check samples/run/hello.cool
$ coolc run samples/run/hello.cool
area = 12
area = 9
area = 3
무엇을 위한 언어인가
세 가지 목표가 나머지 모든 결정을 지배한다.
- 오류를 더 빨리 잡는다 — null 없음, Option/Result, 소진적 match, 확정 초기화, 선언되지 않은 effect는 컴파일 오류.
- 검증이 더 빠르다 — fast path / slow path 분리, 전역 추론 없음, 복잡한 trait solver 없음, 임의 매크로 없음, 인터페이스 해시 기반 무효화.
- 피해 범위가 좁다 — 명시적 capability, ambient authority 없음, affine 소유권.
설계의 전문은 docs/thesis.md, 문법은
docs/grammar.ebnf에 있다.
지금 되는 것
lex → parse → 이름 해소 → 타입 검사 → effect/capability 검사 → move/affinity 검사 → 소진성 검사 → interface 해시 → typed IR → 인터프리터
여섯 종류의 오류를 한국어 진단으로 보고한다: 문법, 이름, 타입, effect, capability, 소유권. 여기에 match 소진성과 두 가지 lint(미사용 import, effect 과잉 선언)가 더해진다.
$ coolc check samples/12_stdlib_effects.cool
samples/12_stdlib_effects.cool:20:14: 선언되지 않은 effect Console.print
(leaks_effect의 effects 절은 {}입니다)
증분 검사
이 프로젝트의 중심 주장이다. 10만 줄 / 200 모듈에서 측정 (bench/bench.ml):
| 시간 | 재검사한 모듈 | |
|---|---|---|
| 전체 검사 (cold) | 253ms | 200 |
| 함수 본문만 수정 | 0.8ms | 1 |
| 함수 시그니처 수정 | 2.5ms | 2 |
핵심은 시간이 아니라 범위다. 본문 수정이 downstream을 한 칸도 건드리지 않는 것, 그리고 시그니처 수정이 사슬 끝까지 가지 않고 해시가 변한 곳에서 멈추는 것 — 이 둘이 아키텍처의 주장 전부다.
시그니처 수정 시나리오에서는 두 모듈 건너의 match가 실제로 깨진다:
m100.cool:15:3: match가 모든 경우를 덮지 않습니다 (빠진 경우: Tri(_))
m101.cool:21:3: match가 모든 경우를 덮지 않습니다 (빠진 경우: Up.Tri(_))
실제로 써본 결과
samples/app은 검사기를 시험하려고 쓴 것이 아니라 일을 하려고 쓴
프로그램이다 — 설정 파서 + 리포트 도구, 2모듈 232줄.
$ coolc run samples/app/main.cool samples/app/example.conf
설정
name = "coollang" (text)
threads = 4 (number)
...
항목 5개, 문제 2개
문제
10행: 이름이 비어 있습니다
11행: = 가 하나여야 합니다: broken = a = b
쓰면서 걸린 것들을 docs/friction.md에 남겼다.
요약하면: 되돌리기 비싼 결정은 하나도 후회되지 않았고, 불편은 전부
되돌리기 싼 것들이었다.
빌드
OCaml 5.x와 dune이 필요하다.
opam install dune
dune build
dune test # 175개 검사
dune exec bench/bench.exe
coolc는 _build/default/bin/main.exe다. 설치하려면 dune install.
저장소 구성
lib/ 컴파일러 (약 4,900줄 OCaml)
lexer.ml 어휘 분석 — 문법을 하나도 모른다
parser.ml 재귀 하강, LL(1), backtracking 없음
resolve.ml 이름 해소 — 모듈 하나만 보고 결정할 수 있는 것
typecheck.ml 타입 + effect + capability
move.ml move / affinity
exhaust.ml 소진성 (Maranget usefulness)
iface.ml interface artifact + 해시
session.ml 모듈 로딩 + 고정점 invalidation
ir.ml 얇은 typed IR
interp.ml 트리 워킹 인터프리터
std/ 표준 라이브러리 (본문 없는 선언, 런타임이 구현)
samples/ 예제 — 통과용 9개, 일부러 틀린 것 7개
bench/ 증분 루프 측정
v0의 성격
이것은 쓸 수 있는 언어가 아니라 설계가 옳은지 증명된 언어다. 되돌리기 비싼 결정 — 문법, 타입, effect, capability, 소유권, 증분 아키텍처 — 이 전부 코드와 테스트로 못 박혔고, 빠른 검증 루프라는 시스템 속성이 측정으로 증명됐다.
그래서 이 코드를 통째로 버리고 v1로 번역해도 잃을 것이 없다. 애초에 그것이 v0의 목적이었다.
아직 없는 것: 진짜 컴파일(해석 실행만 한다), 병렬 실행(scope는 순차),
파일·네트워크 IO, 완전한 제네릭, 에디터 지원, 패키지 관리자. 표준
라이브러리는 len, each, map, concat, show 수준이다.
이름
언어는 coollang, 툴체인 바이너리는 coolc. 소스 확장자는 .cool.