mirror of
https://github.com/odin-lang/Odin.git
synced 2026-10-13 12:36:36 -04:00
C passes a va_list argument by value. The bindings took ^va_list, which only matches x86-64 System V, where va_list is an array that decays to a pointer. On Darwin, Windows and the other targets va_list is a pointer or a struct, so vsnprintf and friends received the address of the list instead of the list and printed or scanned garbage. The foreign declarations now use the platform's form, and the public procedures keep taking ^va_list so callers stay portable. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
409 lines
9.6 KiB
Odin
409 lines
9.6 KiB
Odin
package libc
|
|
|
|
import "core:c"
|
|
import "core:io"
|
|
|
|
when ODIN_OS == .Windows {
|
|
foreign import libc {
|
|
"system:libucrt.lib",
|
|
"system:legacy_stdio_definitions.lib",
|
|
}
|
|
} else when ODIN_OS == .Darwin {
|
|
foreign import libc "system:System"
|
|
} else {
|
|
foreign import libc "system:c"
|
|
}
|
|
|
|
// 7.21 Input/output
|
|
|
|
FILE :: c.FILE
|
|
|
|
Whence :: enum int {
|
|
SET = SEEK_SET,
|
|
CUR = SEEK_CUR,
|
|
END = SEEK_END,
|
|
}
|
|
|
|
// MSVCRT compatible.
|
|
when ODIN_OS == .Windows {
|
|
_IOFBF :: 0x0000
|
|
_IONBF :: 0x0004
|
|
_IOLBF :: 0x0040
|
|
|
|
BUFSIZ :: 512
|
|
|
|
EOF :: int(-1)
|
|
|
|
FOPEN_MAX :: 20
|
|
|
|
FILENAME_MAX :: 260
|
|
|
|
L_tmpnam :: 15 // `"\\" + 12 + NUL`
|
|
|
|
SEEK_SET :: 0
|
|
SEEK_CUR :: 1
|
|
SEEK_END :: 2
|
|
|
|
TMP_MAX :: 32767 // SHRT_MAX
|
|
|
|
fpos_t :: distinct i64
|
|
|
|
@(private="file")
|
|
@(default_calling_convention="c")
|
|
foreign libc {
|
|
__acrt_iob_func :: proc (index: uint) -> ^FILE ---
|
|
}
|
|
|
|
stdin := __acrt_iob_func(0)
|
|
stdout := __acrt_iob_func(1)
|
|
stderr := __acrt_iob_func(2)
|
|
}
|
|
|
|
// GLIBC and MUSL compatible.
|
|
when ODIN_OS == .Linux {
|
|
fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, }
|
|
|
|
_IOFBF :: 0
|
|
_IOLBF :: 1
|
|
_IONBF :: 2
|
|
|
|
BUFSIZ :: 1024
|
|
|
|
EOF :: int(-1)
|
|
|
|
FOPEN_MAX :: 1000
|
|
|
|
FILENAME_MAX :: 4096
|
|
|
|
L_tmpnam :: 20
|
|
|
|
SEEK_SET :: 0
|
|
SEEK_CUR :: 1
|
|
SEEK_END :: 2
|
|
|
|
TMP_MAX :: 308915776
|
|
|
|
foreign libc {
|
|
stderr: ^FILE
|
|
stdin: ^FILE
|
|
stdout: ^FILE
|
|
}
|
|
}
|
|
|
|
when ODIN_OS == .JS {
|
|
fpos_t :: struct #raw_union { _: [16]char, _: longlong, _: double, }
|
|
|
|
_IOFBF :: 0
|
|
_IOLBF :: 1
|
|
_IONBF :: 2
|
|
|
|
BUFSIZ :: 1024
|
|
|
|
EOF :: int(-1)
|
|
|
|
FOPEN_MAX :: 1000
|
|
|
|
FILENAME_MAX :: 4096
|
|
|
|
L_tmpnam :: 20
|
|
|
|
SEEK_SET :: 0
|
|
SEEK_CUR :: 1
|
|
SEEK_END :: 2
|
|
|
|
TMP_MAX :: 308915776
|
|
}
|
|
|
|
when ODIN_OS == .OpenBSD || ODIN_OS == .NetBSD {
|
|
fpos_t :: distinct i64
|
|
|
|
_IOFBF :: 0
|
|
_IOLBF :: 1
|
|
_IONBF :: 1
|
|
|
|
BUFSIZ :: 1024
|
|
|
|
EOF :: int(-1)
|
|
|
|
FOPEN_MAX :: 20
|
|
FILENAME_MAX :: 1024
|
|
|
|
SEEK_SET :: 0
|
|
SEEK_CUR :: 1
|
|
SEEK_END :: 2
|
|
|
|
TMP_MAX :: 308915776
|
|
|
|
foreign libc {
|
|
__sF: [3]FILE
|
|
}
|
|
|
|
stdin: ^FILE = &__sF[0]
|
|
stdout: ^FILE = &__sF[1]
|
|
stderr: ^FILE = &__sF[2]
|
|
}
|
|
|
|
when ODIN_OS == .FreeBSD {
|
|
fpos_t :: distinct i64
|
|
|
|
_IOFBF :: 0
|
|
_IOLBF :: 1
|
|
_IONBF :: 1
|
|
|
|
BUFSIZ :: 1024
|
|
|
|
EOF :: int(-1)
|
|
|
|
FOPEN_MAX :: 20
|
|
FILENAME_MAX :: 1024
|
|
|
|
SEEK_SET :: 0
|
|
SEEK_CUR :: 1
|
|
SEEK_END :: 2
|
|
|
|
TMP_MAX :: 308915776
|
|
|
|
foreign libc {
|
|
@(link_name="__stderrp") stderr: ^FILE
|
|
@(link_name="__stdinp") stdin: ^FILE
|
|
@(link_name="__stdoutp") stdout: ^FILE
|
|
}
|
|
}
|
|
|
|
when ODIN_OS == .Darwin {
|
|
fpos_t :: distinct i64
|
|
|
|
_IOFBF :: 0
|
|
_IOLBF :: 1
|
|
_IONBF :: 2
|
|
|
|
BUFSIZ :: 1024
|
|
|
|
EOF :: int(-1)
|
|
|
|
FOPEN_MAX :: 20
|
|
|
|
FILENAME_MAX :: 1024
|
|
|
|
L_tmpnam :: 1024
|
|
|
|
SEEK_SET :: 0
|
|
SEEK_CUR :: 1
|
|
SEEK_END :: 2
|
|
|
|
TMP_MAX :: 308915776
|
|
|
|
foreign libc {
|
|
@(link_name="__stderrp") stderr: ^FILE
|
|
@(link_name="__stdinp") stdin: ^FILE
|
|
@(link_name="__stdoutp") stdout: ^FILE
|
|
}
|
|
}
|
|
|
|
when ODIN_OS == .NetBSD {
|
|
@(private) LRENAME :: "__posix_rename"
|
|
@(private) LFGETPOS :: "__fgetpos50"
|
|
@(private) LFSETPOS :: "__fsetpos50"
|
|
} else {
|
|
@(private) LRENAME :: "rename"
|
|
@(private) LFGETPOS :: "fgetpos"
|
|
@(private) LFSETPOS :: "fsetpos"
|
|
}
|
|
|
|
@(default_calling_convention="c")
|
|
foreign libc {
|
|
// 7.21.4 Operations on files
|
|
remove :: proc(filename: cstring) -> int ---
|
|
@(link_name=LRENAME)
|
|
rename :: proc(old, new: cstring) -> int ---
|
|
tmpfile :: proc() -> ^FILE ---
|
|
tmpnam :: proc(s: [^]char) -> [^]char ---
|
|
|
|
// 7.21.5 File access functions
|
|
fclose :: proc(stream: ^FILE) -> int ---
|
|
fflush :: proc(stream: ^FILE) -> int ---
|
|
fopen :: proc(filename, mode: cstring) -> ^FILE ---
|
|
freopen :: proc(filename, mode: cstring, stream: ^FILE) -> ^FILE ---
|
|
setbuf :: proc(stream: ^FILE, buf: [^]char) ---
|
|
setvbuf :: proc(stream: ^FILE, buf: [^]char, mode: int, size: size_t) -> int ---
|
|
|
|
// 7.21.6 Formatted input/output functions
|
|
fprintf :: proc(stream: ^FILE, format: cstring, #c_vararg args: ..any) -> int ---
|
|
fscanf :: proc(stream: ^FILE, format: cstring, #c_vararg args: ..any) -> int ---
|
|
printf :: proc(format: cstring, #c_vararg args: ..any) -> int ---
|
|
scanf :: proc(format: cstring, #c_vararg args: ..any) -> int ---
|
|
snprintf :: proc(s: [^]char, n: size_t, format: cstring, #c_vararg args: ..any) -> int ---
|
|
sscanf :: proc(s, format: cstring, #c_vararg args: ..any) -> int ---
|
|
|
|
// 7.21.7 Character input/output functions
|
|
fgetc :: proc(stream: ^FILE) -> int ---
|
|
fgets :: proc(s: [^]char, n: int, stream: ^FILE) -> [^]char ---
|
|
fputc :: proc(s: c.int, stream: ^FILE) -> int ---
|
|
getc :: proc(stream: ^FILE) -> int ---
|
|
getchar :: proc() -> int ---
|
|
putc :: proc(c: int, stream: ^FILE) -> int ---
|
|
putchar :: proc(c: int) -> int ---
|
|
puts :: proc(s: cstring) -> int ---
|
|
ungetc :: proc(c: int, stream: ^FILE) -> int ---
|
|
fread :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
|
|
fwrite :: proc(ptr: rawptr, size: size_t, nmemb: size_t, stream: ^FILE) -> size_t ---
|
|
|
|
// 7.21.9 File positioning functions
|
|
@(link_name=LFGETPOS)
|
|
fgetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
|
|
fseek :: proc(stream: ^FILE, offset: long, whence: Whence) -> int ---
|
|
@(link_name=LFSETPOS)
|
|
fsetpos :: proc(stream: ^FILE, pos: ^fpos_t) -> int ---
|
|
ftell :: proc(stream: ^FILE) -> long ---
|
|
rewind :: proc(stream: ^FILE) ---
|
|
|
|
// 7.21.10 Error-handling functions
|
|
clearerr :: proc(stream: ^FILE) ---
|
|
feof :: proc(stream: ^FILE) -> int ---
|
|
ferror :: proc(stream: ^FILE) -> int ---
|
|
perror :: proc(s: cstring) ---
|
|
}
|
|
|
|
to_stream :: proc(file: ^FILE) -> io.Stream {
|
|
stream_proc :: proc(stream_data: rawptr, mode: io.Stream_Mode, p: []byte, offset: i64, whence: io.Seek_From) -> (n: i64, err: io.Error) {
|
|
unknown_or_eof :: proc(f: ^FILE) -> io.Error {
|
|
switch {
|
|
case ferror(f) != 0:
|
|
return .Unknown
|
|
case feof(f) != 0:
|
|
return .EOF
|
|
case:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
file := (^FILE)(stream_data)
|
|
switch mode {
|
|
case .Close:
|
|
if fclose(file) != 0 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
case .Flush:
|
|
if fflush(file) != 0 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
case .Read:
|
|
n = i64(fread(raw_data(p), size_of(byte), len(p), file))
|
|
if n == 0 { err = unknown_or_eof(file) }
|
|
|
|
case .Read_At:
|
|
curr := ftell(file)
|
|
if curr == -1 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
if fseek(file, long(offset), .SET) != 0 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
defer fseek(file, long(curr), .SET)
|
|
|
|
n = i64(fread(raw_data(p), size_of(byte), len(p), file))
|
|
if n == 0 { err = unknown_or_eof(file) }
|
|
|
|
case .Write:
|
|
n = i64(fwrite(raw_data(p), size_of(byte), len(p), file))
|
|
if n == 0 { err = unknown_or_eof(file) }
|
|
|
|
case .Write_At:
|
|
curr := ftell(file)
|
|
if curr == -1 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
if fseek(file, long(offset), .SET) != 0 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
defer fseek(file, long(curr), .SET)
|
|
|
|
n = i64(fwrite(raw_data(p), size_of(byte), len(p), file))
|
|
if n == 0 { err = unknown_or_eof(file) }
|
|
|
|
case .Seek:
|
|
#assert(int(Whence.SET) == int(io.Seek_From.Start))
|
|
#assert(int(Whence.CUR) == int(io.Seek_From.Current))
|
|
#assert(int(Whence.END) == int(io.Seek_From.End))
|
|
|
|
if fseek(file, long(offset), Whence(whence)) != 0 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
case .Size:
|
|
curr := ftell(file)
|
|
if curr == -1 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
defer fseek(file, curr, .SET)
|
|
|
|
if fseek(file, 0, .END) != 0 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
n = i64(ftell(file))
|
|
if n == -1 {
|
|
return 0, unknown_or_eof(file)
|
|
}
|
|
|
|
case .Destroy:
|
|
return 0, .Unsupported
|
|
|
|
case .Query:
|
|
return io.query_utility({ .Close, .Flush, .Read, .Read_At, .Write, .Write_At, .Seek, .Size, .Query })
|
|
}
|
|
return
|
|
}
|
|
|
|
return {
|
|
data = file,
|
|
procedure = stream_proc,
|
|
}
|
|
}
|
|
|
|
// The v* procedures take a pointer to the va_list on every target, see va_list_arg.
|
|
@(default_calling_convention="c", private)
|
|
foreign libc {
|
|
@(link_name="vfprintf") _vfprintf :: proc(stream: ^FILE, format: cstring, arg: va_list_arg) -> int ---
|
|
@(link_name="vfscanf") _vfscanf :: proc(stream: ^FILE, format: cstring, arg: va_list_arg) -> int ---
|
|
@(link_name="vprintf") _vprintf :: proc(format: cstring, arg: va_list_arg) -> int ---
|
|
@(link_name="vscanf") _vscanf :: proc(format: cstring, arg: va_list_arg) -> int ---
|
|
@(link_name="vsnprintf") _vsnprintf :: proc(s: [^]char, n: size_t, format: cstring, arg: va_list_arg) -> int ---
|
|
@(link_name="vsprintf") _vsprintf :: proc(s: [^]char, format: cstring, arg: va_list_arg) -> int ---
|
|
@(link_name="vsscanf") _vsscanf :: proc(s, format: cstring, arg: va_list_arg) -> int ---
|
|
}
|
|
|
|
vfprintf :: #force_inline proc "c" (stream: ^FILE, format: cstring, arg: ^va_list) -> int {
|
|
return _vfprintf(stream, format, va_list_arg_from(arg))
|
|
}
|
|
|
|
vfscanf :: #force_inline proc "c" (stream: ^FILE, format: cstring, arg: ^va_list) -> int {
|
|
return _vfscanf(stream, format, va_list_arg_from(arg))
|
|
}
|
|
|
|
vprintf :: #force_inline proc "c" (format: cstring, arg: ^va_list) -> int {
|
|
return _vprintf(format, va_list_arg_from(arg))
|
|
}
|
|
|
|
vscanf :: #force_inline proc "c" (format: cstring, arg: ^va_list) -> int {
|
|
return _vscanf(format, va_list_arg_from(arg))
|
|
}
|
|
|
|
vsnprintf :: #force_inline proc "c" (s: [^]char, n: size_t, format: cstring, arg: ^va_list) -> int {
|
|
return _vsnprintf(s, n, format, va_list_arg_from(arg))
|
|
}
|
|
|
|
vsprintf :: #force_inline proc "c" (s: [^]char, format: cstring, arg: ^va_list) -> int {
|
|
return _vsprintf(s, format, va_list_arg_from(arg))
|
|
}
|
|
|
|
vsscanf :: #force_inline proc "c" (s, format: cstring, arg: ^va_list) -> int {
|
|
return _vsscanf(s, format, va_list_arg_from(arg))
|
|
}
|