From 5cc81b91cbb67ba7ec16e07cdd8acfe3b6e73e69 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 14 Sep 2019 23:48:30 +0300 Subject: [PATCH] JavaSript backend (early stage) --- compiler/cc.v | 27 ++- compiler/cgen.v | 30 +-- compiler/cheaders.v | 29 +++ compiler/comptime.v | 18 +- compiler/fn.v | 54 ++--- compiler/gen_c.v | 462 ++++++++++++++++++++++++++++++++++++++ compiler/gen_js.v | 215 ++++++++++++++++++ compiler/live.v | 4 +- compiler/main.v | 81 +++++-- compiler/parser.v | 447 +++++++++++------------------------- compiler/scanner.v | 4 +- compiler/table.v | 80 +------ compiler/token.v | 2 +- vlib/builtin/array.v | 35 ++- vlib/builtin/array_test.v | 50 +++-- vlib/builtin/int.v | 2 +- vlib/builtin/js/array.v | 130 +++++++++++ vlib/builtin/js/builtin.v | 35 +++ vlib/builtin/js/int.v | 99 ++++++++ vlib/builtin/js/map.v | 70 ++++++ vlib/builtin/js/option.v | 31 +++ vlib/builtin/js/string.v | 318 ++++++++++++++++++++++++++ vlib/builtin/js/utf8.v | 27 +++ vlib/builtin/map.v | 2 +- vlib/builtin/string.v | 64 ++++-- vlib/os/os.v | 22 +- vlib/os/os_nix.v | 8 + vlib/os/os_win.v | 36 ++- vlib/strings/builder.v | 10 +- vlib/strings/similarity.v | 4 +- vlib/strings/strings.v | 6 +- 31 files changed, 1818 insertions(+), 584 deletions(-) create mode 100644 compiler/gen_c.v create mode 100644 compiler/gen_js.v create mode 100644 vlib/builtin/js/array.v create mode 100644 vlib/builtin/js/builtin.v create mode 100644 vlib/builtin/js/int.v create mode 100644 vlib/builtin/js/map.v create mode 100644 vlib/builtin/js/option.v create mode 100644 vlib/builtin/js/string.v create mode 100644 vlib/builtin/js/utf8.v diff --git a/compiler/cc.v b/compiler/cc.v index bc015c6cdc..31356cd395 100644 --- a/compiler/cc.v +++ b/compiler/cc.v @@ -12,9 +12,30 @@ import ( fn (v mut V) cc() { // build any thirdparty obj files v.build_thirdparty_obj_files() - - // Just create a c file and exit - if v.out_name.ends_with('.c') { + // Just create a C/JavaScript file and exit + if v.out_name.ends_with('.c') || v.out_name.ends_with('.js') { + // Translating V code to JS by launching vjs + $if !js { + if v.out_name.ends_with('.js') { + vexe := os.executable() + vjs_path := vexe + 'js' + dir := os.dir(vexe) + if !os.file_exists(vjs_path) { + println('V.js compiler not found, building...') + ret := os.system('$vexe -o $vjs_path -os js $dir/compiler') + if ret == 0 { + println('Done.') + } else { + println('Failed.') + exit(1) + } + } + ret := os.system('$vjs_path -o $v.out_name $v.dir') + if ret == 0 { + println('Done. Run it with `node $v.out_name`') + } + } + } os.mv(v.out_name_c, v.out_name) exit(0) } diff --git a/compiler/cgen.v b/compiler/cgen.v index 64bf2ade5e..d57423230b 100644 --- a/compiler/cgen.v +++ b/compiler/cgen.v @@ -6,7 +6,6 @@ module main import os import strings -import time struct CGen { out os.File @@ -275,6 +274,7 @@ fn os_name_to_ifdef(name string) string { case 'netbsd': return '__NetBSD__' case 'dragonfly': return '__DragonFly__' case 'msvc': return '_MSC_VER' + case 'js': return '_VJS' } cerror('bad os ifdef name "$name"') return '' @@ -295,7 +295,7 @@ fn platform_postfix_to_ifdefguard(name string) string { // C struct definitions, ordered // Sort the types, make sure types that are referenced by other types // are added before them. -fn (v mut V) c_type_definitions() string { +fn (v mut V) type_definitions() string { mut types := []Type // structs that need to be sorted mut builtin_types := []Type // builtin types // builtin types need to be on top @@ -318,32 +318,6 @@ fn (v mut V) c_type_definitions() string { types_to_c(types_sorted, v.table) } -fn types_to_c(types []Type, table &Table) string { - mut sb := strings.new_builder(10) - for t in types { - if t.cat != .union_ && t.cat != .struct_ { - continue - } - //if is_objc { - //sb.writeln('@interface $name : $objc_parent { @public') - //} - //if is_atomic { - //sb.write('_Atomic ') - //} - kind := if t.cat == .union_ {'union'} else {'struct'} - sb.writeln('$kind $t.name {') - for field in t.fields { - sb.writeln(table.cgen_name_type_pair(field.name, - field.typ) + ';') - } - sb.writeln('};\n') - //if is_objc { - //sb.writeln('@end') - //} - } - return sb.str() -} - // sort structs by dependant fields fn sort_structs(types []Type) []Type { mut dep_graph := new_dep_graph() diff --git a/compiler/cheaders.v b/compiler/cheaders.v index a7d9a1d5a6..3387878c2e 100644 --- a/compiler/cheaders.v +++ b/compiler/cheaders.v @@ -123,4 +123,33 @@ void init_consts(); ' +js_headers = ' + +class array_string {} +class array_byte {} +class array_int {} +class byte {} +class double {} +class int {} +class f64 {} +class f32 {} +class i64 {} +class i32 {} +class i16 {} +class u64 {} +class u32 {} +class u16 {} +class i8 {} +class u8 {} +class bool {} +class rune {} +class map_string {} +class map_int {} + +function init_consts() { + +} + +' + ) diff --git a/compiler/comptime.v b/compiler/comptime.v index 5239d85b91..4ed72289b9 100644 --- a/compiler/comptime.v +++ b/compiler/comptime.v @@ -175,9 +175,23 @@ fn (p mut Parser) chash() { println('v script') //p.v_script = true } + // Don't parse a non-JS V file (`#-js` flag) + else if hash == '-js' { + $if js { + for p.tok != .eof { + p.next() + } + } $else { + p.next() + } + } else { - if !p.can_chash { - p.error('bad token `#` (embedding C code is no longer supported)') + $if !js { + if !p.can_chash { + println('hash="$hash"') + println(hash.starts_with('include')) + p.error('bad token `#` (embedding C code is no longer supported)') + } } p.genln(hash) } diff --git a/compiler/fn.v b/compiler/fn.v index a036c310ab..e84f87d0c9 100644 --- a/compiler/fn.v +++ b/compiler/fn.v @@ -103,7 +103,7 @@ fn (p mut Parser) is_sig() bool { fn new_fn(mod string, is_public bool) Fn { return Fn { mod: mod - local_vars: [Var{} ; MaxLocalVars] + local_vars: [Var{}].repeat2(MaxLocalVars) is_public: is_public } } @@ -286,7 +286,7 @@ fn (p mut Parser) fn_decl() { } // Generate `User_register()` instead of `register()` // Internally it's still stored as "register" in type User - mut fn_name_cgen := p.table.cgen_name(f) + mut fn_name_cgen := p.table.fn_gen_name(f) // Start generation of the function body skip_main_in_test := f.name == 'main' && p.pref.is_test if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test { @@ -312,7 +312,7 @@ fn (p mut Parser) fn_decl() { } } else { - p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') + p.gen_fn_decl(f, typ, str_args) } } if is_fn_header { @@ -451,9 +451,9 @@ _thread_so = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&reload_so, 0, 0, 0); // Profiling mode? Start counting at the beginning of the function (save current time). if p.pref.is_prof && f.name != 'main' && f.name != 'time__ticks' { p.genln('double _PROF_START = time__ticks();//$f.name') - cgen_name := p.table.cgen_name(f) + cgen_name := p.table.fn_gen_name(f) if f.defer_text.len > f.scope_level { - f.defer_text[f.scope_level] = ' ${cgen_name}_time += time__ticks() - _PROF_START;' + f.defer_text[f.scope_level] = ' ${cgen_name}_time += time__ticks() - _PROF_START;' } } if is_generic { @@ -544,16 +544,16 @@ fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type mut did_gen_something := false for i, arg in f.args { arg_struct += '$arg.typ $arg.name ;'// Add another field (arg) to the tmp struct definition - str_args += 'arg->$arg.name' + str_args += 'arg $dot_ptr $arg.name' if i == 0 && f.is_method { - p.genln('$tmp_struct -> $arg.name = $receiver_var ;') + p.genln('$tmp_struct $dot_ptr $arg.name = $receiver_var ;') if i < f.args.len - 1 { str_args += ',' } continue } // Set the struct values (args) - p.genln('$tmp_struct -> $arg.name = ') + p.genln('$tmp_struct $dot_ptr $arg.name = ') p.expression() p.genln(';') if i < f.args.len - 1 { @@ -570,7 +570,7 @@ fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type arg_struct += '} $arg_struct_name ;' // Also register the wrapper, so we can use the original function without modifying it - fn_name = p.table.cgen_name(f) + fn_name = p.table.fn_gen_name(f) wrapper_name := '${fn_name}_thread_wrapper' wrapper_text := 'void* $wrapper_name($arg_struct_name * arg) {$fn_name( /*f*/$str_args ); }' p.cgen.register_thread_fn(wrapper_name, wrapper_text, arg_struct) @@ -595,6 +595,7 @@ fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type p.check(.rpar) } +// p.tok == fn_name fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { if !f.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.mod != p.mod { if f.name == 'contains' { @@ -610,7 +611,7 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin p.error('use `malloc()` instead of `C.malloc()`') } } - mut cgen_name := p.table.cgen_name(f) + mut cgen_name := p.table.fn_gen_name(f) p.next() mut gen_type := '' if p.tok == .lt { @@ -644,32 +645,16 @@ fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type strin // If we have a method placeholder, // we need to preappend "method(receiver, ...)" else { - mut method_call := '${cgen_name}(' receiver := f.args.first() + //println('r=$receiver.typ RT=$receiver_type') if receiver.is_mut && !p.expr_var.is_mut { - println('$method_call recv=$receiver.name recv_mut=$receiver.is_mut') + //println('$method_call recv=$receiver.name recv_mut=$receiver.is_mut') p.error('`$p.expr_var.name` is immutable, declare it with `mut`') } if !p.expr_var.is_changed { p.mark_var_changed(p.expr_var) } - // if receiver is key_mut or a ref (&), generate & for the first arg - if receiver.ref || (receiver.is_mut && !receiver_type.contains('*')) { - method_call += '& /* ? */' - } - // generate deref (TODO copy pasta later in fn_call_args) - if !receiver.is_mut && receiver_type.contains('*') { - method_call += '*' - } - mut cast := '' - // Method returns (void*) => cast it to int, string, user etc - // number := *(int*)numbers.first() - if f.typ == 'void*' { - // array_int => int - cast = receiver_type.all_after('_') - cast = '*($cast*) ' - } - p.cgen.set_placeholder(method_ph, '$cast $method_call') + p.gen_method_call(receiver_type, f.typ, cgen_name, receiver, method_ph) } // foo() p.fn_call_args(mut f) @@ -778,7 +763,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { return f } // add debug information to panic when -debug arg is passed - if p.v.pref.is_debug && f.name == 'panic' { + if p.v.pref.is_debug && f.name == 'panic' && !p.is_js { mod_name := p.mod.replace('_dot_', '.') fn_name := p.cur_fn.name.replace('${p.mod}__', '') file_path := p.file_path.replace('\\', '\\\\') // escape \ @@ -792,7 +777,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { // println('$i) arg=$arg.name') // Skip receiver, because it was already generated in the expression if i == 0 && f.is_method { - if f.args.len > 1 { + if f.args.len > 1 && !p.is_js { p.gen(',') } continue @@ -836,23 +821,27 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { p.expected_type = arg.typ typ := p.bool_expression() // Optimize `println`: replace it with `printf` to avoid extra allocations and - // function calls. `println(777)` => `printf("%d\n", 777)` + // function calls. + // `println(777)` => `printf("%d\n", 777)` // (If we don't check for void, then V will compile `println(func())`) if i == 0 && (f.name == 'println' || f.name == 'print') && typ != 'string' && typ != 'void' { T := p.table.find_type(typ) $if !windows { + $if !js { fmt := p.typ_to_fmt(typ, 0) if fmt != '' { p.cgen.resetln(p.cgen.cur_line.replace(f.name + ' (', '/*opt*/printf ("' + fmt + '\\n", ')) continue } } + } if typ.ends_with('*') { p.cgen.set_placeholder(ph, 'ptr_str(') p.gen(')') continue } // Make sure this type has a `str()` method + $if !js { if !T.has_method('str') { // Arrays have automatic `str()` methods if T.name.starts_with('array_') { @@ -879,6 +868,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn { } p.cgen.set_placeholder(ph, '${typ}_str(') p.gen(')') + } continue } got := typ diff --git a/compiler/gen_c.v b/compiler/gen_c.v new file mode 100644 index 0000000000..44bff72f8a --- /dev/null +++ b/compiler/gen_c.v @@ -0,0 +1,462 @@ +module main + +import strings + +const ( + dot_ptr = '->' +) + +// returns the type of the new variable +fn (p mut Parser) gen_var_decl(name string, is_static bool) string { + // Generate expression to tmp because we need its type first + // `[typ] [name] = bool_expression();` + pos := p.cgen.add_placeholder() + mut typ := p.bool_expression() + //p.gen('/*after expr*/') + // Option check ? or { + or_else := p.tok == .key_orelse + tmp := p.get_tmp() + if or_else { + // Option_User tmp = get_user(1); + // if (!tmp.ok) { or_statement } + // User user = *(User*)tmp.data; + // p.assigned_var = '' + p.cgen.set_placeholder(pos, '$typ $tmp = ') + p.genln(';') + typ = typ.replace('Option_', '') + p.next() + p.check(.lcbr) + p.genln('if (!$tmp .ok) {') + p.register_var(Var { + name: 'err' + typ: 'string' + is_mut: false + is_used: true + }) + p.genln('string err = $tmp . error;') + p.statements() + p.genln('$typ $name = *($typ*) $tmp . data;') + if !p.returns && p.prev_tok2 != .key_continue && p.prev_tok2 != .key_break { + p.error('`or` block must return/continue/break/panic') + } + p.returns = false + return typ + } + gen_name := p.table.var_cgen_name(name) + mut nt_gen := p.table.cgen_name_type_pair(gen_name, typ) + // `foo := C.Foo{}` => `Foo foo;` + if !p.is_empty_c_struct_init && !typ.starts_with('['){ + nt_gen += '=' + } + if is_static { + nt_gen = 'static $nt_gen' + } + p.cgen.set_placeholder(pos, nt_gen) + return typ +} + +fn (p mut Parser) gen_fn_decl(f Fn, typ, str_args string) { + dll_export_linkage := if p.os == .msvc && p.attr == 'live' && p.pref.is_so { + '__declspec(dllexport) ' + } else if p.attr == 'inline' { + 'static inline ' + } else { + '' + } + fn_name_cgen := p.table.fn_gen_name(f) + //str_args := f.str_args(p.table) + p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') +} + +fn types_to_c(types []Type, table &Table) string { + mut sb := strings.new_builder(10) + for t in types { + if t.cat != .union_ && t.cat != .struct_ { + continue + } + //if is_objc { + //sb.writeln('@interface $name : $objc_parent { @public') + //} + //if is_atomic { + //sb.write('_Atomic ') + //} + kind := if t.cat == .union_ {'union'} else {'struct'} + sb.writeln('$kind $t.name {') + for field in t.fields { + sb.write('\t') + sb.writeln(table.cgen_name_type_pair(field.name, + field.typ) + ';') + } + sb.writeln('};\n') + //if is_objc { + //sb.writeln('@end') + //} + } + return sb.str() +} + +fn (p mut Parser) index_get(typ string, fn_ph int, cfg IndexCfg) { + // Erase var name we generated earlier: "int a = m, 0" + // "m, 0" gets killed since we need to start from scratch. It's messy. + // "m, 0" is an index expression, save it before deleting and insert later in map_get() + mut index_expr := '' + if p.cgen.is_tmp { + index_expr = p.cgen.tmp_line.right(fn_ph) + p.cgen.resetln(p.cgen.tmp_line.left(fn_ph)) + } else { + index_expr = p.cgen.cur_line.right(fn_ph) + p.cgen.resetln(p.cgen.cur_line.left(fn_ph)) + } + // Can't pass integer literal, because map_get() requires a void* + tmp := p.get_tmp() + tmp_ok := p.get_tmp() + if cfg.is_map { + p.gen('$tmp') + def := type_default(typ) + p.cgen.insert_before('$typ $tmp = $def; bool $tmp_ok = map_get($index_expr, & $tmp);') + } + else if cfg.is_arr { + if p.pref.translated && !p.builtin_mod { + p.gen('$index_expr ]') + } + else { + if cfg.is_ptr { + p.gen('( *($typ*) array__get(* $index_expr) )') + } else { + p.gen('( *($typ*) array__get($index_expr) )') + } + } + } + else if cfg.is_str && !p.builtin_mod { + p.gen('string_at($index_expr)') + } + // Zero the string after map_get() if it's nil, numbers are automatically 0 + // This is ugly, but what can I do without generics? + // TODO what about user types? + if cfg.is_map && typ == 'string' { + // p.cgen.insert_before('if (!${tmp}.str) $tmp = tos("", 0);') + p.cgen.insert_before('if (!$tmp_ok) $tmp = tos((byte *)"", 0);') + } + +} + +fn (table mut Table) fn_gen_name(f &Fn) string { + mut name := f.name + if f.is_method { + name = '${f.receiver_typ}_$f.name' + name = name.replace(' ', '') + name = name.replace('*', '') + name = name.replace('+', 'plus') + name = name.replace('-', 'minus') + } + // Avoid name conflicts (with things like abs(), print() etc). + // Generate b_abs(), b_print() + // TODO duplicate functionality + if f.mod == 'builtin' && f.name in CReserved { + return 'v_$name' + } + // Obfuscate but skip certain names + // TODO ugly, fix + if table.obfuscate && f.name != 'main' && f.name != 'WinMain' && f.mod != 'builtin' && !f.is_c && + f.mod != 'darwin' && f.mod != 'os' && !f.name.contains('window_proc') && f.name != 'gg__vec2' && + f.name != 'build_token_str' && f.name != 'build_keys' && f.mod != 'json' && + !name.ends_with('_str') && !name.contains('contains') { + mut idx := table.obf_ids[name] + // No such function yet, register it + if idx == 0 { + table.fn_cnt++ + table.obf_ids[name] = table.fn_cnt + idx = table.fn_cnt + } + old := name + name = 'f_$idx' + println('$old ==> $name') + } + return name +} + +fn (p mut Parser) gen_method_call(receiver_type, ftyp string, cgen_name string, receiver Var,method_ph int) { + //mut cgen_name := p.table.fn_gen_name(f) + mut method_call := cgen_name + '(' + // if receiver is key_mut or a ref (&), generate & for the first arg + if receiver.ref || (receiver.is_mut && !receiver_type.contains('*')) { + method_call += '& /* ? */' + } + // generate deref (TODO copy pasta later in fn_call_args) + if !receiver.is_mut && receiver_type.contains('*') { + method_call += '*' + } + mut cast := '' + // Method returns (void*) => cast it to int, string, user etc + // number := *(int*)numbers.first() + if ftyp == 'void*' { + // array_int => int + cast = receiver_type.all_after('_') + cast = '*($cast*) ' + } + p.cgen.set_placeholder(method_ph, '$cast $method_call') + //return method_call +} + +fn (p mut Parser) gen_array_at(typ_ string, is_arr0 bool, fn_ph int) { + mut typ := typ_ + //p.fgen('[') + // array_int a; a[0] + // type is "array_int", need "int" + // typ = typ.replace('array_', '') + if is_arr0 { + typ = typ.right(6) + } + // array a; a.first() voidptr + // type is "array", need "void*" + if typ == 'array' { + typ = 'void*' + } + // No bounds check in translated from C code + if p.pref.translated && !p.builtin_mod { + // Cast void* to typ*: add (typ*) to the beginning of the assignment : + // ((int*)a.data = ... + p.cgen.set_placeholder(fn_ph, '(($typ*)(') + p.gen('.data))[') + } + else { + p.gen(',') + } +} + +fn (p mut Parser) gen_for_header(i, tmp, var_typ, val string) { + p.genln('for (int $i = 0; $i < ${tmp}.len; $i++) {') + p.genln('$var_typ $val = (($var_typ *) $tmp . data)[$i];') +} + +fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) { + p.genln('array_byte bytes_$tmp = string_bytes( $tmp );') + p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {') + p.genln('$var_typ $val = (($var_typ *) bytes_$tmp . data)[$i];') +} + +fn (p mut Parser) gen_for_map_header(i, tmp, var_typ, val, typ string) { + def := type_default(typ) + p.genln('array_string keys_$tmp = map_keys(& $tmp ); ') + p.genln('for (int l = 0; l < keys_$tmp .len; l++) {') + p.genln('string $i = ((string*)keys_$tmp .data)[l];') + // TODO don't call map_get() for each key, fetch values while traversing + // the tree (replace `map_keys()` above with `map_key_vals()`) + p.genln('$var_typ $val = $def; map_get($tmp, $i, & $val);') +} + +fn (p mut Parser) gen_array_init(typ string, no_alloc bool, new_arr_ph int, nr_elems int) { + mut new_arr := 'new_array_from_c_array' + if no_alloc { + new_arr += '_no_alloc' + } + if nr_elems == 0 && p.pref.ccompiler != 'tcc' { + p.gen(' 0 })') + } else { + p.gen(' })') + } + // Need to do this in the second pass, otherwise it goes to the very top of the out.c file + if !p.first_pass() { + // Due to a tcc bug, the length needs to be specified. + // GCC crashes if it is. + cast := if p.pref.ccompiler == 'tcc' { '($typ[$nr_elems])' } else { '($typ[])' } + p.cgen.set_placeholder(new_arr_ph, + '$new_arr($nr_elems, $nr_elems, sizeof($typ), $cast { ') + } +} + +fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool,fn_ph, assign_pos int, is_cao bool) { + // `a[0] = 7` + // curline right now: `a , 0 = 7` + mut val := p.cgen.cur_line.right(assign_pos) + p.cgen.resetln(p.cgen.cur_line.left(assign_pos)) + mut cao_tmp := p.cgen.cur_line + mut func := '' + if is_map { + func = 'map__set(&' + // CAO on map is a bit more complicated as it loads + // the value inside a pointer instead of returning it. + } + else { + if is_ptr { + func = 'array_set(' + if is_cao { + cao_tmp = '*($p.expected_type *) array__get(*$cao_tmp)' + } + } + else { + func = 'array_set(&/*q*/' + if is_cao { + cao_tmp = '*($p.expected_type *) array__get($cao_tmp)' + } + } + } + p.cgen.set_placeholder(fn_ph, func) + if is_cao { + val = cao_tmp + val.all_before('=') + val.all_after('=') + } + p.gen(', & ($typ []) { $val })') +} + + +// returns true in case of an early return +fn (p mut Parser) gen_struct_init(typ string, t Type) bool { + // TODO hack. If it's a C type, we may need to add "struct" before declaration: + // a := &C.A{} ==> struct A* a = malloc(sizeof(struct A)); + if p.is_c_struct_init { + if t.cat != .c_typedef { + p.cgen.insert_before('struct /*c struct init*/') + } + } + // TODO tm struct struct bug + if typ == 'tm' { + p.cgen.lines[p.cgen.lines.len-1] = '' + } + p.next() + p.check(.lcbr) + ptr := typ.contains('*') + // `user := User{foo:bar}` => `User user = (User){ .foo = bar}` + if !ptr { + if p.is_c_struct_init { + // `face := C.FT_Face{}` => `FT_Face face;` + if p.tok == .rcbr { + p.is_empty_c_struct_init = true + p.check(.rcbr) + return true + } + p.gen('(struct $typ) {') + p.is_c_struct_init = false + } + else { + p.gen('($typ) {') + } + } + else { + // TODO tmp hack for 0 pointers init + // &User{!} ==> 0 + if p.tok == .not { + p.next() + p.gen('0') + p.check(.rcbr) + return true + } + p.gen('($t.name*)memdup(&($t.name) {') + } + return false +} + +fn (p mut Parser) gen_struct_field_init(field string) { + p.gen('.$field = ') +} + +fn (p mut Parser) gen_empty_map(typ string) { + p.gen('new_map(1, sizeof($typ))') +} + +fn (p mut Parser) cast(typ string) { + p.next() + pos := p.cgen.add_placeholder() + if p.tok == .rpar { + // skip `)` if it's `(*int)(ptr)`, not `int(a)` + p.ptr_cast = true + p.next() + } + p.check(.lpar) + p.expected_type = typ + expr_typ := p.bool_expression() + // `face := FT_Face(cobj)` => `FT_Face face = *((FT_Face*)cobj);` + casting_voidptr_to_value := expr_typ == 'void*' && typ != 'int' && + typ != 'byteptr' && !typ.ends_with('*') + p.expected_type = '' + // `string(buffer)` => `tos2(buffer)` + // `string(buffer, len)` => `tos(buffer, len)` + // `string(bytes_array, len)` => `tos(bytes_array.data, len)` + is_byteptr := expr_typ == 'byte*' || expr_typ == 'byteptr' + is_bytearr := expr_typ == 'array_byte' + if typ == 'string' { + if is_byteptr || is_bytearr { + if p.tok == .comma { + p.check(.comma) + p.cgen.set_placeholder(pos, 'tos((byte *)') + if is_bytearr { + p.gen('.data') + } + p.gen(', ') + p.check_types(p.expression(), 'int') + } else { + if is_bytearr { + p.gen('.data') + } + p.cgen.set_placeholder(pos, 'tos2((byte *)') + } + } + // `string(234)` => error + else if expr_typ == 'int' { + p.error('cannot cast `$expr_typ` to `$typ`, use `str()` method instead') + } + else { + p.error('cannot cast `$expr_typ` to `$typ`') + } + } + else if typ == 'byte' && expr_typ == 'string' { + p.error('cannot cast `$expr_typ` to `$typ`, use backquotes `` to create a `$typ` or access the value of an index of `$expr_typ` using []') + } + else if casting_voidptr_to_value { + p.cgen.set_placeholder(pos, '*($typ*)(') + } + else { + p.cgen.set_placeholder(pos, '($typ)(') + } + p.check(.rpar) + p.gen(')') +} + +fn type_default(typ string) string { + if typ.starts_with('array_') { + return 'new_array(0, 1, sizeof( ${typ.right(6)} ))' + } + // Always set pointers to 0 + if typ.ends_with('*') { + return '0' + } + // User struct defined in another module. + if typ.contains('__') { + return '{0}' + } + // Default values for other types are not needed because of mandatory initialization + switch typ { + case 'bool': return '0' + case 'string': return 'tos((byte *)"", 0)' + case 'i8': return '0' + case 'i16': return '0' + case 'i64': return '0' + case 'u16': return '0' + case 'u32': return '0' + case 'u64': return '0' + case 'byte': return '0' + case 'int': return '0' + case 'rune': return '0' + case 'f32': return '0.0' + case 'f64': return '0.0' + case 'byteptr': return '0' + case 'voidptr': return '0' + } + return '{0}' +} + +fn (p mut Parser) gen_array_push(ph int, typ, expr_type, tmp, tmp_typ string) { + push_array := typ == expr_type + if push_array { + p.cgen.set_placeholder(ph, '_PUSH_MANY(&' ) + p.gen('), $tmp, $typ)') + } else { + p.check_types(expr_type, tmp_typ) + // Pass tmp var info to the _PUSH macro + // Prepend tmp initialisation and push call + // Don't dereference if it's already a mutable array argument (`fn foo(mut []int)`) + push_call := if typ.contains('*'){'_PUSH('} else { '_PUSH(&'} + p.cgen.set_placeholder(ph, push_call) + p.gen('), $tmp, $tmp_typ)') + } +} + diff --git a/compiler/gen_js.v b/compiler/gen_js.v new file mode 100644 index 0000000000..51ec16b9a7 --- /dev/null +++ b/compiler/gen_js.v @@ -0,0 +1,215 @@ +module main + +import strings + +const ( + dot_ptr = '.' +) + +fn (p mut Parser) gen_var_decl(name string, is_static bool) string { + p.gen('var $name /* typ */ = ') + typ := p.bool_expression() + or_else := p.tok == .key_orelse + //tmp := p.get_tmp() + if or_else { + //panic('optionals todo') + } + return typ +} + +fn (p mut Parser) gen_fn_decl(f Fn, typ, _str_args string) { + mut str_args := '' + for i, arg in f.args { + str_args += arg.name + ' /* $arg.typ */ ' + if i < f.args.len - 1 { + str_args += ', ' + } + } + name := p.table.fn_gen_name(f) + if f.is_method { + p.genln('\n${f.receiver_typ}.prototype.${name} = function($str_args)/* $typ */ {') + } else { + p.genln('\nfunction $name($str_args) /* $typ */ {') + } +} + +fn types_to_c(types []Type, table &Table) string { + println('js typ to code ') + mut sb := strings.new_builder(10) + for t in types { + if t.cat != .union_ && t.cat != .struct_ { + continue + } + sb.writeln('class $t.name {') + for field in t.fields { + sb.write('\t') + sb.write(field.name) + sb.writeln('; // $field.typ') + } + sb.writeln('}\n') + } + return sb.str() +} + +fn (p mut Parser) index_get(typ string, fn_ph int, cfg IndexCfg) { + p.cgen.cur_line = p.cgen.cur_line.replace(',', '[') + ']' +} + +fn (table mut Table) fn_gen_name(f &Fn) string { + mut name := f.name + if f.is_method { + name = name.replace(' ', '') + name = name.replace('*', '') + name = name.replace('+', 'plus') + name = name.replace('-', 'minus') + return name + } + // Avoid name conflicts (with things like abs(), print() etc). + // Generate b_abs(), b_print() + // TODO duplicate functionality + if f.mod == 'builtin' && f.name in CReserved { + return 'v_$name' + } + return name +} + +fn (p mut Parser) gen_method_call(receiver_type, ftyp string, cgen_name string, receiver Var,method_ph int) { + //mut cgen_name := p.table.fn_gen_name(f) + //mut method_call := cgen_name + '(' + p.gen('.' + cgen_name.all_after('_') + '(') + //p.cgen.set_placeholder(method_ph, '$cast kKE $method_call') + //return method_call +} + + +fn (p mut Parser) gen_array_at(typ string, is_arr0 bool, fn_ph int) { + p.gen('[') +} + +fn (p mut Parser) gen_for_header(i, tmp, var_typ, val string) { + p.genln('for (var $i = 0; $i < ${tmp}.length; $i++) {') + p.genln('var $val = $tmp [$i];') +} + +fn (p mut Parser) gen_for_str_header(i, tmp, var_typ, val string) { + p.genln('for (var $i = 0; $i < $tmp .length; $i ++) {') + p.genln('var $val = $tmp[$i];') +} + +fn (p mut Parser) gen_for_map_header(i, tmp, var_typ, val, typ string) { + p.genln('for (var $i in $tmp) {') + p.genln('var $val = $tmp[$i];') +} + +fn (p mut Parser) gen_array_init(typ string, no_alloc bool, new_arr_ph int, nr_elems int) { + p.cgen.set_placeholder(new_arr_ph, '[') + p.gen(']') +} + +fn (p mut Parser) gen_array_set(typ string, is_ptr, is_map bool,fn_ph, assign_pos int, is_cao bool) { + mut val := p.cgen.cur_line.right(assign_pos) + p.cgen.resetln(p.cgen.cur_line.left(assign_pos)) + p.gen('] =') + cao_tmp := p.cgen.cur_line + if is_cao { + val = cao_tmp + val.all_before('=') + val.all_after('=') + } + p.gen(val) +} + +// returns true in case of an early return +fn (p mut Parser) gen_struct_init(typ string, t Type) bool { + p.next() + p.check(.lcbr) + ptr := typ.contains('*') + if !ptr { + p.gen('{') + } + else { + // TODO tmp hack for 0 pointers init + // &User{!} ==> 0 + if p.tok == .not { + p.next() + p.gen('}') + p.check(.rcbr) + return true + } + } + return false +} + +fn (p mut Parser) gen_struct_field_init(field string) { + p.gen('$field : ') +} + +fn (p mut Parser) gen_empty_map(typ string) { + p.gen('{}') +} + +fn (p mut Parser) cast(typ string) string { + p.next() + pos := p.cgen.add_placeholder() + if p.tok == .rpar { + p.next() + } + p.check(.lpar) + p.bool_expression() + if typ == 'string' { + if p.tok == .comma { + p.check(.comma) + p.cgen.set_placeholder(pos, 'tos(') + //p.gen('tos(') + p.gen(', ') + p.expression() + p.gen(')') + } + } + p.check(.rpar) + return typ +} + +fn type_default(typ string) string { + if typ.starts_with('array_') { + return '[]' + } + // Always set pointers to 0 + if typ.ends_with('*') { + return '0' + } + // User struct defined in another module. + if typ.contains('__') { + return '{}' + } + // Default values for other types are not needed because of mandatory initialization + switch typ { + case 'bool': return '0' + case 'string': return '""' + case 'i8': return '0' + case 'i16': return '0' + case 'i64': return '0' + case 'u16': return '0' + case 'u32': return '0' + case 'u64': return '0' + case 'byte': return '0' + case 'int': return '0' + case 'rune': return '0' + case 'f32': return '0.0' + case 'f64': return '0.0' + case 'byteptr': return '0' + case 'voidptr': return '0' + } + return '{}' +} + +fn (p mut Parser) gen_array_push(ph int, typ, expr_type, tmp, tmp_typ string) { + push_array := typ == expr_type + if push_array { + p.cgen.set_placeholder(ph, 'push(&' ) + p.gen('), $tmp, $typ)') + } else { + p.check_types(expr_type, tmp_typ) + p.gen(')') + p.cgen.cur_line = p.cgen.cur_line.replace(',', '.push') + } +} + diff --git a/compiler/live.v b/compiler/live.v index 220b114ec7..9fd0f3edd8 100644 --- a/compiler/live.v +++ b/compiler/live.v @@ -35,9 +35,9 @@ fn (v mut V) generate_hotcode_reloading_declarations() { } } -fn (v mut V) generate_hotcode_reloading_code() { +fn (v mut V) generate_hot_reload_code() { mut cgen := v.cgen - + // Hot code reloading if v.pref.is_live { file := v.dir diff --git a/compiler/main.v b/compiler/main.v index 6ff39267b7..2318b31977 100644 --- a/compiler/main.v +++ b/compiler/main.v @@ -27,7 +27,8 @@ enum BuildMode { } const ( - SupportedPlatforms = ['windows', 'mac', 'linux', 'freebsd', 'openbsd', 'netbsd', 'dragonfly', 'msvc'] + SupportedPlatforms = ['windows', 'mac', 'linux', 'freebsd', 'openbsd', + 'netbsd', 'dragonfly', 'msvc', 'js'] ModPath = os.home_dir() + '/.vmodules/' ) @@ -40,6 +41,7 @@ enum OS { netbsd dragonfly msvc + js } enum Pass { @@ -208,7 +210,7 @@ fn (v mut V) compile() { println(v.files) } v.add_v_files_to_compile() - if v.pref.is_verbose { + if v.pref.is_verbose || v.pref.is_debug { println('all .v files:') println(v.files) } @@ -220,7 +222,14 @@ fn (v mut V) compile() { // Main pass cgen.pass = Pass.main if v.pref.is_debug { - cgen.genln('#define VDEBUG (1) ') + $if js { + cgen.genln('const VDEBUG = 1;\n') + } $else { + cgen.genln('#define VDEBUG (1)') + } + } + if v.os == .js { + cgen.genln('#define _VJS (1) ') } if v.pref.building_v { @@ -228,9 +237,13 @@ fn (v mut V) compile() { cgen.genln('#define V_COMMIT_HASH "' + vhash() + '"') cgen.genln('#endif') } - - cgen.genln(CommonCHeaders) + $if js { + cgen.genln(js_headers) + } $else { + cgen.genln(CommonCHeaders) + } + v.generate_hotcode_reloading_declarations() imports_json := 'json' in v.table.imports @@ -251,7 +264,9 @@ fn (v mut V) compile() { // `/usr/bin/ld: multiple definition of 'total_m'` // TODO //cgen.genln('i64 total_m = 0; // For counting total RAM allocated') - cgen.genln('int g_test_ok = 1; ') + if v.pref.is_test { + cgen.genln('int g_test_ok = 1; ') + } if 'json' in v.table.imports { cgen.genln(' #define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key)) @@ -261,7 +276,7 @@ fn (v mut V) compile() { if '-debug_alloc' in os.args { cgen.genln('#define DEBUG_ALLOC 1') } - cgen.genln('/*================================== FNS =================================*/') + //cgen.genln('/*================================== FNS =================================*/') cgen.genln('this line will be replaced with definitions') defs_pos := cgen.lines.len - 1 for file in v.files { @@ -276,12 +291,16 @@ fn (v mut V) compile() { v.log('Done parsing.') // Write everything mut d := strings.new_builder(10000)// Avoid unnecessary allocations - d.writeln(cgen.includes.join_lines()) - d.writeln(cgen.typedefs.join_lines()) - d.writeln(v.c_type_definitions()) - d.writeln('\nstring _STR(const char*, ...);\n') - d.writeln('\nstring _STR_TMP(const char*, ...);\n') - d.writeln(cgen.fns.join_lines()) + $if !js { + d.writeln(cgen.includes.join_lines()) + d.writeln(cgen.typedefs.join_lines()) + d.writeln(v.type_definitions()) + d.writeln('\nstring _STR(const char*, ...);\n') + d.writeln('\nstring _STR_TMP(const char*, ...);\n') + d.writeln(cgen.fns.join_lines()) // fn definitions + } $else { + d.writeln(v.type_definitions()) + } d.writeln(cgen.consts.join_lines()) d.writeln(cgen.thread_args.join_lines()) if v.pref.is_prof { @@ -290,23 +309,24 @@ fn (v mut V) compile() { } dd := d.str() cgen.lines[defs_pos] = dd// TODO `def.str()` doesn't compile - - v.generate_main() - - v.generate_hotcode_reloading_code() - - cgen.save() + v.generate_main() + v.generate_hot_reload_code() if v.pref.is_verbose { v.log('flags=') for flag in v.get_os_cflags() { println(' * ' + flag.format()) } } + $if js { + cgen.genln('main();') + } + cgen.save() v.cc() } fn (v mut V) generate_main() { mut cgen := v.cgen + $if js { return } // if v.build_mode in [.default, .embed_vlib] { if v.pref.build_mode == .default_mode || v.pref.build_mode == .embed_vlib { @@ -461,9 +481,18 @@ fn (v &V) v_files_from_dir(dir string) []string { if file.ends_with('_mac.v') && v.os != .mac { continue } + if file.ends_with('_js.v') { + continue + } if file.ends_with('_nix.v') && (v.os == .windows || v.os == .msvc) { continue } + if file.ends_with('_js.v') && v.os != .js { + continue + } + if file.ends_with('_c.v') && v.os == .js { + continue + } res << '$dir/$file' } return res @@ -491,7 +520,7 @@ fn (v mut V) add_v_files_to_compile() { dir = dir.all_before('/') } else { - // Add .v files from the directory being compied + // Add .v files from the directory being compiled files := v.v_files_from_dir(dir) for file in files { user_files << file @@ -579,6 +608,7 @@ fn (v mut V) add_v_files_to_compile() { module_path = '$ModPath/vlib/$mod_p' } */ + if mod == 'builtin' { continue } // builtin files were already added vfiles := v.v_files_from_dir(mod_path) for file in vfiles { if !(file in v.files) { @@ -666,7 +696,7 @@ fn new_v(args[]string) &V { //if args.contains('-lib') { if joined_args.contains('build module ') { build_mode = .build_module - // v -lib ~/v/os => os.o + // v build module ~/v/os => os.o //mod = os.dir(dir) mod = if dir.contains(os.PathSeparator) { dir.all_after(os.PathSeparator) @@ -741,8 +771,11 @@ fn new_v(args[]string) &V { case 'netbsd': _os = .netbsd case 'dragonfly': _os = .dragonfly case 'msvc': _os = .msvc + case 'js': _os = .js } } + //println('OS=$_os') + builtin := 'builtin.v' builtins := [ 'array.v', 'string.v', @@ -752,6 +785,7 @@ fn new_v(args[]string) &V { 'map.v', 'option.v', ] + //println(builtins) // Location of all vlib files vroot := os.dir(os.executable()) //println('VROOT=$vroot') @@ -768,13 +802,16 @@ fn new_v(args[]string) &V { //if !out_name.contains('builtin.o') { for builtin in builtins { mut f := '$vroot/vlib/builtin/$builtin' + __ := 1 + $if js { + f = '$vroot/vlib/builtin/js/$builtin' + } // In default mode we use precompiled vlib.o, point to .vh files with signatures if build_mode == .default_mode || build_mode == .build_module { //f = '$TmpPath/vlib/builtin/${builtin}h' } files << f } - //} mut cflags := '' for ci, cv in args { diff --git a/compiler/parser.v b/compiler/parser.v index 0c5e9da68f..2ce8807c8d 100644 --- a/compiler/parser.v +++ b/compiler/parser.v @@ -65,6 +65,7 @@ mut: cur_gen_type string // "App" to replace "T" in current generic function is_vweb bool is_sql bool + is_js bool sql_i int // $1 $2 $3 sql_params []string // ("select * from users where id = $1", ***"100"***) sql_types []string // int, string and so on; see sql_params @@ -108,6 +109,9 @@ fn (v mut V) new_parser(path string) Parser { vroot: v.vroot } + $if js { + p.is_js = true + } if p.pref.is_repl { p.scanner.should_print_line_on_error = false @@ -1245,7 +1249,7 @@ fn ($v.name mut $v.typ) $p.cur_fn.name (...) { p.gen(' = ') } case Token.plus_assign: - if is_str { + if is_str && !p.is_js { p.gen('= string_add($v.name, ')// TODO can't do `foo.bar += '!'` } else { @@ -1275,7 +1279,7 @@ fn ($v.name mut $v.typ) $p.cur_fn.name (...) { p.scanner.line_nr-- p.error('cannot use type `$expr_type` as type `$p.assigned_type` in assignment') } - if is_str && tok == .plus_assign { + if is_str && tok == .plus_assign && !p.is_js { p.gen(')') } // p.assigned_var = '' @@ -1310,38 +1314,7 @@ fn (p mut Parser) var_decl() { p.error('variable names cannot contain uppercase letters, use snake_case instead') } p.check_space(.decl_assign) // := - // Generate expression to tmp because we need its type first - // [TYP .name =] bool_expression() - pos := p.cgen.add_placeholder() - mut typ := p.bool_expression() - // Option check ? or { - or_else := p.tok == .key_orelse - tmp := p.get_tmp() - if or_else { - // Option_User tmp = get_user(1); - // if (!tmp.ok) { or_statement } - // User user = *(User*)tmp.data; - // p.assigned_var = '' - p.cgen.set_placeholder(pos, '$typ $tmp = ') - p.genln(';') - typ = typ.replace('Option_', '') - p.next() - p.check(.lcbr) - p.genln('if (!$tmp .ok) {') - p.register_var(Var { - name: 'err' - typ: 'string' - is_mut: false - is_used: true - }) - p.genln('string err = $tmp . error;') - p.statements() - p.genln('$typ $name = *($typ*) $tmp . data;') - if !p.returns && p.prev_tok2 != .key_continue && p.prev_tok2 != .key_break { - p.error('`or` block must return/continue/break/panic') - } - p.returns = false - } + typ := p.gen_var_decl(name, is_static) p.register_var(Var { name: name typ: typ @@ -1349,18 +1322,6 @@ fn (p mut Parser) var_decl() { is_alloc: p.is_alloc }) //if p.is_alloc { println('REG VAR IS ALLOC $name') } - if !or_else { - gen_name := p.table.var_cgen_name(name) - mut nt_gen := p.table.cgen_name_type_pair(gen_name, typ) - // `foo := C.Foo{}` => `Foo foo;` - if !p.is_empty_c_struct_init { - nt_gen += '=' - } - if is_static { - nt_gen = 'static $nt_gen' - } - p.cgen.set_placeholder(pos, nt_gen) - } p.var_decl_name = '' p.is_empty_c_struct_init = false } @@ -1415,7 +1376,7 @@ fn (p mut Parser) bterm() string { // if tok in [ .eq, .gt, .lt, .le, .ge, .ne] { if tok == .eq || tok == .gt || tok == .lt || tok == .le || tok == .ge || tok == .ne { p.fgen(' ${p.tok.str()} ') - if is_str { + if is_str && !p.is_js { p.gen(',') } else if p.is_sql && tok == .eq { @@ -1439,7 +1400,7 @@ fn (p mut Parser) bterm() string { p.check_types(p.expression(), typ) } typ = 'bool' - if is_str { //&& !p.is_sql { + if is_str && !p.is_js { //&& !p.is_sql { p.gen(')') switch tok { case Token.eq: p.cgen.set_placeholder(ph, 'string_eq(') @@ -1575,7 +1536,8 @@ fn (p mut Parser) name_expr() string { name += '*' } p.gen('(') - mut typ := p.cast(name) + mut typ := name + p.cast(name) p.gen(')') for p.tok == .dot { typ = p.dot(typ, ph) @@ -1604,7 +1566,8 @@ fn (p mut Parser) name_expr() string { if name == 'T' { name = p.cur_gen_type } - return p.struct_init(name, is_c_struct_init) + p.is_c_struct_init = is_c_struct_init + return p.struct_init(name) } } if is_c { @@ -1698,7 +1661,7 @@ fn (p mut Parser) name_expr() string { func: f } p.table.register_type2(fn_typ) - p.gen(p.table.cgen_name(f)) + p.gen(p.table.fn_gen_name(f)) p.next() return f.typ_str() //'void*' } @@ -1858,7 +1821,7 @@ fn (p mut Parser) dot(str_typ string, method_ph int) string { } mut dot := '.' if str_typ.ends_with('*') || str_typ == 'FT_Face' { // TODO fix C ptr typedefs - dot = '->' + dot = dot_ptr } // field if has_field { @@ -1917,6 +1880,27 @@ struct $f.parent_fn { return method.typ } +enum IndexType { + none + str + map + array + array0 + fixed_array + ptr +} + +fn get_index_type(typ string) IndexType { + if typ.starts_with('map_') { return IndexType.map } + if typ == 'string' { return IndexType.str } + if typ.starts_with('array_') || typ == 'array' { return IndexType.array } + if typ == 'byte*' || typ == 'byteptr' || typ.contains('*') { + return IndexType.ptr + } + if typ[0] == `[` { return IndexType.fixed_array } + return IndexType.none +} + fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { mut typ := typ_ // a[0] @@ -1974,34 +1958,10 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { } } if is_arr { - //p.fgen('[') - // array_int a; a[0] - // type is "array_int", need "int" - // typ = typ.replace('array_', '') if is_arr0 { - if p.fileis('int_test') { - println('\nRRRR0 $typ') - } typ = typ.right(6) - if p.fileis('int_test') { - println('RRRR $typ') - } - } - // array a; a.first() voidptr - // type is "array", need "void*" - if typ == 'array' { - typ = 'void*' - } - // No bounds check in translated from C code - if p.pref.translated && !p.builtin_mod{ - // Cast void* to typ*: add (typ*) to the beginning of the assignment : - // ((int*)a.data = ... - p.cgen.set_placeholder(fn_ph, '(($typ*)(') - p.gen('.data))[') - } - else { - p.gen(',') - } + } + p.gen_array_at(typ, is_arr0, fn_ph) } // map is tricky // need to replace "m[key] = val" with "tmp = val; map_set(&m, key, &tmp)" @@ -2050,43 +2010,14 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { if is_indexer && is_str && !p.builtin_mod { p.error('strings are immutable') } - assign_pos := p.cgen.cur_line.len - is_cao := p.tok ≠ .assign p.assigned_type = typ p.expected_type = typ + assign_pos := p.cgen.cur_line.len + is_cao := p.tok ≠ .assign p.assign_statement(v, fn_ph, is_indexer && (is_map || is_arr)) - // m[key] = val + // `m[key] = val` if is_indexer && (is_map || is_arr) { - // `a[0] = 7` - // curline right now: `a , 0 = 7` - mut val := p.cgen.cur_line.right(assign_pos) - p.cgen.resetln(p.cgen.cur_line.left(assign_pos)) - mut cao_tmp := p.cgen.cur_line - mut func := '' - if is_map { - func = 'map__set(&' - // CAO on map is a bit more complicated as it loads - // the value inside a pointer instead of returning it. - } - else { - if is_ptr { - func = 'array_set(' - if is_cao { - cao_tmp = '*($p.expected_type *) array__get(*$cao_tmp)' - } - } - else { - func = 'array_set(&/*q*/' - if is_cao { - cao_tmp = '*($p.expected_type *) array__get($cao_tmp)' - } - } - } - p.cgen.set_placeholder(fn_ph, func) - if is_cao { - val = cao_tmp + val.all_before('=') + val.all_after('=') - } - p.gen(', & ($typ []) { $val })') + p.gen_array_set(typ, is_ptr, is_map, fn_ph, assign_pos, is_cao) } return typ } @@ -2095,52 +2026,26 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string { // } // m[key]. no =, just a getter else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer { - // Erase var name we generated earlier: "int a = m, 0" - // "m, 0" gets killed since we need to start from scratch. It's messy. - // "m, 0" is an index expression, save it before deleting and insert later in map_get() - mut index_expr := '' - if p.cgen.is_tmp { - index_expr = p.cgen.tmp_line.right(fn_ph) - p.cgen.resetln(p.cgen.tmp_line.left(fn_ph)) - } else { - index_expr = p.cgen.cur_line.right(fn_ph) - p.cgen.resetln(p.cgen.cur_line.left(fn_ph)) - } - // Can't pass integer literal, because map_get() requires a void* - tmp := p.get_tmp() - tmp_ok := p.get_tmp() - if is_map { - p.gen('$tmp') - def := type_default(typ) - p.cgen.insert_before('$typ $tmp = $def; bool $tmp_ok = map_get($index_expr, & $tmp);') - } - else if is_arr { - if p.pref.translated && !p.builtin_mod { - p.gen('$index_expr ]') - } - else { - if is_ptr { - p.gen('( *($typ*) array__get(* $index_expr) )') - } else { - p.gen('( *($typ*) array__get($index_expr) )') - } - } - } - else if is_str && !p.builtin_mod { - p.gen('string_at($index_expr)') - } - // Zero the string after map_get() if it's nil, numbers are automatically 0 - // This is ugly, but what can I do without generics? - // TODO what about user types? - if is_map && typ == 'string' { - // p.cgen.insert_before('if (!${tmp}.str) $tmp = tos("", 0);') - p.cgen.insert_before('if (!$tmp_ok) $tmp = tos((byte *)"", 0);') - } + p.index_get(typ, fn_ph, IndexCfg{ + is_arr: is_arr + is_map: is_map + is_ptr: is_ptr + is_str: is_str + }) } // else if is_arr && is_indexer{} return typ } +struct IndexCfg { + is_map bool + is_str bool + is_ptr bool + is_arr bool + is_arr0 bool + +} + // returns resulting type fn (p mut Parser) expression() string { if p.scanner.file_path.contains('test_test') { @@ -2151,7 +2056,7 @@ fn (p mut Parser) expression() string { ph := p.cgen.add_placeholder() mut typ := p.term() is_str := typ=='string' - // a << b ==> array2_push(&a, b) + // `a << b` ==> `array_push(&a, b)` if p.tok == .left_shift { if typ.contains('array_') { // Can't pass integer literal, because push requires a void* @@ -2171,19 +2076,7 @@ fn (p mut Parser) expression() string { } expr_type := p.expression() // Two arrays of the same type? - push_array := typ == expr_type - if push_array { - p.cgen.set_placeholder(ph, '_PUSH_MANY(&' ) - p.gen('), $tmp, $typ)') - } else { - p.check_types(expr_type, tmp_typ) - // Pass tmp var info to the _PUSH macro - // Prepend tmp initialisation and push call - // Don't dereference if it's already a mutable array argument (`fn foo(mut []int)`) - push_call := if typ.contains('*'){'_PUSH('} else { '_PUSH(&'} - p.cgen.set_placeholder(ph, push_call) - p.gen('), $tmp, $tmp_typ)') - } + p.gen_array_push(ph, typ, expr_type, tmp, tmp_typ) return 'void' } else { @@ -2236,12 +2129,12 @@ fn (p mut Parser) expression() string { tok_op := p.tok is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ) p.check_space(p.tok) - if is_str && tok_op == .plus { + if is_str && tok_op == .plus && !p.is_js { p.cgen.set_placeholder(ph, 'string_add(') p.gen(',') } // 3 + 4 - else if is_num { + else if is_num || p.is_js { if typ == 'void*' { // Msvc errors on void* pointer arithmatic // ... So cast to byte* and then do the add @@ -2259,7 +2152,7 @@ fn (p mut Parser) expression() string { } } p.check_types(p.term(), typ) - if is_str && tok_op == .plus { + if is_str && tok_op == .plus && !p.is_js { p.gen(')') } // Make sure operators are used with correct types @@ -2535,12 +2428,18 @@ fn (p mut Parser) string_expr() { else if p.is_sql { p.gen('\'$str\'') } + else if p.is_js { + p.gen('"$f"') + } else { p.gen('tos2((byte*)"$f")') } p.next() return } + $if js { + p.error('js backend does not support string formatting yet') + } // tmp := p.get_tmp() p.is_alloc = true // $ interpolation means there's allocation mut args := '"' @@ -2601,7 +2500,7 @@ fn (p mut Parser) string_expr() { if fspec == 's' { //println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ') if typ != 'string' { - p.error('only v strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}.') + p.error('only V strings can be formatted with a :${cformat} format, but you have given "${val}", which has type ${typ}.') } args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str' } @@ -2741,10 +2640,10 @@ fn (p mut Parser) array_init() string { mut typ := '' new_arr_ph := p.cgen.add_placeholder() mut i := 0 - pos := p.cgen.cur_line.len// remember cur line to fetch first number in cgen for [0; 10] + pos := p.cgen.cur_line.len// remember cur line to fetch first number in cgen for [0; 10] for p.tok != .rsbr { val_typ := p.bool_expression() - // Get type of the first expression + // Get the type of the first expression if i == 0 { typ = val_typ // fixed width array initialization? (`arr := [20]byte`) @@ -2755,18 +2654,16 @@ fn (p mut Parser) array_init() string { if !nextc.is_space() { p.check(.rsbr) array_elem_typ := p.get_type() - if p.table.known_type(array_elem_typ) { - p.cgen.resetln('') - p.gen('{0}') - p.is_alloc = false - if is_const_len { - return '[${p.mod}__$lit]$array_elem_typ' - } - return '[$lit]$array_elem_typ' - } - else { + if !p.table.known_type(array_elem_typ) { p.error('bad type `$array_elem_typ`') } + p.cgen.resetln('') + //p.gen('{0}') + p.is_alloc = false + if is_const_len { + return '[${p.mod}__$lit]$array_elem_typ' + } + return '[$lit]$array_elem_typ' } } } @@ -2783,10 +2680,11 @@ fn (p mut Parser) array_init() string { i++ // Repeat (a = [0;5] ) if i == 1 && p.tok == .semicolon { + p.warn('`[0 ; len]` syntax was removed. Use `[0].repeat(len)` instead') p.check_space(.semicolon) val := p.cgen.cur_line.right(pos) p.cgen.resetln(p.cgen.cur_line.left(pos)) - p.gen('array_repeat(& ($typ[]){ $val }, ') + p.gen('array_repeat_old(& ($typ[]){ $val }, ') p.check_types(p.bool_expression(), 'int') p.gen(', sizeof($typ) )') p.check(.rsbr) @@ -2801,7 +2699,6 @@ fn (p mut Parser) array_init() string { if p.tok == .name && i == 0 { // vals.len == 0 { typ = p.get_type() - // println('.key_goT TYP after [] $typ') } // ! after array => no malloc and no copy no_alloc := p.tok == .not @@ -2829,56 +2726,20 @@ fn (p mut Parser) array_init() string { // if ptr { // typ += '_ptr" // } - mut new_arr := 'new_array_from_c_array' - if no_alloc { - new_arr += '_no_alloc' - } - - if i == 0 && p.pref.ccompiler != 'tcc' { - p.gen(' 0 })') - } else { - p.gen(' })') - } - - // p.gen('$new_arr($vals.len, $vals.len, sizeof($typ), ($typ[$vals.len]) $c_arr );') - // Need to do this in the second pass, otherwise it goes to the very top of the out.c file - if !p.first_pass() { - //if i == 0 { - //p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) { 0 ') - //} else { - // Due to a tcc bug, the length needs to be specified. - // GCC crashes if it is. - cast := if p.pref.ccompiler == 'tcc' { '($typ[$i])' } else { '($typ[])' } - p.cgen.set_placeholder(new_arr_ph, - '$new_arr($i, $i, sizeof($typ), $cast { ') - //} - } + p.gen_array_init(typ, no_alloc, new_arr_ph, i) typ = 'array_$typ' p.register_array(typ) return typ } -fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { +fn (p mut Parser) struct_init(typ string) string { + //p.gen('/* struct init */') p.is_struct_init = true t := p.table.find_type(typ) - // TODO hack. If it's a C type, we may need to add "struct" before declaration: - // a := &C.A{} ==> struct A* a = malloc(sizeof(struct A)); - if is_c_struct_init { - p.is_c_struct_init = true - if t.cat != .c_typedef { - p.cgen.insert_before('struct /*c struct init*/') - } - } - p.next() + if p.gen_struct_init(typ, t) { return typ } p.scanner.fmt_out.cut(typ.len) ptr := typ.contains('*') - // TODO tm struct struct bug - if typ == 'tm' { - p.cgen.lines[p.cgen.lines.len-1] = '' - } - p.check(.lcbr) - no_star := typ.replace('*', '') - // `user := User{foo:bar}` => `User user = (User){ .foo = bar}` + /* if !ptr { if p.is_c_struct_init { // `face := C.FT_Face{}` => `FT_Face face;` @@ -2891,7 +2752,7 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { p.is_c_struct_init = false } else { - p.gen('($typ) {') + p.gen('($typ /*str init */) {') } } else { @@ -2903,10 +2764,11 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { p.check(.rcbr) return typ } - p.gen('($no_star*)memdup(&($no_star) {') p.is_alloc = true //println('setting is_alloc=true (ret $typ)') + p.gen('($t.name*)memdup(&($t.name) {') } + */ mut did_gen_something := false // Loop thru all struct init keys and assign values // u := User{age:20, name:'bob'} @@ -2924,7 +2786,7 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { } f := t.find_field(field) inited_fields << field - p.gen('.$field = ') + p.gen_struct_field_init(field) p.check(.colon) p.fspace() p.check_types(p.bool_expression(), f.typ) @@ -2954,7 +2816,8 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { } // init map fields if field_typ.starts_with('map_') { - p.gen('.$field.name = new_map(1, sizeof( ${field_typ.right(4)} ))') + p.gen_struct_field_init(field.name) + p.gen_empty_map(field_typ.right(4)) inited_fields << field.name if i != t.fields.len - 1 { p.gen(',') @@ -2964,7 +2827,8 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { } def_val := type_default(field_typ) if def_val != '' && def_val != '{0}' { - p.gen('.$field.name = $def_val') + p.gen_struct_field_init(field.name) + p.gen(def_val) if i != t.fields.len - 1 { p.gen(',') } @@ -3006,74 +2870,17 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string { p.gen('0') } p.gen('}') - if ptr { - p.gen(', sizeof($no_star))') + if ptr && !p.is_js { + p.gen(', sizeof($t.name))') } p.check(.rcbr) p.is_struct_init = false + p.is_c_struct_init = false return typ } // `f32(3)` // tok is `f32` or `)` if `(*int)(ptr)` -fn (p mut Parser) cast(typ string) string { - p.next() - pos := p.cgen.add_placeholder() - if p.tok == .rpar { - // skip `)` if it's `(*int)(ptr)`, not `int(a)` - p.ptr_cast = true - p.next() - } - p.check(.lpar) - p.expected_type = typ - expr_typ := p.bool_expression() - // `face := FT_Face(cobj)` => `FT_Face face = *((FT_Face*)cobj);` - casting_voidptr_to_value := expr_typ == 'void*' && typ != 'int' && - typ != 'byteptr' && !typ.ends_with('*') - p.expected_type = '' - // `string(buffer)` => `tos2(buffer)` - // `string(buffer, len)` => `tos(buffer, len)` - // `string(bytes_array, len)` => `tos(bytes_array.data, len)` - is_byteptr := expr_typ == 'byte*' || expr_typ == 'byteptr' - is_bytearr := expr_typ == 'array_byte' - if typ == 'string' { - if is_byteptr || is_bytearr { - if p.tok == .comma { - p.check(.comma) - p.cgen.set_placeholder(pos, 'tos((byte *)') - if is_bytearr { - p.gen('.data') - } - p.gen(', ') - p.check_types(p.expression(), 'int') - } else { - if is_bytearr { - p.gen('.data') - } - p.cgen.set_placeholder(pos, 'tos2((byte *)') - } - } - // `string(234)` => error - else if expr_typ == 'int' { - p.error('cannot cast `$expr_typ` to `$typ`, use `str()` method instead') - } - else { - p.error('cannot cast `$expr_typ` to `$typ`') - } - } - else if typ == 'byte' && expr_typ == 'string' { - p.error('cannot cast `$expr_typ` to `$typ`, use backquotes `` to create a `$typ` or access the value of an index of `$expr_typ` using []') - } - else if casting_voidptr_to_value { - p.cgen.set_placeholder(pos, '*($typ*)(') - } - else { - p.cgen.set_placeholder(pos, '($typ)(') - } - p.check(.rpar) - p.gen(')') - return typ -} fn (p mut Parser) get_tmp() string { p.tmp_cnt++ @@ -3173,6 +2980,7 @@ fn (p mut Parser) for_st() { next_tok := p.peek() //debug := p.scanner.file_path.contains('r_draw') p.open_scope() + i_type := if p.is_js { 'var' } else { 'int' } if p.tok == .lcbr { // Infinite loop p.gen('while (1) {') @@ -3207,11 +3015,15 @@ fn (p mut Parser) for_st() { } // for i, val in array else if p.peek() == .comma { - // for i, val in array { ==> - // - // array_int tmp = array; - // for (int i = 0; i < tmp.len; i++) { - // int val = tmp[i]; + /* + `for i, val in array {` + ==> + ``` + array_int tmp = array; + for (int i = 0; i < tmp.len; i++) { + int val = tmp[i]; + ``` + */ i := p.check_name() p.check(.comma) val := p.check_name() @@ -3228,7 +3040,11 @@ fn (p mut Parser) for_st() { p.error('cannot range over type `$typ`') } expr := p.cgen.end_tmp() - p.genln('$typ $tmp = $expr ;') + if p.is_js { + p.genln('var $tmp = $expr;') + } else { + p.genln('$typ $tmp = $expr;') + } pad := if is_arr { 6 } else { 4 } var_typ := if is_str { 'byte' } else { typ.right(pad) } // typ = strings.Replace(typ, "_ptr", "*", -1) @@ -3247,9 +3063,9 @@ fn (p mut Parser) for_st() { is_mut: true is_changed: true } + //p.genln(';\nfor ($i_type $i = 0; $i < $tmp .len; $i ++) {') + p.gen_for_header(i, tmp, var_typ, val) p.register_var(i_var) - p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {') - p.genln('$var_typ $val = (($var_typ *) $tmp . data)[$i];') } else if is_map { i_var := Var { @@ -3259,14 +3075,7 @@ fn (p mut Parser) for_st() { is_changed: true } p.register_var(i_var) - p.genln('array_string keys_$tmp = map_keys(& $tmp ); ') - p.genln('for (int l = 0; l < keys_$tmp .len; l++) {') - p.genln(' string $i = ((string*)keys_$tmp .data)[l];') - //p.genln(' string $i = *(string*) ( array__get(keys_$tmp, l) );') - def := type_default(typ) - // TODO don't call map_get() for each key, fetch values while traversing - // the tree (replace `map_keys()` above with `map_key_vals()`) - p.genln('$var_typ $val = $def; map_get($tmp, $i, & $val);') + p.gen_for_map_header(i, tmp, var_typ, val, typ) } else if is_str { i_var := Var { @@ -3276,9 +3085,7 @@ fn (p mut Parser) for_st() { is_changed: true } p.register_var(i_var) - p.genln('array_byte bytes_$tmp = string_bytes( $tmp );') - p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {') - p.genln('$var_typ $val = (($var_typ *) bytes_$tmp . data)[$i];') + p.gen_for_str_header(i, tmp, var_typ, val) } } // `for val in vals` @@ -3305,7 +3112,11 @@ fn (p mut Parser) for_st() { if !is_arr && !is_str && !is_range { p.error('cannot range over type `$typ`') } - p.genln('$typ $tmp = $expr;') + if p.is_js { + p.genln('var $tmp = $expr;') + } else { + p.genln('$typ $tmp = $expr;') + } // TODO var_type := if... mut var_type := '' if is_arr { @@ -3327,23 +3138,17 @@ fn (p mut Parser) for_st() { } p.register_var(val_var) i := p.get_tmp() - if is_range { - p.genln(';\nfor (int $i = $tmp; $i < $range_end; $i++) {') - } - else { - p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {') - } if is_arr { - p.genln('$var_type $val = (($var_type *) ${tmp}.data)[$i];') + p.gen_for_header(i, tmp, var_type, val) } else if is_str { - p.genln('$var_type $val = (($var_type *) ${tmp}.str)[$i];') + p.gen_for_str_header(i, tmp, var_type, val) } - else if is_range { + else if is_range && !p.is_js { + p.genln(';\nfor ($i_type $i = $tmp; $i < $range_end; $i++) {') p.genln('$var_type $val = $i;') } - } - else { + } else { // `for a < b {` p.gen('while (') p.check_types(p.bool_expression(), 'bool') diff --git a/compiler/scanner.v b/compiler/scanner.v index 1ada2553ac..4df09115cd 100644 --- a/compiler/scanner.v +++ b/compiler/scanner.v @@ -402,7 +402,7 @@ fn (s mut Scanner) scan() ScanRes { // @LINE => will be substituted with the V line number where it appears (as a string). // @COLUMN => will be substituted with the column where it appears (as a string). // @VHASH => will be substituted with the shortened commit hash of the V compiler (as a string). - // This allows things like this: + // This allows things like this: // println( 'file: ' + @FILE + ' | line: ' + @LINE + ' | fn: ' + @FN) // ... which is useful while debugging/tracing if name == 'FN' { return scan_res(.str, s.fn_name) } @@ -624,7 +624,7 @@ fn (s &Scanner) error(msg string) { linestart := s.find_current_line_start_position() lineend := s.find_current_line_end_position() column := s.pos - linestart - if s.should_print_line_on_error { + if s.should_print_line_on_error && lineend > linestart { line := s.text.substr( linestart, lineend ) // The pointerline should have the same spaces/tabs as the offending // line, so that it prints the ^ character exactly on the *same spot* diff --git a/compiler/table.v b/compiler/table.v index a45e67e41a..fa8bdd35ec 100644 --- a/compiler/table.v +++ b/compiler/table.v @@ -129,6 +129,7 @@ fn (t Type) str() string { const ( CReserved = [ + 'delete', 'exit', 'unix', //'print', @@ -141,32 +142,21 @@ const ( // Full list of C reserved words, from: https://en.cppreference.com/w/c/keyword 'auto', - 'break', - 'case', 'char', - 'const', - 'continue', 'default', 'do', 'double', - 'else', - 'enum', 'extern', 'float', - 'for', - 'goto', - 'if', 'inline', 'int', 'long', 'register', 'restrict', - 'return', 'short', 'signed', 'sizeof', 'static', - 'struct', 'switch', 'typedef', 'union', @@ -461,7 +451,6 @@ fn (table &Table) type_has_method(typ &Type, name string) bool { // TODO use `?Fn` fn (table &Table) find_method(typ &Type, name string) Fn { - // println('TYPE HAS METHOD $name') // method := typ.find_method(name) t := table.typesmap[typ.name] method := t.find_method(name) @@ -638,38 +627,6 @@ fn (p mut Parser) satisfies_interface(interface_name, _typ string, throw bool) b return true } -fn type_default(typ string) string { - if typ.starts_with('array_') { - return 'new_array(0, 1, sizeof( ${typ.right(6)} ))' - } - // Always set pointers to 0 - if typ.ends_with('*') { - return '0' - } - // User struct defined in another module. - if typ.contains('__') { - return '{0}' - } - // Default values for other types are not needed because of mandatory initialization - switch typ { - case 'bool': return '0' - case 'string': return 'tos((byte *)"", 0)' - case 'i8': return '0' - case 'i16': return '0' - case 'i64': return '0' - case 'u16': return '0' - case 'u32': return '0' - case 'u64': return '0' - case 'byte': return '0' - case 'int': return '0' - case 'rune': return '0' - case 'f32': return '0.0' - case 'f64': return '0.0' - case 'byteptr': return '0' - case 'voidptr': return '0' - } - return '{0}' -} fn (table &Table) is_interface(name string) bool { if !(name in table.typesmap) { @@ -700,41 +657,6 @@ fn (t &Table) find_const(name string) Var { return Var{} } -fn (table mut Table) cgen_name(f &Fn) string { - mut name := f.name - if f.is_method { - name = '${f.receiver_typ}_$f.name' - name = name.replace(' ', '') - name = name.replace('*', '') - name = name.replace('+', 'plus') - name = name.replace('-', 'minus') - } - // Avoid name conflicts (with things like abs(), print() etc). - // Generate b_abs(), b_print() - // TODO duplicate functionality - if f.mod == 'builtin' && f.name in CReserved { - return 'v_$name' - } - // Obfuscate but skip certain names - // TODO ugly, fix - if table.obfuscate && f.name != 'main' && f.name != 'WinMain' && f.mod != 'builtin' && !f.is_c && - f.mod != 'darwin' && f.mod != 'os' && !f.name.contains('window_proc') && f.name != 'gg__vec2' && - f.name != 'build_token_str' && f.name != 'build_keys' && f.mod != 'json' && - !name.ends_with('_str') && !name.contains('contains') { - mut idx := table.obf_ids[name] - // No such function yet, register it - if idx == 0 { - table.fn_cnt++ - table.obf_ids[name] = table.fn_cnt - idx = table.fn_cnt - } - old := name - name = 'f_$idx' - println('$old ==> $name') - } - return name -} - // ('s', 'string') => 'string s' // ('nums', '[20]byte') => 'byte nums[20]' // ('myfn', 'fn(int) string') => 'string (*myfn)(int)' diff --git a/compiler/token.v b/compiler/token.v index 8da09622f4..1e9a312ce0 100644 --- a/compiler/token.v +++ b/compiler/token.v @@ -124,7 +124,7 @@ fn build_keys() map[string]int { // TODO remove once we have `enum Token { name('name') if('if') ... }` fn build_token_str() []string { - mut s := [''; NrTokens] + mut s := [''].repeat2(NrTokens) s[Token.keyword_beg] = '' s[Token.keyword_end] = '' s[Token.eof] = 'eof' diff --git a/vlib/builtin/array.v b/vlib/builtin/array.v index 5d8bb61d70..133e1ce0a3 100644 --- a/vlib/builtin/array.v +++ b/vlib/builtin/array.v @@ -58,7 +58,7 @@ fn new_array_from_c_array_no_alloc(len, cap, elm_size int, c_array voidptr) arra } // Private function, used by V (`[0; 100]`) -fn array_repeat(val voidptr, nr_repeats, elm_size int) array { +fn array_repeat_old(val voidptr, nr_repeats, elm_size int) array { arr := array { len: nr_repeats cap: nr_repeats @@ -71,6 +71,35 @@ fn array_repeat(val voidptr, nr_repeats, elm_size int) array { return arr } +pub fn (a array) repeat(nr_repeats int) array { + arr := array { + len: nr_repeats + cap: nr_repeats + element_size: a.element_size + data: malloc(nr_repeats * a.element_size) + } + val := a.data + 0 //nr_repeats * a.element_size + for i := 0; i < nr_repeats; i++ { + C.memcpy(arr.data + i * a.element_size, val, a.element_size) + } + return arr +} + +// TODO remove +pub fn (a array) repeat2(nr_repeats int) array { + arr := array { + len: nr_repeats + cap: nr_repeats + element_size: a.element_size + data: malloc(nr_repeats * a.element_size) + } + val := a.data + 0 //nr_repeats * a.element_size + for i := 0; i < nr_repeats; i++ { + C.memcpy(arr.data + i * a.element_size, val, a.element_size) + } + return arr +} + pub fn (a mut array) sort_with_compare(compare voidptr) { C.qsort(a.data, a.len, a.element_size, compare) } @@ -232,7 +261,9 @@ pub fn (a []string) str() string { sb.write('[') for i := 0; i < a.len; i++ { val := a[i] - sb.write('"$val"') + sb.write('"') + sb.write(val) + sb.write('"') if i < a.len - 1 { sb.write(', ') } diff --git a/vlib/builtin/array_test.v b/vlib/builtin/array_test.v index f7fded9cec..f9f1c74a7c 100644 --- a/vlib/builtin/array_test.v +++ b/vlib/builtin/array_test.v @@ -141,7 +141,7 @@ fn test_slice() { fn test_push_many() { mut a := [1, 2, 3] b := [4, 5, 6] - a << b + a << b assert a.len == 6 assert a[0] == 1 assert a[3] == 4 @@ -163,42 +163,44 @@ fn test_reverse() { const ( N = 5 -) +) fn test_fixed() { - mut nums := [4]int - assert nums[0] == 0 - assert nums[1] == 0 - assert nums[2] == 0 - assert nums[3] == 0 - nums[1] = 7 - assert nums[1] == 7 + /* + mut nums := [4]int + assert nums[0] == 0 + assert nums[1] == 0 + assert nums[2] == 0 + assert nums[3] == 0 + nums[1] = 7 + assert nums[1] == 7 /////// - nums2 := [N]int - assert nums2[N - 1] == 0 -} + nums2 := [N]int + assert nums2[N - 1] == 0 + */ +} fn modify (numbers mut []int) { numbers[0] = 777 } -fn test_mut_slice() { +fn test_mut_slice() { mut n := [1,2,3] - modify(mut n.left(2)) - assert n[0] == 777 - modify(mut n.right(2)) - assert n[2] == 777 + modify(mut n.left(2)) + assert n[0] == 777 + modify(mut n.right(2)) + assert n[2] == 777 println(n) } fn test_clone() { - nums := [1, 2, 3, 4, 100] - nums2 := nums.clone() - assert nums2.len == 5 - assert nums2.str() == '[1, 2, 3, 4, 100]' - assert nums.slice(1, 3).str() == '[2, 3]' -} - + nums := [1, 2, 3, 4, 100] + nums2 := nums.clone() + assert nums2.len == 5 + assert nums2.str() == '[1, 2, 3, 4, 100]' + assert nums.slice(1, 3).str() == '[2, 3]' +} + fn test_doubling() { mut nums := [1, 2, 3, 4, 5] for i := 0; i < nums.len; i++ { diff --git a/vlib/builtin/int.v b/vlib/builtin/int.v index 35ba7a60d2..00ceb116c8 100644 --- a/vlib/builtin/int.v +++ b/vlib/builtin/int.v @@ -213,7 +213,7 @@ pub fn (c byte) is_capital() bool { } pub fn (b []byte) clone() []byte { - mut res := [byte(0); b.len] + mut res := [byte(0)].repeat2(b.len) for i := 0; i < b.len; i++ { res[i] = b[i] } diff --git a/vlib/builtin/js/array.v b/vlib/builtin/js/array.v new file mode 100644 index 0000000000..e8235332c0 --- /dev/null +++ b/vlib/builtin/js/array.v @@ -0,0 +1,130 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +import strings + +struct array { +pub: + data voidptr + len int + cap int + element_size int +} + +/* +// Private function, used by V (`nums := []int`) +fn new_array(mylen, cap, elm_size int) array { + arr := array { + len: mylen + cap: cap + element_size: elm_size + } + return arr +} + + +// TODO +pub fn _make(len, cap, elm_size int) array { + return new_array(len, cap, elm_size) +} + + +*/ +fn array_repeat(val voidptr, nr_repeats, elm_size int) array { + return val +} + +pub fn (a array) repeat2(nr_repeats int) array { + #return Array(a[0]).fill(nr_repeats) + return a +} + +pub fn (a mut array) sort_with_compare(compare voidptr) { +} + +pub fn (a mut array) insert(i int, val voidptr) { +} + +pub fn (a mut array) prepend(val voidptr) { + a.insert(0, val) +} + +pub fn (a mut array) delete_elm(idx int) { +} + +/* +pub fn (a array) first() voidptr { + if a.len == 0 { + panic('array.first: empty array') + } + return a.data + 0 +} + +pub fn (a array) last() voidptr { + if a.len == 0 { + panic('array.last: empty array') + } + return a.data + (a.len - 1) * a.element_size +} +*/ + +pub fn (s array) left(n int) array { + if n >= s.len { + return s + } + return s.slice(0, n) +} + +pub fn (s array) right(n int) array { + if n >= s.len { + return s + } + return s.slice(n, s.len) +} + +pub fn (s array) slice(start, _end int) array { + return s +} + +pub fn (a array) reverse() array { + return a +} + +pub fn (a array) clone() array { + return a +} + +pub fn (a array) free() { +} + +// "[ 'a', 'b', 'c' ]" +pub fn (a []string) str() string { + mut sb := strings.new_builder(a.len * 3) + sb.write('[') + for i := 0; i < a.len; i++ { + val := a[i] + sb.write('"') + sb.write(val) + sb.write('"') + if i < a.len - 1 { + sb.write(', ') + } + } + sb.write(']') + return sb.str() +} + +pub fn (b []byte) hex() string { + return 'sdf' +} + +pub fn (arr mut array) _push_many(val voidptr, size int) { +} + +pub fn free(voidptr) { + +} + diff --git a/vlib/builtin/js/builtin.v b/vlib/builtin/js/builtin.v new file mode 100644 index 0000000000..75096cb324 --- /dev/null +++ b/vlib/builtin/js/builtin.v @@ -0,0 +1,35 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +pub fn exit(code int) { + println('js.exit()') +} + +// isnil returns true if an object is nil (only for C objects). +pub fn isnil(v voidptr) bool { + return v == 0 +} + +pub fn panic(s string) { + println('V panic: ' + s) + exit(1) +} + +pub fn println(s string) { + #console.log(s) +} + +pub fn eprintln(s string) { + #console.log(s) +} + +pub fn print(s string) { + #console.log(s) +} + + + + diff --git a/vlib/builtin/js/int.v b/vlib/builtin/js/int.v new file mode 100644 index 0000000000..cb926ee180 --- /dev/null +++ b/vlib/builtin/js/int.v @@ -0,0 +1,99 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +#include +#include + +pub fn (d double) str() string { + return '0' +} + +pub fn (d f64) str() string { + return '0' +} + +pub fn (d f32) str() string { + return '0' +} + +pub fn ptr_str(ptr voidptr) string { + return '0' +} + +// compare floats using C epsilon +pub fn (a f64) eq(b f64) bool { + //return C.fabs(a - b) <= C.DBL_EPSILON + return (a - b) <= 0.01 +} + +// fn (nn i32) str() string { +// return i +// } +pub fn (nn int) str() string { + return '0' +} + +pub fn (nn u32) str() string { + return '0' +} + +pub fn (nn u8) str() string { + return '0' +} + +pub fn (nn i64) str() string { + return '0' +} + +pub fn (nn u64) str() string { + return '0' +} + +pub fn (b bool) str() string { + if b { + return 'true' + } + return 'false' +} + +pub fn (n int) hex() string { + return '0' +} + +pub fn (n i64) hex() string { + return '0' +} + +pub fn (a []byte) contains(val byte) bool { + for aa in a { + if aa == val { + return true + } + } + return false +} + +pub fn (c rune) str() string { + return '0' +} + +pub fn (c byte) str() string { + return '0' +} + +pub fn (c byte) is_capital() bool { + return c >= `A` && c <= `Z` +} + +pub fn (b []byte) clone() []byte { + mut res := [byte(0)].repeat2(b.len) + for i := 0; i < b.len; i++ { + res[i] = b[i] + } + return res +} + + diff --git a/vlib/builtin/js/map.v b/vlib/builtin/js/map.v new file mode 100644 index 0000000000..c77c5fc70c --- /dev/null +++ b/vlib/builtin/js/map.v @@ -0,0 +1,70 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +import strings + +struct map { + obj voidptr +} + +//fn (m mut map) insert(n mut mapnode, key string, val voidptr) { +//} + +//////fn (n & mapnode) find(key string, out voidptr, element_size int) bool{ + //return false +//} + +// same as `find`, but doesn't return a value. Used by `exists` +//fn (n & mapnode) find2(key string, element_size int) bool{ + //return false +//} + +fn (m mut map) _set(key string, val voidptr) { +} + +//fn preorder_keys(node &mapnode, keys mut []string, key_i int) int { + //return 0 +//} + +pub fn (m mut map) keys() []string { + return [''] +} + +fn (m map) get(key string, out voidptr) bool { + return false +} + +pub fn (m mut map) delete(key string) { +} + +fn (m map) _exists(key string) bool { + return false +} + +pub fn (m map) print() { + println('<<<<<<<<') + println('>>>>>>>>>>') +} + +pub fn (m map) free() { + // C.free(m.table) + // C.free(m.keys_table) +} + +pub fn (m map_string) str() string { + /* + if m.size == 0 { + return '{}' + } + */ + mut sb := strings.new_builder(50) + sb.writeln('{') + for key, val in m { + //sb.writeln(' "$key" => "$val"') + } + sb.writeln('}') + return sb.str() +} diff --git a/vlib/builtin/js/option.v b/vlib/builtin/js/option.v new file mode 100644 index 0000000000..acbb47b6bc --- /dev/null +++ b/vlib/builtin/js/option.v @@ -0,0 +1,31 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +struct Option { + data [255]byte + error string + ok bool +} + +// `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }` +fn opt_ok(data voidptr, size int) Option { + if size >= 255 { + panic('option size too big') + } + res := Option { + ok: true + } + C.memcpy(res.data, data, size) + return res +} + +pub fn error(s string) Option { + return Option { + error: s + } +} + + diff --git a/vlib/builtin/js/string.v b/vlib/builtin/js/string.v new file mode 100644 index 0000000000..0adb2b3db5 --- /dev/null +++ b/vlib/builtin/js/string.v @@ -0,0 +1,318 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +struct string { +//mut: + //hash_cache int +pub: + str byteptr + len int +} + +// For C strings only +fn C.strlen(s byteptr) int + +fn todo() { } + + +pub fn (a string) clone() string { + return a +} + +pub fn (s string) replace(rep, with_ string) string { + return s +} + +pub fn (s string) int() int { + return 0 +} + +pub fn (s string) i64() i64 { + return 0 +} + +pub fn (s string) f32() f32 { + return 0.0 +} + +pub fn (s string) f64() f64 { + return 0.0 +} + +pub fn (s string) u32() u32 { + return u32(0) +} + +pub fn (s string) u64() u64 { + return u64(0) +} + +pub fn (s string) split(delim string) []string { + return s.split(delim) +} + +pub fn (s string) split_single(delim byte) []string { + return s.split(delim.str()) +} + +pub fn (s string) split_into_lines() []string { + return s.split('\n') +} + +// 'hello'.left(2) => 'he' +pub fn (s string) left(n int) string { + if n >= s.len { + return s + } + return s.substr(0, n) +} +// 'hello'.right(2) => 'llo' +pub fn (s string) right(n int) string { + if n >= s.len { + return '' + } + return s.substr(n, s.len) +} + +pub fn (s string) substr(start, end int) string { + return 'a' +} + +pub fn (s string) index(p string) int { + return -1 +} + +pub fn (s string) index_any(chars string) int { + return -1 +} + +pub fn (s string) last_index(p string) int { + return -1 +} + +pub fn (s string) index_after(p string, start int) int { + return -1 +} + +// counts occurrences of substr in s +pub fn (s string) count(substr string) int { + return 0 // TODO can never get here - v doesn't know that +} + +pub fn (s string) contains(p string) bool { + return false +} + +pub fn (s string) starts_with(p string) bool { + return false +} + +pub fn (s string) ends_with(p string) bool { + return false +} + +// TODO only works with ASCII +pub fn (s string) to_lower() string { + return s +} + +pub fn (s string) to_upper() string { + return s +} + +pub fn (s string) capitalize() string { + return s +} + +pub fn (s string) title() string { + return s +} + +// 'hey [man] how you doin' +// find_between('[', ']') == 'man' +pub fn (s string) find_between(start, end string) string { + start_pos := s.index(start) + if start_pos == -1 { + return '' + } + // First get everything to the right of 'start' + val := s.right(start_pos + start.len) + end_pos := val.index(end) + if end_pos == -1 { + return val + } + return val.left(end_pos) +} + +// TODO generic +pub fn (ar []string) contains(val string) bool { + for s in ar { + if s == val { + return true + } + } + return false +} + +// TODO generic +pub fn (ar []int) contains(val int) bool { + for i, s in ar { + if s == val { + return true + } + } + return false +} + + +fn is_space(c byte) bool { + return C.isspace(c) +} + +pub fn (c byte) is_space() bool { + return is_space(c) +} + +pub fn (s string) trim_space() string { + #return s.str.trim(' '); + return '' +} + +pub fn (s string) trim(cutset string) string { + #return s.str.trim(cutset); + return '' +} + +pub fn (s string) trim_left(cutset string) string { + #return s.str.trimLeft(cutset); + return '' +} + +pub fn (s string) trim_right(cutset string) string { + #return s.str.trimRight(cutset); + return '' +} + +// fn print_cur_thread() { +// //C.printf("tid = %08x \n", pthread_self()); +// } +pub fn (s mut []string) sort() { + +} + +pub fn (s mut []string) sort_ignore_case() { +} + +pub fn (s mut []string) sort_by_len() { +} + +fn (s string) at(idx int) byte { + if idx < 0 || idx >= s.len { + panic('string index out of range') + } + return s.str[idx] +} +pub fn (c byte) is_digit() bool { + return c >= `0` && c <= `9` +} + +pub fn (c byte) is_hex_digit() bool { + return c.is_digit() || (c >= `a` && c <= `f`) || (c >= `A` && c <= `F`) +} + +pub fn (c byte) is_oct_digit() bool { + return c >= `0` && c <= `7` +} + +pub fn (c byte) is_letter() bool { + return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) +} + +pub fn (s string) free() { +} + +/* +fn (arr []string) free() { + for s in arr { + s.free() + } + C.free(arr.data) +} +*/ + +// all_before('23:34:45.234', '.') == '23:34:45' +pub fn (s string) all_before(dot string) string { + pos := s.index(dot) + if pos == -1 { + return s + } + return s.left(pos) +} + +pub fn (s string) all_before_last(dot string) string { + pos := s.last_index(dot) + if pos == -1 { + return s + } + return s.left(pos) +} + +pub fn (s string) all_after(dot string) string { + pos := s.last_index(dot) + if pos == -1 { + return s + } + return s.right(pos + dot.len) +} + +// fn (s []string) substr(a, b int) string { +// return join_strings(s.slice_fast(a, b)) +// } +pub fn (a []string) join(del string) string { + return '' +} + +pub fn (s []string) join_lines() string { + return s.join('\n') +} + +pub fn (s string) reverse() string { + return s +} + +pub fn (s string) limit(max int) string { + if s.len <= max { + return s + } + return s.substr(0, max) +} + +// TODO is_white_space() +pub fn (c byte) is_white() bool { + i := int(c) + return i == 10 || i == 32 || i == 9 || i == 13 || c == `\r` +} + + +pub fn (s string) hash() int { + //mut h := s.hash_cache + mut h := 0 + if h == 0 && s.len > 0 { + for c in s { + h = h * 31 + int(c) + } + } + return h +} + +pub fn (s string) bytes() []byte { + if s.len == 0 { + return []byte + } + mut buf := [byte(0)].repeat2(s.len) + C.memcpy(buf.data, s.str, s.len) + return buf +} diff --git a/vlib/builtin/js/utf8.v b/vlib/builtin/js/utf8.v new file mode 100644 index 0000000000..f845a4b1f8 --- /dev/null +++ b/vlib/builtin/js/utf8.v @@ -0,0 +1,27 @@ +// Copyright (c) 2019 Alexander Medvednikov. All rights reserved. +// Use of this source code is governed by an MIT license +// that can be found in the LICENSE file. + +module builtin + +pub fn utf8_char_len(b byte) int { + return (( 0xe5000000 >> (( b >> 3 ) & 0x1e )) & 3 ) + 1 +} + +// Convert utf32 to utf8 +// utf32 == Codepoint +pub fn utf32_to_str(code u32) string { + return '' +} + +// TODO copypasta +pub fn utf32_to_str_no_malloc(code u32, buf voidptr) string { + return '' +} + +// Convert utf8 to utf32 +pub fn (_rune string) utf32_code() int { + return 0 +} + + diff --git a/vlib/builtin/map.v b/vlib/builtin/map.v index dfc8b1edcc..17bd44561c 100644 --- a/vlib/builtin/map.v +++ b/vlib/builtin/map.v @@ -169,7 +169,7 @@ fn preorder_keys(node &mapnode, keys mut []string, key_i int) int { } pub fn (m mut map) keys() []string { - mut keys := [''; m.size] + mut keys := [''].repeat2(m.size) if isnil(m.root) { return keys } diff --git a/vlib/builtin/string.v b/vlib/builtin/string.v index 116cbc6393..08c63f9601 100644 --- a/vlib/builtin/string.v +++ b/vlib/builtin/string.v @@ -78,10 +78,9 @@ pub fn (s string) replace(rep, with string) string { if s.len == 0 || rep.len == 0 { return s } - // println('"$s" replace "$rep" with "$with" rep.len=$rep.len') // TODO PERF Allocating ints is expensive. Should be a stack array // Get locations of all reps within this string - mut idxs := []int{} + mut idxs := []int mut rem := s mut rstart := 0 for { @@ -352,37 +351,58 @@ pub fn (s string) substr(start, end int) string { return res } -// KMP search -pub fn (s string) index(p string) int { +pub fn (s string) index_old(p string) int { if p.len > s.len { return -1 } - mut prefix := [0; p.len] - mut j := 0 - for i := 1; i < p.len; i++ { - for p[j] != p[i] && j > 0 { - j = prefix[j - 1] - } - if p[j] == p[i] { - j++ - } - prefix[i] = j - } - j = 0 - for i := 0; i < s.len; i++ { - for p[j] != s[i] && j > 0 { - j = prefix[j - 1] - } - if p[j] == s[i] { + mut i := 0 + for i < s.len { + mut j := 0 + mut ii := i + for j < p.len && s[ii] == p[j] { j++ + ii++ } if j == p.len { return i - p.len + 1 } + i++ } return -1 } +// KMP search +pub fn (s string) index(p string) int { + if p.len > s.len { + return -1 + } + mut prefix := [0].repeat2(p.len) + mut j := 0 + for i := 1; i < p.len; i++ { + for p[j] != p[i] && j > 0 { + j = prefix[j - 1] + } + if p[j] == p[i] { + j++ + } + prefix[i] = j + } + j = 0 + for i := 0; i < s.len; i++ { + for p[j] != s[i] && j > 0 { + j = prefix[j - 1] + } + if p[j] == s[i] { + j++ + } + if j == p.len { + return i - p.len + 1 + } + } + return -1 +} + + pub fn (s string) index_any(chars string) int { for c in chars { index := s.index(c.str()) @@ -874,7 +894,7 @@ pub fn (s string) bytes() []byte { if s.len == 0 { return []byte } - mut buf := [byte(0); s.len] + mut buf := [byte(0)].repeat2(s.len) C.memcpy(buf.data, s.str, s.len) return buf } diff --git a/vlib/os/os.v b/vlib/os/os.v index e229cfe58c..31fef7e5c1 100644 --- a/vlib/os/os.v +++ b/vlib/os/os.v @@ -69,25 +69,6 @@ fn C.ftell(fp voidptr) int fn C.getenv(byteptr) byteptr fn C.sigaction(int, voidptr, int) -fn init_os_args(argc int, argv &byteptr) []string { - mut args := []string - $if windows { - mut args_list := &voidptr(0) - mut args_count := 0 - args_list = C.CommandLineToArgvW(C.GetCommandLine(), &args_count) - for i := 0; i < args_count; i++ { - args << string_from_wide(&u16(args_list[i])) - } - - C.LocalFree(args_list) - } $else { - for i := 0; i < argc; i++ { - args << string(argv[i]) - } - } - return args -} - fn parse_windows_cmd_line(cmd byteptr) []string { s := string(cmd) return s.split(' ') @@ -100,8 +81,7 @@ pub fn read_file(path string) ?string { $if windows { fp = C._wfopen(path.to_wide(), mode.to_wide()) } $else { - cpath := path.str - fp = C.fopen(cpath, mode.str) + fp = C.fopen(path.str, mode.str) } if isnil(fp) { return error('failed to open file "$path"') diff --git a/vlib/os/os_nix.v b/vlib/os/os_nix.v index a5a48ae5bd..8077ce849a 100644 --- a/vlib/os/os_nix.v +++ b/vlib/os/os_nix.v @@ -7,6 +7,14 @@ const ( PathSeparator = '/' ) +fn init_os_args(argc int, argv &byteptr) []string { + mut args := []string + for i := 0; i < argc; i++ { + args << string(argv[i]) + } + return args +} + // get_error_msg return error code representation in string. pub fn get_error_msg(code int) string { diff --git a/vlib/os/os_win.v b/vlib/os/os_win.v index 042711217f..b41fba8870 100644 --- a/vlib/os/os_win.v +++ b/vlib/os/os_win.v @@ -4,8 +4,8 @@ module os #include const ( - PathSeparator = '\\' -) + PathSeparator = '\\' +) // Ref - https://docs.microsoft.com/en-us/windows/desktop/winprog/windows-data-types // A handle to an object. @@ -30,7 +30,7 @@ mut: nFileSizeLow u32 dwReserved0 u32 dwReserved1 u32 - cFileName [260]u16 // MAX_PATH = 260 + cFileName [260]u16 // MAX_PATH = 260 cAlternateFileName [14]u16 // 14 dwFileType u32 dwCreatorType u32 @@ -38,6 +38,18 @@ mut: } +fn init_os_args(argc int, argv &byteptr) []string { + mut args := []string + mut args_list := &voidptr(0) + mut args_count := 0 + args_list = C.CommandLineToArgvW(C.GetCommandLine(), &args_count) + for i := 0; i < args_count; i++ { + args << string_from_wide(&u16(args_list[i])) + } + C.LocalFree(args_list) + return args +} + pub fn ls(path string) []string { mut find_file_data := win32finddata{} @@ -54,7 +66,7 @@ pub fn ls(path string) []string { } // NOTE: Should eventually have path struct & os dependant path seperator (eg os.PATH_SEPERATOR) // we need to add files to path eg. c:\windows\*.dll or :\windows\* - path_files := '$path\\*' + path_files := '$path\\*' // NOTE:TODO: once we have a way to convert utf16 wide character to utf8 // we should use FindFirstFileW and FindNextFileW h_find_files := C.FindFirstFile(path_files.to_wide(), &find_file_data) @@ -70,7 +82,7 @@ pub fn ls(path string) []string { } C.FindClose(h_find_files) return dir_files -} +} pub fn dir_exists(path string) bool { _path := path.replace('/', '\\') @@ -82,7 +94,7 @@ pub fn dir_exists(path string) bool { return true } return false -} +} // mkdir creates a new directory with the specified path. pub fn mkdir(path string) { @@ -93,7 +105,7 @@ pub fn mkdir(path string) { mkdir(_path.all_before_last('\\')) } C.CreateDirectory(_path.to_wide(), 0) -} +} // Ref - https://docs.microsoft.com/en-us/cpp/c-runtime-library/reference/get-osfhandle?view=vs-2019 // get_file_handle retrieves the operating-system file handle that is associated with the specified file descriptor. @@ -108,10 +120,10 @@ pub fn get_file_handle(path string) HANDLE { } // Ref - https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulefilenamea -// get_module_filename retrieves the fully qualified path for the file that contains the specified module. +// get_module_filename retrieves the fully qualified path for the file that contains the specified module. // The module must have been loaded by the current process. pub fn get_module_filename(handle HANDLE) ?string { - mut sz := int(4096) // Optimized length + mut sz := int(4096) // Optimized length mut buf := &u16(malloc(4096)) for { status := C.GetModuleFileName(handle, &buf, sz) @@ -142,15 +154,15 @@ const ( SUBLANG_NEUTRAL = 0x00 SUBLANG_DEFAULT = 0x01 LANG_NEUTRAL = (SUBLANG_NEUTRAL) -) +) // Ref - https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--12000-15999- const ( MAX_ERROR_CODE = 15841 // ERROR_API_UNAVAILABLE ) -// ptr_win_get_error_msg return string (voidptr) -// representation of error, only for windows. +// ptr_win_get_error_msg return string (voidptr) +// representation of error, only for windows. fn ptr_win_get_error_msg(code u32) voidptr { mut buf := voidptr(0) // Check for code overflow diff --git a/vlib/strings/builder.v b/vlib/strings/builder.v index 120c00e6a9..a36037c231 100644 --- a/vlib/strings/builder.v +++ b/vlib/strings/builder.v @@ -2,10 +2,10 @@ // Use of this source code is governed by an MIT license // that can be found in the LICENSE file. -module strings +module strings struct Builder { -mut: +mut: buf []byte pub: len int @@ -13,19 +13,19 @@ pub: pub fn new_builder(initial_size int) Builder { return Builder { - buf: _make(0, initial_size, sizeof(byte)) + //buf: _make(0, initial_size, sizeof(byte)) } } pub fn (b mut Builder) write(s string) { b.buf._push_many(s.str, s.len) - //b.buf << []byte(s) // TODO + //b.buf << []byte(s) // TODO b.len += s.len } pub fn (b mut Builder) writeln(s string) { b.buf._push_many(s.str, s.len) - //b.buf << []byte(s) // TODO + //b.buf << []byte(s) // TODO b.buf << `\n` b.len += s.len + 1 } diff --git a/vlib/strings/similarity.v b/vlib/strings/similarity.v index 5fb0d1ac00..dfa5596665 100644 --- a/vlib/strings/similarity.v +++ b/vlib/strings/similarity.v @@ -1,9 +1,11 @@ module strings +#-js + // use levenshtein distance algorithm to calculate // the distance between between two strings (lower is closer) pub fn levenshtein_distance(a, b string) int { - mut f := [int(0); b.len+1] + mut f := [0].repeat2(b.len+1) for ca in a { mut j := 1 mut fj1 := f[0] diff --git a/vlib/strings/strings.v b/vlib/strings/strings.v index 919e831cd7..f7180ab493 100644 --- a/vlib/strings/strings.v +++ b/vlib/strings/strings.v @@ -1,11 +1,11 @@ -module strings +module strings pub fn repeat(c byte, n int) string { if n <= 0 { return '' } - mut arr := malloc(n + 1) - //mut arr := [byte(0); n + 1] + //mut arr := malloc(n + 1) + mut arr := [byte(0)].repeat2(n + 1) for i := 0; i < n; i++ { arr[i] = c }