제6화 컴파일러는 누가 만들었을까?

코드의 일생 07

띄어 쓴 자리, 줄 바꾼 자리

빈칸과 줄바꿈은 언제 뜻이 될까?

첫 바퀴를 다 돈 한줄이가 다시 첫 정거장에 왔어.

톡톡에게는 아직 못다 한 이야기가 있대.

오늘의 이상한 질문

1 + 2의 빈칸 하나를 줄바꿈으로 바꾸면, 가지는 토큰을 몇 문장으로 묶을까?

그렇게 생각했구나.

두 바구니를 같이 들여다보자.

만화

다시 첫 정거장

톡톡나 아직 할 말이 남았어. 빈칸 이야기야.

한줄이빈칸? 제1화에서 다 떨어졌잖아.

붙어 버린 숫자

한줄이빈칸을 빼면 어떻게 돼?

톡톡숫자가 이어져서 이번엔 12 한 조각이네.

줄을 바꾼 한줄이

한줄이그럼 줄을 바꿔 볼래. 1 하고, 다음 줄에 2.

톡톡줄바꿈은 떨어지지 않네. 바구니에 챙겨 둘게.

정리대

가지1 다음에 칸막이가 섰네. 여기서 한 문장이 끝났구나.

한줄이내 마지막 줄바꿈은 어디 갔어?

톡톡정리대에서 빠졌어. 어떤 줄바꿈이 칸막이가 되는지는 같이 찾아보자.

칸막이 없는 두 숫자

가지1 다음에 바로 2가 왔어. 칸막이가 없으니 어디서 끊어야 할지 모르겠어.

한줄이빈칸이 있었는데도?

톡톡빈칸은 둘을 갈라 놓고 떨어졌어. 내 바구니에는 안 들어가.

어느 틈이 무엇을 남기는지 직접 바꿔 보자.

작은 실험

틈 바꾸기

글자 사이 틈에 무엇을 두느냐에 따라 두 바구니에는 무엇이 남을까?

한줄이의 글자 사이 틈에 무엇을 둘지 골라 봐.

톡톡의 바구니와 가지의 바구니에 무엇이 남는지 비교해 보자.

이 쪽은 컴파일러를 실행하지 않아. 바구니에 담긴 토큰과 검사 결과는 토파즈 5.21.1로 미리 남긴 기록이야.

리본은 파일에 쓴 줄바꿈이야. 마개는 글자를 다 읽은 자리야.

1 줄바꿈 2 줄바꿈 에는 리본이 두 개 있고, 둘째 리본 바로 뒤에 파일 끝 마개가 있어.

토파즈 5.21.1 기록

숫자 사이

1과 2 사이

틈을 골라 봐.

틈에 붙이기, 빈칸, 줄바꿈 가운데 하나를 넣으면 아래 다섯 줄이 그 파일의 기록으로 바뀌어.

빈칸이 없으니 12가 한 토큰이 됐어. 줄 끝 줄바꿈은 정리대에서 빠졌어. 검사는 통과했어.

빈칸이 1과 2를 갈라 놓고 바구니에는 들어가지 않았어. 가지의 바구니에 칸막이가 없어서 검사가 멈췄어.

1 뒤 줄바꿈이 칸막이가 됐어. 맨 끝 줄바꿈은 빠졌어. 검사는 통과했어.

빈칸이 없으니 12가 한 토큰이 됐어. 줄 끝 줄바꿈은 정리대에서 빠졌어. 검사는 통과했어.

한줄이

  1. 1
  2. 2
  3. 줄바꿈

톡톡의 바구니

  1. 숫자 12
  2. 줄바꿈
  3. 파일 끝

정리대

  • 1번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 12
  2. 파일 끝

검사

검사 통과

main.tpz: types-ok (1 module)
main.tpz: resolve-ok (1 module)
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
토큰 관측 명령
topaz compiler observe inputs/g1/main.tpz --out-dir out/g1 --compiler self --terminal typed
입력 바이트
3
입력 sha256
a1fb50e6c86fae1679ef3351296fd6713411a08cf8dd1790a4fd05fae8688164
판
5.21.1
끝 표시
0

같은 판으로 같은 절차를 두 번 따랐어. 두 번의 기록은 바이트까지 같았어.

이 입력에서 두 판(5.21.0, 5.21.1)의 검사 출력과 종료 코드, 대조한 토큰·나무·진단 파일이 바이트까지 같았어.

원문 열기

빈칸이 1과 2를 갈라 놓고 바구니에는 들어가지 않았어. 가지의 바구니에 칸막이가 없어서 검사가 멈췄어.

한줄이

  1. 1
  2. 빈칸
  3. 2
  4. 줄바꿈

톡톡의 바구니

  1. 숫자 1
  2. 숫자 2
  3. 줄바꿈
  4. 파일 끝

바구니에 들어가지 않은 빈칸

정리대

  • 1번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 1
  2. 숫자 2
  3. 파일 끝

검사

검사가 멈췄어

error[TPZ2001]: expected a statement separator
 --> main.tpz:1:3
  |
1 | 1 2
  |   ^

main.tpz: 1 diagnostic
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
토큰 관측 명령
topaz compiler observe inputs/g2/main.tpz --out-dir out/g2 --compiler self --terminal typed
입력 바이트
4
입력 sha256
f251ddc12234e0da8d3b778bd0f7463fb477f16f47757f5617dc8b4ff4d4f14a
판
5.21.1
끝 표시
1

같은 판으로 같은 절차를 두 번 따랐어. 두 번의 기록은 바이트까지 같았어.

이 입력에서 두 판(5.21.0, 5.21.1)의 검사 출력과 종료 코드, 대조한 토큰·나무·진단 파일이 바이트까지 같았어.

원문 열기

1 뒤 줄바꿈이 칸막이가 됐어. 맨 끝 줄바꿈은 빠졌어. 검사는 통과했어.

한줄이

  1. 1
  2. 줄바꿈
  3. 2
  4. 줄바꿈

톡톡의 바구니

  1. 숫자 1
  2. 줄바꿈
  3. 숫자 2
  4. 줄바꿈
  5. 파일 끝

정리대

  • 1번째 줄바꿈은 칸막이가 됐어.
  • 2번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 1
  2. 칸막이, 줄바꿈 자리에서
  3. 숫자 2
  4. 파일 끝

검사

검사 통과

main.tpz: types-ok (1 module)
main.tpz: resolve-ok (1 module)
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
토큰 관측 명령
topaz compiler observe inputs/g3/main.tpz --out-dir out/g3 --compiler self --terminal typed
입력 바이트
4
입력 sha256
a6e2b7a040683432de03a18fd8a1939a2fdf82585b364bfc874bdd4095c4cae1
판
5.21.1
끝 표시
0

같은 판으로 같은 절차를 두 번 따랐어. 두 번의 기록은 바이트까지 같았어.

이 입력에서 두 판(5.21.0, 5.21.1)의 검사 출력과 종료 코드, 대조한 토큰·나무·진단 파일이 바이트까지 같았어.

원문 열기

토파즈 5.21.1 기록

더하기 양옆

+ 앞과 뒤, 어느 틈을 줄바꿈으로 바꿔 볼래?

+ 앞
+ 뒤

틈을 골라 봐.

두 틈에 각각 빈칸이나 줄바꿈을 넣어 봐. 바구니 둘을 견주면 차이가 보여.

줄바꿈은 맨 끝 하나뿐이고 정리대에서 빠졌어. 가지는 이 토큰들을 한 문장으로 묶었어.

+ 뒤 줄바꿈이 정리대에서 빠졌어. 가지의 바구니는 1 + 2일 때와 자리까지 같아.

1 뒤 줄바꿈이 칸막이가 됐어. 검사는 통과했지만 1과 + 2가 따로 선 두 문장이야.

1 뒤 줄바꿈은 칸막이가 되고 + 뒤 줄바꿈은 빠졌어. 가지의 바구니는 1 줄바꿈 + 2일 때와 같아.

줄바꿈은 맨 끝 하나뿐이고 정리대에서 빠졌어. 가지는 이 토큰들을 한 문장으로 묶었어.

한줄이

  1. 1
  2. 빈칸
  3. +
  4. 빈칸
  5. 2
  6. 줄바꿈

톡톡의 바구니

  1. 숫자 1
  2. 더하기 기호
  3. 숫자 2
  4. 줄바꿈
  5. 파일 끝

바구니에 들어가지 않은 빈칸

정리대

  • 1번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 1
  2. 더하기 기호
  3. 숫자 2
  4. 파일 끝

검사

검사 통과

main.tpz: types-ok (1 module)
main.tpz: resolve-ok (1 module)
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
입력 바이트
6
입력 sha256
cb898749d76e51fdaf6c6636920ccdb4f415fc1cffd1e2497636b38cc2469807
판
5.21.1
끝 표시
0

이 1 + 2 판은 제1화의 토큰 기록과 제5화의 검사 기록을 함께 보여 줘. 두 회차가 읽은 소스는 같은 여섯 바이트야.

원문 열기

+ 뒤 줄바꿈이 정리대에서 빠졌어. 가지의 바구니는 1 + 2일 때와 자리까지 같아.

한줄이

  1. 1
  2. 빈칸
  3. +
  4. 줄바꿈
  5. 2
  6. 줄바꿈

톡톡의 바구니

  1. 숫자 1
  2. 더하기 기호
  3. 줄바꿈
  4. 숫자 2
  5. 줄바꿈
  6. 파일 끝

바구니에 들어가지 않은 빈칸

정리대

  • 1번째 줄바꿈은 빠졌어.
  • 2번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 1
  2. 더하기 기호
  3. 숫자 2
  4. 파일 끝

검사

검사 통과

main.tpz: types-ok (1 module)
main.tpz: resolve-ok (1 module)
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
토큰 관측 명령
topaz compiler observe inputs/g4/main.tpz --out-dir out/g4 --compiler self --terminal typed
입력 바이트
6
입력 sha256
fc7f432f888fb56e24acee32e9b2fce48ff3b2e819b3fcb181a723a99391e10c
판
5.21.1
끝 표시
0

같은 판으로 같은 절차를 두 번 따랐어. 두 번의 기록은 바이트까지 같았어.

이 입력에서 두 판(5.21.0, 5.21.1)의 검사 출력과 종료 코드, 대조한 토큰·나무·진단 파일이 바이트까지 같았어.

원문 열기

1 뒤 줄바꿈이 칸막이가 됐어. 검사는 통과했지만 1과 + 2가 따로 선 두 문장이야.

한줄이

  1. 1
  2. 줄바꿈
  3. +
  4. 빈칸
  5. 2
  6. 줄바꿈

톡톡의 바구니

  1. 숫자 1
  2. 줄바꿈
  3. 더하기 기호
  4. 숫자 2
  5. 줄바꿈
  6. 파일 끝

바구니에 들어가지 않은 빈칸

정리대

  • 1번째 줄바꿈은 칸막이가 됐어.
  • 2번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 1
  2. 칸막이, 줄바꿈 자리에서
  3. 더하기 기호
  4. 숫자 2
  5. 파일 끝

검사

검사 통과

main.tpz: types-ok (1 module)
main.tpz: resolve-ok (1 module)
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
토큰 관측 명령
topaz compiler observe inputs/g5/main.tpz --out-dir out/g5 --compiler self --terminal typed
입력 바이트
6
입력 sha256
2df649c6846fb74122764a28a90e1c2e5efce91bdda7dbc15447038a6db4b348
판
5.21.1
끝 표시
0

같은 판으로 같은 절차를 두 번 따랐어. 두 번의 기록은 바이트까지 같았어.

이 입력에서 두 판(5.21.0, 5.21.1)의 검사 출력과 종료 코드, 대조한 토큰·나무·진단 파일이 바이트까지 같았어.

원문 열기

1 뒤 줄바꿈은 칸막이가 되고 + 뒤 줄바꿈은 빠졌어. 가지의 바구니는 1 줄바꿈 + 2일 때와 같아.

한줄이

  1. 1
  2. 줄바꿈
  3. +
  4. 줄바꿈
  5. 2
  6. 줄바꿈

톡톡의 바구니

  1. 숫자 1
  2. 줄바꿈
  3. 더하기 기호
  4. 줄바꿈
  5. 숫자 2
  6. 줄바꿈
  7. 파일 끝

정리대

  • 1번째 줄바꿈은 칸막이가 됐어.
  • 2번째 줄바꿈은 빠졌어.
  • 3번째 줄바꿈은 빠졌어.

가지의 바구니

  1. 숫자 1
  2. 칸막이, 줄바꿈 자리에서
  3. 더하기 기호
  4. 숫자 2
  5. 파일 끝

검사

검사 통과

main.tpz: types-ok (1 module)
main.tpz: resolve-ok (1 module)
이 기록은 어디서 왔을까?
검사 명령
topaz check main.tpz
토큰 관측 명령
topaz compiler observe inputs/g6/main.tpz --out-dir out/g6 --compiler self --terminal typed
입력 바이트
6
입력 sha256
44d2c4ba874bfb725b76233edb4b4075dd16b6f7e47ba7425940c6ec3700623e
판
5.21.1
끝 표시
0

같은 판으로 같은 절차를 두 번 따랐어. 두 번의 기록은 바이트까지 같았어.

이 입력에서 두 판(5.21.0, 5.21.1)의 검사 출력과 종료 코드, 대조한 토큰·나무·진단 파일이 바이트까지 같았어.

원문 열기

세 틈을 모두 한 번씩 골라 보면 발견 카드가 열려.

빈칸은 가르고, 줄바꿈은 정리대에서 갈 길이 정해져.

빈칸은 토큰 사이를 갈라 놓고 톡톡의 바구니에는 들어가지 않았어. 빈칸을 빼면 12 한 토큰이 됐고, 빈칸만 두면 칸막이가 없어 검사가 멈췄어.

줄바꿈은 톡톡의 바구니에 조각으로 남았어. 이 두 실험판에서 1 뒤 줄바꿈은 칸막이가 됐고, + 뒤 줄바꿈과 맨 끝 줄바꿈은 빠졌어.

+ 뒤에서 줄을 바꾸자 가지는 1 + 2를 한 문장으로 묶었어. + 앞에서 줄을 바꾸자 이번에는 1이 한 문장, + 2가 또 한 문장이 됐어. 둘째 문장의 +는 2 하나에 붙어. 두 파일 모두 검사를 통과했어.

기록에서 더 찾아낸 짝

  • 1 2와 1 줄바꿈 2

    1. 숫자 1
    2. 숫자 2
    3. 파일 끝
    1. 숫자 1
    2. 칸막이, 줄바꿈 자리에서
    3. 숫자 2
    4. 파일 끝

    가지의 바구니가 달라.

  • 1 + 2와 1 + 줄바꿈 2

    1. 숫자 1
    2. 더하기 기호
    3. 숫자 2
    4. 파일 끝
    1. 숫자 1
    2. 더하기 기호
    3. 숫자 2
    4. 파일 끝

    가지의 바구니가 같아. 토큰의 차례와 종류, 시작과 끝 자리, 글자, 새로 만든 토큰인지까지 견줬어.

  • 1 줄바꿈 + 2와 1 줄바꿈 + 줄바꿈 2

    1. 숫자 1
    2. 칸막이, 줄바꿈 자리에서
    3. 더하기 기호
    4. 숫자 2
    5. 파일 끝
    1. 숫자 1
    2. 칸막이, 줄바꿈 자리에서
    3. 더하기 기호
    4. 숫자 2
    5. 파일 끝

    가지의 바구니가 같아. 토큰의 차례와 종류, 시작과 끝 자리, 글자, 새로 만든 토큰인지까지 견줬어.

바구니와 정리대, 칸막이, 리본은 이야기 속 그림이야. 토큰의 종류와 자리, 검사 결과는 기록에서 왔어.

진짜 속사정

토큰 흐름은 두 벌이야

톡톡의 바구니에는 글자를 읽으며 찾은 토큰이 담겨. 정리대는 그 토큰을 받아 가지에게 보낼 바구니를 꾸려. 가지는 그 바구니에서 토큰을 꺼내 문장으로 묶어.

토파즈 기록은 첫 바구니의 토큰 흐름을 raw, 정리된 흐름을 layout이라고 불러.

가지의 바구니에서 1과 2 사이에 선 칸막이를 봤지? 기록에서 이 토큰의 이름은 separator야. 여기서는 두 문장의 경계를 알려 줘서 문장 구분자라고 불러.

1 줄바꿈 2 줄바꿈 의 layout에 있는 문장 구분자 한 줄
종류separator
자리두 번째 바이트 하나, 기록의 범위 [1,2)
첫 줄바꿈 자리에 만든 문장 경계true

마지막 칸이 true인 건 정리대가 첫 줄바꿈 자리에 문장 경계를 표시하면서 이 칸막이가 생겼다는 뜻이야.

공개 소스에는 lex가 찾은 토큰을 바로 다음 줄에서 normalize에 건네는 코드가 있어. 제6화 지도 뒷면 5행에서 불러오던 바로 그 normalize야.

src/main.tpz 261~262행
261  let raw = lex(request.source)262  let layout = normalize(raw.tokens, request.source)

컴파일러가 프로그램을 읽어 들이는 다른 파일에서는 세 줄이 이어져. lex가 찾은 토큰을 normalize가 정리하고, parse는 정리된 토큰을 받아.

resolver/graph.tpz 91~93행
91  let raw = lex(source)92  let layout = normalize(raw.tokens, source)93  let parsed = parse(layout.tokens, source, maxAstNodes, maxAstDepth, languageMode)?

빈칸은 쓸모없을까?

아니. 빈칸은 토큰 사이를 갈라. 12는 한 토큰인데 1 2는 두 토큰이야.

다만 빈칸은 문장을 나누지는 못해. 1 2에서 가지는 문장 구분자를 기다리다 expected a statement separator라는 쪽지를 남기고 멈췄어.

줄바꿈은 언제나 문장을 끝낼까?

아니. 오늘 기록에서 + 뒤의 줄바꿈과 맨 끝의 줄바꿈은 빠졌어.

토파즈 문서는 이렇게 적어 두었어.

계산이 줄바꿈에서 끊기면 +나 -의 위치를 보세요. 새 문장 앞에서는 값 하나에 적용하는 단항 연산자로 해석될 수 있습니다.

어휘 구조와 레이아웃, 새 창

그래서 1 줄바꿈 + 2는 검사를 통과하지만 1과 + 2가 따로 선 두 문장이야. 계산을 다음 줄로 이으려면 +를 앞줄 끝에 남겨.

빈칸과 줄바꿈을 다루는 규칙은 언어마다 달라. 오늘 본 것은 토파즈 5.21.1의 규칙과 기록이야.

토파즈 5.21.1 기록과 공개 소스

더 들여다보기

두 바구니의 원문 읽기

토파즈의 토큰 기록은 두 흐름을 한 파일에 함께 적어. 줄마다 stream 칸에 raw나 layout이 있어.

기록에 적힌 것 1 줄바꿈 2 줄바꿈 의 토큰 기록 9줄
streamkindspanspellingsynthetic
rawinteger[0,1)1false
rawnewline[1,2)줄바꿈false
rawinteger[2,3)2false
rawnewline[3,4)줄바꿈false
raweof[4,4)빈 글자false
layoutinteger[0,1)1false
layoutseparator[1,2)줄바꿈true
layoutinteger[2,3)2false
layouteof[4,4)빈 글자false
stream
이 토큰이 어느 흐름에 속하는지
kind
토큰의 종류
span
소스에서 차지한 바이트 범위. [1,2)는 두 번째 바이트 하나야. 파일 끝은 [4,4)라서 차지한 바이트가 없어
spelling
그 범위에 있는 글자. 줄바꿈 한 글자는 기록에서 \n으로 보이고, 파일 끝은 빈 글자야
synthetic
정리대가 줄바꿈 자리에 문장 경계를 표시해 만든 구분자는 true. 직접 쓴 ;를 가리키는 구분자는 false야

이 사이트가 맞대 본 것 1 + 줄바꿈 2의 layout 흐름은 1 + 2의 layout 흐름과 토큰의 차례, 종류, 시작과 끝, spelling, synthetic이 모두 같았어. 1 줄바꿈 + 2와 1 줄바꿈 + 줄바꿈 2의 layout 흐름도 서로 같았어.

이 사이트가 맞대 본 것 span에는 입력마다 다른 sourceId도 적혀 있어서 그 칸은 견주지 않았어.

normalize 소스 따라 읽기

토파즈 공개 소스

숫자 사이의 빈칸을 만나면 글자를 읽는 위치가 한 칸 앞으로 가. 줄바꿈을 만나면 그 자리를 가리키는 newline 토큰이 생겨. 정리 단계에서는 기억해 둔 줄바꿈 자리에 separator를 넣는 코드도 볼 수 있어.

줄 번호는 파일의 줄 번호 그대로야.

raw.tpz 610~619행
610    if isAsciiSpace(value) {611      index += 1612      continue613    }614    if value == 10 {615      let lo = if index > 0 && byteAt(bytes, index - 1) == 13 { index - 1 } else { index }616      tokens.push(token("newline", lo, index + 1))617      index += 1618      continue619    }

isAsciiSpace에 걸리는 빈칸은 index += 1로 한 칸 건너뛰고 토큰을 남기지 않아. 줄바꿈 글자(10)를 만나면 "newline" 토큰을 남겨.

layout.tpz 304~312행
304  if !isLeading(current.kind) && !currentPipeContinues &&305    !isCloser(current.kind) && current.kind != "eof" {306    let mut output = state.output307    output.push(token(308      "separator",309      pending.lo,310      pending.hi,311      true,312    ))

다음 토큰이 앞에서 이어 주는 기호도, 닫는 괄호도, 파일 끝도 아니고 파이프로 이어지는 자리도 아니면, 기억해 둔 줄바꿈 자리(pending.lo, pending.hi)에 "separator"를 넣어. 마지막 true가 기록의 synthetic 칸이 돼.

어떤 줄바꿈을 기억할까?
layout.tpz 245~265행
245function observeNewline(246  state: LayoutState,247  current: PreviewToken,248  frameDepth: int,249) -> LayoutState {250  let pipeContinues = previousIs(state.previous, "pipe") &&251    patternContinuesAt(state.pattern, frameDepth)252  let frame = topFrame(state.frames)253  if frame.mode == LayoutMode.Separator && frame.hasItem &&254    lastTokenKind(state.output) != "separator" && !previousIsTrailing(state.previous) &&255    !pipeContinues {256    match state.pending {257      case Some(_) => ()258      case None => return LayoutState {259        ...state,260        pending: Some(current),261      }262    }263  }264  state265}

observeNewline은 조건이 맞을 때만 줄바꿈을 pending으로 기억해. 바로 앞 토큰이 +처럼 뒤로 이어 주는 기호면(previousIsTrailing) 기억하지 않아. 이미 기억해 둔 줄바꿈이 있으면 그것을 그대로 둬.

토파즈 문서도 이렇게 적어. 이 문장의 합계는 문서 예제의 이름이야.

=와 +로 끝난 줄에는 값이 더 필요하므로 합계 표현식이 다음 줄로 이어집니다.

어휘 구조와 레이아웃, 새 창

왜 *는 이어질까?
layout.tpz 49~69행
49function lineContinuation(kind: string) -> Option<LineContinuation> {50  if kind == "gt" || kind == "gt-gt" || kind == "keyword/else" {51    Some(LineContinuation.Leading)52  } else if kind == "plus" || kind == "minus" || kind == "bang" ||53    kind == "tilde" || kind == "comma" || kind == "fat-arrow" ||54    kind == "left-paren" || kind == "left-bracket" {55    Some(LineContinuation.Trailing)56  } else if kind == "star-star" || kind == "star" || kind == "slash" ||57    kind == "percent" || kind == "dot-dot" || kind == "dot-dot-lt" ||58    kind == "lt" || kind == "le" || kind == "ge" || kind == "eq-eq" ||59    kind == "not-eq" || kind == "keyword/in" || kind == "and-and" ||60    kind == "or-or" || kind == "question-question" || kind == "pipe-gt" ||61    kind == "eq" || kind == "plus-eq" || kind == "minus-eq" ||62    kind == "star-eq" || kind == "slash-eq" || kind == "percent-eq" ||63    kind == "question-question-eq" || kind == "dot" ||64    kind == "question-dot" || kind == "keyword/by" {65    Some(LineContinuation.Both)66  } else {67    None68  }69}

"plus"는 줄 끝에서 뒤로 이어 주는 목록(Trailing)에만 있어. 그래서 새 줄 맨 앞의 +는 계산을 잇지 못했어. "star"는 양쪽 모두 이어 주는 목록(Both)에 있어.

기록에 적힌 것 1 줄바꿈 * 2도 검사했어. 가지의 바구니에 문장 구분자가 없었어.

이 사이트가 맞대 본 것 이 기록은 소스에서 "star"가 양쪽 모두 이어 주는 목록에 있는 것과 맞아.

1 2의 쪽지는 어디서 나올까?
parser/core.tpz 443~457행
443export function itemBoundary(parser: Parser) -> () {444  if at(parser, "separator") {445    skipSeparators(parser)446  } else if !at(parser, "right-brace") && !at(parser, "eof") {447    addDiagnostic(448      parser,449      "TPZ2001",450      "expected a statement separator",451      current(parser).lo,452      current(parser).hi,453    )454    synchronize(parser)455  }456  ()457}

이 예제에서 첫 식 1을 읽은 뒤 2를 만난 자리에 쪽지를 남겼어. 쪽지의 글은 expected a statement separator야.

옮겨 온 소스는 같은 판의 라이선스와 고지를 따라. LICENSE 읽기, 새 창 NOTICE 읽기, 새 창

한 줄 정리

오늘 알게 된 것

오늘 숫자 사이에 둔 빈칸은 1과 2를 갈라 놓고 첫 바구니에서 빠졌어.

줄바꿈은 첫 바구니에 남았어. 다음 바구니에서 칸막이가 될지는 그 줄바꿈이 놓인 자리에 따라 달랐어.

1 줄바꿈 2 줄바꿈 에는 줄바꿈이 둘이야. 가지의 바구니에서 칸막이가 된 것은 어느 쪽일까?

답 펼쳐 보기

첫 번째야. 1과 2 사이에 섰어.

두 번째 줄바꿈 바로 뒤에는 파일 끝 마개가 와서, 거기에는 칸막이가 생기지 않았어.

이제 줄을 바꿀 때 그 줄이 어디서 끝나는지도 보이겠지?

속사정 도감낱말 다시 보기문장 구분자

다음 이야기

제8화누가 먼저 묶일까

가지이번엔 내 차례야. 기호 둘이 나오면 나무는 어떻게 생길까?

길의 둘째 정거장에서 가지가 조립대를 다시 펼쳤어.

2 + 3 * 4를 나무로 묶으면, 어느 식이 다른 식 안에 들어갈까?

다음에는 둘째 정거장으로 가 보자.