Files
agila_open_tool/tools/v/vlib/v2_toberemoved/builder/parse.v
T

1217 lines
39 KiB
V

// Copyright (c) 2020-2024 Joe Conigliaro. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module builder
import os
import v2.ast
import v2.parser
import v2.pref as vpref
import v2.token
fn should_expand_single_file_input(input string) bool {
if os.file_name(input).ends_with('_test.v') {
return true
}
module_name := file_module_name(input) or { return false }
return module_name != 'main'
}
fn is_module_line_space(ch u8) bool {
return ch == ` ` || ch == `\t` || ch == `\v` || ch == `\f`
}
fn is_module_source_space(ch u8) bool {
return is_module_line_space(ch) || ch == `\n` || ch == `\r`
}
fn file_module_name(path string) ?string {
content := os.read_file(path) or { return none }
mut start := 0
for start < content.len {
if is_module_source_space(content[start]) {
start++
continue
}
if start + 1 < content.len && content[start] == `/` && content[start + 1] == `/` {
start += 2
for start < content.len && content[start] != `\n` && content[start] != `\r` {
start++
}
continue
}
if start + 1 < content.len && content[start] == `/` && content[start + 1] == `*` {
start += 2
for start + 1 < content.len {
if content[start] == `*` && content[start + 1] == `/` {
start += 2
break
}
start++
}
if start >= content.len {
break
}
continue
}
break
}
if content.len - start >= 7 && content[start] == `m` && content[start + 1] == `o`
&& content[start + 2] == `d` && content[start + 3] == `u` && content[start + 4] == `l`
&& content[start + 5] == `e` && is_module_line_space(content[start + 6]) {
mut name_start := start + 7
for name_start < content.len && is_module_line_space(content[name_start]) {
name_start++
}
mut name_end := name_start
for name_end < content.len && !is_module_source_space(content[name_end]) {
name_end++
}
if name_start < name_end {
return content[name_start..name_end]
}
return none
}
return none
}
fn directory_primary_module(files []string) string {
for file in files {
if module_name := file_module_name(file) {
return module_name
}
}
return 'main'
}
fn collect_same_module_subdir_files(root string, dir string, module_name string, user_defines []string, target_os string, mut files []string, mut seen map[string]bool) {
for entry in list_dir_entries(dir) {
if entry == '' || entry.starts_with('.') {
continue
}
path := os.join_path(dir, entry)
if !os.is_dir(path) {
continue
}
if path != root && os.exists(os.join_path(path, 'v.mod')) {
continue
}
for file in get_v_files_from_dir(path, user_defines, target_os) {
if file in seen {
continue
}
if file_module_name(file) or { 'main' } != module_name {
continue
}
files << file
seen[file] = true
}
collect_same_module_subdir_files(root, path, module_name, user_defines, target_os, mut
files, mut seen)
}
}
fn get_user_v_files_from_dir(dir string, user_defines []string, target_os string) []string {
mut files := get_v_files_from_dir(dir, user_defines, target_os)
module_name := directory_primary_module(files)
mut seen := map[string]bool{}
for file in files {
seen[file] = true
}
collect_same_module_subdir_files(dir, dir, module_name, user_defines, target_os, mut files, mut
seen)
return files
}
fn ast_comptime_flag_matches(name string, user_defines []string, target_os string) bool {
lower_name := name.to_lower()
match lower_name {
'macos', 'darwin', 'mac', 'linux', 'windows', 'bsd', 'freebsd', 'openbsd', 'netbsd',
'dragonfly', 'android', 'termux', 'ios', 'solaris', 'qnx', 'serenity', 'plan9', 'vinix',
'none' {
return flag_os_matches(lower_name, target_os)
}
'cross' {
return flag_os_matches(lower_name, target_os) || lower_name in user_defines
}
'freestanding', 'bare' {
return lower_name in user_defines
}
else {}
}
return lower_name in user_defines
}
fn ast_comptime_cond_matches(cond ast.Expr, user_defines []string, target_os string) bool {
return ast_comptime_cond_matches_with_explicit(cond, user_defines, user_defines, target_os)
}
fn ast_comptime_cond_matches_with_explicit(cond ast.Expr, user_defines []string, explicit_user_defines []string, target_os string) bool {
return ast_comptime_cond_matches_with_options(cond, user_defines, explicit_user_defines,
target_os, true)
}
fn ast_comptime_cond_matches_with_options(cond ast.Expr, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool) bool {
match cond {
ast.Ident {
return ast_comptime_flag_matches(cond.name, user_defines, target_os)
}
ast.ComptimeExpr {
return ast_comptime_cond_matches_with_options(cond.expr, user_defines,
explicit_user_defines, target_os, allow_pkgconfig)
}
ast.CallExpr {
if pkg_name := ast_pkgconfig_call_name(cond) {
if !allow_pkgconfig {
return false
}
return vpref.comptime_pkgconfig_value(pkg_name)
}
}
ast.CallOrCastExpr {
if pkg_name := ast_pkgconfig_call_name(cond) {
if !allow_pkgconfig {
return false
}
return vpref.comptime_pkgconfig_value(pkg_name)
}
}
ast.PrefixExpr {
if cond.op == .not {
return !ast_comptime_cond_matches_with_options(cond.expr, user_defines,
explicit_user_defines, target_os, allow_pkgconfig)
}
}
ast.InfixExpr {
if cond.op == .and {
return
ast_comptime_cond_matches_with_options(cond.lhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
&& ast_comptime_cond_matches_with_options(cond.rhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
}
if cond.op == .logical_or {
return
ast_comptime_cond_matches_with_options(cond.lhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
|| ast_comptime_cond_matches_with_options(cond.rhs, user_defines, explicit_user_defines, target_os, allow_pkgconfig)
}
}
ast.PostfixExpr {
if cond.op == .question && cond.expr is ast.Ident {
return vpref.comptime_optional_define_value(cond.expr.name, user_defines,
explicit_user_defines)
}
}
ast.ParenExpr {
return ast_comptime_cond_matches_with_options(cond.expr, user_defines,
explicit_user_defines, target_os, allow_pkgconfig)
}
else {}
}
return false
}
fn ast_pkgconfig_call_name(expr ast.Expr) ?string {
match expr {
ast.CallExpr {
if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' && expr.args.len == 1 {
return ast_string_literal_value(expr.args[0])
}
}
ast.CallOrCastExpr {
if expr.lhs is ast.Ident && expr.lhs.name == 'pkgconfig' {
return ast_string_literal_value(expr.expr)
}
}
else {}
}
return none
}
fn ast_string_literal_value(expr ast.Expr) ?string {
if expr is ast.StringLiteral {
return unquote_ast_string_literal_value(expr.value)
}
return none
}
fn unquote_ast_string_literal_value(value string) string {
if value.len >= 2 && ((value[0] == `"` && value[value.len - 1] == `"`)
|| (value[0] == `'` && value[value.len - 1] == `'`)) {
return value[1..value.len - 1]
}
return value
}
fn collect_active_imports_from_if_expr(node ast.IfExpr, user_defines []string, target_os string, mut imports []ast.ImportStmt) {
collect_active_imports_from_if_expr_with_explicit(node, user_defines, user_defines, target_os, mut
imports)
}
fn collect_active_imports_from_if_expr_with_explicit(node ast.IfExpr, user_defines []string, explicit_user_defines []string, target_os string, mut imports []ast.ImportStmt) {
collect_active_imports_from_if_expr_with_options(node, user_defines, explicit_user_defines,
target_os, true, mut imports)
}
fn collect_active_imports_from_if_expr_with_options(node ast.IfExpr, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool, mut imports []ast.ImportStmt) {
if ast_comptime_cond_matches_with_options(node.cond, user_defines, explicit_user_defines,
target_os, allow_pkgconfig)
{
collect_active_imports_from_stmts_with_options(node.stmts, user_defines,
explicit_user_defines, target_os, allow_pkgconfig, mut imports)
return
}
match node.else_expr {
ast.IfExpr {
if node.else_expr.cond is ast.EmptyExpr {
collect_active_imports_from_stmts_with_options(node.else_expr.stmts, user_defines,
explicit_user_defines, target_os, allow_pkgconfig, mut imports)
} else {
collect_active_imports_from_if_expr_with_options(node.else_expr, user_defines,
explicit_user_defines, target_os, allow_pkgconfig, mut imports)
}
}
else {}
}
}
fn collect_active_imports_from_stmts(stmts []ast.Stmt, user_defines []string, target_os string, mut imports []ast.ImportStmt) {
collect_active_imports_from_stmts_with_explicit(stmts, user_defines, user_defines, target_os, mut
imports)
}
fn collect_active_imports_from_stmts_with_explicit(stmts []ast.Stmt, user_defines []string, explicit_user_defines []string, target_os string, mut imports []ast.ImportStmt) {
collect_active_imports_from_stmts_with_options(stmts, user_defines, explicit_user_defines,
target_os, true, mut imports)
}
fn collect_active_imports_from_stmts_with_options(stmts []ast.Stmt, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool, mut imports []ast.ImportStmt) {
for stmt in stmts {
match stmt {
ast.ImportStmt {
imports << stmt
}
ast.ExprStmt {
if stmt.expr is ast.ComptimeExpr && stmt.expr.expr is ast.IfExpr {
collect_active_imports_from_if_expr_with_options(stmt.expr.expr, user_defines,
explicit_user_defines, target_os, allow_pkgconfig, mut imports)
}
}
else {}
}
}
}
fn imports_contain(imports []ast.ImportStmt, name string) bool {
for imp in imports {
if imp.name == name {
return true
}
}
return false
}
fn add_implicit_sync_import_if_needed(mut imports []ast.ImportStmt, current_module string, uses_channel bool) {
if !uses_channel || current_module == 'sync' || imports_contain(imports, 'sync') {
return
}
imports << ast.ImportStmt{
name: 'sync'
}
}
fn field_decl_uses_channel(field ast.FieldDecl) bool {
return type_expr_uses_channel(field.typ) || expr_type_slots_use_channel(field.value)
}
fn fields_use_channel(fields []ast.FieldDecl) bool {
for field in fields {
if field_decl_uses_channel(field) {
return true
}
}
return false
}
struct ChannelScanOptions {
user_defines []string
explicit_user_defines []string
target_os string
allow_pkgconfig bool
filter_comptime bool
}
fn field_inits_use_channel(fields []ast.FieldInit) bool {
return field_inits_use_channel_with_options(fields, ChannelScanOptions{
allow_pkgconfig: true
})
}
fn field_inits_use_channel_with_options(fields []ast.FieldInit, options ChannelScanOptions) bool {
for field in fields {
if expr_type_slots_use_channel_with_options(field.value, options) {
return true
}
}
return false
}
fn fn_type_uses_channel(typ ast.FnType) bool {
for gp in typ.generic_params {
if type_expr_uses_channel(gp) {
return true
}
}
for param in typ.params {
if type_expr_uses_channel(param.typ) {
return true
}
}
return type_expr_uses_channel(typ.return_type)
}
fn stmts_use_channel(stmts []ast.Stmt) bool {
return stmts_use_channel_with_options(stmts, ChannelScanOptions{
allow_pkgconfig: true
})
}
fn stmts_use_channel_with_options(stmts []ast.Stmt, options ChannelScanOptions) bool {
for stmt in stmts {
if stmt_uses_channel_with_options(stmt, options) {
return true
}
}
return false
}
fn stmt_uses_channel(stmt ast.Stmt) bool {
return stmt_uses_channel_with_options(stmt, ChannelScanOptions{
allow_pkgconfig: true
})
}
fn fn_decl_body_is_active_for_channel_scan(decl ast.FnDecl, options ChannelScanOptions) bool {
for attr in decl.attributes {
if attr.comptime_cond is ast.EmptyExpr {
continue
}
if !ast_comptime_cond_matches_with_options(attr.comptime_cond, options.user_defines,
options.explicit_user_defines, options.target_os, options.allow_pkgconfig) {
return false
}
}
return true
}
fn stmt_uses_channel_with_options(stmt ast.Stmt, options ChannelScanOptions) bool {
match stmt {
ast.AssertStmt {
return expr_type_slots_use_channel_with_options(stmt.expr, options)
|| expr_type_slots_use_channel_with_options(stmt.extra, options)
}
ast.AssignStmt {
return exprs_type_slots_use_channel_with_options(stmt.lhs, options)
|| exprs_type_slots_use_channel_with_options(stmt.rhs, options)
}
ast.BlockStmt {
return stmts_use_channel_with_options(stmt.stmts, options)
}
ast.ComptimeStmt {
return stmt_uses_channel_with_options(stmt.stmt, options)
}
ast.ConstDecl {
return field_inits_use_channel_with_options(stmt.fields, options)
}
ast.DeferStmt {
return stmts_use_channel_with_options(stmt.stmts, options)
}
ast.EnumDecl {
for field in stmt.fields {
if expr_type_slots_use_channel_with_options(field.value, options) {
return true
}
}
}
ast.ExprStmt {
if options.filter_comptime && stmt.expr is ast.ComptimeExpr
&& stmt.expr.expr is ast.IfExpr {
return comptime_if_expr_uses_channel_with_options(stmt.expr.expr, options)
}
return expr_type_slots_use_channel_with_options(stmt.expr, options)
}
ast.FnDecl {
signature_uses_channel := type_expr_uses_channel(stmt.receiver.typ)
|| fn_type_uses_channel(stmt.typ)
if !fn_decl_body_is_active_for_channel_scan(stmt, options) {
return signature_uses_channel
}
return signature_uses_channel || stmts_use_channel_with_options(stmt.stmts, options)
}
ast.ForInStmt {
return expr_type_slots_use_channel_with_options(stmt.expr, options)
}
ast.ForStmt {
return stmt_uses_channel_with_options(stmt.init, options)
|| expr_type_slots_use_channel_with_options(stmt.cond, options)
|| stmt_uses_channel_with_options(stmt.post, options)
|| stmts_use_channel_with_options(stmt.stmts, options)
}
ast.GlobalDecl {
return fields_use_channel(stmt.fields)
}
ast.InterfaceDecl {
return fields_use_channel(stmt.fields) || type_exprs_use_channel(stmt.embedded)
}
ast.LabelStmt {
return stmt_uses_channel_with_options(stmt.stmt, options)
}
ast.ReturnStmt {
return exprs_type_slots_use_channel_with_options(stmt.exprs, options)
}
ast.StructDecl {
return type_exprs_use_channel(stmt.implements) || type_exprs_use_channel(stmt.embedded)
|| type_exprs_use_channel(stmt.generic_params) || fields_use_channel(stmt.fields)
}
ast.TypeDecl {
return type_expr_uses_channel(stmt.base_type) || type_exprs_use_channel(stmt.variants)
|| type_exprs_use_channel(stmt.generic_params)
}
else {}
}
return false
}
fn type_exprs_use_channel(exprs []ast.Expr) bool {
for expr in exprs {
if type_expr_uses_channel(expr) {
return true
}
}
return false
}
fn exprs_type_slots_use_channel(exprs []ast.Expr) bool {
return exprs_type_slots_use_channel_with_options(exprs, ChannelScanOptions{
allow_pkgconfig: true
})
}
fn exprs_type_slots_use_channel_with_options(exprs []ast.Expr, options ChannelScanOptions) bool {
for expr in exprs {
if expr_type_slots_use_channel_with_options(expr, options) {
return true
}
}
return false
}
fn string_inters_use_channel(inters []ast.StringInter) bool {
return string_inters_use_channel_with_options(inters, ChannelScanOptions{
allow_pkgconfig: true
})
}
fn string_inters_use_channel_with_options(inters []ast.StringInter, options ChannelScanOptions) bool {
for inter in inters {
if expr_type_slots_use_channel_with_options(inter.expr, options)
|| expr_type_slots_use_channel_with_options(inter.format_expr, options) {
return true
}
}
return false
}
fn comptime_if_expr_uses_channel_with_options(node ast.IfExpr, options ChannelScanOptions) bool {
if ast_comptime_cond_matches_with_options(node.cond, options.user_defines,
options.explicit_user_defines, options.target_os, options.allow_pkgconfig)
{
return stmts_use_channel_with_options(node.stmts, options)
}
match node.else_expr {
ast.IfExpr {
if node.else_expr.cond is ast.EmptyExpr {
return stmts_use_channel_with_options(node.else_expr.stmts, options)
}
return comptime_if_expr_uses_channel_with_options(node.else_expr, options)
}
else {
return expr_type_slots_use_channel_with_options(node.else_expr, options)
}
}
}
fn expr_type_slots_use_channel(expr ast.Expr) bool {
return expr_type_slots_use_channel_with_options(expr, ChannelScanOptions{
allow_pkgconfig: true
})
}
fn expr_type_slots_use_channel_with_options(expr ast.Expr, options ChannelScanOptions) bool {
match expr {
ast.ArrayInitExpr {
return type_expr_uses_channel(expr.typ)
|| expr_type_slots_use_channel_with_options(expr.init, options)
|| exprs_type_slots_use_channel_with_options(expr.exprs, options)
|| expr_type_slots_use_channel_with_options(expr.cap, options)
|| expr_type_slots_use_channel_with_options(expr.len, options)
|| expr_type_slots_use_channel_with_options(expr.update_expr, options)
}
ast.AsCastExpr {
return type_expr_uses_channel(expr.typ)
|| expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.AssocExpr {
return type_expr_uses_channel(expr.typ)
|| expr_type_slots_use_channel_with_options(expr.expr, options)
|| field_inits_use_channel_with_options(expr.fields, options)
}
ast.CallExpr {
return expr_type_slots_use_channel_with_options(expr.lhs, options)
|| exprs_type_slots_use_channel_with_options(expr.args, options)
}
ast.CallOrCastExpr {
return type_expr_uses_channel(expr.lhs)
|| expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.CastExpr {
return type_expr_uses_channel(expr.typ)
|| expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.ComptimeExpr {
if options.filter_comptime && expr.expr is ast.IfExpr {
return comptime_if_expr_uses_channel_with_options(expr.expr, options)
}
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.FieldInit {
return expr_type_slots_use_channel_with_options(expr.value, options)
}
ast.FnLiteral {
return fn_type_uses_channel(expr.typ)
|| stmts_use_channel_with_options(expr.stmts, options)
}
ast.GenericArgOrIndexExpr {
return type_expr_uses_channel(expr.lhs) || type_expr_uses_channel(expr.expr)
}
ast.GenericArgs {
return type_expr_uses_channel(expr.lhs) || type_exprs_use_channel(expr.args)
}
ast.IfExpr {
return expr_type_slots_use_channel_with_options(expr.cond, options)
|| stmts_use_channel_with_options(expr.stmts, options)
|| expr_type_slots_use_channel_with_options(expr.else_expr, options)
}
ast.IfGuardExpr {
return stmt_uses_channel_with_options(ast.Stmt(expr.stmt), options)
}
ast.IndexExpr {
return expr_type_slots_use_channel_with_options(expr.lhs, options)
|| expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.InfixExpr {
return expr_type_slots_use_channel_with_options(expr.lhs, options)
|| expr_type_slots_use_channel_with_options(expr.rhs, options)
}
ast.InitExpr {
return type_expr_uses_channel(expr.typ)
|| field_inits_use_channel_with_options(expr.fields, options)
}
ast.KeywordOperator {
return exprs_type_slots_use_channel_with_options(expr.exprs, options)
}
ast.LambdaExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.ModifierExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.LockExpr {
return exprs_type_slots_use_channel_with_options(expr.lock_exprs, options)
|| exprs_type_slots_use_channel_with_options(expr.rlock_exprs, options)
|| stmts_use_channel_with_options(expr.stmts, options)
}
ast.MapInitExpr {
return type_expr_uses_channel(expr.typ)
|| exprs_type_slots_use_channel_with_options(expr.keys, options)
|| exprs_type_slots_use_channel_with_options(expr.vals, options)
}
ast.MatchExpr {
if expr_type_slots_use_channel_with_options(expr.expr, options) {
return true
}
for branch in expr.branches {
if exprs_type_slots_use_channel_with_options(branch.cond, options)
|| stmts_use_channel_with_options(branch.stmts, options) {
return true
}
}
}
ast.OrExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
|| stmts_use_channel_with_options(expr.stmts, options)
}
ast.ParenExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.PostfixExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.PrefixExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.RangeExpr {
return expr_type_slots_use_channel_with_options(expr.start, options)
|| expr_type_slots_use_channel_with_options(expr.end, options)
}
ast.SelectExpr {
return stmt_uses_channel_with_options(expr.stmt, options)
|| stmts_use_channel_with_options(expr.stmts, options)
|| expr_type_slots_use_channel_with_options(expr.next, options)
}
ast.SelectorExpr {
return expr_type_slots_use_channel_with_options(expr.lhs, options)
}
ast.SqlExpr {
return expr_type_slots_use_channel_with_options(expr.expr, options)
}
ast.StringInterLiteral {
return string_inters_use_channel_with_options(expr.inters, options)
}
ast.Tuple {
return exprs_type_slots_use_channel_with_options(expr.exprs, options)
}
ast.UnsafeExpr {
return stmts_use_channel_with_options(expr.stmts, options)
}
ast.Type {
return type_expr_uses_channel(expr)
}
else {}
}
return false
}
fn type_expr_uses_channel(expr ast.Expr) bool {
match expr {
ast.Type {
match expr {
ast.ChannelType {
return true
}
ast.ArrayType {
return type_expr_uses_channel(expr.elem_type)
}
ast.ArrayFixedType {
return type_expr_uses_channel(expr.elem_type)
}
ast.FnType {
return fn_type_uses_channel(expr)
}
ast.GenericType {
return type_expr_uses_channel(expr.name) || type_exprs_use_channel(expr.params)
}
ast.MapType {
return type_expr_uses_channel(expr.key_type)
|| type_expr_uses_channel(expr.value_type)
}
ast.OptionType {
return type_expr_uses_channel(expr.base_type)
}
ast.PointerType {
return type_expr_uses_channel(expr.base_type)
}
ast.ResultType {
return type_expr_uses_channel(expr.base_type)
}
ast.ThreadType {
return type_expr_uses_channel(expr.elem_type)
}
ast.TupleType {
return type_exprs_use_channel(expr.types)
}
ast.AnonStructType {
return type_exprs_use_channel(expr.generic_params)
|| type_exprs_use_channel(expr.embedded) || fields_use_channel(expr.fields)
}
else {}
}
}
else {}
}
return false
}
fn active_file_imports(file ast.File, user_defines []string, target_os string) []ast.ImportStmt {
return active_file_imports_with_explicit(file, user_defines, user_defines, target_os)
}
fn active_file_imports_with_explicit(file ast.File, user_defines []string, explicit_user_defines []string, target_os string) []ast.ImportStmt {
return active_file_imports_with_options(file, user_defines, explicit_user_defines, target_os,
true)
}
fn active_file_imports_with_options(file ast.File, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool) []ast.ImportStmt {
mut imports := file.imports.clone()
collect_active_imports_from_stmts_with_options(file.stmts, user_defines, explicit_user_defines,
target_os, allow_pkgconfig, mut imports)
add_implicit_sync_import_if_needed(mut imports, file.mod, stmts_use_channel_with_options(file.stmts, ChannelScanOptions{
user_defines: user_defines
explicit_user_defines: explicit_user_defines
target_os: target_os
allow_pkgconfig: allow_pkgconfig
filter_comptime: true
}))
return imports
}
// active_file_imports_from_flat mirrors active_file_imports but reads the
// import list and top-level stmts straight from the FlatAst, so import
// discovery can run without rehydrating an ast.File.
fn active_file_imports_from_flat(flat &ast.FlatAst, ff ast.FlatFile, user_defines []string, explicit_user_defines []string, target_os string) []ast.ImportStmt {
return active_file_imports_from_flat_with_options(flat, ff, user_defines,
explicit_user_defines, target_os, true)
}
fn active_file_imports_from_flat_with_options(flat &ast.FlatAst, ff ast.FlatFile, user_defines []string, explicit_user_defines []string, target_os string, allow_pkgconfig bool) []ast.ImportStmt {
// s253: walk the FlatAst via cursors instead of rehydrating the whole file to
// legacy ast.Stmt with a top-level decode. The decode-then-walk path (a) costs a
// full legacy-AST materialisation per file just to answer "does this file use a
// channel / which comptime imports are active?", and (b) crashes on the arm64
// self-host — the legacy `*_use_channel` walkers pass `[]Expr`/sum types by
// value through deep recursion over decoded nodes, hitting the arm64
// chained-access bug. Cursor reads are plain int-array lookups, so the walk is
// cheap and arm64-safe; only the tiny comptime-condition sub-exprs are decoded
// (reusing the exact legacy evaluator) so semantics match the AST path.
mut imports := ast.Cursor{
flat: unsafe { flat }
id: ff.file_id
}.list_at(1).import_stmts()
options := ChannelScanOptions{
user_defines: user_defines
explicit_user_defines: explicit_user_defines
target_os: target_os
allow_pkgconfig: allow_pkgconfig
filter_comptime: true
}
file_node := ast.Cursor{
flat: unsafe { flat }
id: ff.file_id
}
stmts := file_node.list_at(2) // file node edge 2 = top-level statement list
flat_collect_active_imports(stmts, options, mut imports)
module_name := if ff.mod_idx >= 0 && ff.mod_idx < flat.strings.len {
flat.strings[ff.mod_idx]
} else {
'main'
}
add_implicit_sync_import_if_needed(mut imports, module_name, flat_stmts_use_channel(stmts,
options))
return imports
}
// flat_collect_active_imports is the cursor-native mirror of
// `collect_active_imports_from_stmts_with_options`: it appends top-level
// `import` statements and the imports of active comptime `$if` branches.
fn flat_collect_active_imports(stmts ast.CursorList, options ChannelScanOptions, mut imports []ast.ImportStmt) {
for i in 0 .. stmts.len() {
flat_collect_active_imports_stmt(stmts.at(i), options, mut imports)
}
}
fn flat_collect_active_imports_stmt(s ast.Cursor, options ChannelScanOptions, mut imports []ast.ImportStmt) {
match s.kind() {
.stmt_import {
imports << s.import_stmt()
}
.stmt_expr {
inner := s.edge(0)
if inner.kind() == .expr_comptime {
cif := inner.edge(0)
if cif.kind() == .expr_if {
flat_collect_active_imports_from_if(cif, options, mut imports)
}
}
}
else {}
}
}
// flat_collect_active_imports_from_if mirrors
// `collect_active_imports_from_if_expr_with_options`. expr_if layout:
// edge0 = cond, edge1 = else_expr, edge2.. = then-branch statements.
fn flat_collect_active_imports_from_if(if_c ast.Cursor, options ChannelScanOptions, mut imports []ast.ImportStmt) {
if flat_comptime_cond_matches(if_c.edge(0), options) {
for i in 2 .. if_c.edge_count() {
flat_collect_active_imports_stmt(if_c.edge(i), options, mut imports)
}
return
}
else_c := if_c.edge(1)
if else_c.kind() == .expr_if {
if else_c.edge(0).kind() == .expr_empty {
for i in 2 .. else_c.edge_count() {
flat_collect_active_imports_stmt(else_c.edge(i), options, mut imports)
}
} else {
flat_collect_active_imports_from_if(else_c, options, mut imports)
}
}
}
// flat_stmts_use_channel is the cursor-native mirror of
// `stmts_use_channel_with_options`.
fn flat_stmts_use_channel(stmts ast.CursorList, options ChannelScanOptions) bool {
for i in 0 .. stmts.len() {
if flat_node_uses_channel(stmts.at(i), options) {
return true
}
}
return false
}
// flat_node_uses_channel returns true if the active subtree rooted at `c`
// references a channel type. Channel types are always encoded as `.typ_channel`
// nodes, so a generic structural recursion over every edge finds them without
// per-kind edge knowledge (aux_list nodes are transparent — their edges are the
// list items). Two arms must prune to match the legacy scan's comptime
// filtering: a FnDecl body is scanned only when its comptime attributes keep it
// active, and a comptime `$if` (ComptimeExpr wrapping an IfExpr) scans only the
// branch selected by the condition.
fn flat_node_uses_channel(c ast.Cursor, options ChannelScanOptions) bool {
if !c.is_valid() {
return false
}
k := c.kind()
if k == .typ_channel {
return true
}
if k == .stmt_fn_decl {
// stmt_fn_decl layout: edge0 = receiver, edge1 = fn type, edge2 = attrs,
// edge3 = body. Signature (receiver + fn type) is always active; the body
// only when comptime-active. Attributes never hold a channel type (matches
// the legacy FnDecl arm, which scans only receiver.typ + fn_type + body).
if flat_node_uses_channel(c.edge(0), options) {
return true
}
if flat_node_uses_channel(c.edge(1), options) {
return true
}
if flat_fn_decl_body_active_for_channel_scan(c, options) {
return flat_node_uses_channel(c.edge(3), options)
}
return false
}
if k == .expr_comptime && options.filter_comptime {
inner := c.edge(0)
if inner.kind() == .expr_if {
return flat_comptime_if_uses_channel(inner, options)
}
return flat_node_uses_channel(inner, options)
}
for i in 0 .. c.edge_count() {
if flat_node_uses_channel(c.edge(i), options) {
return true
}
}
return false
}
// flat_comptime_if_uses_channel mirrors
// `comptime_if_expr_uses_channel_with_options`: only the branch selected by the
// comptime condition is scanned for channel usage.
fn flat_comptime_if_uses_channel(if_c ast.Cursor, options ChannelScanOptions) bool {
if flat_comptime_cond_matches(if_c.edge(0), options) {
for i in 2 .. if_c.edge_count() {
if flat_node_uses_channel(if_c.edge(i), options) {
return true
}
}
return false
}
else_c := if_c.edge(1)
if else_c.kind() == .expr_if {
if else_c.edge(0).kind() == .expr_empty {
for i in 2 .. else_c.edge_count() {
if flat_node_uses_channel(else_c.edge(i), options) {
return true
}
}
return false
}
return flat_comptime_if_uses_channel(else_c, options)
}
return flat_node_uses_channel(else_c, options)
}
// flat_fn_decl_body_active_for_channel_scan mirrors
// `fn_decl_body_is_active_for_channel_scan`: a FnDecl whose comptime attributes
// (`@[if ...]`) don't match the current target is inactive, so its body is
// skipped. aux_attribute layout: edge0 = value, edge1 = comptime_cond.
fn flat_fn_decl_body_active_for_channel_scan(fn_c ast.Cursor, options ChannelScanOptions) bool {
attrs := fn_c.list_at(2)
for i in 0 .. attrs.len() {
cond := attrs.at(i).edge(1)
if !cond.is_valid() || cond.kind() == .expr_empty {
continue
}
if !flat_comptime_cond_matches(cond, options) {
return false
}
}
return true
}
// flat_comptime_cond_matches evaluates a comptime condition from FlatAst
// cursors. Call/pkgconfig conditions still fall back to the legacy expression
// evaluator until call argument cursors are ported.
fn flat_comptime_cond_matches(cond ast.Cursor, options ChannelScanOptions) bool {
if !cond.is_valid() {
return false
}
match cond.kind() {
.expr_ident {
return ast_comptime_flag_matches(cond.name(), options.user_defines, options.target_os)
}
.expr_comptime, .expr_paren {
return flat_comptime_cond_matches(cond.edge(0), options)
}
.expr_prefix {
op := unsafe { token.Token(int(cond.aux())) }
if op == .not {
return !flat_comptime_cond_matches(cond.edge(0), options)
}
}
.expr_infix {
op := unsafe { token.Token(int(cond.aux())) }
if op == .and {
return flat_comptime_cond_matches(cond.edge(0), options)
&& flat_comptime_cond_matches(cond.edge(1), options)
}
if op == .logical_or {
return flat_comptime_cond_matches(cond.edge(0), options)
|| flat_comptime_cond_matches(cond.edge(1), options)
}
}
.expr_postfix {
op := unsafe { token.Token(int(cond.aux())) }
inner := cond.edge(0)
if op == .question && inner.kind() == .expr_ident {
return vpref.comptime_optional_define_value(inner.name(), options.user_defines,
options.explicit_user_defines)
}
}
.expr_call, .expr_call_or_cast {
if pkg_name := flat_pkgconfig_call_name(cond) {
if !options.allow_pkgconfig {
return false
}
return vpref.comptime_pkgconfig_value(pkg_name)
}
}
else {}
}
return false
}
fn flat_pkgconfig_call_name(expr ast.Cursor) ?string {
match expr.kind() {
.expr_call {
lhs := expr.edge(0)
if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig'
&& expr.edge_count() == 2 {
return flat_string_literal_value(expr.edge(1))
}
}
.expr_call_or_cast {
lhs := expr.edge(0)
if lhs.is_valid() && lhs.kind() == .expr_ident && lhs.name() == 'pkgconfig' {
return flat_string_literal_value(expr.edge(1))
}
}
else {}
}
return none
}
fn flat_string_literal_value(expr ast.Cursor) ?string {
if !expr.is_valid() || expr.kind() != .expr_string {
return none
}
return unquote_ast_string_literal_value(expr.name())
}
// parse_batch routes normal parsing through the streaming-into-shared-
// FlatBuilder path.
fn (mut b Builder) parse_batch(mut parser_reused parser.Parser, files []string) {
b.ensure_flat_builder_inited()
parser_reused.parse_files_into_flat(files, mut b.file_set, mut b.flat_builder)
}
// ensure_flat_builder_inited lazily seeds b.flat_builder on first use.
// Use init_flat_builder_for_paths upfront when the input set is known to
// avoid arena reallocs; this fallback only kicks in when the streaming
// path is entered without that pre-sizing pass.
fn (mut b Builder) ensure_flat_builder_inited() {
if b.flat_builder_inited {
return
}
b.flat_builder = ast.new_flat_builder()
b.flat_builder_inited = true
}
// init_flat_builder_for_paths sizes the persistent flat_builder arenas
// from the total source bytes of the supplied paths. Used by parse_files
// to pre-size before any parse_batch call so the streaming path avoids
// the geometric realloc churn the one-shot flatten_files() side-steps.
// The 2x scale factor accounts for imports, which the caller does not
// yet know — empirically core+user is ~half of the final byte total.
fn (mut b Builder) init_flat_builder_for_paths(paths []string) {
if b.flat_builder_inited {
return
}
mut total_bytes := i64(0)
for path in paths {
if path == '' {
continue
}
total_bytes += os.file_size(path)
}
nodes_cap, edges_cap, strings_cap := ast.arena_caps_for_bytes(total_bytes * 2)
b.flat_builder = ast.new_flat_builder_with_capacity(nodes_cap, edges_cap, strings_cap)
b.flat_builder_inited = true
}
fn (mut b Builder) parse_files(files []string) []ast.File {
mut parser_reused := parser.Parser.new(b.pref)
skip_builtin := b.pref.skip_builtin
target_os := b.pref.source_filter_target_os()
allow_pkgconfig_imports := !b.pref.is_cross_target()
mut use_core_headers := false
// Resolve core-source paths upfront so the pre-size pass below can
// stat them alongside user files. We still parse in the same batch
// shape as before so module-init ordering is unchanged.
mut core_module_files := [][]string{}
mut cached_core_files := []string{}
if !skip_builtin {
use_core_headers = b.can_use_cached_core_headers_for_parse()
b.used_vh_for_parse = use_core_headers
if use_core_headers {
cached_core_files = b.core_cached_parse_paths()
} else {
for module_path in core_cached_module_paths {
vlib_path := b.pref.get_vlib_module_path(module_path)
core_module_files << get_v_files_from_dir(vlib_path, b.pref.user_defines, target_os)
}
}
}
// Expand user input paths: allow compiling module directories (e.g. `v2 .`).
mut expanded_user_files := []string{}
mut seen_user_files := map[string]bool{}
for input in files {
if input == '' {
continue
}
if os.is_dir(input) {
dir_files := get_user_v_files_from_dir(input, b.pref.user_defines, target_os)
for dir_file in dir_files {
if dir_file != '' && dir_file !in seen_user_files {
expanded_user_files << dir_file
seen_user_files[dir_file] = true
}
}
continue
}
if should_expand_single_file_input(input) {
if input !in seen_user_files {
expanded_user_files << input
seen_user_files[input] = true
}
dir_files := get_v_files_from_dir(os.dir(input), b.pref.user_defines, target_os)
for dir_file in dir_files {
if dir_file != '' && dir_file !in seen_user_files {
expanded_user_files << dir_file
seen_user_files[dir_file] = true
}
}
continue
}
if input !in seen_user_files {
expanded_user_files << input
seen_user_files[input] = true
}
}
// Directory inputs and non-main module files were expanded above. Single-file
// `main` programs stay isolated, so `v2 hello.v` parses only `hello.v`,
// while `v2 .` and `v2 vlib/math/math_test.v` still parse their module files.
virtual_main_modules := b.collect_virtual_main_modules_from_paths(expanded_user_files)
if b.can_use_cached_virtual_headers_for_parse(virtual_main_modules) {
expanded_user_files = b.replace_virtual_sources_with_headers(expanded_user_files,
virtual_main_modules)
b.used_virtual_vh_for_parse = true
}
mut pre_size_paths := []string{}
pre_size_paths << cached_core_files
for module_files in core_module_files {
pre_size_paths << module_files
}
pre_size_paths << expanded_user_files
b.init_flat_builder_for_paths(pre_size_paths)
if !skip_builtin {
if use_core_headers {
b.parse_batch(mut parser_reused, cached_core_files)
} else {
for module_files in core_module_files {
b.parse_batch(mut parser_reused, module_files)
}
}
}
b.parse_batch(mut parser_reused, expanded_user_files)
skip_imports := b.pref.skip_imports
if skip_imports {
// Flat path: b.flat_builder is the canonical store; build() derives
// b.flat from it after parse completes.
return []ast.File{}
}
// parse imports
use_import_headers := b.can_use_cached_import_headers_for_parse()
b.used_import_vh_for_parse = use_import_headers
mut parsed_imports := []string{}
if !skip_builtin {
parsed_imports << core_cached_module_paths
}
// Walk the flat store directly. parse_batch may append new files to
// b.flat_builder, so re-read the length each iteration.
for afi := 0; afi < b.flat_builder.flat.files.len; afi++ {
ff := b.flat_builder.flat.files[afi]
ast_file_name := b.flat_builder.flat.file_name(ff)
for mod in active_file_imports_from_flat_with_options(&b.flat_builder.flat, ff,
b.pref.user_defines, b.pref.explicit_user_defines, target_os, allow_pkgconfig_imports) {
if mod.name in parsed_imports {
continue
}
if use_core_headers || use_import_headers {
if cached_path := b.cached_import_parse_path(mod.name) {
b.parse_batch(mut parser_reused, [cached_path])
parsed_imports << mod.name
continue
}
}
mod_path := b.pref.get_module_path(mod.name, ast_file_name)
module_files := get_v_files_from_dir(mod_path, b.pref.user_defines, target_os)
if module_files.len == 0 {
continue
}
b.parse_batch(mut parser_reused, module_files)
parsed_imports << mod.name
}
}
return []ast.File{}
}