From ff92c3409d07490d9c303ea2fc6be4393817c0fe Mon Sep 17 00:00:00 2001 From: spaceface777 Date: Fri, 18 Sep 2020 00:58:54 +0200 Subject: [PATCH] ast: merge `IfExpr` and `CompIf` (#6011) --- vlib/sync/sync_nix.c.v | 3 +- vlib/v/ast/ast.v | 53 +----- vlib/v/builder/c.v | 5 +- vlib/v/builder/cc.v | 14 +- vlib/v/builder/msvc.v | 11 +- vlib/v/checker/checker.v | 175 ++++++++++++++---- .../checker/tests/unnecessary_parenthesis.out | 6 +- vlib/v/compiler_errors_test.v | 3 +- vlib/v/fmt/fmt.v | 34 +--- .../match_with_commented_branches_keep.vv | 1 - vlib/v/gen/cgen.v | 8 +- vlib/v/gen/comptime.v | 141 ++++++++------ vlib/v/gen/js/js.v | 3 - vlib/v/parser/comptime.v | 170 ----------------- vlib/v/parser/if_match.v | 30 ++- vlib/v/parser/parser.v | 9 +- vlib/v/parser/pratt.v | 61 +++--- vlib/v/pref/default.v | 1 + vlib/v/pref/pref.v | 19 +- vlib/v/token/token.v | 3 +- 20 files changed, 357 insertions(+), 393 deletions(-) diff --git a/vlib/sync/sync_nix.c.v b/vlib/sync/sync_nix.c.v index dc4386ac0a..f5903e123c 100644 --- a/vlib/sync/sync_nix.c.v +++ b/vlib/sync/sync_nix.c.v @@ -8,9 +8,8 @@ import time #flag -lpthread $if macos { #include -} $else { - #include } +#include // [init_with=new_mutex] // TODO: implement support for this struct attribute, and disallow Mutex{} from outside the sync.new_mutex() function. [ref_only] diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index 12015c4772..4f2436f901 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -16,18 +16,13 @@ pub type Expr = AnonFn | ArrayInit | AsCast | Assoc | BoolLiteral | CallExpr | C SelectorExpr | SizeOf | SqlExpr | StringInterLiteral | StringLiteral | StructInit | Type | TypeOf | UnsafeExpr -pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | CompIf | ConstDecl | - DeferStmt | EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | - GoStmt | GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | - SqlStmt | StructDecl | TypeDecl +pub type Stmt = AssertStmt | AssignStmt | Block | BranchStmt | CompFor | ConstDecl | DeferStmt | + EnumDecl | ExprStmt | FnDecl | ForCStmt | ForInStmt | ForStmt | GlobalDecl | GoStmt | + GotoLabel | GotoStmt | HashStmt | Import | InterfaceDecl | Module | Return | SqlStmt | + StructDecl | TypeDecl pub type ScopeObject = ConstField | GlobalDecl | Var -// pub type Type = StructType | ArrayType -// pub struct StructType { -// fields []Field -// } -// pub struct ArrayType {} pub struct Type { pub: typ table.Type @@ -468,6 +463,7 @@ pub mut: pub struct IfExpr { pub: + is_comptime bool tok_kind token.Kind left Expr // `a` in `a := if ...` pos token.Position @@ -482,13 +478,13 @@ pub mut: pub struct IfBranch { pub: cond Expr - stmts []Stmt pos token.Position body_pos token.Position comments []Comment left_as_name string // `name` in `if cond is SumType as name` mut_name bool // `if mut name is` pub mut: + stmts []Stmt smartcast bool // true when cond is `x is SumType`, set in checker.if_expr } @@ -557,42 +553,6 @@ pub: post_comments []Comment } -/* -CompIf.is_opt: -`$if xyz? {}` => this compile time `if` is optional, -and .is_opt reflects the presence of ? at the end. -When .is_opt is true, the code should compile, even -if `xyz` is NOT defined. -If .is_opt is false, then when `xyz` is not defined, -the compilation will fail. - -`$if method.type is string {}` will produce CompIf with: -.is_typecheck true, -.tchk_expr: method.type -.tchk_type: string -.tchk_match: true on each iteration, having a string `method.type` -*/ -pub enum CompIfKind { - platform - typecheck -} - -pub struct CompIf { -pub: - val string - stmts []Stmt - is_not bool - kind CompIfKind - tchk_expr Expr - tchk_type table.Type - pos token.Position -pub mut: - tchk_match bool - is_opt bool - has_else bool - else_stmts []Stmt -} - pub enum CompForKind { methods fields @@ -1134,7 +1094,6 @@ pub fn (stmt Stmt) position() token.Position { // BranchStmt { // } */ - CompIf { return stmt.pos } ConstDecl { return stmt.pos } /* // DeferStmt { diff --git a/vlib/v/builder/c.v b/vlib/v/builder/c.v index 8c128e4add..0bbbcd3eac 100644 --- a/vlib/v/builder/c.v +++ b/vlib/v/builder/c.v @@ -58,9 +58,8 @@ pub fn (mut b Builder) compile_c() { // println(files) } $if windows { - b.pref.ccompiler = b.find_win_cc() or { - panic(no_compiler_error) - } + b.find_win_cc() or { verror(no_compiler_error) } + // TODO Probably extend this to other OS's? } // v1 compiler files // v.add_v_files_to_compile() diff --git a/vlib/v/builder/cc.v b/vlib/v/builder/cc.v index 07cfb4584f..fe6bb878bf 100644 --- a/vlib/v/builder/cc.v +++ b/vlib/v/builder/cc.v @@ -42,7 +42,7 @@ const ( fn todo() { } -fn (v &Builder) find_win_cc() ?string { +fn (mut v Builder) find_win_cc() ? { $if !windows { return none } @@ -58,15 +58,19 @@ fn (v &Builder) find_win_cc() ?string { thirdparty_tcc := os.join_path(vpath, 'thirdparty', 'tcc', 'tcc.exe') os.exec('$thirdparty_tcc -v') or { if v.pref.is_verbose { - println('No C compiler found') + println('tcc not found') } return none } - return thirdparty_tcc + v.pref.ccompiler = thirdparty_tcc + v.pref.ccompiler_type = .tinyc + return } - return 'msvc' + v.pref.ccompiler = 'msvc' + v.pref.ccompiler_type = .msvc + return } - return v.pref.ccompiler + v.pref.ccompiler_type = pref.cc_from_string(v.pref.ccompiler) } fn (mut v Builder) cc() { diff --git a/vlib/v/builder/msvc.v b/vlib/v/builder/msvc.v index 9babda9aaa..d1c9ecf655 100644 --- a/vlib/v/builder/msvc.v +++ b/vlib/v/builder/msvc.v @@ -84,18 +84,19 @@ fn find_windows_kit_root(host_arch string) ?WindowsKit { path := 'SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots' rc := C.RegOpenKeyEx(hkey_local_machine, path.to_wide(), 0, key_query_value | key_wow64_32key | key_enumerate_sub_keys, &root_key) - defer { - C.RegCloseKey(root_key) - } + // TODO: Fix defer inside ifs + // defer { + // C.RegCloseKey(root_key) + // } if rc != 0 { return error('Unable to open root key') } // Try and find win10 kit kit_root := find_windows_kit_internal(root_key, ['KitsRoot10', 'KitsRoot81']) or { + C.RegCloseKey(root_key) return error('Unable to find a windows kit') } kit_lib := kit_root + 'Lib' - // println(kit_lib) files := os.ls(kit_lib)? mut highest_path := '' mut highest_int := 0 @@ -109,7 +110,7 @@ fn find_windows_kit_root(host_arch string) ?WindowsKit { } kit_lib_highest := kit_lib + '\\$highest_path' kit_include_highest := kit_lib_highest.replace('Lib', 'Include') - // println('$kit_lib_highest $kit_include_highest') + C.RegCloseKey(root_key) return WindowsKit{ um_lib_path: kit_lib_highest + '\\um\\$host_arch' ucrt_lib_path: kit_lib_highest + '\\ucrt\\$host_arch' diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 1909dd0f5b..0e4cd52b4c 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -16,6 +16,16 @@ const ( enum_max = 0x7FFFFFFF ) +const ( + valid_comp_if_os = ['windows', 'ios', 'mac', 'macos', 'mach', 'darwin', 'hpux', 'gnu', + 'qnx', 'linux', 'freebsd', 'openbsd', 'netbsd', 'bsd', 'dragonfly', 'android', 'solaris', 'haiku', + 'linux_or_macos', + ] + valid_comp_if_compilers = ['gcc', 'tinyc', 'clang', 'mingw', 'msvc', 'cplusplus'] + valid_comp_if_platforms = ['amd64', 'aarch64', 'x64', 'x32', 'little_endian', 'big_endian'] + valid_comp_if_other = ['js', 'debug', 'test', 'glibc', 'prealloc', 'no_bounds_checking'] +) + pub struct Checker { pub mut: table &table.Table @@ -40,6 +50,7 @@ pub mut: mod string // current module name is_builtin_mod bool // are we in `builtin`? inside_unsafe bool + skip_flags bool // should `#flag` and `#include` be skipped cur_generic_type table.Type mut: expr_level int // to avoid infinit recursion segfaults due to compiler bugs @@ -2078,12 +2089,14 @@ fn (mut c Checker) stmt(node ast.Stmt) { // c.expected_type = table.void_type match mut node { ast.AssertStmt { + cur_exp_typ := c.expected_type assert_type := c.expr(node.expr) if assert_type != table.bool_type_idx { atype_name := c.table.get_type_symbol(assert_type).name c.error('assert can be used only with `bool` expressions, but found `$atype_name` instead', node.pos) } + c.expected_type = cur_exp_typ } ast.AssignStmt { c.assign_stmt(mut node) @@ -2107,17 +2120,6 @@ fn (mut c Checker) stmt(node ast.Stmt) { // node.typ = c.expr(node.expr) c.stmts(node.stmts) } - ast.CompIf { - c.stmts(node.stmts) - if node.has_else { - c.stmts(node.else_stmts) - } - mut stmts := node.stmts.clone() - stmts << node.else_stmts - if has_return := c.has_return(stmts) { - c.returns = has_return - } - } ast.ConstDecl { mut field_names := []string{} mut field_order := []int{} @@ -2303,6 +2305,9 @@ fn (mut c Checker) stmt(node ast.Stmt) { } fn (mut c Checker) hash_stmt(mut node ast.HashStmt) { + if c.skip_flags { + return + } if c.pref.backend == .js { if !c.file.path.ends_with('.js.v') { c.error('Hash statements are only allowed in backend specific files such "x.js.v"', @@ -3123,33 +3128,38 @@ pub fn (mut c Checker) unsafe_expr(mut node ast.UnsafeExpr) table.Type { } pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { - mut expr_required := false - if c.expected_type != table.void_type { - // sym := c.table.get_type_symbol(c.expected_type) - // println('$c.file.path $node.pos.line_nr IF is expr: checker exp type = ' + sym.source_name) - expr_required = true - } + is_ct := node.is_comptime + if_kind := if is_ct { '\$if' } else { 'if' } + expr_required := c.expected_type != table.void_type former_expected_type := c.expected_type node.typ = table.void_type mut require_return := false mut branch_without_return := false - for i, branch in node.branches { + mut should_skip := false // Whether the current branch should be skipped + mut found_branch := false // Whether a matching branch was found- skip the rest + for i in 0 .. node.branches.len { + mut branch := node.branches[i] if branch.cond is ast.ParExpr { - c.error('unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`.', + c.error('unnecessary `()` in `$if_kind` condition, use `$if_kind expr {` instead of `$if_kind (expr) {`.', branch.pos) } if !node.has_else || i < node.branches.len - 1 { - // check condition type is boolean - cond_typ := c.expr(branch.cond) - if cond_typ.idx() !in [table.bool_type_idx, table.void_type_idx] && !c.pref.translated { - // void types are skipped, because they mean the var was initialized incorrectly - // (via missing function etc) - typ_sym := c.table.get_type_symbol(cond_typ) - c.error('non-bool type `$typ_sym.source_name` used as if condition', branch.pos) + if is_ct { + should_skip = c.comp_if_branch(branch.cond, branch.pos) + } else { + // check condition type is boolean + cond_typ := c.expr(branch.cond) + if cond_typ.idx() !in [table.bool_type_idx, table.void_type_idx] && !c.pref.translated { + // void types are skipped, because they mean the var was initialized incorrectly + // (via missing function etc) + typ_sym := c.table.get_type_symbol(cond_typ) + c.error('non-bool type `$typ_sym.source_name` used as if condition', + branch.pos) + } } } // smartcast sumtypes and interfaces when using `is` - if branch.cond is ast.InfixExpr { + if !is_ct && branch.cond is ast.InfixExpr { infix := branch.cond as ast.InfixExpr if infix.op == .key_is && (infix.left is ast.Ident || infix.left is ast.SelectorExpr) && infix.right is ast.Type { @@ -3185,7 +3195,25 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { } } } - c.stmts(branch.stmts) + if is_ct { // Skip checking if needed + cur_skip_flags := c.skip_flags + if found_branch { + c.skip_flags = true + } else if should_skip { + c.skip_flags = true + should_skip = false // Reset the value of `should_skip` for the next branch + } else { + found_branch = true // If a branch wasn't skipped, the rest must be + } + if !c.skip_flags || c.pref.output_cross_c { + c.stmts(branch.stmts) + } else { + node.branches[i].stmts = [] + } + c.skip_flags = cur_skip_flags + } else { + c.stmts(branch.stmts) + } if expr_required { if branch.stmts.len > 0 && branch.stmts[branch.stmts.len - 1] is ast.ExprStmt { mut last_expr := branch.stmts[branch.stmts.len - 1] as ast.ExprStmt @@ -3227,10 +3255,11 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { node.pos) } } else { - c.error('`if` expression requires an expression as the last statement of every branch', + c.error('`$if_kind` expression requires an expression as the last statement of every branch', branch.pos) } } + // Also check for returns inside a comp.if's statements, even if its contents aren't parsed if has_return := c.has_return(branch.stmts) { if has_return { require_return = true @@ -3253,21 +3282,99 @@ pub fn (mut c Checker) if_expr(mut node ast.IfExpr) table.Type { } if expr_required { if !node.has_else { - c.error('`if` expression needs `else` clause', node.pos) + d := if is_ct { '$' } else { '' } + c.error('`$if_kind` expression needs `${d}else` clause', node.pos) } return node.typ } return table.bool_type } +// comp_if_branch checks the condition of a compile-time `if` branch. It returns a `bool` that +// saying whether that branch's contents should be skipped (targets a different os for example) +fn (mut c Checker) comp_if_branch(cond ast.Expr, pos token.Position) bool { + // TODO: better error messages here + match cond { + ast.ParExpr { + return c.comp_if_branch(cond.expr, pos) + } + ast.PrefixExpr { + if cond.op != .not { + c.error('invalid `\$if` condition', cond.pos) + } + return !c.comp_if_branch(cond.right, cond.pos) + } + ast.PostfixExpr { + if cond.op != .question { + c.error('invalid \$if postfix operator', cond.pos) + } else if cond.expr is ast.Ident as ident { + return ident.name !in c.pref.compile_defines_all + } else { + c.error('invalid `\$if` condition', cond.pos) + } + } + ast.InfixExpr { + match cond.op { + .and { + l := c.comp_if_branch(cond.left, cond.pos) + r := c.comp_if_branch(cond.right, cond.pos) + return l && r + } + .logical_or { + l := c.comp_if_branch(cond.left, cond.pos) + r := c.comp_if_branch(cond.right, cond.pos) + return l || r + } + .key_is, .not_is { + // $if method.@type is string + // TODO better checks here, will be done in comp. for PR + if cond.left !is ast.SelectorExpr || cond.right !is ast.Type { + c.error('invalid `\$if` condition', cond.pos) + } + } + .eq, .ne { + // $if method.args.len == 1 + // TODO better checks here, will be done in comp. for PR + if cond.left !is ast.SelectorExpr || cond.right !is ast.IntegerLiteral { + c.error('invalid `\$if` condition', cond.pos) + } + } + else { + c.error('invalid `\$if` condition', cond.pos) + } + } + } + ast.Ident { + if cond.name in valid_comp_if_os { + return cond.name != c.pref.os.str().to_lower() // TODO hack + } else if cond.name in valid_comp_if_compilers { + return pref.cc_from_string(cond.name) != c.pref.ccompiler_type + } else if cond.name in valid_comp_if_platforms { + return false // TODO + } else if cond.name in valid_comp_if_other { + // TODO: This should probably be moved + match cond.name as name { + 'js' { return c.pref.backend != .js } + 'debug' { return !c.pref.is_debug } + 'test' { return !c.pref.is_test } + 'glibc' { return false } // TODO + 'prealloc' { return !c.pref.prealloc } + 'no_bounds_checking' { return cond.name !in c.pref.compile_defines_all } + else { return false } + } + } + } + else { + c.error('invalid `\$if` condition', pos) + } + } + return false +} + fn (c &Checker) has_return(stmts []ast.Stmt) ?bool { // complexity means either more match or ifs mut has_complexity := false for s in stmts { - if s is ast.CompIf { - has_complexity = true - break - } if s is ast.ExprStmt { if s.expr is ast.IfExpr || s.expr is ast.MatchExpr { has_complexity = true diff --git a/vlib/v/checker/tests/unnecessary_parenthesis.out b/vlib/v/checker/tests/unnecessary_parenthesis.out index 4aa477142d..eee14f79fb 100644 --- a/vlib/v/checker/tests/unnecessary_parenthesis.out +++ b/vlib/v/checker/tests/unnecessary_parenthesis.out @@ -1,17 +1,17 @@ -vlib/v/checker/tests/unnecessary_parenthesis.vv:2:2: error: unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`. +vlib/v/checker/tests/unnecessary_parenthesis.vv:2:2: error: unnecessary `()` in `if` condition, use `if expr {` instead of `if (expr) {`. 1 | fn main() { 2 | if (1 == 1) { | ~~~~~~~~~~~ 3 | println('yeay') 4 | } else if (1 == 2) { -vlib/v/checker/tests/unnecessary_parenthesis.vv:4:4: error: unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`. +vlib/v/checker/tests/unnecessary_parenthesis.vv:4:4: error: unnecessary `()` in `if` condition, use `if expr {` instead of `if (expr) {`. 2 | if (1 == 1) { 3 | println('yeay') 4 | } else if (1 == 2) { | ~~~~~~~~~~~~~~~~ 5 | println("oh no :'(") 6 | } else if (1 == 3) { -vlib/v/checker/tests/unnecessary_parenthesis.vv:6:4: error: unnecessary `()` in an if condition. use `if expr {` instead of `if (expr) {`. +vlib/v/checker/tests/unnecessary_parenthesis.vv:6:4: error: unnecessary `()` in `if` condition, use `if expr {` instead of `if (expr) {`. 4 | } else if (1 == 2) { 5 | println("oh no :'(") 6 | } else if (1 == 3) { diff --git a/vlib/v/compiler_errors_test.v b/vlib/v/compiler_errors_test.v index 7a4eb0f07a..893929045a 100644 --- a/vlib/v/compiler_errors_test.v +++ b/vlib/v/compiler_errors_test.v @@ -9,7 +9,8 @@ import benchmark const ( skip_files = [ - 'nonexisting' + 'vlib/v/checker/tests/return_missing_comp_if.vv' + 'vlib/v/checker/tests/return_missing_comp_if_nested.vv' ] ) diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index 53b76be00e..e5fe7996ca 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -307,28 +307,6 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { f.stmts(it.stmts) f.writeln('}') } - ast.CompIf { - inversion := if it.is_not { '!' } else { '' } - is_opt := if it.is_opt { ' ?' } else { '' } - mut typecheck := '' - if it.kind == .typecheck { - typ := f.no_cur_mod(f.table.type_to_str(it.tchk_type)) - typecheck = ' is $typ' - f.write('\$if $inversion') - f.expr(it.tchk_expr) - f.write(is_opt) - f.write(typecheck) - f.writeln(' {') - } else { - f.writeln('\$if $inversion$it.val$is_opt {') - } - f.stmts(it.stmts) - if it.has_else { - f.writeln('} \$else {') - f.stmts(it.else_stmts) - } - f.writeln('}') - } ast.ConstDecl { f.const_decl(it) } @@ -942,7 +920,12 @@ pub fn (mut f Fmt) expr(node ast.Expr) { } ast.PostfixExpr { f.expr(node.expr) - f.write(node.op.str()) + // `$if foo ?` + if node.op == .question { + f.write(' ?') + } else { + f.write('$node.op') + } } ast.PrefixExpr { f.write(node.op.str()) @@ -1361,6 +1344,7 @@ pub fn (mut f Fmt) infix_expr(node ast.InfixExpr) { } pub fn (mut f Fmt) if_expr(it ast.IfExpr) { + dollar := if it.is_comptime { '$' } else { '' } single_line := it.branches.len == 2 && it.has_else && it.branches[0].stmts.len == 1 && it.branches[1].stmts.len == 1 && (it.is_expr || f.is_assign) @@ -1386,10 +1370,10 @@ pub fn (mut f Fmt) if_expr(it ast.IfExpr) { } else { f.write('} ') } - f.write('else ') + f.write('${dollar}else ') } if i < it.branches.len - 1 || !it.has_else { - f.write('if ') + f.write('${dollar}if ') if branch.mut_name { f.write('mut ') } diff --git a/vlib/v/fmt/tests/match_with_commented_branches_keep.vv b/vlib/v/fmt/tests/match_with_commented_branches_keep.vv index 4396b2fdbb..3240eb55b0 100644 --- a/vlib/v/fmt/tests/match_with_commented_branches_keep.vv +++ b/vlib/v/fmt/tests/match_with_commented_branches_keep.vv @@ -13,7 +13,6 @@ pub fn (stmt Stmt) position() Position { // } */ Comment { return stmt.pos } - CompIf { return stmt.pos } ConstDecl { return stmt.pos } /* // DeferStmt { diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index fe059dc254..e37dfe755b 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -777,10 +777,6 @@ fn (mut g Gen) stmt(node ast.Stmt) { ast.CompFor { g.comp_for(node) } - ast.CompIf { - g.write_v_source_line_info(node.pos) - g.comp_if(node) - } ast.DeferStmt { mut defer_stmt := *node defer_stmt.ifdef = g.defer_ifdef @@ -2821,6 +2817,10 @@ fn (mut g Gen) concat_expr(node ast.ConcatExpr) { } fn (mut g Gen) if_expr(node ast.IfExpr) { + if node.is_comptime { + g.comp_if(node) + return + } if node.is_expr || g.inside_ternary != 0 { g.inside_ternary++ g.write('(') diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index fcd92d110e..3a9ad59bf6 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -101,67 +101,94 @@ fn cgen_attrs(attrs []table.Attr) []string { return res } -fn (mut g Gen) comp_if(mut it ast.CompIf) { - if it.stmts.len == 0 && it.else_stmts.len == 0 { - return - } - if it.kind == .typecheck { - mut comptime_var_type := table.Type(0) - mut name := '' - if it.tchk_expr is ast.SelectorExpr { - se := it.tchk_expr as ast.SelectorExpr - name = '${se.expr}.$se.field_name' - comptime_var_type = g.comptime_var_type_map[name] +fn (mut g Gen) comp_if(node ast.IfExpr) { + line := if node.is_expr { + stmt_str := g.go_before_stmt(0) + g.write(tabs[g.indent]) + stmt_str.trim_space() + } else { '' } + for i, branch in node.branches { + start_pos := g.out.len + if i == node.branches.len - 1 && node.has_else { + g.writeln('#else') + } else { + if i == 0 { + g.write('#if ') + } else { + g.write('#elif ') + } + g.comp_if_expr(branch.cond) + g.writeln('') } - // if comptime_var_type == 0 { - // $if trace_gen ? { - // eprintln('Known compile time types: ') - // eprintln(g.comptime_var_type_map.str()) - // } - // // verror('the compile time type of `$it.tchk_expr.str()` is unknown') - // return - // } - it_type_name := g.table.get_type_name(it.tchk_type) - should_write := (comptime_var_type == it.tchk_type && !it.is_not) || - (comptime_var_type != it.tchk_type && it.is_not) - if should_write { - inversion := if it.is_not { '!' } else { '' } - g.writeln('/* \$if $name ${inversion}is $it_type_name */ {') - g.stmts(it.stmts) - g.writeln('}') - } else if it.has_else { - g.writeln('/* \$else */ {') - g.stmts(it.else_stmts) - g.writeln('}') + expr_str := g.out.last_n(g.out.len - start_pos).trim_space() + g.defer_ifdef = expr_str + if node.is_expr { + len := branch.stmts.len + if len > 0 { + last := branch.stmts[len - 1] as ast.ExprStmt + if len > 1 { + tmp := g.new_tmp_var() + styp := g.typ(last.typ) + g.indent++ + g.writeln('$styp $tmp;') + g.writeln('{') + g.stmts(branch.stmts[0 .. len - 1]) + g.write('\t$tmp = ') + g.stmt(last) + g.writeln('}') + g.indent-- + g.writeln('$line $tmp;') + } else { + g.write('$line ') + g.stmt(last) + } + } + } else { + // Only wrap the contents in {} if we're inside a function, not on the top level scope + should_create_scope := g.fn_decl != 0 + if should_create_scope { g.writeln('{') } + g.stmts(branch.stmts) + if should_create_scope { g.writeln('}') } } - return - } - ifdef := g.comp_if_to_ifdef(it.val, it.is_opt) - g.empty_line = false - if it.is_not { - g.writeln('// \$if !$it.val {\n#ifndef ' + ifdef) - } else { - g.writeln('// \$if $it.val {\n#ifdef ' + ifdef) - } - // NOTE: g.defer_ifdef is needed for defers called witin an ifdef - // in v1 this code would be completely excluded - g.defer_ifdef = if it.is_not { '#ifndef ' + ifdef } else { '#ifdef ' + ifdef } - // println('comp if stmts $g.file.path:$it.pos.line_nr') - g.indent-- - g.stmts(it.stmts) - g.indent++ - g.defer_ifdef = '' - if it.has_else { - g.empty_line = false - g.writeln('#else') - g.defer_ifdef = if it.is_not { '#ifdef ' + ifdef } else { '#ifndef ' + ifdef } - g.indent-- - g.stmts(it.else_stmts) - g.indent++ g.defer_ifdef = '' } - g.empty_line = false - g.writeln('#endif\n// } $it.val') + if node.is_expr { g.write('#endif') } else { g.writeln('#endif') } +} + +fn (mut g Gen) comp_if_expr(cond ast.Expr) { + match cond { + ast.ParExpr { + g.write('(') + g.comp_if_expr(cond.expr) + g.write(')') + } ast.PrefixExpr { + g.write(cond.op.str()) + g.comp_if_expr(cond.right) + } ast.PostfixExpr { + ifdef := g.comp_if_to_ifdef((cond.expr as ast.Ident).name, true) + g.write('defined($ifdef)') + } ast.InfixExpr { + match cond.op { + .and, .logical_or { + g.comp_if_expr(cond.left) + g.write(' $cond.op ') + g.comp_if_expr(cond.right) + } + .key_is, .not_is { + se := cond.left as ast.SelectorExpr + name := '${se.expr}.$se.field_name' + exp_type := g.comptime_var_type_map[name] + got_type := (cond.right as ast.Type).typ + g.write('$exp_type == $got_type') + } .eq, .ne { + // TODO Implement `$if method.args.len == 1` + } else {} + } + } ast.Ident { + ifdef := g.comp_if_to_ifdef(cond.name, false) + g.write('defined($ifdef)') + } else {} + } } fn (mut g Gen) comp_for(node ast.CompFor) { diff --git a/vlib/v/gen/js/js.v b/vlib/v/gen/js/js.v index d1461c7ed3..b4fbc9cc09 100644 --- a/vlib/v/gen/js/js.v +++ b/vlib/v/gen/js/js.v @@ -445,9 +445,6 @@ fn (mut g JsGen) stmt(node ast.Stmt) { g.gen_branch_stmt(node) } ast.CompFor {} - ast.CompIf { - // skip: JS has no compile time if - } ast.ConstDecl { g.gen_const_decl(node) } diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index b2d10c569d..022aa9e867 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -132,152 +132,6 @@ fn (mut p Parser) comp_for() ast.CompFor { } } -fn (mut p Parser) comp_if() ast.Stmt { - pos := p.tok.position() - p.next() - // if p.tok.kind == .name && p.tok.lit == 'vweb' { - // return p.vweb() - // } - p.check(.key_if) - mut is_not := p.tok.kind == .not - inversion_pos := p.tok.position() - if is_not { - p.next() - } - // - name_pos_start := p.tok.position() - mut val := '' - mut tchk_expr := ast.Expr{} - if p.peek_tok.kind == .dot { - vname := p.parse_ident(.v) - cobj := p.scope.find(vname.name) or { - p.error_with_pos('unknown variable `$vname.name`', name_pos_start) - return ast.Stmt{} - } - if cobj is ast.Var { - tchk_expr = p.dot_expr(vname) - val = vname.name - if tchk_expr is ast.SelectorExpr as tchk_expr2 { - if p.tok.kind == .lsbr && tchk_expr2.field_name == 'args' { - tchk_expr = p.index_expr(tchk_expr) - if p.tok.kind == .dot && p.peek_tok.lit == 'Type' { - tchk_expr = p.dot_expr(tchk_expr) - } else { - p.error_with_pos('only the `Type` field is supported for arguments', - p.peek_tok.position()) - } - } else if tchk_expr2.field_name !in ['Type', 'ReturnType'] { - p.error_with_pos('only the `Type` and `ReturnType` fields are supported for now', - name_pos_start) - } - } - } else { - p.error_with_pos('`$vname.name` is not a variable', name_pos_start) - } - } else { - val = p.check_name() - } - name_pos := name_pos_start.extend(p.tok.position()) - // - mut stmts := []ast.Stmt{} - mut skip_os := false - mut skip_cc := false - if val in supported_platforms { - os := os_from_string(val) - if (!is_not && os != p.pref.os) || (is_not && os == p.pref.os) { - skip_os = true - } - } else if val in supported_ccompilers { - if p.pref.ccompiler.len == 2 && p.pref.ccompiler == 'cc' { - // we just do not know, so we can not skip: - skip_cc = false - }else { - cc := cc_from_string(val) - user_cc := cc_from_string(p.pref.ccompiler) - if (!is_not && cc != user_cc) || (is_not && cc == user_cc) { - skip_cc = true - } - } - } - mut skip := skip_os || skip_cc - // `$if os {` or `$if compiler {` for a different target, skip everything inside - // to avoid compilation errors (like including or calling WinAPI fns - // on non-Windows systems) - if !p.pref.is_fmt && !p.pref.output_cross_c && skip { - p.check(.lcbr) - // p.warn('skipping $if $val os=$os p.pref.os=$p.pref.os') - mut stack := 1 - for { - if p.tok.kind == .key_return { - p.returns = true - } - if p.tok.kind == .lcbr { - stack++ - } else if p.tok.kind == .rcbr { - stack-- - } - if p.tok.kind == .eof { - break - } - if stack <= 0 && p.tok.kind == .rcbr { - // p.warn('exiting $stack') - p.next() - break - } - p.next() - } - } else { - skip = false - } - mut is_opt := false - mut is_typecheck := false - mut tchk_type := table.Type(0) - if p.tok.kind == .question { - p.next() - is_opt = true - } else if p.tok.kind in [.key_is, .not_is] { - typecheck_inversion := p.tok.kind == .not_is - p.next() - tchk_type = p.parse_type() - is_typecheck = true - if is_not { - name := p.table.get_type_name(tchk_type) - p.error_with_pos('use `\$if $tchk_expr !is $name {`, not `\$if !$tchk_expr is $name {`', - inversion_pos) - } - is_not = typecheck_inversion - } - if !skip { - stmts = p.parse_block() - } - if !is_typecheck && val.len == 0 { - p.error_with_pos('Only `\$if compvarname.field is type {}` is supported', name_pos) - } - if is_typecheck { - match tchk_expr { - ast.SelectorExpr {} - else { p.error_with_pos('Only compvarname.field is supported', name_pos) } - } - } - mut node := ast.CompIf{ - is_not: is_not - is_opt: is_opt - kind: if is_typecheck { ast.CompIfKind.typecheck } else { ast.CompIfKind.platform } - pos: pos - val: val - tchk_type: tchk_type - tchk_expr: tchk_expr - stmts: stmts - } - if p.tok.kind == .dollar && p.peek_tok.kind == .key_else { - p.next() - p.next() - node.has_else = true - node.else_stmts = p.parse_block() - } - return node -} - // TODO import warning bug const ( todo_delete_me = pref.OS.linux @@ -339,30 +193,6 @@ fn os_from_string(os string) pref.OS { return .linux } -// Helper function to convert string names to CC enum -pub fn cc_from_string(cc_str string) pref.CompilerType { - if cc_str.len == 0 { - return .gcc - } - cc := cc_str.replace('\\', '/').split('/').last().all_before('.') - if 'tcc' in cc { - return .tinyc - } - if 'tinyc' in cc { - return .tinyc - } - if 'clang' in cc { - return .clang - } - if 'mingw' in cc { - return .mingw - } - if 'msvc' in cc { - return .msvc - } - return .gcc -} - // `app.$action()` (`action` is a string) // `typ` is `App` in this example // fn (mut p Parser) comptime_method_call(typ table.Type) ast.ComptimeCall { diff --git a/vlib/v/parser/if_match.v b/vlib/v/parser/if_match.v index 03260f4d78..b97523bb9b 100644 --- a/vlib/v/parser/if_match.v +++ b/vlib/v/parser/if_match.v @@ -7,20 +7,28 @@ import v.ast import v.table import v.token -fn (mut p Parser) if_expr() ast.IfExpr { +fn (mut p Parser) if_expr(is_comptime bool) ast.IfExpr { was_inside_if_expr := p.inside_if_expr + was_inside_ct_if_expr := p.inside_ct_if_expr defer { p.inside_if_expr = was_inside_if_expr + p.inside_ct_if_expr = was_inside_ct_if_expr } p.inside_if_expr = true - pos := p.tok.position() + pos := if is_comptime { + p.inside_ct_if_expr = true + p.next() // `$` + p.prev_tok.position().extend(p.tok.position()) + } else { + p.tok.position() + } mut branches := []ast.IfBranch{} mut has_else := false mut comments := []ast.Comment{} mut prev_guard := false for p.tok.kind in [.key_if, .key_else] { p.inside_if = true - start_pos := p.tok.position() + start_pos := if is_comptime { p.prev_tok.position().extend(p.tok.position()) } else { p.tok.position() } if p.tok.kind == .key_else { comments << p.eat_comments() p.check(.key_else) @@ -59,6 +67,7 @@ fn (mut p Parser) if_expr() ast.IfExpr { comments = [] break } + if is_comptime { p.check(.dollar) } } // `if` or `else if` p.check(.key_if) @@ -73,7 +82,7 @@ fn (mut p Parser) if_expr() ast.IfExpr { mut cond := ast.Expr{} mut is_guard := false // `if x := opt() {` - if p.peek_tok.kind == .decl_assign { + if !is_comptime && p.peek_tok.kind == .decl_assign { p.open_scope() is_guard = true var_pos := p.tok.position() @@ -102,7 +111,9 @@ fn (mut p Parser) if_expr() ast.IfExpr { // if sum is T is_is_cast := infix.op == .key_is is_ident := infix.left is ast.Ident - left_as_name = if is_is_cast && p.tok.kind == .key_as { + left_as_name = if is_comptime { + '' + } else if is_is_cast && p.tok.kind == .key_as { p.next() p.check_name() } else if is_ident { @@ -129,11 +140,20 @@ fn (mut p Parser) if_expr() ast.IfExpr { mut_name: mut_name } comments = p.eat_comments() + if is_comptime { + if p.tok.kind == .key_else { + p.error('use `\$else` instead of `else` in compile-time `if` branches') + } + if p.peek_tok.kind == .key_else { + p.check(.dollar) + } + } if p.tok.kind != .key_else { break } } return ast.IfExpr{ + is_comptime: is_comptime branches: branches post_comments: comments pos: pos diff --git a/vlib/v/parser/parser.v b/vlib/v/parser/parser.v index 1b62df5f1f..62ecf52645 100644 --- a/vlib/v/parser/parser.v +++ b/vlib/v/parser/parser.v @@ -29,6 +29,7 @@ mut: language table.Language inside_if bool inside_if_expr bool + inside_ct_if_expr bool inside_or_expr bool inside_for bool inside_fn bool @@ -462,7 +463,9 @@ pub fn (mut p Parser) top_stmt() ast.Stmt { return p.struct_decl() } .dollar { - return p.comp_if() + return ast.ExprStmt{ + expr: p.if_expr(true) + } } .hash { return p.hash() @@ -599,7 +602,9 @@ pub fn (mut p Parser) stmt(is_top_level bool) ast.Stmt { } .dollar { if p.peek_tok.kind == .key_if { - return p.comp_if() + return ast.ExprStmt{ + expr: p.if_expr(true) + } } else if p.peek_tok.kind == .key_for { return p.comp_for() } else if p.peek_tok.kind == .name { diff --git a/vlib/v/parser/pratt.v b/vlib/v/parser/pratt.v index c9249dac64..b4812722ac 100644 --- a/vlib/v/parser/pratt.v +++ b/vlib/v/parser/pratt.v @@ -47,10 +47,14 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { node = p.enum_val() } .dollar { - if p.peek_tok.kind == .name { - return p.vweb() - } else { - p.error('unexpected $') + match p.peek_tok.kind { + .name { + return p.vweb() + } .key_if { + return p.if_expr(true) + } else { + p.error('unexpected $') + } } } .chartoken { @@ -89,7 +93,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { } } .key_if { - node = p.if_expr() + node = p.if_expr(false) } .key_unsafe { p.next() @@ -191,25 +195,34 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { p.check(.rcbr) } .key_fn { - // Anonymous function - node = p.anon_fn() - // its a call - // NOTE: this could be moved to just before the pratt loop - // then anything can be a call, eg. `index[2]()` or `struct.field()` - // but this would take a bit of modification - if p.tok.kind == .lpar { - p.next() - pos := p.tok.position() - args := p.call_args() - p.check(.rpar) - node = ast.CallExpr{ - name: 'anon' - left: node - args: args - pos: pos + if p.expecting_type { + // Anonymous function type + start_pos := p.tok.position() + return ast.Type{ + typ: p.parse_type() + pos: start_pos.extend(p.prev_tok.position()) } + } else { + // Anonymous function + node = p.anon_fn() + // its a call + // NOTE: this could be moved to just before the pratt loop + // then anything can be a call, eg. `index[2]()` or `struct.field()` + // but this would take a bit of modification + if p.tok.kind == .lpar { + p.next() + pos := p.tok.position() + args := p.call_args() + p.check(.rpar) + node = ast.CallExpr{ + name: 'anon' + left: node + args: args + pos: pos + } + } + return node } - return node } else { p.error('expr(): bad token `$p.tok.kind.str()`') @@ -262,7 +275,7 @@ pub fn (mut p Parser) expr(precedence int) ast.Expr { if p.tok.kind == .key_as && p.inside_if { return node } - } else if p.tok.kind in [.inc, .dec] { + } else if p.tok.kind in [.inc, .dec] || (p.tok.kind == .question && p.inside_ct_if_expr) { // Postfix // detect `f(x++)`, `a[x++]` if p.peek_tok.kind in [.rpar, .rsbr] && @@ -294,10 +307,12 @@ fn (mut p Parser) infix_expr(left ast.Expr) ast.Expr { pos := p.tok.position() p.next() mut right := ast.Expr{} + prev_expecting_type := p.expecting_type if op in [.key_is, .not_is] { p.expecting_type = true } right = p.expr(precedence) + p.expecting_type = prev_expecting_type if p.pref.is_vet && op in [.key_in, .not_in] && right is ast.ArrayInit && (right as ast.ArrayInit).exprs.len == 1 { p.vet_error('Use `var == value` instead of `var in [value]`', pos.line_nr) diff --git a/vlib/v/pref/default.v b/vlib/v/pref/default.v index c33f82473f..82f3f56f23 100644 --- a/vlib/v/pref/default.v +++ b/vlib/v/pref/default.v @@ -59,6 +59,7 @@ pub fn (mut p Preferences) fill_with_defaults() { if p.ccompiler == '' { p.ccompiler = default_c_compiler() } + p.ccompiler_type = cc_from_string(p.ccompiler) p.is_test = p.path.ends_with('_test.v') p.is_vsh = p.path.ends_with('.vsh') p.is_script = p.is_vsh || p.path.ends_with('.v') || p.path.ends_with('.vv') diff --git a/vlib/v/pref/pref.v b/vlib/v/pref/pref.v index 45e57080e5..4e1f68f7e7 100644 --- a/vlib/v/pref/pref.v +++ b/vlib/v/pref/pref.v @@ -32,11 +32,12 @@ pub enum Backend { } pub enum CompilerType { + gcc tinyc clang mingw msvc - gcc + cplusplus } const ( @@ -81,7 +82,8 @@ pub mut: // For example, passing -cflags -Os will cause the C compiler to optimize the generated binaries for size. // You could pass several -cflags XXX arguments. They will be merged with each other. // You can also quote several options at the same time: -cflags '-Os -fno-inline-small-functions'. - ccompiler string // the name of the used C compiler + ccompiler string // the name of the C compiler used + ccompiler_type CompilerType // the type of the C compiler used third_party_option string building_v bool autofree bool @@ -376,6 +378,19 @@ pub fn backend_from_string(s string) ?Backend { } } +// Helper function to convert string names to CC enum +pub fn cc_from_string(cc_str string) CompilerType { + if cc_str.len == 0 { return .gcc } // TODO + cc := cc_str.replace('\\', '/').split('/').last().all_before('.') + if '++' in cc { return .cplusplus } + if 'tcc' in cc { return .tinyc } + if 'tinyc' in cc { return .tinyc } + if 'clang' in cc { return .clang } + if 'mingw' in cc { return .mingw } + if 'msvc' in cc { return .msvc } + return .gcc +} + fn parse_define(mut prefs Preferences, define string) { define_parts := define.split('=') if define_parts.len == 1 { diff --git a/vlib/v/token/token.v b/vlib/v/token/token.v index 3f02010f0e..4166c3bc2c 100644 --- a/vlib/v/token/token.v +++ b/vlib/v/token/token.v @@ -329,9 +329,10 @@ pub fn build_precedences() []Precedence { p[Kind.lsbr] = .index p[Kind.dot] = .call - // `++` | `--` + // `++` | `--` | `?` p[Kind.inc] = .postfix p[Kind.dec] = .postfix + p[Kind.question] = .postfix // `*` | `/` | `%` | `<<` | `>>` | `&` p[Kind.mul] = .product p[Kind.div] = .product