diff --git a/examples/compiletime/compile-time-for.v b/examples/compiletime/compile-time-for.v new file mode 100644 index 0000000000..4c0bf3e0c0 --- /dev/null +++ b/examples/compiletime/compile-time-for.v @@ -0,0 +1,45 @@ +module main + +struct App { + test string [test] +mut: + a string +} + +fn main() { + println('All functions') + $for method in App.methods { + $if method.@type is int { + println('hi') + } + println('$method.name.len') + println('$method.name.str') + println('Method: $method.name') + println('Attributes: $method.attrs') + println('Return type: $method.ret_type') + } + println('All integer functions') + $for method in App.methods { + println('Method: $method.name') + println('Attributes: $method.attrs') + } + $for field in App.fields { + $if field.@type is string { + println(field) + } + } +} + +fn (mut app App) method_one() { +} + +fn (mut app App) method_two() { +} + +fn (mut app App) method_three() int { + return 0 +} + +fn (mut app App) method_four() int { + return 1 +} diff --git a/vlib/builtin/builtin.v b/vlib/builtin/builtin.v index ece4bdc3c1..3e564862bb 100644 --- a/vlib/builtin/builtin.v +++ b/vlib/builtin/builtin.v @@ -282,3 +282,21 @@ pub: value string method string } + +pub struct FunctionData { +pub: + name string + attrs []string + ret_type string + @type int +} + +pub struct FieldData { +pub: + name string + attrs []string + typ string + is_pub bool + is_mut bool + @type int +} diff --git a/vlib/v/ast/ast.v b/vlib/v/ast/ast.v index e0a7667c18..1881e127f9 100644 --- a/vlib/v/ast/ast.v +++ b/vlib/v/ast/ast.v @@ -346,9 +346,9 @@ pub struct File { pub: path string mod Module - scope &Scope global_scope &Scope pub mut: + scope &Scope stmts []Stmt imports []Import errors []errors.Error @@ -524,23 +524,43 @@ 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 +} + pub struct CompFor { pub: val_var string stmts []Stmt + kind CompForKind pub mut: // expr Expr typ table.Type diff --git a/vlib/v/ast/str.v b/vlib/v/ast/str.v index a10fcc006b..d6bebd6575 100644 --- a/vlib/v/ast/str.v +++ b/vlib/v/ast/str.v @@ -314,3 +314,10 @@ pub fn (node Stmt) str() string { } } } + +pub fn (e CompForKind) str() string { + match e { + .methods { return 'methods' } + .fields { return 'fields' } + } +} diff --git a/vlib/v/checker/checker.v b/vlib/v/checker/checker.v index 552e62df2f..acf94e6a0c 100644 --- a/vlib/v/checker/checker.v +++ b/vlib/v/checker/checker.v @@ -1913,7 +1913,6 @@ fn (mut c Checker) stmt(node ast.Stmt) { c.stmts(node.stmts) } ast.CompIf { - // c.expr(node.cond) c.stmts(node.stmts) if node.has_else { c.stmts(node.else_stmts) diff --git a/vlib/v/fmt/fmt.v b/vlib/v/fmt/fmt.v index a3712d44f7..eb5f19cccc 100644 --- a/vlib/v/fmt/fmt.v +++ b/vlib/v/fmt/fmt.v @@ -305,11 +305,27 @@ pub fn (mut f Fmt) stmt(node ast.Stmt) { else {} } } - ast.CompFor {} + ast.CompFor { + typ := f.no_cur_mod(f.table.type_to_str(it.typ)) + f.writeln('\$for $it.val_var in ${typ}($it.kind.str()) {') + f.stmts(it.stmts) + f.writeln('}') + } ast.CompIf { inversion := if it.is_not { '!' } else { '' } is_opt := if it.is_opt { ' ?' } else { '' } - f.writeln('\$if $inversion$it.val$is_opt {') + 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 {') diff --git a/vlib/v/gen/cgen.v b/vlib/v/gen/cgen.v index f2ed4d1350..7c0e6a83e8 100644 --- a/vlib/v/gen/cgen.v +++ b/vlib/v/gen/cgen.v @@ -24,80 +24,81 @@ const ( ) struct Gen { - table &table.Table - pref &pref.Preferences - module_built string + table &table.Table + pref &pref.Preferences + module_built string mut: - out strings.Builder - cheaders strings.Builder - includes strings.Builder // all C #includes required by V modules - typedefs strings.Builder - typedefs2 strings.Builder - type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) - definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) - inits map[string]strings.Builder // contents of `void _vinit(){}` - cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` - gowrappers strings.Builder // all go callsite wrappers - stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined - auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs - comptime_defines strings.Builder // custom defines, given by -d/-define flags on the CLI - pcs_declarations strings.Builder // -prof profile counter declarations for each function - hotcode_definitions strings.Builder // -live declarations & functions - options strings.Builder // `Option_xxxx` types - json_forward_decls strings.Builder // json type forward decls - enum_typedefs strings.Builder // enum types - sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc - file ast.File - fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 - last_fn_c_name string - tmp_count int - variadic_args map[string]int - is_c_call bool // e.g. `C.printf("v")` - is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) - is_assign_rhs bool // inside right part of assign after `=` (val expr) - is_array_set bool - is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc - is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) - is_shared bool // for initialization of hidden mutex in `[rw]shared` literals - optionals []string // to avoid duplicates TODO perf, use map - shareds []int // types with hidden mutex for which decl has been emitted - inside_ternary int // ?: comma separated statements on a single line - inside_map_postfix bool // inside map++/-- postfix expr - inside_map_infix bool // inside map< fn counter name - attrs []string // attributes before next decl stmt - is_builtin_mod bool - hotcode_fn_names []string + out strings.Builder + cheaders strings.Builder + includes strings.Builder // all C #includes required by V modules + typedefs strings.Builder + typedefs2 strings.Builder + type_definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + definitions strings.Builder // typedefs, defines etc (everything that goes to the top of the file) + inits map[string]strings.Builder // contents of `void _vinit(){}` + cleanups map[string]strings.Builder // contents of `void _vcleanup(){}` + gowrappers strings.Builder // all go callsite wrappers + stringliterals strings.Builder // all string literals (they depend on tos3() beeing defined + auto_str_funcs strings.Builder // function bodies of all auto generated _str funcs + comptime_defines strings.Builder // custom defines, given by -d/-define flags on the CLI + pcs_declarations strings.Builder // -prof profile counter declarations for each function + hotcode_definitions strings.Builder // -live declarations & functions + options strings.Builder // `Option_xxxx` types + json_forward_decls strings.Builder // json type forward decls + enum_typedefs strings.Builder // enum types + sql_buf strings.Builder // for writing exprs to args via `sqlite3_bind_int()` etc + file ast.File + fn_decl &ast.FnDecl // pointer to the FnDecl we are currently inside otherwise 0 + last_fn_c_name string + tmp_count int + variadic_args map[string]int + is_c_call bool // e.g. `C.printf("v")` + is_assign_lhs bool // inside left part of assign expr (for array_set(), etc) + is_assign_rhs bool // inside right part of assign after `=` (val expr) + is_array_set bool + is_amp bool // for `&Foo{}` to merge PrefixExpr `&` and StructInit `Foo{}`; also for `&byte(0)` etc + is_sql bool // Inside `sql db{}` statement, generating sql instead of C (e.g. `and` instead of `&&` etc) + is_shared bool // for initialization of hidden mutex in `[rw]shared` literals + optionals []string // to avoid duplicates TODO perf, use map + shareds []int // types with hidden mutex for which decl has been emitted + inside_ternary int // ?: comma separated statements on a single line + inside_map_postfix bool // inside map++/-- postfix expr + inside_map_infix bool // inside map< fn counter name + attrs []string // attributes before next decl stmt + is_builtin_mod bool + hotcode_fn_names []string // cur_fn ast.FnDecl - cur_generic_type table.Type // `int`, `string`, etc in `foo()` - sql_i int - sql_stmt_name string - sql_side SqlExprSide // left or right, to distinguish idents in `name == name` - inside_vweb_tmpl bool - inside_return bool - strs_to_free string - inside_call bool - has_main bool - inside_const bool - comp_for_method string // $for method in T { + cur_generic_type table.Type // `int`, `string`, etc in `foo()` + sql_i int + sql_stmt_name string + sql_side SqlExprSide // left or right, to distinguish idents in `name == name` + inside_vweb_tmpl bool + inside_return bool + strs_to_free string + inside_call bool + has_main bool + inside_const bool + comp_for_method string // $for method in T { + comptime_var_type_map map[string]table.Type } const ( diff --git a/vlib/v/gen/comptime.v b/vlib/v/gen/comptime.v index bfcc2c8283..ff96439be5 100644 --- a/vlib/v/gen/comptime.v +++ b/vlib/v/gen/comptime.v @@ -43,7 +43,7 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) { if m.args.len > 1 { g.write(', ') } - for i in 1 .. m.args.len{ + for i in 1 .. m.args.len { if node.left is ast.Ident { left_name := node.left as ast.Ident if m.args[i].name == left_name.name { @@ -88,10 +88,39 @@ fn (mut g Gen) comptime_call(node ast.ComptimeCall) { } } -fn (mut g Gen) comp_if(it ast.CompIf) { +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) + if it.tchk_expr is ast.SelectorExpr { + se := it.tchk_expr as ast.SelectorExpr + x := se.expr.str() + comptime_var_type = g.comptime_var_type_map[ x ] + } + 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') + } + ret_type_name := g.table.get_type_symbol( comptime_var_type ).name + it_type_name := g.table.get_type_symbol(it.tchk_type).name + types_match := comptime_var_type == it.tchk_type + g.writeln('{ // \$if ${it.val} is ${it_type_name}, typecheck start, $comptime_var_type == $it.tchk_type => $ret_type_name == $it_type_name => $types_match ') + mut stmts := it.stmts + if !types_match { + stmts = []ast.Stmt{} + if it.has_else { + stmts = it.else_stmts + } + } + g.stmts(stmts) + g.writeln('} // typecheck end') + return + } ifdef := g.comp_if_to_ifdef(it.val, it.is_opt) g.empty_line = false if it.is_not { @@ -121,42 +150,83 @@ fn (mut g Gen) comp_if(it ast.CompIf) { } fn (mut g Gen) comp_for(node ast.CompFor) { - g.writeln('// 2comptime $' + 'for {') sym := g.table.get_type_symbol(g.unwrap_generic(node.typ)) - vweb_result_type := table.new_type(g.table.find_type_idx('vweb.Result')) + g.writeln('{ // 2comptime: \$for $node.val_var in ${sym.name}(${node.kind.str()}) {') + // vweb_result_type := table.new_type(g.table.find_type_idx('vweb.Result')) mut i := 0 // g.writeln('string method = tos_lit("");') - mut methods := sym.methods.filter(it.attrs.len == 0) // methods without attrs first - methods_with_attrs := sym.methods.filter(it.attrs.len > 0) // methods without attrs first - methods << methods_with_attrs - for method in methods { // sym.methods { - // if method.attrs.len == 0 { - // continue - // } - if method.return_type != vweb_result_type { // table.void_type { - continue + if node.kind == .methods { + mut methods := sym.methods.filter(it.attrs.len == 0) // methods without attrs first + methods_with_attrs := sym.methods.filter(it.attrs.len > 0) // methods without attrs first + methods << methods_with_attrs + if methods.len > 0 { + g.writeln('\tFunctionData $node.val_var;') + g.writeln('\tmemset(&${node.val_var}, 0, sizeof(FunctionData));') } - g.comp_for_method = method.name - g.writeln('\t// method $i') - if i == 0 { - g.write('\tstring ') - } - g.writeln('method = tos_lit("$method.name");') - if i == 0 { - g.write('\tarray_string ') - } - if method.attrs.len == 0 { - g.writeln('attrs = new_array_from_c_array(0, 0, sizeof(string), _MOV((string[0]){}));') - } else { - mut attrs := []string{} - for attrib in method.attrs { - attrs << 'tos_lit("$attrib")' + for method in methods { // sym.methods { + /* + if method.return_type != vweb_result_type { // table.void_type { + continue } - g.writeln('attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + attrs.join(', ') + '}));') + */ + g.comp_for_method = method.name + g.writeln('\t// method $i') + g.writeln('\t${node.val_var}.name = tos_lit("$method.name");') + if method.attrs.len == 0 { + g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') + } else { + mut attrs := []string{} + for attrib in method.attrs { + attrs << 'tos_lit("$attrib")' + } + g.writeln('\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + + attrs.join(', ') + '}));') + } + method_sym := g.table.get_type_symbol(method.return_type) + g.writeln('\t${node.val_var}.ret_type = tos_lit("${method_sym.name}");') + g.writeln('\t${node.val_var}.type = ${int(method.return_type).str()};') + // + g.comptime_var_type_map[ node.val_var ] = method.return_type + g.stmts(node.stmts) + i++ + g.writeln('') + } + g.comptime_var_type_map.delete( node.val_var ) + } else if node.kind == .fields { + // TODO add fields + if sym.info is table.Struct { + info := sym.info as table.Struct + mut fields := info.fields.filter(it.attrs.len == 0) + fields_with_attrs := info.fields.filter(it.attrs.len > 0) + fields << fields_with_attrs + if fields.len > 0 { + g.writeln('\tFieldData $node.val_var;') + g.writeln('\tmemset(&${node.val_var}, 0, sizeof(FieldData));') + } + for field in fields { + g.writeln('\t// field $i') + g.writeln('\t${node.val_var}.name = tos_lit("$field.name");') + if field.attrs.len == 0 { + g.writeln('\t${node.val_var}.attrs = __new_array_with_default(0, 0, sizeof(string), 0);') + } else { + mut attrs := []string{} + for attrib in field.attrs { + attrs << 'tos_lit("$attrib")' + } + g.writeln('\t${node.val_var}.attrs = new_array_from_c_array($attrs.len, $attrs.len, sizeof(string), _MOV((string[$attrs.len]){' + attrs.join(', ') + '}));') + } + field_sym := g.table.get_type_symbol( field.typ ) + g.writeln('\t${node.val_var}.typ = tos_lit("$field_sym.name");') + g.writeln('\t${node.val_var}.type = ${int(field.typ).str()};') + g.writeln('\t${node.val_var}.is_pub = $field.is_pub;') + g.writeln('\t${node.val_var}.is_mut = $field.is_mut;') + g.comptime_var_type_map[ node.val_var ] = field.typ + g.stmts(node.stmts) + i++ + g.writeln('') + } + g.comptime_var_type_map.delete( node.val_var ) } - g.stmts(node.stmts) - i++ - g.writeln('') } - g.writeln('// } comptime for') + g.writeln('} // } comptime for') } diff --git a/vlib/v/parser/comptime.v b/vlib/v/parser/comptime.v index 6d27dd3111..f8c931b375 100644 --- a/vlib/v/parser/comptime.v +++ b/vlib/v/parser/comptime.v @@ -22,8 +22,7 @@ fn (mut p Parser) resolve_vroot(flag string) string { vmod_file_location := mcache.get_by_folder(p.file_name_dir) if vmod_file_location.vmod_file.len == 0 { // There was no actual v.mod file found. - p.error('To use @VROOT, you need' + ' to have a "v.mod" file in $p.file_name_dir,' + - ' or in one of its parent folders.') + p.error('To use @VROOT, you need' + ' to have a "v.mod" file in $p.file_name_dir,' + ' or in one of its parent folders.') } vmod_path := vmod_file_location.vmod_folder if p.pref.is_fmt { @@ -91,11 +90,8 @@ fn (mut p Parser) vweb() ast.ComptimeCall { p.check(.rpar) // Compile vweb html template to V code, parse that V code and embed the resulting V function // that returns an html string. - fn_path := p.cur_fn_name.split('_') html_name := '${fn_path.last()}.html' - - // Looking next to the vweb program dir := os.dir(p.scanner.file_path) mut path := os.join_path(dir, fn_path.join('/')) @@ -155,26 +151,37 @@ fn (mut p Parser) vweb() ast.ComptimeCall { } fn (mut p Parser) comp_for() ast.CompFor { + // p.comp_for() handles these special forms: + // $for method in App(methods) { + // $for field in App(fields) { p.next() p.check(.key_for) val_var := p.check_name() - p.scope.register(val_var, ast.Var{ - name: val_var - typ: table.string_type - }) - p.scope.register('attrs', ast.Var{ - name: 'attrs' - typ: p.table.find_type_idx('array_string') - }) p.check(.key_in) - // expr := p.expr(0) - typ := p.parse_type() - // p.check(.dot) - // p.check_name() + lang := p.parse_language() + typ := p.parse_any_type(lang, false, false) + p.check(.dot) + for_val := p.check_name() + mut kind := ast.CompForKind.methods + if for_val == 'methods' { + p.scope.register(val_var, ast.Var{ + name: val_var + typ: p.table.find_type_idx('FunctionData') + }) + } else if for_val == 'fields' { + p.scope.register(val_var, ast.Var{ + name: val_var + typ: p.table.find_type_idx('FieldData') + }) + kind = .fields + } else { + p.error('unknown kind `$for_val`, available are: `methods` or `fields`') + } stmts := p.parse_block() return ast.CompFor{ val_var: val_var stmts: stmts + kind: kind typ: typ } } @@ -190,7 +197,33 @@ fn (mut p Parser) comp_if() ast.Stmt { if is_not { p.next() } - val := p.check_name() + // + name_pos_start := p.tok.position() + mut val := '' + mut tchk_expr := ast.Expr{} + if p.peek_tok.kind == .dot { + vname := p.parse_ident(table.Language.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 { + if tchk_expr.field_name !in ['type', '@type'] { + p.error_with_pos('only the `.@type` field name is 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 := false if val in supported_platforms { @@ -235,18 +268,36 @@ fn (mut p Parser) comp_if() ast.Stmt { 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 == .key_is { + p.next() + tchk_type = p.parse_type() + is_typecheck = true } 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 { diff --git a/vlib/v/parser/parse_type.v b/vlib/v/parser/parse_type.v index fccc7b2fc3..539c4d93e7 100644 --- a/vlib/v/parser/parse_type.v +++ b/vlib/v/parser/parse_type.v @@ -143,7 +143,7 @@ pub fn (mut p Parser) parse_type() table.Type { mut typ := table.void_type if p.tok.kind != .lcbr { pos := p.tok.position() - typ = p.parse_any_type(language, nr_muls > 0) + typ = p.parse_any_type(language, nr_muls > 0, true) if typ == table.void_type { p.error_with_pos('use `?` instead of `?void`', pos) } @@ -163,13 +163,13 @@ pub fn (mut p Parser) parse_type() table.Type { return typ } -pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr bool) table.Type { +pub fn (mut p Parser) parse_any_type(language table.Language, is_ptr, check_dot bool) table.Type { mut name := p.tok.lit if language == .c { name = 'C.$name' } else if language == .js { name = 'JS.$name' - } else if p.peek_tok.kind == .dot { + } else if p.peek_tok.kind == .dot && check_dot { // `module.Type` // /if !(p.tok.lit in p.table.imports) { if !p.known_import(name) { diff --git a/vlib/v/tests/comptime_for_test.v b/vlib/v/tests/comptime_for_test.v index 94b8c28bbe..a6e4f29113 100644 --- a/vlib/v/tests/comptime_for_test.v +++ b/vlib/v/tests/comptime_for_test.v @@ -1,4 +1,15 @@ struct App { + a string + b string +mut: + c int + d f32 +pub: + e f32 + f u64 +pub mut: + g string + h byte } ['foo/bar/three'] @@ -9,19 +20,55 @@ fn (mut app App) run() { fn (mut app App) method2() { } -fn test_comptime_for() { - /* - app := App{} - - $for method in App { //.method_attrs { - words := attrs.split('/') - println(words) - //println(method.value) - } - assert true - println('DONE') - */ - if true {} - // - else{} +fn (mut app App) int_method1() int { + return 0 +} + +fn (mut app App) int_method2() int { + return 1 +} + +fn no_lines(s string) string { return s.replace('\n', ' ') } + +fn test_comptime_for() { + println(@FN) + methods := ['run', 'method2', 'int_method1', 'int_method2'] + $for method in App.methods { + println(' method: $method.name | ' + no_lines('$method')) + assert method.name in methods + } +} + +fn test_comptime_for_with_if() { + println(@FN) + methods := ['int_method1', 'int_method2'] + $for method in App.methods { + println(' method: ' + no_lines('$method')) + $if method.@type is int { + println(method.attrs) + assert method.name in methods + } + } +} + +fn test_comptime_for_fields() { + println(@FN) + $for field in App.fields { + println(' field: $field.name | ' + no_lines('$field')) + $if field.@type is string { + assert field.name in ['a', 'b', 'g'] + } + $if field.@type is f32 { + assert field.name in ['d', 'e'] + } + if field.is_mut { + assert field.name in ['c', 'd', 'g', 'h'] + } + if field.is_pub { + assert field.name in ['e', 'f', 'g', 'h'] + } + if field.is_pub && field.is_mut { + assert field.name in ['g', 'h'] + } + } } diff --git a/vlib/vweb/vweb.v b/vlib/vweb/vweb.v index 32506861af..28178b2356 100644 --- a/vlib/vweb/vweb.v +++ b/vlib/vweb/vweb.v @@ -362,91 +362,94 @@ fn handle_conn(conn net.Socket, mut app T) { mut vars := []string{cap: route_words_a.len} mut action := '' - $for method in T { - route_words_a = [][]string{} - if attrs.len == 0 { - // No routing for this method. If it matches, call it and finish matching - // since such methods have a priority. - // For example URL `/register` matches route `/:user`, but `fn register()` - // should be called first. - if (req.method == 'GET' && url_words[0] == method && url_words.len == 1) || (req.method == 'POST' && url_words[0] + '_post' == method) { - println('easy match method=$method') - app.$method(vars) - return - } - } else { - // Get methods - // Get is default - if 'post' in attrs { - if req.method == 'POST' { - route_words_a = attrs.filter(it.to_lower() != 'post').map(it[1..].split('/')) - } - } else if 'put' in attrs { - if req.method == 'PUT' { - route_words_a = attrs.filter(it.to_lower() != 'put').map(it[1..].split('/')) - } - } else if 'patch' in attrs { - if req.method == 'PATCH' { - route_words_a = attrs.filter(it.to_lower() != 'patch').map(it[1..].split('/')) - } - } else if 'delete' in attrs { - if req.method == 'DELETE' { - route_words_a = attrs.filter(it.to_lower() != 'delete').map(it[1..].split('/')) - } - } else if 'head' in attrs { - if req.method == 'HEAD' { - route_words_a = attrs.filter(it.to_lower() != 'head').map(it[1..].split('/')) - } - } else if 'options' in attrs { - if req.method == 'OPTIONS' { - route_words_a = attrs.filter(it.to_lower() != 'options').map(it[1..].split('/')) + $for method in T.methods { + $if method.@type is Result { + attrs := method.attrs + route_words_a = [][]string{} + if attrs.len == 0 { + // No routing for this method. If it matches, call it and finish matching + // since such methods have a priority. + // For example URL `/register` matches route `/:user`, but `fn register()` + // should be called first. + if (req.method == 'GET' && url_words[0] == method.name && url_words.len == 1) || (req.method == 'POST' && url_words[0] + '_post' == method.name) { + println('easy match method=$method.name') + app.$method(vars) + return } } else { - route_words_a = attrs.filter(it.to_lower() != 'get').map(it[1..].split('/')) - } - if route_words_a.len > 0 { - for route_words in route_words_a { - if url_words.len == route_words.len || (url_words.len >= route_words.len - 1 && route_words.last().ends_with('...')) { - // match `/:user/:repo/tree` to `/vlang/v/tree` - mut matching := false - mut unknown := false - mut variables := []string{cap: route_words.len} - for i in 0..route_words.len { - if url_words.len == i { - variables << '' - matching = true - unknown = true - break - } - if url_words[i] == route_words[i] { - // no parameter - matching = true - continue - } else if route_words[i].starts_with(':') { - // is parameter - if i < route_words.len && !route_words[i].ends_with('...') { - // normal parameter - variables << url_words[i] - } else { - // array parameter only in the end - variables << url_words[i..].join('/') + // Get methods + // Get is default + if 'post' in attrs { + if req.method == 'POST' { + route_words_a = attrs.filter(it.to_lower() != 'post').map(it[1..].split('/')) + } + } else if 'put' in attrs { + if req.method == 'PUT' { + route_words_a = attrs.filter(it.to_lower() != 'put').map(it[1..].split('/')) + } + } else if 'patch' in attrs { + if req.method == 'PATCH' { + route_words_a = attrs.filter(it.to_lower() != 'patch').map(it[1..].split('/')) + } + } else if 'delete' in attrs { + if req.method == 'DELETE' { + route_words_a = attrs.filter(it.to_lower() != 'delete').map(it[1..].split('/')) + } + } else if 'head' in attrs { + if req.method == 'HEAD' { + route_words_a = attrs.filter(it.to_lower() != 'head').map(it[1..].split('/')) + } + } else if 'options' in attrs { + if req.method == 'OPTIONS' { + route_words_a = attrs.filter(it.to_lower() != 'options').map(it[1..].split('/')) + } + } else { + route_words_a = attrs.filter(it.to_lower() != 'get').map(it[1..].split('/')) + } + if route_words_a.len > 0 { + for route_words in route_words_a { + if url_words.len == route_words.len || (url_words.len >= route_words.len - 1 && route_words.last().ends_with('...')) { + // match `/:user/:repo/tree` to `/vlang/v/tree` + mut matching := false + mut unknown := false + mut variables := []string{cap: route_words.len} + for i in 0..route_words.len { + if url_words.len == i { + variables << '' + matching = true + unknown = true + break + } + if url_words[i] == route_words[i] { + // no parameter + matching = true + continue + } else if route_words[i].starts_with(':') { + // is parameter + if i < route_words.len && !route_words[i].ends_with('...') { + // normal parameter + variables << url_words[i] + } else { + // array parameter only in the end + variables << url_words[i..].join('/') + } + matching = true + unknown = true + continue + } else { + matching = false + break } - matching = true - unknown = true - continue - } else { - matching = false - break } - } - if matching && !unknown { - // absolute router words like `/test/site` - app.$method(vars) - return - } else if matching && unknown { - // router words with paramter like `/:test/site` - action = method - vars = variables + if matching && !unknown { + // absolute router words like `/test/site` + app.$method(vars) + return + } else if matching && unknown { + // router words with paramter like `/:test/site` + action = method.name + vars = variables + } } } } @@ -463,11 +466,13 @@ fn handle_conn(conn net.Socket, mut app T) { fn send_action(action string, vars []string, mut app T) { // TODO remove this function - $for method in T { - // search again for method - if action == method && attrs.len > 0 { - // call action method - app.$method(vars) + $for method in T.methods { + $if method.@type is Result { + // search again for method + if action == method.name && method.attrs.len > 0 { + // call action method + app.$method(vars) + } } } }