{ sankit.pas -- a Free Pascal interface unit for libsan.a, the SAN
  Kit chess library (see ../SAN_SRC), a portable Linux build of
  Steven J. Edwards's 1993/1994 SAN Kit.

  Scope: this binds exactly the functions, types, and global
  variables used by the three programs in this directory
  (01_hello_board.pas, 02_setup_position.pas, 03_san_notation.pas --
  Pascal translations of ../examples/01_hello_board.c,
  02_setup_position.c, and 03_san_notation.c). It is not a complete
  binding of the library; adding more is meant to be a matter of
  following the same pattern for whichever function or global you
  need next -- see "How this was built" below.

  How this was built
  -------------------
  SAN Kit's C types are almost all plain typedefs over "int" (siT)
  or "char *" (charptrT); on this platform (x86-64 Linux, gcc) that
  means every SAN Kit "siT"-family type (sqT, cpT, cT, castT, ...)
  is a 4-byte signed integer -- Pascal's LongInt -- and every
  "charptrT" is an 8-byte pointer -- Pascal's PChar. This was
  checked, not assumed: a small throwaway C program
  (`printf("%zu", sizeof(siT))` and so on) was compiled against
  sandef.h and run to confirm every size and every numeric constant
  used below (square numbers, color-piece codes, array lengths...)
  matches what the C headers actually define, rather than working
  those out from the macro arithmetic by hand.

  The one C type this unit deliberately does *not* reproduce is mT,
  the move-record struct SANDecodeAux() returns a pointer to (mptrT)
  and SANEncode() takes one back. None of the three examples here
  ever reads or writes one of its fields directly -- a move is only
  ever received from one SAN Kit call and handed straight to
  another -- so it's bound here as an opaque Pointer (PMove) instead
  of a Pascal record matching mT's exact C layout. Binding mT
  properly (matching every field's type, order, and alignment) would
  be needed for any program that inspects a move's own squares or
  flags directly, but isn't for these three.

  Every function below is declared "cdecl", the calling convention
  the C compiler that built libsan.a actually used (the x86-64 SysV
  ABI's standard convention, and also FPC's own default on this
  platform, but named explicitly here rather than left implicit, in
  case this unit is ever ported to a platform where the default
  differs) -- and "external name '...'", spelling out the exact C
  symbol to bind to, rather than relying on FPC's own (case-folding)
  default name mangling to happen to match.

  A note on mixed C/Pascal output
  --------------------------------
  Several SAN Kit functions (SANDspBoard, and anything that plays a
  move without the "nabd"/"namd" options set) print straight to
  stdout themselves, via C's own stdio. Pascal's Write/WriteLn use a
  *separate* buffer over that same file descriptor, and neither side
  flushes on the other's schedule -- when stdout isn't a terminal
  (piped to a file, or into another program, exactly what redirecting
  a program's output for testing does), C's own stdio defaults to
  fully buffered rather than line-buffered, so its output can arrive
  well after -- not interleaved with -- whatever Pascal wrote in
  between, even though the actual calls happened in the intended
  order. FlushCOutput() below (a thin binding for C's own
  fflush(NULL), which flushes every open C stream at once) is the
  fix: call it after any SAN Kit call that might have written
  something, and call Flush(Output) (a normal, if easy to forget,
  Pascal standard-library call) before it, and both sides' output
  interleaves in the true call order regardless of whether stdout is
  a terminal or a pipe. All three example programs in this directory
  do both around every SAN Kit call that prints.
}

unit SanKit;

interface

{$linklib san}
{$linklib c}
{$linklib m}

const
  { #define sanL 16 (sandef.h) -- the fixed size of a SAN move-text
    buffer; must be at least 8, with room to spare for alternatives }
  sanL = 16;

  { #define optnL 9 -- the size of optnv[] below; optn_nabd/optn_namd
    are the two indices 02_setup_position.pas and
    03_san_notation.pas both set, matching every other program in
    this whole project ("no auto board display" / "no auto move
    display" -- silences exactly the C-side stdout output the note
    above is about, for calls that would otherwise trigger it) }
  optnL = 9;
  optn_nabd = 2;
  optn_namd = 3;

  { color, piece, and color-piece code table sizes (cL, pL, cpL in
    sandef.h) -- the bounds of accv[]/acpv[]/cv_c_cpv[]/cv_p_cpv[]
    below }
  cL = 4;
  pL = 8;
  cpL = 16;

  { the two colors (c_w/c_b in sandef.h) }
  c_w = 0;
  c_b = 1;

  { square numbers (sq_e1/sq_e2/sq_e5/sq_nil in sandef.h), computed
    there as (rank shl 3) or file; e1=4, e2=12, e5=36 }
  sq_nil = -1;
  sq_e1 = 4;
  sq_e2 = 12;
  sq_e5 = 36;

  { color-piece codes (cp_wp/cp_wk/cp_bk in sandef.h) }
  cp_wp = 0;
  cp_wk = 5;
  cp_bk = 11;

  { the empty-square color-piece code (sandef.h) -- what SANGetSq()
    returns for a square with nothing on it; not needed by any of
    the numbered examples (02-14) since none of them print an
    entire board square by square, but needed by anything that does
    (see custom_board.pas) }
  cp_v0 = 12;

  { extended for cv_gtc_gsv_bug*.pas / history_capacity.pas /
    position_status.pas below -- everything above this point is
    unchanged from the original three-example unit }

  { #define gsL 13 / gtcL 4 (sandef.h) -- the bounds of
    cv_gtc_gsv[]/gtcstrv[] below }
  gsL = 13;
  gtcL = 4;

  { the two checkmate status codes SANStatus() can return
    (sandef.h); see cv_gtc_gsv_bug_simple.pas and
    cv_gtc_gsv_bug.pas }
  gs_wcmt = 2;
  gs_bcmt = 3;

  { the SAN Kit's internal move-history capacity (sandef.h): fmpL is
    the *intended* limit (SANStatus() returns gs_over once fmvn
    exceeds it); hL = fmpL*2 + aL is the library's true hard
    capacity, a small extra safety cushion beyond that -- see
    history_capacity.pas }
  fmpL = 200;
  aL = 32;
  hL = fmpL * 2 + aL;

  { #define fileL 8 / rankL 8 (sandef.h) -- the bounds of
    acfv[]/acrv[] below }
  fileL = 8;
  rankL = 8;

  { castling-rights bits (cast_wk/cast_wq/cast_bk/cast_bq in
    sandef.h); see position_status.pas }
  cast_wk = 1;
  cast_wq = 2;
  cast_bk = 4;
  cast_bq = 8;

  { extended again for the remaining eleven examples (04-14) below
    -- everything above this point is unchanged from the earlier,
    smaller unit }

  { the rest of gsT's values (sandef.h) not already defined above;
    see 06_game_status.pas }
  gs_norm = 0;
  gs_wsmt = 4;
  gs_bsmt = 5;
  gs_dhmc = 6;
  gs_dmat = 7;

  { #define rcL 2 (sandef.h) -- the size of ssv[]/cpmv[] below (one
    entry per color) }
  rcL = 2;

  { #define ssL 5 (sandef.h) -- the size of ssstrv[] below, and the
    five move-selection strategy codes themselves; see
    13_search_strategies.pas }
  ssL = 5;
  ss_eat = 0;
  ss_gtp = 1;
  ss_ran = 2;
  ss_sc0 = 3;
  ss_sc1 = 4;

type
  { char[16] in C (sanT, sandef.h) -- a fixed move-text buffer, not
    a pointer; SANDecodeAux()/SANEncode() take/fill one of these
    (decaying to a plain char* at the C ABI level, which is what
    the PChar parameters below actually bind to -- see
    SanTToPChar() further down) }
  TSanBuf = array[0..sanL - 1] of Char;

  { mptrT (sandef.h): a pointer to a legal move, opaque here -- see
    "How this was built" above }
  PMove = Pointer;

  { eT (sandef.h): everything curr_e below tracks about the current
    position beyond the board itself. Field order and every field's
    type and size were checked against the real C struct with
    offsetof()/sizeof() (a throwaway C program, the same technique
    "How this was built" above describes for scalar types) rather
    than assumed: sizeof(eT) is 40 bytes, with no padding between
    the six 4-byte fields or before the two 8-byte ones that follow
    them, which is exactly what this record's own layout produces
    by default -- confirmed with SizeOf(TEnv) at the top of
    position_status.pas, not just asserted here. }
  TEnv = record
    e_actc: LongInt;            { active color: c_w or c_b }
    e_pasc: LongInt;            { passive color }
    e_cast: LongInt;            { castling rights, cast_w*/cast_b* bits }
    e_epsq: LongInt;            { en passant target square, or sq_nil }
    e_hmvc: LongInt;            { halfmove clock }
    e_fmvn: LongInt;            { fullmove number }
    e_mhca: Int64;              { position hash address }
    e_mhcs: Int64;              { position hash signature }
  end;

  { mT (sandef.h): one legal move. Field order/types/sizes checked
    the same way as TEnv above -- sizeof(mT) is 28 bytes, seven
    plain 4-byte fields with no padding at all, confirmed with a
    throwaway C program before this was written, not assumed.
    Unlike PMove above, this is a genuine typed pointer target (see
    PMoveRec below), used where a program needs to walk
    curr_g.g_base as a real array rather than just pass a move
    pointer stright through from one SAN Kit call to another. }
  TMove = record
    m_flag: LongInt;            { mf_chec/mf_chmt/... bits, see sandef.h }
    m_frsq: LongInt;             { from square }
    m_tosq: LongInt;             { to square }
    m_frcp: LongInt;             { from color-piece }
    m_tocp: LongInt;             { to color-piece (cp_v0 if no capture) }
    m_scmv: LongInt;             { special-case move code (promotion, ...) }
    m_eval: LongInt;             { evaluation (unused by any of these
                                    examples) }
  end;
  PMoveRec = ^TMove;

  { gT (sandef.h): a generated move list -- what curr_g (below) is.
    sizeof(gT) is 32 bytes: three plain 4-byte fields, then 4 bytes
    of C compiler padding to align the two pointer fields that
    follow (g_pad below stands in for that padding explicitly,
    rather than leaving FPC to insert it implicitly, so the layout
    is documented rather than incidental) at an 8-byte boundary,
    then two 8-byte pointers -- confirmed the same way as TMove
    above. See 04_move_generation.pas and 13_search_strategies.pas. }
  TGen = record
    g_flag: LongInt;             { generation flags }
    g_strt: LongInt;             { start index in shared move-tree storage }
    g_gmvc: LongInt;             { generated move count }
    g_pad: LongInt;              { compiler padding in the C struct;
                                    see this type's header comment }
    g_base: PMoveRec;            { g_base[0..g_gmvc-1]: the moves themselves }
    g_curr: PMoveRec;            { the move currently being examined,
                                    where relevant (not used directly
                                    by any of these examples) }
  end;

  { tagT (sandef.h): one PGN header tag (e.g. [White "..."]).
    sizeof(tagT) is 40 bytes: four 8-byte pointers, then a 4-byte
    field, then 4 bytes of C compiler padding at the very end (to
    round the struct's overall size back up to an 8-byte multiple,
    since it contains pointers) -- again confirmed, not assumed; FPC
    adds that same trailing padding by default, with no
    $PACKRECORDS directive needed. See 11_pgn_reading.pas, the only
    one of these examples that reads a tag's fields directly rather
    than just passing its pointer on to another SAN Kit call
    (09_pgn_tags.pas does the latter, and so keeps using the plain
    opaque Pointer type for a tag, the same way PMove works for a
    move it never inspects). }
  TTag = record
    tag_name: PChar;
    tag_value: PChar;
    tag_prev: Pointer;
    tag_next: Pointer;
    tag_flag: LongInt;
  end;
  PTag = ^TTag;

var
  { extern siT optnv[optnL] (sanvar.h): the engine-wide option
    flags; see optn_nabd/optn_namd above }
  optnv: array [0 .. optnL - 1] of LongInt; cvar; external;

  { extern char accv[cL] / acpv[pL] (sanvar.h): the display
    character for each color ('w'/'b') and each piece
    ('P','N','B','R','Q','K') }
  accv: array [0 .. cL - 1] of Char; cvar; external;
  acpv: array [0 .. pL - 1] of Char; cvar; external;

  { extern cT cv_c_cpv[cpL] / pT cv_p_cpv[cpL] (sanvar.h): split a
    color-piece code (cpT, as SANGetSq() returns) into its color and
    its piece, for indexing accv[]/acpv[] above }
  cv_c_cpv: array [0 .. cpL - 1] of LongInt; cvar; external;
  cv_p_cpv: array [0 .. cpL - 1] of LongInt; cvar; external;

  { extern gtcT cv_gtc_gsv[gsL] (sanvar.h): the (in two cases,
    incorrectly -- see cv_gtc_gsv_bug_simple.pas) mapping from a
    detailed game status (gsT, as SANStatus() returns) to a coarse
    win/lose/draw code (gtcT) }
  cv_gtc_gsv: array [0 .. gsL - 1] of LongInt; cvar; external;

  { extern charptrT gtcstrv[gtcL] (sanvar.h): the display text
    ("1-0"/"0-1"/"1/2-1/2"/"*") for each gtcT code above -- an array
    of *pointers* to strings, unlike accv/acpv above, since each C
    charptrT is itself a pointer, not inline character storage }
  gtcstrv: array [0 .. gtcL - 1] of PChar; cvar; external;

  { extern char acfv[fileL] / acrv[rankL] (sanvar.h): the display
    character for each file ('a'..'h') and rank ('1'..'8') }
  acfv: array [0 .. fileL - 1] of Char; cvar; external;
  acrv: array [0 .. rankL - 1] of Char; cvar; external;

  { extern eT curr_e (sanvar.h): the current position's own state,
    beyond the board -- see TEnv above }
  curr_e: TEnv; cvar; external;

  { extern gT curr_g (sanvar.h): the legal move list most recently
    generated by SANGenMoves() (or the like); see TGen above and
    04_move_generation.pas / 13_search_strategies.pas }
  curr_g: TGen; cvar; external;

  { extern mT select_m (sanvar.h): scratch storage the currently
    active search strategy (see ssv[] below) writes its chosen move
    into, via SANSelect(); see 13_search_strategies.pas }
  select_m: TMove; cvar; external;

  { extern ssT ssv[rcL] (sanvar.h): which move-selection strategy
    (ss_eat/ss_gtp/ss_ran/ss_sc0/ss_sc1 above) is active for each
    color; extern charptrT ssstrv[ssL]: each strategy's display
    name. See 13_search_strategies.pas. }
  ssv: array [0 .. rcL - 1] of LongInt; cvar; external;
  ssstrv: array [0 .. ssL - 1] of PChar; cvar; external;

  { extern cpevT cpmv[rcL] (sanvar.h): each color's total material,
    as last computed by SANSetMaterial(); see 08_material_count.pas }
  cpmv: array [0 .. rcL - 1] of LongInt; cvar; external;

  { extern siT hn (sanvar.h): how many moves are currently on the
    real game-history stack (grown by SANPlayMove(), shrunk by
    SANUnplayMove()); see 05_play_unplay.pas }
  hn: LongInt; cvar; external;

  { extern bT gb (sanvar.h): the board itself. Bound here only as an
    opaque placeholder -- see SANEPDGenBasic()'s own comment further
    down for why none of these examples need its real (256-byte, by
    C's own sizeof(bT)) layout, only its address. }
  gb: record dummy: Byte; end; cvar; external;

  { one-time library setup (sanint.h); call this, and nothing else
    in this unit, first -- see 01_hello_board.pas }
procedure SANInit; cdecl; external name 'SANInit';

{ print the current position as an ASCII diagram (sandsp.h) }
procedure SANDspBoard; cdecl; external name 'SANDspBoard';

{ the current position as a FEN string (sanfen.h); the returned
  PChar points into memory SAN Kit itself owns (freshly allocated on
  each call -- see SANMemFree() in ../examples/README.md's notes on
  the C originals if you need to free it yourself in a longer-lived
  program; none of these three examples need to) }
function SANFENGen: PChar; cdecl; external name 'SANFENGen';

{ load a position from a FEN string (sanfen.h); returns nonzero on
  success, 0 if the text isn't a well-formed FEN }
function SANFENSet(s: PChar): LongInt; cdecl; external name 'SANFENSet';

{ empty the board (sanval.h) -- the first step in building a
  position square by square, see 02_setup_position.pas }
procedure SANClrBoard; cdecl; external name 'SANClrBoard';

{ place a color-piece code on a square (sanval.h) }
procedure SANPutSq(sq, cp: LongInt); cdecl; external name 'SANPutSq';

{ read back the color-piece code on a square (sanval.h) }
function SANGetSq(sq: LongInt): LongInt; cdecl; external name 'SANGetSq';

{ the rest of a position's state, set one field at a time
  (sanval.h): whose move it is, castling rights, the en passant
  target square, the halfmove clock, and the fullmove number }
procedure SANPutACTC(c: LongInt); cdecl; external name 'SANPutACTC';
procedure SANPutCAST(cast: LongInt); cdecl; external name 'SANPutCAST';
procedure SANPutEPSQ(sq: LongInt); cdecl; external name 'SANPutEPSQ';
procedure SANPutHMVC(hmvc: LongInt); cdecl; external name 'SANPutHMVC';
procedure SANPutFMVN(fmvn: LongInt); cdecl; external name 'SANPutFMVN';

{ recompute everything derived from the board and the fields above
  (material counts, the piece lists, the position hash, the legal
  move list...) after using SANPutSq()/SANPutACTC()/etc. to build a
  position by hand (sanval.h) -- see 02_setup_position.pas }
procedure SANUpdate; cdecl; external name 'SANUpdate';

{ parse SAN move text against the current legal move list
  (sanmnd.h), returning a pointer to the matching legal move, or nil
  if the text doesn't match any legal move here }
function SANDecodeAux(san: PChar): PMove; cdecl; external name 'SANDecodeAux';

{ play a legal move (sanmpu.h) -- normally one just returned by
  SANDecodeAux() above }
procedure SANPlayMove(mptr: PMove); cdecl; external name 'SANPlayMove';

{ classify the current position -- checkmate, stalemate, a draw, or
  gs_norm if the game is simply still going (sanrst.h); see
  cv_gtc_gsv_bug.pas }
function SANStatus: LongInt; cdecl; external name 'SANStatus';

{ format a move back into SAN move text (sanmne.h), filling a
  caller-supplied sanL-byte buffer }
procedure SANEncode(mptr: PMove; san: PChar); cdecl; external name 'SANEncode';

{ (re)generate the legal move list for the side to move, into
  curr_g above (sangen.h); see 04_move_generation.pas and
  13_search_strategies.pas }
procedure SANGenMoves; cdecl; external name 'SANGenMoves';

{ undo the most recently played move (sanmpu.h); see
  05_play_unplay.pas }
procedure SANUnplayMove; cdecl; external name 'SANUnplayMove';

{ is White's king currently attacked (sanrst.h)? nonzero means yes;
  see 06_game_status.pas }
function SANWhiteInCheck: LongInt; cdecl; external name 'SANWhiteInCheck';

{ recursively play out every legal move to the given depth and
  return the total leaf count -- the classic chess-programming
  "perft" exercise (sanenu.h). Returns liT, an 8-byte type (unlike
  everything else bound here, which is either a plain 4-byte siT or
  a pointer), hence Int64 rather than LongInt for both the parameter
  and the result. See 07_perft_enumerate.pas. }
function SANEnumerate(depth: Int64): Int64; cdecl; external name 'SANEnumerate';

{ recompute cpmv[] above from the board (sanmsc.h); see
  08_material_count.pas }
procedure SANSetMaterial; cdecl; external name 'SANSetMaterial';

{ "positional factors" evaluation (sanpos.h) -- always 0 in this
  1994 codebase; see 08_material_count.pas for why }
function SANPosEval: LongInt; cdecl; external name 'SANPosEval';

{ find a PGN header tag by name (santag.h), e.g. "White" or "Event";
  returns nil if there is no such tag. See 09_pgn_tags.pas (which
  only ever passes the result stright to SANTagUpdate() below,
  without reading any of its fields directly) and
  11_pgn_reading.pas (which does read tag_value, via TTag/PTag
  above). }
function SANTagLocate(name: PChar): PTag; cdecl; external name 'SANTagLocate';

{ change a tag's value (santag.h); see 09_pgn_tags.pas }
procedure SANTagUpdate(tagptr: PTag; value: PChar); cdecl;
  external name 'SANTagUpdate';

{ write the current game (tags + move text) as PGN to a C stdio
  stream (sanpgn.h); see 09_pgn_tags.pas and StdOutAsCFile() below
  for how to get such a stream for this process's own stdout }
procedure SANPGNEventReport(stream: Pointer); cdecl;
  external name 'SANPGNEventReport';

{ the current position as an EPD string (sanepd.h) -- like
  SANFENGen() above, but without the halfmove clock or fullmove
  number, and reading straight from the live game state; see
  10_epd.pas }
function SANEPDGenBasicCurrent: PChar; cdecl;
  external name 'SANEPDGenBasicCurrent';

{ an EPD string for an arbitrary board/color/castling/en-passant,
  without touching the current game state (sanepd.h); "bptr" is a
  pointer to a bT board -- @gb above, for the live position, is the
  only board this unit has any way to refer to, so that's the only
  argument any of these examples ever actually pass for it. See
  10_epd.pas. }
function SANEPDGenBasic(bptr: Pointer; actc, cast, epsq: LongInt): PChar;
  cdecl; external name 'SANEPDGenBasic';

{ read and replay exactly one game from a PGN file (sanrdr.h),
  leaving the SAN Kit's global game state (board, tags, history)
  exactly as it was at the end of that game; "gnptr" receives the
  number of games read. Returns nonzero on success. See
  11_pgn_reading.pas. }
function SANRGSingle(fn: PChar; gnptr: PLongInt): LongInt; cdecl;
  external name 'SANRGSingle';

{ recompute the current position's hash address/signature
  (curr_e.e_mhca/e_mhcs above) from scratch, from the raw board
  (sanhsh.h); see 12_hashing.pas }
procedure SANHashCreate; cdecl; external name 'SANHashCreate';

{ ask the currently active strategy (ssv[] above) to choose a move
  for the side to move, filling select_m above (sansel.h); see
  13_search_strategies.pas }
procedure SANSelect; cdecl; external name 'SANSelect';

{ pull the next whitespace-separated token out of "line", starting
  at (and advancing) the zero-based index "iptr" points to
  (sanutl.h); returns a freshly allocated, nil-terminated C string
  the caller must release with SANMemFree() below, or nil once there
  are no more tokens. See 14_tokens_and_utils.pas. }
function SANToken(line: PChar; iptr: PLongInt): PChar; cdecl;
  external name 'SANToken';

{ release memory SANToken() (or various other SAN Kit calls)
  allocated (sanutl.h) }
procedure SANMemFree(p: Pointer); cdecl; external name 'SANMemFree';

{ does "s" look like, and then evaluate as, a small (non-negative)
  integer written in decimal (sanutl.h)? See 14_tokens_and_utils.pas. }
function SANCheckShortInteger(s: PChar): LongInt; cdecl;
  external name 'SANCheckShortInteger';
function SANEvalShortInteger(s: PChar): LongInt; cdecl;
  external name 'SANEvalShortInteger';

{ case-insensitive string comparison (sanutl.h); nonzero means equal }
function SANCIEqStr(s1, s2: PChar): LongInt; cdecl;
  external name 'SANCIEqStr';

{ flush every open C stdio stream at once (the C library's own
  fflush(NULL), called here as FlushCOutput(nil)) -- see "A note on
  mixed C/Pascal output" above. The parameter must always be passed
  explicitly (as nil): cdecl leaves argument passing entirely to the
  caller, so declaring this with no parameter at all would leave
  fflush() reading whatever happened to be left in the argument
  register from some earlier call, instead of the NULL it needs. }
function FlushCOutput(stream: Pointer): LongInt; cdecl; external name 'fflush';

{ the C standard library's own fdopen(), bound here purely to get a
  FILE* usable as SANPGNEventReport()'s "stream" argument above: a
  FILE* is a C type this unit otherwise has no representation for at
  all (nothing here reads or writes one directly, only ever passes
  it straight to library calls that expect one), and fdopen(1, "w")
  -- see StdOutAsCFile() below -- is a portable way to get one for
  this process's own stdout without needing to know whether any
  given C library exposes "stdout" itself as a directly bindable
  symbol (glibc's, notably, is a macro over other internals, not a
  plain extern variable, on some platforms/versions). }
function fdopen(fd: LongInt; mode: PChar): Pointer; cdecl;
  external name 'fdopen';

{ a FILE* for this process's own stdout, suitable for
  SANPGNEventReport() above -- see fdopen()'s own comment for why
  it's obtained this way rather than more directly }
function StdOutAsCFile: Pointer;

{ convert a TSanBuf to the PChar the functions above actually
  expect (sanT decays to char* at the C ABI level -- see "How this
  was built" above) }
function SanBufPtr(var buf: TSanBuf): PChar; inline;

implementation

function StdOutAsCFile: Pointer;
begin
  StdOutAsCFile := fdopen (1, 'w');
end;

function SanBufPtr(var buf: TSanBuf): PChar; inline;
begin
  SanBufPtr := PChar(@buf[0]);
end;

end.
