checker,cgen: add support for a #postinclude directive

This commit is contained in:
Delyan Angelov 2025-02-12 01:18:10 +02:00
parent a4101f4ed8
commit d82757abd9
No known key found for this signature in database
GPG Key ID: 66886C0F12D595ED
4 changed files with 51 additions and 32 deletions

View File

@ -7949,26 +7949,36 @@ seamlessly across all platforms.
However, since the Windows header libraries use extremely generic names such as `Rectangle`,
this will cause a conflict if you wish to use C code that also has a name defined as `Rectangle`.
For very specific cases like this, we have `#preinclude`.
For very specific cases like this, V has `#preinclude` and `#postinclude` directives.
This will allow things to be configured before V adds in its built in libraries.
These directives allow things to be configured *before* V adds in its built in libraries,
and *after* all of the V code generation has completed (and thus all of the prototypes,
declarations and definitions are already present).
Example usage:
```v ignore
// This will include before built in libraries are used.
#preinclude "pre_include.h"
// This will include after built in libraries are used.
#include "include.h"
// This will include after all of the V code generation is complete,
// including the one for the main function of the project
#postinclude "post_include.h"
```
An example of what might be included in `pre_include.h`
can be [found here](https://github.com/irishgreencitrus/raylib.v/blob/main/include/pre.h)
This is an advanced feature, and will not be necessary
outside of very specific cases with C interop,
meaning it could cause more issues than it solves.
The `#postinclude` directive on the other hand is useful for allowing the integration
of frameworks like SDL3 or Sokol, that insist on having callbacks in your code, instead
of behaving like ordinary libraries, and allowing you to decide when to call them.
Consider it last resort!
NOTE: these are advanced features, and will not be necessary outside of very specific cases
with C interop. Other than those, using them could cause more issues than it solves.
Consider using them as a last resort!
## Other V Features

View File

@ -2612,7 +2612,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
return
}
match node.kind {
'include', 'insert', 'preinclude' {
'include', 'insert', 'preinclude', 'postinclude' {
original_flag := node.main
mut flag := node.main
if flag.contains('@VROOT') {
@ -2655,7 +2655,7 @@ fn (mut c Checker) hash_stmt(mut node ast.HashStmt) {
node.main = d
}
flag_no_comment := flag.all_before('//').trim_space()
if node.kind == 'include' || node.kind == 'preinclude' {
if node.kind in ['include', 'preinclude', 'postinclude'] {
if !((flag_no_comment.starts_with('"') && flag_no_comment.ends_with('"'))
|| (flag_no_comment.starts_with('<') && flag_no_comment.ends_with('>'))) {
c.error('including C files should use either `"header_file.h"` or `<header_file.h>` quoting',

View File

@ -48,6 +48,7 @@ mut:
// line_nr int
cheaders strings.Builder
preincludes strings.Builder // allows includes to go before `definitions`
postincludes strings.Builder // allows includes to go after all the rest of the code generation
includes strings.Builder // all C #includes required by V modules
typedefs strings.Builder
enum_typedefs strings.Builder // enum types
@ -300,6 +301,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
cheaders: strings.new_builder(15000)
includes: strings.new_builder(100)
preincludes: strings.new_builder(100)
postincludes: strings.new_builder(100)
typedefs: strings.new_builder(100)
enum_typedefs: strings.new_builder(100)
type_definitions: strings.new_builder(100)
@ -387,6 +389,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
global_g.out.write(g.out) or { panic(err) }
global_g.cheaders.write(g.cheaders) or { panic(err) }
global_g.preincludes.write(g.preincludes) or { panic(err) }
global_g.postincludes.write(g.postincludes) or { panic(err) }
global_g.includes.write(g.includes) or { panic(err) }
global_g.typedefs.write(g.typedefs) or { panic(err) }
global_g.type_definitions.write(g.type_definitions) or { panic(err) }
@ -567,7 +570,6 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
}
b.write_string2('\n// V comptime_definitions:\n', g.comptime_definitions.str())
b.write_string2('\n// V typedefs:\n', g.typedefs.str())
b.write_string2('\n // V preincludes:\n', g.preincludes.str())
b.write_string2('\n// V cheaders:', g.cheaders.str())
if g.pcs_declarations.len > 0 {
b.write_string2('\n// V profile counters:\n', g.pcs_declarations.str())
@ -741,6 +743,7 @@ pub fn gen(files []&ast.File, mut table ast.Table, pref_ &pref.Preferences) GenO
extern_out_str := g.extern_out.str()
b.write_string(out_str)
b.writeln('// THE END.')
b.write_string2('\n // V postincludes:\n', g.postincludes.str())
util.timing_measure('cgen common')
$if trace_all_generic_fn_keys ? {
gkeys := g.table.fn_generic_types.keys()
@ -5436,6 +5439,21 @@ fn (mut g Gen) gen_option_error(target_type ast.Type, expr ast.Expr) {
g.write(', .data={EMPTY_STRUCT_INITIALIZATION} }')
}
fn (mut g Gen) hash_stmt_guarded_include(node ast.HashStmt) string {
mut missing_message := 'Header file ${node.main}, needed for module `${node.mod}` was not found.'
if node.msg != '' {
missing_message += ' ${node.msg}.'
} else {
missing_message += ' Please install the corresponding development headers.'
}
mut guarded_include := get_guarded_include_text(node.main, missing_message)
if node.main == '<errno.h>' {
// fails with musl-gcc and msvc; but an unguarded include works:
guarded_include = '#include ${node.main}'
}
return guarded_include
}
fn (mut g Gen) hash_stmt(node ast.HashStmt) {
line_nr := node.pos.line_nr + 1
mut ct_condition := ''
@ -5451,17 +5469,7 @@ fn (mut g Gen) hash_stmt(node ast.HashStmt) {
}
// #include etc
if node.kind == 'include' {
mut missing_message := 'Header file ${node.main}, needed for module `${node.mod}` was not found.'
if node.msg != '' {
missing_message += ' ${node.msg}.'
} else {
missing_message += ' Please install the corresponding development headers.'
}
mut guarded_include := get_guarded_include_text(node.main, missing_message)
if node.main == '<errno.h>' {
// fails with musl-gcc and msvc; but an unguarded include works:
guarded_include = '#include ${node.main}'
}
guarded_include := g.hash_stmt_guarded_include(node)
if node.main.contains('.m') {
g.definitions.writeln('')
if ct_condition != '' {
@ -5486,17 +5494,7 @@ fn (mut g Gen) hash_stmt(node ast.HashStmt) {
}
}
} else if node.kind == 'preinclude' {
mut missing_message := 'Header file ${node.main}, needed for module `${node.mod}` was not found.'
if node.msg != '' {
missing_message += ' ${node.msg}.'
} else {
missing_message += ' Please install the corresponding development headers.'
}
mut guarded_include := get_guarded_include_text(node.main, missing_message)
if node.main == '<errno.h>' {
// fails with musl-gcc and msvc; but an unguarded include works:
guarded_include = '#include ${node.main}'
}
guarded_include := g.hash_stmt_guarded_include(node)
if node.main.contains('.m') {
// Might need to support '#preinclude' for .m files as well but for the moment
// this does the same as '#include' for them
@ -5522,6 +5520,17 @@ fn (mut g Gen) hash_stmt(node ast.HashStmt) {
g.preincludes.writeln('#endif // \$if ${ct_condition}')
}
}
} else if node.kind == 'postinclude' {
guarded_include := g.hash_stmt_guarded_include(node)
g.postincludes.writeln('')
if ct_condition != '' {
g.postincludes.writeln('#if ${ct_condition}')
}
g.postincludes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:')
g.postincludes.writeln(guarded_include)
if ct_condition != '' {
g.postincludes.writeln('#endif // \$if ${ct_condition}')
}
} else if node.kind == 'insert' {
if ct_condition != '' {
g.includes.writeln('#if ${ct_condition}')

View File

@ -75,7 +75,7 @@ fn (mut g Gen) stmt(node ast.Stmt) {
}
match node.kind {
'include', 'preinclude', 'define', 'insert' {
'include', 'preinclude', 'postinclude', 'define', 'insert' {
g.v_error('#${node.kind} is not supported with the native backend',
node.pos)
}