Lowent 매뉴얼←↑→

fmt — 호출자의 버퍼에 조립하는 포매팅

소스
lib/fmt.low
층
L0 — 순수 계산(호출자의 버퍼)
권한
없음

수와 글자를 호출자가 준 버퍼에 찍어 문자열을 만든다. 출력할 줄이나 로그 한 줄을 조립할 때 쓴다. 이 모듈은 종이를 만들지 않고 건네받은 종이 (mut slice u8)에 글씨만 쓴다.

use fmt .

let p option u64 . be fmt.put_u64 buf 0 1234 .
guard is_some p . else return 1 .

0 은 pos(다음에 쓸 자리)이고, 돌려받는 option u64 는 성공하면 새 pos, 자리가 모자라면 none 이다.

포매팅은 출력이 아니다. 모든 op 이 호출자의 버퍼에 바이트를 조립할 뿐이고 하나의 예외 없이 effects none 이다. 실제 출력은 cap io 를 든 호출자가 write_out 이나 outbuf 로 따로 한다. 그래서 조립 코드는 권한 없이 아무 데서나 부를 수 있고, 시험은 화면이 아니라 버퍼를 대조하면 되며, 무엇을 언제 내보낼지는 호출자가 정한다. 수를 바이트로 바꾸는 일은 커널도 할당도 필요 없어 라이브러리다 — 내장 연산은 하나도 늘지 않았고, 파일 전체가 VM·네이티브 대조 안에 있다.

설계와 경계#

op 한눈에#

op시그니처안 될 때
put_byteproc (buf mut slice u8, pos u64, b u8) → option u64자리가 없으면 none, 버퍼 그대로
put_strproc (buf, pos, s slice u8) → option u64모두 들어가지 않으면 none, 버퍼 그대로
dec_widthfn (n u64) → u64실패 없음
put_u64proc (buf, pos, n u64) → option u64자리가 없으면 none, 버퍼 그대로
put_i64proc (buf, pos, n i64) → option u64자리가 없으면 none
hex_widthfn (n u64) → u64실패 없음
put_hexproc (buf, pos, n u64) → option u64자리가 없으면 none, 버퍼 그대로
put_boolproc (buf, pos, b bool) → option u64자리가 없으면 none
put_nlproc (buf, pos) → option u64자리가 없으면 none

표 50.1 — fmt 의 op — 모두 effects none

이름의 put_ 은 “버퍼에 써 넣는다”, _width 는 “몇 바이트가 필요한지 세기만 한다” 는 뜻이다. 버퍼를 고치는 것은 proc, 세기만 하는 것은 fn 이지만 아홉 모두 바깥에 닿지 않는다.

op 상세#

모든 라이터의 앞 두 매개변수는 같다. buf 는 어디에 쓸지(할당하지 않으므로 받아야 하고, 고치므로 mut), pos 는 그 안 어느 자리부터 쓸지(커서를 모듈이 기억하지 않으므로 직전 호출이 준 새 pos 를 넘기는 것이 “이어 쓴다” 의 유일한 표현)다.

쓰는 법#

조립은 순수하게, 출력은 마지막에 cap io 로 한 번. 돌려받은 새 pos 를 다음 호출에 이어서 넘기는 것이 핵심이다.

module report .

use fmt .

proc main
  input out cap io .
  input al  cap allocator .
  output u8 .
  effects alloc io .
do
  let g option mut slice u8 . . be alloc_bytes al capacity 64 .
  guard is_some g . else return 70 .
  let buf mut slice u8 . be some_value g .

  rem 조립 --- 여기까지 한 바이트도 밖으로 나가지 않았다
  let p1 option u64 . be fmt.put_str buf 0 "answer=" .
  guard is_some p1 . else return 71 .
  let p2 option u64 . be fmt.put_u64 buf (some_value p1) 42 .
  guard is_some p2 . else return 72 .
  let p3 option u64 . be fmt.put_str buf (some_value p2) " hex=" .
  guard is_some p3 . else return 73 .
  let p4 option u64 . be fmt.put_hex buf (some_value p3) 255 .
  guard is_some p4 . else return 74 .
  let p5 option u64 . be fmt.put_nl buf (some_value p4) .
  guard is_some p5 . else return 75 .

  rem 출력 --- 조립된 앞부분(0 … p5)만 내보낸다
  let w u64 be write_out out 1 (subslice buf 0 (some_value p5)) .
  return 0 .
end

출력은 answer=42 hex=ff 와 줄바꿈 하나다. 조각마다 종료 코드를 달리 두면 어디서 모자랐는지 바로 안다. 버퍼 크기를 미리 재는 것도 같은 도구로 된다.

fn need_for input n u64 . output u64 . do
  let w u64 be fmt.dec_width n .
  return add (add 7 w) 1 .
end

반례#

반례. effects none op 안에서 출력한다

proc bad input out cap io . output u64 . effects none . do
  return write_out out 1 "x" .
end

선언이 거짓말이 되어 E-EFFECT(fn 이면 E-EFFECT-CALC)로 거절된다. effects io 로 바꿔도 cap io 입력이 없으면 E-EFFECT-NO-CAP 이다.

반례. 돌려받은 pos 를 버리고 옛 pos 를 다시 쓴다

let p1 option u64 . be fmt.put_str buf 0 "answer=" .
let p2 option u64 . be fmt.put_u64 buf 0 42 .        rem ✗ pos 는 some_value p1 이어야 한다

번역은 통과하고 결과가 answer=42 대신 42swer= 처럼 겹쳐 나온다. 출력 앞부분이 뭉개져 있으면 pos 를 이어 넘겼는지부터 본다.

반례. none 을 무시하고 진행한다

자리가 모자라면 그 조각은 아예 쓰이지 않은 것이다. guard is_some … else return … 없이 진행하면 중간 조각이 빠진 채 나간다. 긴 값일 때만 출력이 이상하면 버퍼 부족이다. 전량 아니면 무는 op 하나의 보장이지 이어진 호출 전체의 보장이 아니다. 거꾸로 guard 없이 some_value 로 꺼내면, 자리가 모자란 순간 E-VM-NONE 으로 멈춘다 — 짧은 값으로만 시험하면 드러나지 않는다.

반례. write_out 에 버퍼 전체를 준다

subslice buf 0 (some_value p5) 대신 buf 를 주면 쓰지 않은 뒷부분까지 나간다. 줄 끝에 정체 모를 바이트가 붙는다. 마지막 pos 가 곧 쓴 길이다.

주의#