В Scheme нет полного аналога функции snprintf, поэтому я решил написать его сам — и не просто написать, а вызвать из сишной библиотеки с помощью FFI.
snprintf принимает переменное число аргументов, однако FFI требует задать полный прототип функции перед её использованием. Здесь я покажу, как можно обойти это ограничение и создавать прототипы в runtime.
Шаг 1. Ищем и подгружаем нужные dll'ки и определяем имя функции snprintf.
На разных системах snprintf находится в разных библиотеках, поэтому мы последовательно переберем варианты с libc и msvcrt. Так все будет работать практически на любом варианте UNIX и в Windows.
#lang racket
(require ffi/unsafe)
(require ffi/vector)
(define (load-oneof which)
(cond
((eq? which '()) #f)
(else
(with-handlers ([exn:fail? (lambda (exn) (load-oneof (cdr which)))])
(ffi-lib (car which))))))
(define (search-func-name where which)
(cond
((eq? which '()) #f)
(else
(let*
((func-name (car which))
(rest (cdr which)))
(with-handlers ([exn:fail? (lambda (exn) (search-func-name where rest))])
(begin
(get-ffi-obj func-name where (_fun _int -> _void))
func-name))))))
(define crt (load-oneof '("libc" "msvcrt")))
(define snprintf-name (search-func-name crt '("snprintf" "_snprintf")))
Шаг 2. Определяем функцию snprintf.
Основная проблема здесь в том, что функция принимает переменное число аргументов, а интерфейс FFI требует определить прототип функции с заданным числом аргументов.
Чтобы обойти это ограничение, я на лету генерирую прототип функции с нужным числом аргументов. Также обратите внимание, что snprintf генерирует массив байтов, который мы потом преобразуем в строку.
(define (snprintf max-len format . args)
(let*
((mem (make-u8vector max-len 0))
(input-types
(foldl
(lambda (a result)
(cond
((string? a) (append result (list _string)))
((integer? a) (append result (list _int)))
((real? a) (append result (list _double)))
(else result)
))
(list _u8vector _int _string) args)
)
(func (get-ffi-obj snprintf-name crt (_cprocedure input-types _int)))
(len (apply func `(,(u8vector-"cpointer mem) ,max-len ,format ,@args)))
(output (open-output-bytes)))
(begin
(display mem output)
(bytes-"string/utf-8 (subbytes (get-output-bytes output) 0 len))
)
)
)
Шаг 3. Используем функцию:
(snprintf 100 "йцукен %.2lf %04d" 11.2 1)