1
0
mirror of https://github.com/vlang/v.git synced 2023-08-10 21:13:21 +03:00

JavaSript backend (early stage)

This commit is contained in:
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() { fn (v mut V) cc() {
// build any thirdparty obj files // build any thirdparty obj files
v.build_thirdparty_obj_files() v.build_thirdparty_obj_files()
// Just create a C/JavaScript file and exit
// Just create a c file and exit if v.out_name.ends_with('.c') || v.out_name.ends_with('.js') {
if v.out_name.ends_with('.c') { // 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) os.mv(v.out_name_c, v.out_name)
exit(0) exit(0)
} }

View File

@ -6,7 +6,6 @@ module main
import os import os
import strings import strings
import time
struct CGen { struct CGen {
out os.File out os.File
@ -275,6 +274,7 @@ fn os_name_to_ifdef(name string) string {
case 'netbsd': return '__NetBSD__' case 'netbsd': return '__NetBSD__'
case 'dragonfly': return '__DragonFly__' case 'dragonfly': return '__DragonFly__'
case 'msvc': return '_MSC_VER' case 'msvc': return '_MSC_VER'
case 'js': return '_VJS'
} }
cerror('bad os ifdef name "$name"') cerror('bad os ifdef name "$name"')
return '' return ''
@ -295,7 +295,7 @@ fn platform_postfix_to_ifdefguard(name string) string {
// C struct definitions, ordered // C struct definitions, ordered
// Sort the types, make sure types that are referenced by other types // Sort the types, make sure types that are referenced by other types
// are added before them. // 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 types := []Type // structs that need to be sorted
mut builtin_types := []Type // builtin types mut builtin_types := []Type // builtin types
// builtin types need to be on top // 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) 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 // sort structs by dependant fields
fn sort_structs(types []Type) []Type { fn sort_structs(types []Type) []Type {
mut dep_graph := new_dep_graph() 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,10 +175,24 @@ fn (p mut Parser) chash() {
println('v script') println('v script')
//p.v_script = true //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 { else {
$if !js {
if !p.can_chash { if !p.can_chash {
println('hash="$hash"')
println(hash.starts_with('include'))
p.error('bad token `#` (embedding C code is no longer supported)') p.error('bad token `#` (embedding C code is no longer supported)')
} }
}
p.genln(hash) 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 { fn new_fn(mod string, is_public bool) Fn {
return Fn { return Fn {
mod: mod mod: mod
local_vars: [Var{} ; MaxLocalVars] local_vars: [Var{}].repeat2(MaxLocalVars)
is_public: is_public is_public: is_public
} }
} }
@ -286,7 +286,7 @@ fn (p mut Parser) fn_decl() {
} }
// Generate `User_register()` instead of `register()` // Generate `User_register()` instead of `register()`
// Internally it's still stored as "register" in type User // 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 // Start generation of the function body
skip_main_in_test := f.name == 'main' && p.pref.is_test 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 { 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 { else {
p.genln('$dll_export_linkage$typ $fn_name_cgen($str_args) {') p.gen_fn_decl(f, typ, str_args)
} }
} }
if is_fn_header { if is_fn_header {
@ -451,7 +451,7 @@ _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). // 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' { if p.pref.is_prof && f.name != 'main' && f.name != 'time__ticks' {
p.genln('double _PROF_START = time__ticks();//$f.name') 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 { 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;'
} }
@ -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 mut did_gen_something := false
for i, arg in f.args { for i, arg in f.args {
arg_struct += '$arg.typ $arg.name ;'// Add another field (arg) to the tmp struct definition 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 { 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 { if i < f.args.len - 1 {
str_args += ',' str_args += ','
} }
continue continue
} }
// Set the struct values (args) // Set the struct values (args)
p.genln('$tmp_struct -> $arg.name = ') p.genln('$tmp_struct $dot_ptr $arg.name = ')
p.expression() p.expression()
p.genln(';') p.genln(';')
if i < f.args.len - 1 { 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 ;' arg_struct += '} $arg_struct_name ;'
// Also register the wrapper, so we can use the original function without modifying it // 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_name := '${fn_name}_thread_wrapper'
wrapper_text := 'void* $wrapper_name($arg_struct_name * arg) {$fn_name( /*f*/$str_args ); }' 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) 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.check(.rpar)
} }
// p.tok == fn_name
fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { 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.is_public && !f.is_c && !p.pref.is_test && !f.is_interface && f.mod != p.mod {
if f.name == 'contains' { 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()`') 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() p.next()
mut gen_type := '' mut gen_type := ''
if p.tok == .lt { 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, // If we have a method placeholder,
// we need to preappend "method(receiver, ...)" // we need to preappend "method(receiver, ...)"
else { else {
mut method_call := '${cgen_name}('
receiver := f.args.first() receiver := f.args.first()
//println('r=$receiver.typ RT=$receiver_type')
if receiver.is_mut && !p.expr_var.is_mut { 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`') p.error('`$p.expr_var.name` is immutable, declare it with `mut`')
} }
if !p.expr_var.is_changed { if !p.expr_var.is_changed {
p.mark_var_changed(p.expr_var) p.mark_var_changed(p.expr_var)
} }
// if receiver is key_mut or a ref (&), generate & for the first arg p.gen_method_call(receiver_type, f.typ, cgen_name, receiver, method_ph)
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')
} }
// foo<Bar>() // foo<Bar>()
p.fn_call_args(mut f) p.fn_call_args(mut f)
@ -778,7 +763,7 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn {
return f return f
} }
// add debug information to panic when -debug arg is passed // 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_', '.') mod_name := p.mod.replace('_dot_', '.')
fn_name := p.cur_fn.name.replace('${p.mod}__', '') fn_name := p.cur_fn.name.replace('${p.mod}__', '')
file_path := p.file_path.replace('\\', '\\\\') // escape \ 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') // println('$i) arg=$arg.name')
// Skip receiver, because it was already generated in the expression // Skip receiver, because it was already generated in the expression
if i == 0 && f.is_method { if i == 0 && f.is_method {
if f.args.len > 1 { if f.args.len > 1 && !p.is_js {
p.gen(',') p.gen(',')
} }
continue continue
@ -836,23 +821,27 @@ fn (p mut Parser) fn_call_args(f mut Fn) &Fn {
p.expected_type = arg.typ p.expected_type = arg.typ
typ := p.bool_expression() typ := p.bool_expression()
// Optimize `println`: replace it with `printf` to avoid extra allocations and // 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 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' { if i == 0 && (f.name == 'println' || f.name == 'print') && typ != 'string' && typ != 'void' {
T := p.table.find_type(typ) T := p.table.find_type(typ)
$if !windows { $if !windows {
$if !js {
fmt := p.typ_to_fmt(typ, 0) fmt := p.typ_to_fmt(typ, 0)
if fmt != '' { if fmt != '' {
p.cgen.resetln(p.cgen.cur_line.replace(f.name + ' (', '/*opt*/printf ("' + fmt + '\\n", ')) p.cgen.resetln(p.cgen.cur_line.replace(f.name + ' (', '/*opt*/printf ("' + fmt + '\\n", '))
continue continue
} }
} }
}
if typ.ends_with('*') { if typ.ends_with('*') {
p.cgen.set_placeholder(ph, 'ptr_str(') p.cgen.set_placeholder(ph, 'ptr_str(')
p.gen(')') p.gen(')')
continue continue
} }
// Make sure this type has a `str()` method // Make sure this type has a `str()` method
$if !js {
if !T.has_method('str') { if !T.has_method('str') {
// Arrays have automatic `str()` methods // Arrays have automatic `str()` methods
if T.name.starts_with('array_') { 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.cgen.set_placeholder(ph, '${typ}_str(')
p.gen(')') p.gen(')')
}
continue continue
} }
got := typ got := typ

462
compiler/gen_c.v Normal file
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 Normal file
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,7 +35,7 @@ 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 mut cgen := v.cgen
// Hot code reloading // Hot code reloading

View File

@ -27,7 +27,8 @@ enum BuildMode {
} }
const ( 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/' ModPath = os.home_dir() + '/.vmodules/'
) )
@ -40,6 +41,7 @@ enum OS {
netbsd netbsd
dragonfly dragonfly
msvc msvc
js
} }
enum Pass { enum Pass {
@ -208,7 +210,7 @@ fn (v mut V) compile() {
println(v.files) println(v.files)
} }
v.add_v_files_to_compile() 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('all .v files:')
println(v.files) println(v.files)
} }
@ -220,8 +222,15 @@ fn (v mut V) compile() {
// Main pass // Main pass
cgen.pass = Pass.main cgen.pass = Pass.main
if v.pref.is_debug { if v.pref.is_debug {
$if js {
cgen.genln('const VDEBUG = 1;\n')
} $else {
cgen.genln('#define VDEBUG (1)') cgen.genln('#define VDEBUG (1)')
} }
}
if v.os == .js {
cgen.genln('#define _VJS (1) ')
}
if v.pref.building_v { if v.pref.building_v {
cgen.genln('#ifndef V_COMMIT_HASH') cgen.genln('#ifndef V_COMMIT_HASH')
@ -229,7 +238,11 @@ fn (v mut V) compile() {
cgen.genln('#endif') cgen.genln('#endif')
} }
$if js {
cgen.genln(js_headers)
} $else {
cgen.genln(CommonCHeaders) cgen.genln(CommonCHeaders)
}
v.generate_hotcode_reloading_declarations() v.generate_hotcode_reloading_declarations()
@ -251,7 +264,9 @@ fn (v mut V) compile() {
// `/usr/bin/ld: multiple definition of 'total_m'` // `/usr/bin/ld: multiple definition of 'total_m'`
// TODO // TODO
//cgen.genln('i64 total_m = 0; // For counting total RAM allocated') //cgen.genln('i64 total_m = 0; // For counting total RAM allocated')
if v.pref.is_test {
cgen.genln('int g_test_ok = 1; ') cgen.genln('int g_test_ok = 1; ')
}
if 'json' in v.table.imports { if 'json' in v.table.imports {
cgen.genln(' cgen.genln('
#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key)) #define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key))
@ -261,7 +276,7 @@ fn (v mut V) compile() {
if '-debug_alloc' in os.args { if '-debug_alloc' in os.args {
cgen.genln('#define DEBUG_ALLOC 1') cgen.genln('#define DEBUG_ALLOC 1')
} }
cgen.genln('/*================================== FNS =================================*/') //cgen.genln('/*================================== FNS =================================*/')
cgen.genln('this line will be replaced with definitions') cgen.genln('this line will be replaced with definitions')
defs_pos := cgen.lines.len - 1 defs_pos := cgen.lines.len - 1
for file in v.files { for file in v.files {
@ -276,12 +291,16 @@ fn (v mut V) compile() {
v.log('Done parsing.') v.log('Done parsing.')
// Write everything // Write everything
mut d := strings.new_builder(10000)// Avoid unnecessary allocations mut d := strings.new_builder(10000)// Avoid unnecessary allocations
$if !js {
d.writeln(cgen.includes.join_lines()) d.writeln(cgen.includes.join_lines())
d.writeln(cgen.typedefs.join_lines()) d.writeln(cgen.typedefs.join_lines())
d.writeln(v.c_type_definitions()) d.writeln(v.type_definitions())
d.writeln('\nstring _STR(const char*, ...);\n') d.writeln('\nstring _STR(const char*, ...);\n')
d.writeln('\nstring _STR_TMP(const char*, ...);\n') d.writeln('\nstring _STR_TMP(const char*, ...);\n')
d.writeln(cgen.fns.join_lines()) d.writeln(cgen.fns.join_lines()) // fn definitions
} $else {
d.writeln(v.type_definitions())
}
d.writeln(cgen.consts.join_lines()) d.writeln(cgen.consts.join_lines())
d.writeln(cgen.thread_args.join_lines()) d.writeln(cgen.thread_args.join_lines())
if v.pref.is_prof { if v.pref.is_prof {
@ -290,23 +309,24 @@ fn (v mut V) compile() {
} }
dd := d.str() dd := d.str()
cgen.lines[defs_pos] = dd// TODO `def.str()` doesn't compile cgen.lines[defs_pos] = dd// TODO `def.str()` doesn't compile
v.generate_main() v.generate_main()
v.generate_hot_reload_code()
v.generate_hotcode_reloading_code()
cgen.save()
if v.pref.is_verbose { if v.pref.is_verbose {
v.log('flags=') v.log('flags=')
for flag in v.get_os_cflags() { for flag in v.get_os_cflags() {
println(' * ' + flag.format()) println(' * ' + flag.format())
} }
} }
$if js {
cgen.genln('main();')
}
cgen.save()
v.cc() v.cc()
} }
fn (v mut V) generate_main() { fn (v mut V) generate_main() {
mut cgen := v.cgen mut cgen := v.cgen
$if js { return }
// if v.build_mode in [.default, .embed_vlib] { // if v.build_mode in [.default, .embed_vlib] {
if v.pref.build_mode == .default_mode || v.pref.build_mode == .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 { if file.ends_with('_mac.v') && v.os != .mac {
continue continue
} }
if file.ends_with('_js.v') {
continue
}
if file.ends_with('_nix.v') && (v.os == .windows || v.os == .msvc) { if file.ends_with('_nix.v') && (v.os == .windows || v.os == .msvc) {
continue continue
} }
if file.ends_with('_js.v') && v.os != .js {
continue
}
if file.ends_with('_c.v') && v.os == .js {
continue
}
res << '$dir/$file' res << '$dir/$file'
} }
return res return res
@ -491,7 +520,7 @@ fn (v mut V) add_v_files_to_compile() {
dir = dir.all_before('/') dir = dir.all_before('/')
} }
else { else {
// Add .v files from the directory being compied // Add .v files from the directory being compiled
files := v.v_files_from_dir(dir) files := v.v_files_from_dir(dir)
for file in files { for file in files {
user_files << file user_files << file
@ -579,6 +608,7 @@ fn (v mut V) add_v_files_to_compile() {
module_path = '$ModPath/vlib/$mod_p' module_path = '$ModPath/vlib/$mod_p'
} }
*/ */
if mod == 'builtin' { continue } // builtin files were already added
vfiles := v.v_files_from_dir(mod_path) vfiles := v.v_files_from_dir(mod_path)
for file in vfiles { for file in vfiles {
if !(file in v.files) { if !(file in v.files) {
@ -666,7 +696,7 @@ fn new_v(args[]string) &V {
//if args.contains('-lib') { //if args.contains('-lib') {
if joined_args.contains('build module ') { if joined_args.contains('build module ') {
build_mode = .build_module build_mode = .build_module
// v -lib ~/v/os => os.o // v build module ~/v/os => os.o
//mod = os.dir(dir) //mod = os.dir(dir)
mod = if dir.contains(os.PathSeparator) { mod = if dir.contains(os.PathSeparator) {
dir.all_after(os.PathSeparator) dir.all_after(os.PathSeparator)
@ -741,8 +771,11 @@ fn new_v(args[]string) &V {
case 'netbsd': _os = .netbsd case 'netbsd': _os = .netbsd
case 'dragonfly': _os = .dragonfly case 'dragonfly': _os = .dragonfly
case 'msvc': _os = .msvc case 'msvc': _os = .msvc
case 'js': _os = .js
} }
} }
//println('OS=$_os')
builtin := 'builtin.v'
builtins := [ builtins := [
'array.v', 'array.v',
'string.v', 'string.v',
@ -752,6 +785,7 @@ fn new_v(args[]string) &V {
'map.v', 'map.v',
'option.v', 'option.v',
] ]
//println(builtins)
// Location of all vlib files // Location of all vlib files
vroot := os.dir(os.executable()) vroot := os.dir(os.executable())
//println('VROOT=$vroot') //println('VROOT=$vroot')
@ -768,13 +802,16 @@ fn new_v(args[]string) &V {
//if !out_name.contains('builtin.o') { //if !out_name.contains('builtin.o') {
for builtin in builtins { for builtin in builtins {
mut f := '$vroot/vlib/builtin/$builtin' 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 // In default mode we use precompiled vlib.o, point to .vh files with signatures
if build_mode == .default_mode || build_mode == .build_module { if build_mode == .default_mode || build_mode == .build_module {
//f = '$TmpPath/vlib/builtin/${builtin}h' //f = '$TmpPath/vlib/builtin/${builtin}h'
} }
files << f files << f
} }
//}
mut cflags := '' mut cflags := ''
for ci, cv in args { for ci, cv in args {

View File

@ -65,6 +65,7 @@ mut:
cur_gen_type string // "App" to replace "T" in current generic function cur_gen_type string // "App" to replace "T" in current generic function
is_vweb bool is_vweb bool
is_sql bool is_sql bool
is_js bool
sql_i int // $1 $2 $3 sql_i int // $1 $2 $3
sql_params []string // ("select * from users where id = $1", ***"100"***) sql_params []string // ("select * from users where id = $1", ***"100"***)
sql_types []string // int, string and so on; see sql_params 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 vroot: v.vroot
} }
$if js {
p.is_js = true
}
if p.pref.is_repl { if p.pref.is_repl {
p.scanner.should_print_line_on_error = false p.scanner.should_print_line_on_error = false
@ -1245,7 +1249,7 @@ fn ($v.name mut $v.typ) $p.cur_fn.name (...) {
p.gen(' = ') p.gen(' = ')
} }
case Token.plus_assign: 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 += '!'` p.gen('= string_add($v.name, ')// TODO can't do `foo.bar += '!'`
} }
else { else {
@ -1275,7 +1279,7 @@ fn ($v.name mut $v.typ) $p.cur_fn.name (...) {
p.scanner.line_nr-- p.scanner.line_nr--
p.error('cannot use type `$expr_type` as type `$p.assigned_type` in assignment') 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.gen(')')
} }
// p.assigned_var = '' // 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.error('variable names cannot contain uppercase letters, use snake_case instead')
} }
p.check_space(.decl_assign) // := p.check_space(.decl_assign) // :=
// Generate expression to tmp because we need its type first typ := p.gen_var_decl(name, is_static)
// [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
}
p.register_var(Var { p.register_var(Var {
name: name name: name
typ: typ typ: typ
@ -1349,18 +1322,6 @@ fn (p mut Parser) var_decl() {
is_alloc: p.is_alloc is_alloc: p.is_alloc
}) })
//if p.is_alloc { println('REG VAR IS ALLOC $name') } //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.var_decl_name = ''
p.is_empty_c_struct_init = false 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 in [ .eq, .gt, .lt, .le, .ge, .ne] {
if tok == .eq || tok == .gt || tok == .lt || tok == .le || tok == .ge || tok == .ne { if tok == .eq || tok == .gt || tok == .lt || tok == .le || tok == .ge || tok == .ne {
p.fgen(' ${p.tok.str()} ') p.fgen(' ${p.tok.str()} ')
if is_str { if is_str && !p.is_js {
p.gen(',') p.gen(',')
} }
else if p.is_sql && tok == .eq { else if p.is_sql && tok == .eq {
@ -1439,7 +1400,7 @@ fn (p mut Parser) bterm() string {
p.check_types(p.expression(), typ) p.check_types(p.expression(), typ)
} }
typ = 'bool' typ = 'bool'
if is_str { //&& !p.is_sql { if is_str && !p.is_js { //&& !p.is_sql {
p.gen(')') p.gen(')')
switch tok { switch tok {
case Token.eq: p.cgen.set_placeholder(ph, 'string_eq(') case Token.eq: p.cgen.set_placeholder(ph, 'string_eq(')
@ -1575,7 +1536,8 @@ fn (p mut Parser) name_expr() string {
name += '*' name += '*'
} }
p.gen('(') p.gen('(')
mut typ := p.cast(name) mut typ := name
p.cast(name)
p.gen(')') p.gen(')')
for p.tok == .dot { for p.tok == .dot {
typ = p.dot(typ, ph) typ = p.dot(typ, ph)
@ -1604,7 +1566,8 @@ fn (p mut Parser) name_expr() string {
if name == 'T' { if name == 'T' {
name = p.cur_gen_type 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 { if is_c {
@ -1698,7 +1661,7 @@ fn (p mut Parser) name_expr() string {
func: f func: f
} }
p.table.register_type2(fn_typ) p.table.register_type2(fn_typ)
p.gen(p.table.cgen_name(f)) p.gen(p.table.fn_gen_name(f))
p.next() p.next()
return f.typ_str() //'void*' return f.typ_str() //'void*'
} }
@ -1858,7 +1821,7 @@ fn (p mut Parser) dot(str_typ string, method_ph int) string {
} }
mut dot := '.' mut dot := '.'
if str_typ.ends_with('*') || str_typ == 'FT_Face' { // TODO fix C ptr typedefs if str_typ.ends_with('*') || str_typ == 'FT_Face' { // TODO fix C ptr typedefs
dot = '->' dot = dot_ptr
} }
// field // field
if has_field { if has_field {
@ -1917,6 +1880,27 @@ struct $f.parent_fn {
return method.typ 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 { fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
mut typ := typ_ mut typ := typ_
// a[0] // a[0]
@ -1974,34 +1958,10 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
} }
} }
if is_arr { if is_arr {
//p.fgen('[')
// array_int a; a[0]
// type is "array_int", need "int"
// typ = typ.replace('array_', '')
if is_arr0 { if is_arr0 {
if p.fileis('int_test') {
println('\nRRRR0 $typ')
}
typ = typ.right(6) 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 // map is tricky
// need to replace "m[key] = val" with "tmp = val; map_set(&m, key, &tmp)" // 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 { if is_indexer && is_str && !p.builtin_mod {
p.error('strings are immutable') p.error('strings are immutable')
} }
assign_pos := p.cgen.cur_line.len
is_cao := p.tok .assign
p.assigned_type = typ p.assigned_type = typ
p.expected_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)) 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) { if is_indexer && (is_map || is_arr) {
// `a[0] = 7` p.gen_array_set(typ, is_ptr, is_map, fn_ph, assign_pos, is_cao)
// 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 })')
} }
return typ return typ
} }
@ -2095,52 +2026,26 @@ fn (p mut Parser) index_expr(typ_ string, fn_ph int) string {
// } // }
// m[key]. no =, just a getter // m[key]. no =, just a getter
else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer { else if (is_map || is_arr || (is_str && !p.builtin_mod)) && is_indexer {
// Erase var name we generated earlier: "int a = m, 0" p.index_get(typ, fn_ph, IndexCfg{
// "m, 0" gets killed since we need to start from scratch. It's messy. is_arr: is_arr
// "m, 0" is an index expression, save it before deleting and insert later in map_get() is_map: is_map
mut index_expr := '' is_ptr: is_ptr
if p.cgen.is_tmp { is_str: is_str
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);')
}
} }
// else if is_arr && is_indexer{} // else if is_arr && is_indexer{}
return typ return typ
} }
struct IndexCfg {
is_map bool
is_str bool
is_ptr bool
is_arr bool
is_arr0 bool
}
// returns resulting type // returns resulting type
fn (p mut Parser) expression() string { fn (p mut Parser) expression() string {
if p.scanner.file_path.contains('test_test') { if p.scanner.file_path.contains('test_test') {
@ -2151,7 +2056,7 @@ fn (p mut Parser) expression() string {
ph := p.cgen.add_placeholder() ph := p.cgen.add_placeholder()
mut typ := p.term() mut typ := p.term()
is_str := typ=='string' is_str := typ=='string'
// a << b ==> array2_push(&a, b) // `a << b` ==> `array_push(&a, b)`
if p.tok == .left_shift { if p.tok == .left_shift {
if typ.contains('array_') { if typ.contains('array_') {
// Can't pass integer literal, because push requires a void* // Can't pass integer literal, because push requires a void*
@ -2171,19 +2076,7 @@ fn (p mut Parser) expression() string {
} }
expr_type := p.expression() expr_type := p.expression()
// Two arrays of the same type? // Two arrays of the same type?
push_array := typ == expr_type p.gen_array_push(ph, typ, expr_type, tmp, tmp_typ)
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)')
}
return 'void' return 'void'
} }
else { else {
@ -2236,12 +2129,12 @@ fn (p mut Parser) expression() string {
tok_op := p.tok tok_op := p.tok
is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ) is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ)
p.check_space(p.tok) 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.cgen.set_placeholder(ph, 'string_add(')
p.gen(',') p.gen(',')
} }
// 3 + 4 // 3 + 4
else if is_num { else if is_num || p.is_js {
if typ == 'void*' { if typ == 'void*' {
// Msvc errors on void* pointer arithmatic // Msvc errors on void* pointer arithmatic
// ... So cast to byte* and then do the add // ... 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) p.check_types(p.term(), typ)
if is_str && tok_op == .plus { if is_str && tok_op == .plus && !p.is_js {
p.gen(')') p.gen(')')
} }
// Make sure operators are used with correct types // Make sure operators are used with correct types
@ -2535,12 +2428,18 @@ fn (p mut Parser) string_expr() {
else if p.is_sql { else if p.is_sql {
p.gen('\'$str\'') p.gen('\'$str\'')
} }
else if p.is_js {
p.gen('"$f"')
}
else { else {
p.gen('tos2((byte*)"$f")') p.gen('tos2((byte*)"$f")')
} }
p.next() p.next()
return return
} }
$if js {
p.error('js backend does not support string formatting yet')
}
// tmp := p.get_tmp() // tmp := p.get_tmp()
p.is_alloc = true // $ interpolation means there's allocation p.is_alloc = true // $ interpolation means there's allocation
mut args := '"' mut args := '"'
@ -2601,7 +2500,7 @@ fn (p mut Parser) string_expr() {
if fspec == 's' { if fspec == 's' {
//println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ') //println('custom str F=$cformat | format_specifier: "$fspec" | typ: $typ ')
if typ != 'string' { 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' args = args.all_before_last('${val}.len, ${val}.str') + '${val}.str'
} }
@ -2744,7 +2643,7 @@ fn (p mut Parser) array_init() string {
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 { for p.tok != .rsbr {
val_typ := p.bool_expression() val_typ := p.bool_expression()
// Get type of the first expression // Get the type of the first expression
if i == 0 { if i == 0 {
typ = val_typ typ = val_typ
// fixed width array initialization? (`arr := [20]byte`) // fixed width array initialization? (`arr := [20]byte`)
@ -2755,19 +2654,17 @@ fn (p mut Parser) array_init() string {
if !nextc.is_space() { if !nextc.is_space() {
p.check(.rsbr) p.check(.rsbr)
array_elem_typ := p.get_type() array_elem_typ := p.get_type()
if p.table.known_type(array_elem_typ) { if !p.table.known_type(array_elem_typ) {
p.error('bad type `$array_elem_typ`')
}
p.cgen.resetln('') p.cgen.resetln('')
p.gen('{0}') //p.gen('{0}')
p.is_alloc = false p.is_alloc = false
if is_const_len { if is_const_len {
return '[${p.mod}__$lit]$array_elem_typ' return '[${p.mod}__$lit]$array_elem_typ'
} }
return '[$lit]$array_elem_typ' return '[$lit]$array_elem_typ'
} }
else {
p.error('bad type `$array_elem_typ`')
}
}
} }
} }
if val_typ != typ { if val_typ != typ {
@ -2783,10 +2680,11 @@ fn (p mut Parser) array_init() string {
i++ i++
// Repeat (a = [0;5] ) // Repeat (a = [0;5] )
if i == 1 && p.tok == .semicolon { if i == 1 && p.tok == .semicolon {
p.warn('`[0 ; len]` syntax was removed. Use `[0].repeat(len)` instead')
p.check_space(.semicolon) p.check_space(.semicolon)
val := p.cgen.cur_line.right(pos) val := p.cgen.cur_line.right(pos)
p.cgen.resetln(p.cgen.cur_line.left(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.check_types(p.bool_expression(), 'int')
p.gen(', sizeof($typ) )') p.gen(', sizeof($typ) )')
p.check(.rsbr) p.check(.rsbr)
@ -2801,7 +2699,6 @@ fn (p mut Parser) array_init() string {
if p.tok == .name && i == 0 { if p.tok == .name && i == 0 {
// vals.len == 0 { // vals.len == 0 {
typ = p.get_type() typ = p.get_type()
// println('.key_goT TYP after [] $typ')
} }
// ! after array => no malloc and no copy // ! after array => no malloc and no copy
no_alloc := p.tok == .not no_alloc := p.tok == .not
@ -2829,56 +2726,20 @@ fn (p mut Parser) array_init() string {
// if ptr { // if ptr {
// typ += '_ptr" // typ += '_ptr"
// } // }
mut new_arr := 'new_array_from_c_array' p.gen_array_init(typ, no_alloc, new_arr_ph, i)
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 { ')
//}
}
typ = 'array_$typ' typ = 'array_$typ'
p.register_array(typ) p.register_array(typ)
return 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 p.is_struct_init = true
t := p.table.find_type(typ) t := p.table.find_type(typ)
// TODO hack. If it's a C type, we may need to add "struct" before declaration: if p.gen_struct_init(typ, t) { return typ }
// 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()
p.scanner.fmt_out.cut(typ.len) p.scanner.fmt_out.cut(typ.len)
ptr := typ.contains('*') 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 !ptr {
if p.is_c_struct_init { if p.is_c_struct_init {
// `face := C.FT_Face{}` => `FT_Face face;` // `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 p.is_c_struct_init = false
} }
else { else {
p.gen('($typ) {') p.gen('($typ /*str init */) {')
} }
} }
else { else {
@ -2903,10 +2764,11 @@ fn (p mut Parser) struct_init(typ string, is_c_struct_init bool) string {
p.check(.rcbr) p.check(.rcbr)
return typ return typ
} }
p.gen('($no_star*)memdup(&($no_star) {')
p.is_alloc = true p.is_alloc = true
//println('setting is_alloc=true (ret $typ)') //println('setting is_alloc=true (ret $typ)')
p.gen('($t.name*)memdup(&($t.name) {')
} }
*/
mut did_gen_something := false mut did_gen_something := false
// Loop thru all struct init keys and assign values // Loop thru all struct init keys and assign values
// u := User{age:20, name:'bob'} // 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) f := t.find_field(field)
inited_fields << field inited_fields << field
p.gen('.$field = ') p.gen_struct_field_init(field)
p.check(.colon) p.check(.colon)
p.fspace() p.fspace()
p.check_types(p.bool_expression(), f.typ) 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 // init map fields
if field_typ.starts_with('map_') { 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 inited_fields << field.name
if i != t.fields.len - 1 { if i != t.fields.len - 1 {
p.gen(',') 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) def_val := type_default(field_typ)
if def_val != '' && def_val != '{0}' { 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 { if i != t.fields.len - 1 {
p.gen(',') 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('0')
} }
p.gen('}') p.gen('}')
if ptr { if ptr && !p.is_js {
p.gen(', sizeof($no_star))') p.gen(', sizeof($t.name))')
} }
p.check(.rcbr) p.check(.rcbr)
p.is_struct_init = false p.is_struct_init = false
p.is_c_struct_init = false
return typ return typ
} }
// `f32(3)` // `f32(3)`
// tok is `f32` or `)` if `(*int)(ptr)` // 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 { fn (p mut Parser) get_tmp() string {
p.tmp_cnt++ p.tmp_cnt++
@ -3173,6 +2980,7 @@ fn (p mut Parser) for_st() {
next_tok := p.peek() next_tok := p.peek()
//debug := p.scanner.file_path.contains('r_draw') //debug := p.scanner.file_path.contains('r_draw')
p.open_scope() p.open_scope()
i_type := if p.is_js { 'var' } else { 'int' }
if p.tok == .lcbr { if p.tok == .lcbr {
// Infinite loop // Infinite loop
p.gen('while (1) {') p.gen('while (1) {')
@ -3207,11 +3015,15 @@ fn (p mut Parser) for_st() {
} }
// for i, val in array // for i, val in array
else if p.peek() == .comma { else if p.peek() == .comma {
// for i, val in array { ==> /*
// `for i, val in array {`
// array_int tmp = array; ==>
// for (int i = 0; i < tmp.len; i++) { ```
// int val = tmp[i]; array_int tmp = array;
for (int i = 0; i < tmp.len; i++) {
int val = tmp[i];
```
*/
i := p.check_name() i := p.check_name()
p.check(.comma) p.check(.comma)
val := p.check_name() val := p.check_name()
@ -3228,7 +3040,11 @@ fn (p mut Parser) for_st() {
p.error('cannot range over type `$typ`') p.error('cannot range over type `$typ`')
} }
expr := p.cgen.end_tmp() expr := p.cgen.end_tmp()
if p.is_js {
p.genln('var $tmp = $expr;')
} else {
p.genln('$typ $tmp = $expr;') p.genln('$typ $tmp = $expr;')
}
pad := if is_arr { 6 } else { 4 } pad := if is_arr { 6 } else { 4 }
var_typ := if is_str { 'byte' } else { typ.right(pad) } var_typ := if is_str { 'byte' } else { typ.right(pad) }
// typ = strings.Replace(typ, "_ptr", "*", -1) // typ = strings.Replace(typ, "_ptr", "*", -1)
@ -3247,9 +3063,9 @@ fn (p mut Parser) for_st() {
is_mut: true is_mut: true
is_changed: 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.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 { else if is_map {
i_var := Var { i_var := Var {
@ -3259,14 +3075,7 @@ fn (p mut Parser) for_st() {
is_changed: true is_changed: true
} }
p.register_var(i_var) p.register_var(i_var)
p.genln('array_string keys_$tmp = map_keys(& $tmp ); ') p.gen_for_map_header(i, tmp, var_typ, val, typ)
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);')
} }
else if is_str { else if is_str {
i_var := Var { i_var := Var {
@ -3276,9 +3085,7 @@ fn (p mut Parser) for_st() {
is_changed: true is_changed: true
} }
p.register_var(i_var) p.register_var(i_var)
p.genln('array_byte bytes_$tmp = string_bytes( $tmp );') p.gen_for_str_header(i, tmp, var_typ, val)
p.genln(';\nfor (int $i = 0; $i < $tmp .len; $i ++) {')
p.genln('$var_typ $val = (($var_typ *) bytes_$tmp . data)[$i];')
} }
} }
// `for val in vals` // `for val in vals`
@ -3305,7 +3112,11 @@ fn (p mut Parser) for_st() {
if !is_arr && !is_str && !is_range { if !is_arr && !is_str && !is_range {
p.error('cannot range over type `$typ`') p.error('cannot range over type `$typ`')
} }
if p.is_js {
p.genln('var $tmp = $expr;')
} else {
p.genln('$typ $tmp = $expr;') p.genln('$typ $tmp = $expr;')
}
// TODO var_type := if... // TODO var_type := if...
mut var_type := '' mut var_type := ''
if is_arr { if is_arr {
@ -3327,23 +3138,17 @@ fn (p mut Parser) for_st() {
} }
p.register_var(val_var) p.register_var(val_var)
i := p.get_tmp() 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 { 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 { 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;') p.genln('$var_type $val = $i;')
} }
} } else {
else {
// `for a < b {` // `for a < b {`
p.gen('while (') p.gen('while (')
p.check_types(p.bool_expression(), 'bool') p.check_types(p.bool_expression(), 'bool')

View File

@ -624,7 +624,7 @@ fn (s &Scanner) error(msg string) {
linestart := s.find_current_line_start_position() linestart := s.find_current_line_start_position()
lineend := s.find_current_line_end_position() lineend := s.find_current_line_end_position()
column := s.pos - linestart 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 ) line := s.text.substr( linestart, lineend )
// The pointerline should have the same spaces/tabs as the offending // The pointerline should have the same spaces/tabs as the offending
// line, so that it prints the ^ character exactly on the *same spot* // line, so that it prints the ^ character exactly on the *same spot*

View File

@ -129,6 +129,7 @@ fn (t Type) str() string {
const ( const (
CReserved = [ CReserved = [
'delete',
'exit', 'exit',
'unix', 'unix',
//'print', //'print',
@ -141,32 +142,21 @@ const (
// Full list of C reserved words, from: https://en.cppreference.com/w/c/keyword // Full list of C reserved words, from: https://en.cppreference.com/w/c/keyword
'auto', 'auto',
'break',
'case',
'char', 'char',
'const',
'continue',
'default', 'default',
'do', 'do',
'double', 'double',
'else',
'enum',
'extern', 'extern',
'float', 'float',
'for',
'goto',
'if',
'inline', 'inline',
'int', 'int',
'long', 'long',
'register', 'register',
'restrict', 'restrict',
'return',
'short', 'short',
'signed', 'signed',
'sizeof', 'sizeof',
'static', 'static',
'struct',
'switch', 'switch',
'typedef', 'typedef',
'union', 'union',
@ -461,7 +451,6 @@ fn (table &Table) type_has_method(typ &Type, name string) bool {
// TODO use `?Fn` // TODO use `?Fn`
fn (table &Table) find_method(typ &Type, name string) Fn { fn (table &Table) find_method(typ &Type, name string) Fn {
// println('TYPE HAS METHOD $name')
// method := typ.find_method(name) // method := typ.find_method(name)
t := table.typesmap[typ.name] t := table.typesmap[typ.name]
method := t.find_method(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 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 { fn (table &Table) is_interface(name string) bool {
if !(name in table.typesmap) { if !(name in table.typesmap) {
@ -700,41 +657,6 @@ fn (t &Table) find_const(name string) Var {
return 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' // ('s', 'string') => 'string s'
// ('nums', '[20]byte') => 'byte nums[20]' // ('nums', '[20]byte') => 'byte nums[20]'
// ('myfn', 'fn(int) string') => 'string (*myfn)(int)' // ('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') ... }` // TODO remove once we have `enum Token { name('name') if('if') ... }`
fn build_token_str() []string { fn build_token_str() []string {
mut s := [''; NrTokens] mut s := [''].repeat2(NrTokens)
s[Token.keyword_beg] = '' s[Token.keyword_beg] = ''
s[Token.keyword_end] = '' s[Token.keyword_end] = ''
s[Token.eof] = 'eof' 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]`) // 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 { arr := array {
len: nr_repeats len: nr_repeats
cap: nr_repeats cap: nr_repeats
@ -71,6 +71,35 @@ fn array_repeat(val voidptr, nr_repeats, elm_size int) array {
return arr 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) { pub fn (a mut array) sort_with_compare(compare voidptr) {
C.qsort(a.data, a.len, a.element_size, compare) C.qsort(a.data, a.len, a.element_size, compare)
} }
@ -232,7 +261,9 @@ pub fn (a []string) str() string {
sb.write('[') sb.write('[')
for i := 0; i < a.len; i++ { for i := 0; i < a.len; i++ {
val := a[i] val := a[i]
sb.write('"$val"') sb.write('"')
sb.write(val)
sb.write('"')
if i < a.len - 1 { if i < a.len - 1 {
sb.write(', ') sb.write(', ')
} }

View File

@ -166,6 +166,7 @@ const (
) )
fn test_fixed() { fn test_fixed() {
/*
mut nums := [4]int mut nums := [4]int
assert nums[0] == 0 assert nums[0] == 0
assert nums[1] == 0 assert nums[1] == 0
@ -176,6 +177,7 @@ fn test_fixed() {
/////// ///////
nums2 := [N]int nums2 := [N]int
assert nums2[N - 1] == 0 assert nums2[N - 1] == 0
*/
} }
fn modify (numbers mut []int) { fn modify (numbers mut []int) {

View File

@ -213,7 +213,7 @@ pub fn (c byte) is_capital() bool {
} }
pub fn (b []byte) clone() []byte { 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++ { for i := 0; i < b.len; i++ {
res[i] = b[i] res[i] = b[i]
} }

130
vlib/builtin/js/array.v Normal file
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) {
}

35
vlib/builtin/js/builtin.v Normal file
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)
}

99
vlib/builtin/js/int.v Normal file
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
}

70
vlib/builtin/js/map.v Normal file
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()
}

31
vlib/builtin/js/option.v Normal file
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
}
}

318
vlib/builtin/js/string.v Normal file
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
}

27
vlib/builtin/js/utf8.v Normal file
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 { pub fn (m mut map) keys() []string {
mut keys := [''; m.size] mut keys := [''].repeat2(m.size)
if isnil(m.root) { if isnil(m.root) {
return keys return keys
} }

View File

@ -78,10 +78,9 @@ pub fn (s string) replace(rep, with string) string {
if s.len == 0 || rep.len == 0 { if s.len == 0 || rep.len == 0 {
return s return s
} }
// println('"$s" replace "$rep" with "$with" rep.len=$rep.len')
// TODO PERF Allocating ints is expensive. Should be a stack array // TODO PERF Allocating ints is expensive. Should be a stack array
// Get locations of all reps within this string // Get locations of all reps within this string
mut idxs := []int{} mut idxs := []int
mut rem := s mut rem := s
mut rstart := 0 mut rstart := 0
for { for {
@ -352,12 +351,32 @@ pub fn (s string) substr(start, end int) string {
return res return res
} }
pub fn (s string) index_old(p string) int {
if p.len > s.len {
return -1
}
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 // KMP search
pub fn (s string) index(p string) int { pub fn (s string) index(p string) int {
if p.len > s.len { if p.len > s.len {
return -1 return -1
} }
mut prefix := [0; p.len] mut prefix := [0].repeat2(p.len)
mut j := 0 mut j := 0
for i := 1; i < p.len; i++ { for i := 1; i < p.len; i++ {
for p[j] != p[i] && j > 0 { for p[j] != p[i] && j > 0 {
@ -383,6 +402,7 @@ pub fn (s string) index(p string) int {
return -1 return -1
} }
pub fn (s string) index_any(chars string) int { pub fn (s string) index_any(chars string) int {
for c in chars { for c in chars {
index := s.index(c.str()) index := s.index(c.str())
@ -874,7 +894,7 @@ pub fn (s string) bytes() []byte {
if s.len == 0 { if s.len == 0 {
return []byte return []byte
} }
mut buf := [byte(0); s.len] mut buf := [byte(0)].repeat2(s.len)
C.memcpy(buf.data, s.str, s.len) C.memcpy(buf.data, s.str, s.len)
return buf return buf
} }

View File

@ -69,25 +69,6 @@ fn C.ftell(fp voidptr) int
fn C.getenv(byteptr) byteptr fn C.getenv(byteptr) byteptr
fn C.sigaction(int, voidptr, int) 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 { fn parse_windows_cmd_line(cmd byteptr) []string {
s := string(cmd) s := string(cmd)
return s.split(' ') return s.split(' ')
@ -100,8 +81,7 @@ pub fn read_file(path string) ?string {
$if windows { $if windows {
fp = C._wfopen(path.to_wide(), mode.to_wide()) fp = C._wfopen(path.to_wide(), mode.to_wide())
} $else { } $else {
cpath := path.str fp = C.fopen(path.str, mode.str)
fp = C.fopen(cpath, mode.str)
} }
if isnil(fp) { if isnil(fp) {
return error('failed to open file "$path"') return error('failed to open file "$path"')

View File

@ -7,6 +7,14 @@ const (
PathSeparator = '/' 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. // get_error_msg return error code representation in string.
pub fn get_error_msg(code int) string { pub fn get_error_msg(code int) string {

View File

@ -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 { pub fn ls(path string) []string {
mut find_file_data := win32finddata{} mut find_file_data := win32finddata{}

View File

@ -13,7 +13,7 @@ pub:
pub fn new_builder(initial_size int) Builder { pub fn new_builder(initial_size int) Builder {
return Builder { return Builder {
buf: _make(0, initial_size, sizeof(byte)) //buf: _make(0, initial_size, sizeof(byte))
} }
} }

View File

@ -1,9 +1,11 @@
module strings module strings
#-js
// use levenshtein distance algorithm to calculate // use levenshtein distance algorithm to calculate
// the distance between between two strings (lower is closer) // the distance between between two strings (lower is closer)
pub fn levenshtein_distance(a, b string) int { 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 { for ca in a {
mut j := 1 mut j := 1
mut fj1 := f[0] mut fj1 := f[0]

View File

@ -4,8 +4,8 @@ pub fn repeat(c byte, n int) string {
if n <= 0 { if n <= 0 {
return '' return ''
} }
mut arr := malloc(n + 1) //mut arr := malloc(n + 1)
//mut arr := [byte(0); n + 1] mut arr := [byte(0)].repeat2(n + 1)
for i := 0; i < n; i++ { for i := 0; i < n; i++ {
arr[i] = c arr[i] = c
} }