JavaSript backend (early stage)

pull/1989/head
Alexander Medvednikov 2019-09-14 23:48:30 +03:00
parent 982a162fbf
commit 5cc81b91cb
31 changed files with 1818 additions and 584 deletions

View File

@ -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)
}

View File

@ -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()

View File

@ -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() {
}
'
)

View File

@ -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)
}

View File

@ -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<Bar>()
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

462
compiler/gen_c.v 100644
View File

@ -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)')
}
}

215
compiler/gen_js.v 100644
View File

@ -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')
}
}

View File

@ -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

View File

@ -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 {

View File

@ -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')

View File

@ -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*

View File

@ -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)'

View File

@ -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'

View File

@ -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(', ')
}

View File

@ -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++ {

View File

@ -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]
}

View File

@ -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) {
}

View File

@ -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)
}

View File

@ -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 <float.h>
#include <math.h>
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
}

View File

@ -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()
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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
}

View File

@ -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"')

View File

@ -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 {

View File

@ -4,8 +4,8 @@ module os
#include <winsock2.h>
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

View File

@ -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
}

View File

@ -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]

View File

@ -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
}