www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Compile time format string check

reply Ada <notemail not.com> writes:
I have managed to write a function dprintf that will expand the 
string argument to (length, pointer).
But now I wish there was a way to verify the format string if it 
actually matches the rest of the parameters.
%.\*s should match with string
%s should match with char*

Is there a way to do it at compile time without using template 
instantiation?

Here is the code for dprintf (any suggestions for nicer code are 
welcome):

import core.stdc.stdio : printf, fgets, stdin;
import core.stdc.stdlib : malloc;
import core.stdc.string : memcpy;
import std.typecons : tuple;

     auto expandArg(T)(T arg)
     {
         static if (is(T == string))
             return tuple(cast(int)arg.length, arg.ptr);
         else
             return tuple(arg);
     }

     auto expandAll()
     {
         return tuple();
     }

     auto expandAll(T, Rest...)(T first, Rest rest)
     {
         return tuple(
             expandArg(first).expand,
             expandAll(rest).expand
         );
     }

     void dprintf(Args...)(const char* fmt, Args args)
     {
         auto expanded = expandAll(args);
         printf(fmt, expanded.expand);
     }
Mar 22
next sibling parent Ada <notemail not.com> writes:
On Sunday, 22 March 2026 at 21:57:19 UTC, Ada wrote:
 Is there a way to do it at compile time without using template 
 instantiation?
also, the code is written with BetterC mode in mind.
Mar 22
prev sibling parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
If its a string literal it is already handled as part of bindings: 
https://dlang.org/spec/pragma.html#printf

I understand that you may be wanting a runtime error, but I'd suggest 
disallowing that entirely and go with a template parameter to provide 
your format to make sure its seeable to the compiler.
Mar 22
parent reply Ada <notemail not.com> writes:
On Sunday, 22 March 2026 at 22:49:24 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 If its a string literal it is already handled as part of 
 bindings: https://dlang.org/spec/pragma.html#printf

 I understand that you may be wanting a runtime error, but I'd 
 suggest disallowing that entirely and go with a template 
 parameter to provide your format to make sure its seeable to 
 the compiler.
I clearly said compile-time. maybe you misspoke. It's a bit of a nuisance because the function call then doesn't resemble C that much anymore. (it's called betterc) Can you help me rewrite my code to properly verify that the format specifiers match the arguments? If it's possible without templates, otherwise with templates.
Mar 22
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 23/03/2026 6:29 PM, Ada wrote:
 On Sunday, 22 March 2026 at 22:49:24 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 If its a string literal it is already handled as part of bindings: 
 https://dlang.org/spec/pragma.html#printf

 I understand that you may be wanting a runtime error, but I'd suggest 
 disallowing that entirely and go with a template parameter to provide 
 your format to make sure its seeable to the compiler.
I clearly said compile-time. maybe you misspoke. It's a bit of a nuisance because the function call then doesn't resemble C that much anymore. (it's called betterc)
The only way to do it at CT, is either with the pragma with a string literal for the format, or the format must be provided via a template parameter. You cannot do this, with a function parameter at CT.
Mar 22
parent reply Ada <notemail not.com> writes:
On Monday, 23 March 2026 at 05:33:05 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 On 23/03/2026 6:29 PM, Ada wrote:
 On Sunday, 22 March 2026 at 22:49:24 UTC, Richard (Rikki) 
 Andrew Cattermole wrote:
 [...]
I clearly said compile-time. maybe you misspoke. It's a bit of a nuisance because the function call then doesn't resemble C that much anymore. (it's called betterc)
The only way to do it at CT, is either with the pragma with a string literal for the format, or the format must be provided via a template parameter. You cannot do this, with a function parameter at CT.
In that case, how would you do it with the pragma or the template parameter?
Mar 22
parent reply "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 23/03/2026 6:36 PM, Ada wrote:
 On Monday, 23 March 2026 at 05:33:05 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 On 23/03/2026 6:29 PM, Ada wrote:
 On Sunday, 22 March 2026 at 22:49:24 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 [...]
I clearly said compile-time. maybe you misspoke. It's a bit of a nuisance because the function call then doesn't resemble C that much anymore. (it's called betterc)
The only way to do it at CT, is either with the pragma with a string literal for the format, or the format must be provided via a template parameter. You cannot do this, with a function parameter at CT.
In that case, how would you do it with the pragma or the template parameter?
Start with the pragma, I linked the docs for it. Parsing out the format spec by itself isn't a fun job and is libc dependent.
Mar 22
parent reply Ada <notemail not.com> writes:
On Monday, 23 March 2026 at 05:46:15 UTC, Richard (Rikki) Andrew 
Cattermole wrote:
 On 23/03/2026 6:36 PM, Ada wrote:
 On Monday, 23 March 2026 at 05:33:05 UTC, Richard (Rikki) 
 Andrew Cattermole wrote:
 On 23/03/2026 6:29 PM, Ada wrote:
 On Sunday, 22 March 2026 at 22:49:24 UTC, Richard (Rikki) 
 Andrew Cattermole wrote:
 [...]
I clearly said compile-time. maybe you misspoke. It's a bit of a nuisance because the function call then doesn't resemble C that much anymore. (it's called betterc)
The only way to do it at CT, is either with the pragma with a string literal for the format, or the format must be provided via a template parameter. You cannot do this, with a function parameter at CT.
In that case, how would you do it with the pragma or the template parameter?
Start with the pragma, I linked the docs for it. Parsing out the format spec by itself isn't a fun job and is libc dependent.
Hmm, just to be clear. The pragma just says that the function marked with it will follow the C99 Standard 7.19.6.1. Does that standard specify that custom format specifiers can be defined by any chance? Because that's essentially what I am trying to achieve.
Mar 23
parent "Richard (Rikki) Andrew Cattermole" <richard cattermole.co.nz> writes:
On 23/03/2026 8:13 PM, Ada wrote:
 On Monday, 23 March 2026 at 05:46:15 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 On 23/03/2026 6:36 PM, Ada wrote:
 On Monday, 23 March 2026 at 05:33:05 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 On 23/03/2026 6:29 PM, Ada wrote:
 On Sunday, 22 March 2026 at 22:49:24 UTC, Richard (Rikki) Andrew 
 Cattermole wrote:
 [...]
I clearly said compile-time. maybe you misspoke. It's a bit of a nuisance because the function call then doesn't resemble C that much anymore. (it's called betterc)
The only way to do it at CT, is either with the pragma with a string literal for the format, or the format must be provided via a template parameter. You cannot do this, with a function parameter at CT.
In that case, how would you do it with the pragma or the template parameter?
Start with the pragma, I linked the docs for it. Parsing out the format spec by itself isn't a fun job and is libc dependent.
Hmm, just to be clear. The pragma just says that the function marked with it will follow the C99 Standard 7.19.6.1. Does that standard specify that custom format specifiers can be defined by any chance?
No.
 Because that's essentially what I am trying to achieve.
Yeah you are a tad on your own on this one. Here is one way to do it with recursive functions. I had to create a indexOf util function as std.algorithm appears that it might've had a regression (WONTFIX) for betterC. ```d size_t indexOf(string haystack, char needle) { foreach(i, c; haystack) { if (c == needle) return i; } return haystack.length; } void myPrint(string Format, Args...)(auto ref Args args) { import core.stdc.stdio : printf; size_t parseFormat(string text, size_t offset) { if (text[offset + 1] == 's' || text[offset + 1] == 'c') return 1; else assert(0, "invalid format"); } void before(string text) { printf("%.*s", cast(int)text.length, text.ptr); } void actual(string Format3, T)(auto ref T value) { static if (is(T == string) && Format3[1] == 's') { printf("%.*s", cast(int)value.length, value.ptr); } else { printf(Format3.ptr, value); } } void handle(string Format2, size_t Arg)() { enum Offset = Format2.indexOf('%'); enum Length = Format2.length > Offset ? (parseFormat(Format2, Offset) + 1) : 0; static if (Offset <= Format2.length) { if (Offset > 0) before(Format2[0 .. Offset]); static if (Arg < Args.length) actual!(Format2[Offset .. Offset + Length])(args[Arg]); static if (Format2.length > Offset + Length) static if (Arg + 1 <= Args.length) handle!(Format2[Offset + Length .. $], Arg + 1); } else { // no format??? static assert(0, "Missing format for argument"); } } handle!(Format, 0); } extern(C) { void main() { import core.stdc.stdio : printf; myPrint!"b%s%ce\n"("Hellorld", '!'); printf("done\n"); } } ```
Mar 23