diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index bdfc660624..08e068c693 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -117,6 +117,8 @@ pub type Stmt = AsmStmt | StructDecl | TypeDecl +pub type HashStmtNode = IfExpr | HashStmt + pub struct EmptyScopeObject { pub mut: name string diff --git a/vlib/v/gen/c/cgen.v b/vlib/v/gen/c/cgen.v index ad2b3110c0..210e8d80df 100644 --- a/vlib/v/gen/c/cgen.v +++ b/vlib/v/gen/c/cgen.v @@ -274,6 +274,11 @@ mut: export_funcs []string // for .dll export function names // type_default_impl_level int + preinclude_nodes []&ast.HashStmtNode // allows hash stmts to go before `includes` + include_nodes []&ast.HashStmtNode // all hash stmts to go `includes` + definition_nodes []&ast.HashStmtNode // allows hash stmts to go `definitions` + postinclude_nodes []&ast.HashStmtNode // allows hash stmts to go after all the rest of the code generation + curr_comptime_node &ast.Expr = unsafe { nil } // current `$if` expr } @[heap] @@ -920,6 +925,11 @@ pub fn (mut g Gen) gen_file() { g.inside_ternary = 0 } g.stmts(g.file.stmts) + + // after all other stmts executed, we got info about hash stmts in top, + // write them to corresponding sections + g.gen_hash_stmts_in_top() + // Transfer embedded files for path in g.file.embedded_files { if path !in g.embedded_files { @@ -5740,113 +5750,178 @@ fn (mut g Gen) hash_stmt_guarded_include(node ast.HashStmt) string { } fn (mut g Gen) hash_stmt(node ast.HashStmt) { - line_nr := node.pos.line_nr + 1 - mut ct_condition := '' + // we only record the hash stmt's node here, send it to corresponding `_nodes`, let `gen_hash_stmts_in_top()` gen the code. + the_node := if g.comptime.inside_comptime_if && !isnil(g.curr_comptime_node) + && g.curr_comptime_node is ast.IfExpr { + &ast.HashStmtNode(g.curr_comptime_node as ast.IfExpr) + } else { + &ast.HashStmtNode(&node) + } - if node.ct_conds.len > 0 { - mut comptime_branch_context_str := g.gen_branch_context_string() - mut is_true := ast.ComptTimeCondResult{} - mut sb := strings.new_builder(256) - for idx, ct_expr in node.ct_conds { - idx_str := comptime_branch_context_str + '|${g.file.path}|${ct_expr.pos()}|' - if comptime_is_true := g.table.comptime_is_true[idx_str] { - // `g.table.comptime_is_true` are the branch condition results set by `checker` - is_true = comptime_is_true + match node.kind { + 'include', 'insert', 'define' { + if node.main.contains('.m') { + // Objective C code import, include it after V types, so that e.g. `string` is + // available there + if the_node !in g.definition_nodes { + g.definition_nodes << the_node + } } else { - g.error('checker error: condition result idx string not found => [${idx_str}]', - ct_expr.pos()) + if the_node !in g.include_nodes { + g.include_nodes << the_node + } + } + } + 'preinclude' { + if node.main.contains('.m') { + // Objective C code import, include it after V types, so that e.g. `string` is + // available there + if the_node !in g.definition_nodes { + g.definition_nodes << the_node + } + } else { + if the_node !in g.preinclude_nodes { + g.preinclude_nodes << the_node + } + } + } + 'postinclude' { + if the_node !in g.postinclude_nodes { + g.postinclude_nodes << the_node + } + } + else {} + } +} + +fn (mut g Gen) gen_hash_stmts(mut sb strings.Builder, node &ast.HashStmtNode, section string) { + match node { + ast.IfExpr { + mut comptime_branch_context_str := g.gen_branch_context_string() + mut is_true := ast.ComptTimeCondResult{} + for i, branch in node.branches { + idx_str := if branch.cond.pos() == token.Pos{} { + comptime_branch_context_str + '|${g.file.path}|${branch.pos}|' + } else { + comptime_branch_context_str + '|${g.file.path}|${branch.cond.pos()}|' + } + if comptime_is_true := g.table.comptime_is_true[idx_str] { + // `g.table.comptime_is_true` are the branch condition results set by `checker` + is_true = comptime_is_true + } else { + g.error('checker error: condition result idx string not found => [${idx_str}]', + node.branches[i].cond.pos()) + return + } + if !node.has_else || i < node.branches.len - 1 { + if i == 0 { + sb.write_string('\n#if ') + } else { + sb.write_string('\n#elif ') + } + // directly use `checker` evaluate results + // for `cgen`, we can use `is_true.c_str` or `is_true.value` here + sb.writeln('${is_true.c_str}') + $if debug_comptime_branch_context ? { + sb.writeln('/* ${node.branches[i].cond} | generic=[${comptime_branch_context_str}] */') + } + } else { + sb.writeln('#else') + $if debug_comptime_branch_context ? { + sb.writeln('/* else | generic=[${comptime_branch_context_str}] */') + } + } + if is_true.val || g.pref.output_cross_c { + // check only `IfExpr` and `HashStmt` + for stmt in branch.stmts { + if stmt is ast.ExprStmt { + if stmt.expr is ast.IfExpr && (stmt.expr as ast.IfExpr).is_comptime { + g.gen_hash_stmts(mut sb, stmt.expr, section) + } + } else if stmt is ast.HashStmt { + g.gen_hash_stmts(mut sb, stmt, section) + } + } + } + } + sb.writeln('#endif') + } + ast.HashStmt { + // #preinclude => `preincludes` section + // #inlude,#define,#insert => `includes` section + // #postinclude => `postincludes` section + // '*.m' in #include or #preinclude => `definitions` section + need_gen_stmt := match section { + 'preincludes' { + if node.kind == 'preinclude' && !node.main.contains('.m') { true } else { false } + } + 'includes' { + if node.kind in ['include', 'define', 'insert'] && !node.main.contains('.m') { + true + } else { + false + } + } + 'definitions' { + // Objective C code import, include it after V types, so that e.g. `string` is + // available there + if node.kind in ['include', 'preinclude'] && node.main.contains('.m') { + true + } else { + false + } + } + 'postincludes' { + if node.kind == 'postinclude' { true } else { false } + } + else { + false + } + } + if !need_gen_stmt { return } - sb.write_string(is_true.c_str) - if idx < node.ct_conds.len - 1 { - sb.write_string(' && ') + line_nr := node.pos.line_nr + 1 + match node.kind { + 'include', 'preinclude', 'postinclude' { + guarded_include := g.hash_stmt_guarded_include(node) + sb.writeln('') + sb.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') + sb.writeln(guarded_include) + } + 'insert' { + sb.writeln('') + sb.writeln('// inserted by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') + sb.writeln(node.val) + } + 'define' { + sb.writeln('// defined by module `${node.mod}`') + sb.writeln('#define ${node.main}') + } + else {} } } - ct_condition = sb.str() + // TODO: support $match } - // #include etc - if node.kind == 'include' { - guarded_include := g.hash_stmt_guarded_include(node) - if node.main.contains('.m') { - g.definitions.writeln('') - if ct_condition != '' { - g.definitions.writeln('#if ${ct_condition}') - } - // Objective C code import, include it after V types, so that e.g. `string` is - // available there - g.definitions.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') - g.definitions.writeln(guarded_include) - if ct_condition != '' { - g.definitions.writeln('#endif // \$if ${ct_condition}') - } - } else { - g.includes.writeln('') - if ct_condition != '' { - g.includes.writeln('#if ${ct_condition}') - } - g.includes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') - g.includes.writeln(guarded_include) - if ct_condition != '' { - g.includes.writeln('#endif // \$if ${ct_condition}') - } - } - } else if node.kind == 'preinclude' { - 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 - g.definitions.writeln('') - if ct_condition != '' { - g.definitions.writeln('#if ${ct_condition}') - } - // Objective C code import, include it after V types, so that e.g. `string` is - // available there - g.definitions.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') - g.definitions.writeln(guarded_include) - if ct_condition != '' { - g.definitions.writeln('#endif // \$if ${ct_condition}') - } - } else { - g.preincludes.writeln('') - if ct_condition != '' { - g.preincludes.writeln('#if ${ct_condition}') - } - g.preincludes.writeln('// added by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') - g.preincludes.writeln(guarded_include) - if ct_condition != '' { - 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}') - } - g.includes.writeln('// inserted by module `${node.mod}`, file: ${os.file_name(node.source_file)}:${line_nr}:') - g.includes.writeln(node.val) - if ct_condition != '' { - g.includes.writeln('#endif // \$if ${ct_condition}') - } - } else if node.kind == 'define' { - if ct_condition != '' { - g.includes.writeln('#if ${ct_condition}') - } - g.includes.writeln('// defined by module `${node.mod}`') - g.includes.writeln('#define ${node.main}') - if ct_condition != '' { - g.includes.writeln('#endif // \$if ${ct_condition}') - } +} + +fn (mut g Gen) gen_hash_stmts_in_top() { + for node in g.preinclude_nodes { + g.gen_hash_stmts(mut g.preincludes, node, 'preincludes') } + for node in g.include_nodes { + g.gen_hash_stmts(mut g.includes, node, 'includes') + } + for node in g.definition_nodes { + g.gen_hash_stmts(mut g.definitions, node, 'definitions') + } + for node in g.postinclude_nodes { + g.gen_hash_stmts(mut g.postincludes, node, 'postincludes') + } + g.preinclude_nodes.clear() + g.include_nodes.clear() + g.definition_nodes.clear() + g.postinclude_nodes.clear() } fn (mut g Gen) branch_stmt(node ast.BranchStmt) { diff --git a/vlib/v/gen/c/comptime.v b/vlib/v/gen/c/comptime.v index e48d9044e8..88571cd09a 100644 --- a/vlib/v/gen/c/comptime.v +++ b/vlib/v/gen/c/comptime.v @@ -401,9 +401,20 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { '' } + // save node for processing hash stmts + // we only save the first node, when there is embedded $if + old_curr_comptime_node := g.curr_comptime_node + if !g.comptime.inside_comptime_if { + g.curr_comptime_node = &node + } + defer { + g.curr_comptime_node = old_curr_comptime_node + } mut comptime_branch_context_str := g.gen_branch_context_string() mut is_true := ast.ComptTimeCondResult{} for i, branch in node.branches { + g.push_new_comptime_info() + g.comptime.inside_comptime_if = true start_pos := g.out.len // `idx_str` is composed of two parts: // The first part represents the current context of the branch statement, `comptime_branch_context_str`, formatted like `T=int,X=string,method.name=json` @@ -501,6 +512,7 @@ fn (mut g Gen) comptime_if(node ast.IfExpr) { g.writeln('}') } } + g.pop_comptime_info() } g.defer_ifdef = '' g.writeln('#endif') @@ -563,6 +575,7 @@ fn (mut g Gen) push_new_comptime_info() { g.type_resolver.info_stack << type_resolver.ResolverInfo{ saved_type_map: g.type_resolver.type_map.clone() inside_comptime_for: g.comptime.inside_comptime_for + inside_comptime_if: g.comptime.inside_comptime_if comptime_for_variant_var: g.comptime.comptime_for_variant_var comptime_for_field_var: g.comptime.comptime_for_field_var comptime_for_field_type: g.comptime.comptime_for_field_type @@ -582,6 +595,7 @@ fn (mut g Gen) pop_comptime_info() { old := g.type_resolver.info_stack.pop() g.type_resolver.type_map = old.saved_type_map.clone() g.comptime.inside_comptime_for = old.inside_comptime_for + g.comptime.inside_comptime_if = old.inside_comptime_if g.comptime.comptime_for_variant_var = old.comptime_for_variant_var g.comptime.comptime_for_field_var = old.comptime_for_field_var g.comptime.comptime_for_field_type = old.comptime_for_field_type diff --git a/vlib/v/gen/c/testdata/comptime_if_top_hash_stmts.c.must_have b/vlib/v/gen/c/testdata/comptime_if_top_hash_stmts.c.must_have new file mode 100644 index 0000000000..27839e249e --- /dev/null +++ b/vlib/v/gen/c/testdata/comptime_if_top_hash_stmts.c.must_have @@ -0,0 +1,52 @@ +#if defined(_WIN32) +#if defined(CUSTOM_DEFINE_user_d1) +#elif defined(CUSTOM_DEFINE_user_d2) +#include "pre1.h" +#elif defined(__linux__) +#if defined(CUSTOM_DEFINE_user_d3) +#include "pre2.h" +#elif defined(CUSTOM_DEFINE_user_d4) +#if defined(_WIN32) +#if defined(CUSTOM_DEFINE_user_d1) +#include "w1.h" +#define user_d1 +#elif defined(CUSTOM_DEFINE_user_d2) +#include "w2.h" +#define user_d2 +#else +#include "w3.h" +#define user_not_d1_d2 +#define user_all_windows +#include "windows.h" +#elif defined(__linux__) +#if defined(CUSTOM_DEFINE_user_d3) +#include "l1.h" +#define user_d3 +#elif defined(CUSTOM_DEFINE_user_d4) +#include "l2.h" +#define user_d4 +#define user_all_linux +#include "stdio.h" +#else +#define user_all_other +#include "other.h" +#if defined(_WIN32) +#if defined(CUSTOM_DEFINE_user_d1) +#elif defined(CUSTOM_DEFINE_user_d2) +#else +#include "macos1.m" +#elif defined(__linux__) +#if defined(CUSTOM_DEFINE_user_d3) +#include "macos2.m" +#elif defined(CUSTOM_DEFINE_user_d4) +#endif +#if defined(_WIN32) +#if defined(CUSTOM_DEFINE_user_d1) +#elif defined(CUSTOM_DEFINE_user_d2) +#include "post1.h" +#elif defined(__linux__) +#if defined(CUSTOM_DEFINE_user_d3) +#elif defined(CUSTOM_DEFINE_user_d4) +#include "post2.h" +#else +#include "post3.h" diff --git a/vlib/v/gen/c/testdata/comptime_if_top_hash_stmts.vv b/vlib/v/gen/c/testdata/comptime_if_top_hash_stmts.vv new file mode 100644 index 0000000000..1feb899cd0 --- /dev/null +++ b/vlib/v/gen/c/testdata/comptime_if_top_hash_stmts.vv @@ -0,0 +1,40 @@ +// vtest vflags: -os cross +module main + +$if windows { + $if user_d1 ? { + #include "w1.h" + #define user_d1 + } $else $if user_d2 ? { + #include "w2.h" + #define user_d2 + #preinclude "pre1.h" + } $else { + #include "w3.h" + #define user_not_d1_d2 + #include "macos1.m" + #postinclude "post1.h" + } + #define user_all_windows + #include "windows.h" +} $else $if linux { + $if user_d3 ? { + #include "l1.h" + #include "macos2.m" + #define user_d3 + #preinclude "pre2.h" + } $else $if user_d4 ? { + #include "l2.h" + #define user_d4 + #postinclude "post2.h" + } + #define user_all_linux + #include "stdio.h" +} $else { + #define user_all_other + #include "other.h" + #postinclude "post3.h" +} + +fn main() { +}