From d32e538073e55c603992b5b65ebc837b01c28576 Mon Sep 17 00:00:00 2001 From: Alexander Medvednikov Date: Sat, 22 Jun 2019 20:20:28 +0200 Subject: [PATCH] V 0.0.12 open-source release --- base64/base64.v | 49 + builtin/array.v | 223 ++ builtin/builtin.v | 127 + builtin/int.v | 163 ++ builtin/map.v | 159 ++ builtin/option.v | 8 + builtin/smap.v | 97 + builtin/string.v | 814 +++++++ builtin/string_builder.v | 32 + builtin/utf8.v | 338 +++ compiler/cgen.v | 290 +++ compiler/fn.v | 848 +++++++ compiler/jsgen.v | 158 ++ compiler/main.v | 845 +++++++ compiler/parser.v | 3216 ++++++++++++++++++++++++++ compiler/scanner.v | 630 +++++ compiler/table.v | 644 ++++++ compiler/token.v | 265 +++ examples/concurrent_news_fetcher.v | 35 + examples/links_scraper.v | 15 + examples/tetris/tetris.v | 339 +++ examples/word_counter/word_counter.v | 58 + gg/gg.v | 712 ++++++ glfw/glfw.v | 303 +++ glm/glm.v | 314 +++ gx/gx.v | 77 + http/download_lin.v | 59 + http/download_mac.v | 64 + http/download_win.v | 18 + http/http.v | 94 + http/http_mac.v | 202 ++ http/http_win.v | 203 ++ json/json_primitives.v | 66 + math/math.v | 55 + os/os.v | 490 ++++ os/os_mac.v | 67 + os/os_win.v | 28 + rand/rand.v | 14 + stbi/stbi.v | 61 + time/time.v | 330 +++ time/time_lin.v | 15 + time/time_mac.v | 28 + time/time_win.v | 20 + 43 files changed, 12573 insertions(+) create mode 100644 base64/base64.v create mode 100644 builtin/array.v create mode 100644 builtin/builtin.v create mode 100644 builtin/int.v create mode 100644 builtin/map.v create mode 100644 builtin/option.v create mode 100644 builtin/smap.v create mode 100644 builtin/string.v create mode 100644 builtin/string_builder.v create mode 100644 builtin/utf8.v create mode 100644 compiler/cgen.v create mode 100644 compiler/fn.v create mode 100644 compiler/jsgen.v create mode 100644 compiler/main.v create mode 100644 compiler/parser.v create mode 100644 compiler/scanner.v create mode 100644 compiler/table.v create mode 100644 compiler/token.v create mode 100644 examples/concurrent_news_fetcher.v create mode 100644 examples/links_scraper.v create mode 100644 examples/tetris/tetris.v create mode 100644 examples/word_counter/word_counter.v create mode 100644 gg/gg.v create mode 100644 glfw/glfw.v create mode 100644 glm/glm.v create mode 100644 gx/gx.v create mode 100644 http/download_lin.v create mode 100644 http/download_mac.v create mode 100644 http/download_win.v create mode 100644 http/http.v create mode 100644 http/http_mac.v create mode 100644 http/http_win.v create mode 100644 json/json_primitives.v create mode 100644 math/math.v create mode 100644 os/os.v create mode 100644 os/os_mac.v create mode 100644 os/os_win.v create mode 100644 rand/rand.v create mode 100644 stbi/stbi.v create mode 100644 time/time.v create mode 100644 time/time_lin.v create mode 100644 time/time_mac.v create mode 100644 time/time_win.v diff --git a/base64/base64.v b/base64/base64.v new file mode 100644 index 0000000000..266b1e99c6 --- /dev/null +++ b/base64/base64.v @@ -0,0 +1,49 @@ +// 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 base64 + +const ( + Index = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 62, 63, 62, 62, 63, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0, 0, 0, + 0, 0, 0, 0, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, + 17, 18, 19, 20, 21, 22, 23, 24, 25, 0, 0, 0, 0, 63, 0, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, + 47, 48, 49, 50, 51] +) + +fn decode(data string) string { + p := data.cstr() + len := data.len + mut pad := 0 + if len > 0 && (len % 4 != 0 || p[len - 1] == `=`) { + pad = 1 + } + L := ((len + 3) / 4 - pad) * 4 + str_len := L / 4 * 3 + pad + mut str := malloc(str_len + 2) + mut j := 0 + for i := 0; i < L; i += 4 { + n := (Index[p[i]] << 18) | (Index[p[i + 1]] << 12) | + (Index[p[i + 2]] << 6) | (Index[p[i + 3]]) + str[j] = n >> 16 + j++ + str[j] = n >> 8 & 0xff + j++ + str[j] = n & 0xff + j++ + } + if pad > 0 { + mut nn := (Index[p[L]] << 18) | (Index[p[L + 1]] << 12) + str[str_len - 1] = nn >> 16 + if len > L + 2 && p[L + 2] != `=` { + nn = nn | (Index[p[L + 2]] << 6) + str[str_len] = nn >> 8 & 0xff + } + } + str[str_len + 1] = `\0` + return string(str) +} + diff --git a/builtin/array.v b/builtin/array.v new file mode 100644 index 0000000000..8a236474fc --- /dev/null +++ b/builtin/array.v @@ -0,0 +1,223 @@ +module builtin + +struct array { + // Using a void pointer allows to implement arrays without generics and without generating + // extra code for every type. + data voidptr +pub: + len int + cap int + element_size int +} + +// Private function, used by V (`nums := []int`) +fn new_array(mylen int, cap, elm_size int) array { + arr := array { + len: mylen + cap: cap + element_size: elm_size + data: malloc(cap * elm_size) + } + return arr +} + +// Private function, used by V (`nums := [1, 2, 3]`) +fn new_array_from_c_array(len, cap, elm_size int, c_array voidptr) array { + arr := array { + len: len + cap: cap + element_size: elm_size + data: malloc(cap * elm_size) + } + // TODO Write all memory functions (like memcpy) in V + C.memcpy(arr.data, c_array, len * elm_size) + return arr +} + +// Private function, used by V (`nums := [1, 2, 3] !`) +fn new_array_from_c_array_no_alloc(len, cap, elm_size int, c_array voidptr) array { + arr := array { + len: len + cap: cap + element_size: elm_size + data: c_array + } + return arr +} + +// Private function, used by V (`[0; 100]`) +fn array_repeat(val voidptr, nr_repeats int, elm_size int) array { + arr := array { + len: nr_repeats + cap: nr_repeats + element_size: elm_size + data: malloc(nr_repeats * elm_size) + } + for i := 0; i < nr_repeats; i++ { + C.memcpy(arr.data + i * elm_size, val, elm_size) + } + return arr +} + +fn (a mut array) append_array(b array) { + for i := 0; i < b.len; i++ { + val := b[i] + a._push(val) + } +} + +fn (a mut array) sort_with_compare(compare voidptr) { + C.qsort(a.data, a.len, a.element_size, compare) +} + +fn (a mut array) insert(i int, val voidptr) { + if i >= a.len { + panic('array.insert: index larger than length') + return + } + a._push(val) + size := a.element_size + C.memmove(a.data + (i + 1) * size, a.data + i * size, (a.len - i) * size) + a.set(i, val) +} + +fn (a mut array) prepend(val voidptr) { + a.insert(0, val) +} + +fn (a mut array) delete(idx int) { + size := a.element_size + C.memmove(a.data + idx * size, a.data + (idx + 1) * size, (a.len - idx) * size) + a.len-- + a.cap-- +} + +fn (a array) _get(i int) voidptr { + if i < 0 || i >= a.len { + panic('array index out of range: $i/$a.len') + } + return a.data + i * a.element_size +} + +fn (a array) first() voidptr { + if a.len == 0 { + panic('array.first: empty array') + } + return a.data + 0 +} + +fn (a array) last() voidptr { + if a.len == 0 { + panic('array.last: empty array') + } + return a.data + (a.len - 1) * a.element_size +} + +fn (s array) left(n int) array { + if n >= s.len { + return s + } + return s.slice(0, n) +} + +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 { + mut end := _end + if start > end { + panic('invalid slice index: $start > $end') + } + if end >= s.len { + end = s.len + } + l := end - start + res := array { + element_size: s.element_size + data: s.data + start * s.element_size + len: l + cap: l + } + return res +} + +fn (a mut array) set(idx int, val voidptr) { + if idx < 0 || idx >= a.len { + panic('array index out of range: $idx / $a.len') + } + C.memcpy(a.data + a.element_size * idx, val, a.element_size) +} + +fn (arr mut array) _push(val voidptr) { + if arr.len >= arr.cap - 1 { + cap := (arr.len + 1) * 2 + // println('_push: realloc, new cap=$cap') + if arr.cap == 0 { + arr.data = malloc(cap * arr.element_size) + } + else { + arr.data = C.realloc(arr.data, cap * arr.element_size) + } + arr.cap = cap + } + C.memcpy(arr.data + arr.element_size * arr.len, val, arr.element_size) + arr.len++ +} + +fn (arr mut array) _push_many(val voidptr, size int) { + if arr.len >= arr.cap - size { + cap := (arr.len + size) * 2 + // println('_push: realloc, new cap=$cap') + if arr.cap == 0 { + arr.data = malloc(cap * arr.element_size) + } + else { + arr.data = C.realloc(arr.data, cap * arr.element_size) + } + arr.cap = cap + } + C.memcpy(arr.data + arr.element_size * arr.len, val, arr.element_size * size) + arr.len += size +} + +fn (a[]int) str() string { + mut res := '[' + for i := 0; i < a.len; i++ { + val := a[i] + res += '$val' + if i < a.len - 1 { + res += ', ' + } + } + res += ']' + return res +} + +fn (a[]int) free() { + // println('array free') + C.free(a.data) +} + +// TODO generic +// "[ 'a', 'b', 'c' ]" +fn (a[]string) str() string { + mut res := '[' + for i := 0; i < a.len; i++ { + val := a[i] + res += '"$val"' + if i < a.len - 1 { + res += ', ' + } + } + res += ']' + return res +} + +fn free(a voidptr) { + C.free(a) +} + diff --git a/builtin/builtin.v b/builtin/builtin.v new file mode 100644 index 0000000000..53a8c32055 --- /dev/null +++ b/builtin/builtin.v @@ -0,0 +1,127 @@ +module builtin + +pub fn exit(reason string) { + if reason == '' { + panic('exit empty reason') + } + println2('exit(): $reason') + C.exit(0) +} + +// isnil returns true if an object is nil (only for C objects). +pub fn isnil(v voidptr) bool { + return v == 0 +} + +fn on_panic(f fn (int) int) { + // TODO +} + +pub fn print_backtrace() { + return + $if mac { + buffer := [100]voidptr + nr_ptrs := C.backtrace(buffer, 100) + C.backtrace_symbols_fd(buffer, nr_ptrs, 1) + } +} + +pub fn panic(s string) { + println2('V panic: $s') + print_backtrace() + C.exit(1) +} + +pub fn println(s string) { + // Should never happen + if isnil(s.str) { + panic('println(NIL)') + } + C.printf('%.*s\n', s.len, s.str) +} + +pub fn eprintln(s string) { + if isnil(s.str) { + panic('eprintln(NIL)') + } + $if mac { + C.fprintf(stderr, '%.*s\n', s.len, s.str) + } + // TODO issues with stderr and cross compiling for Linux + $else { + println2(s) + } +} + +pub fn print(s string) { + C.printf('%.*s', s.len, s.str) +} + +fn println2(s string) { + C.printf('%.*s\n', s.len, s.str) +} + +pub fn malloc(n int) byteptr { + if n < 0 { + panic('malloc(<0)') + } +#ifdef VPLAY + if n > 10000 { + panic('allocating more than 10 KB is not allowed in the playground') + } +#endif +#ifdef DEBUG_ALLOC + total := i64(0) + # total_m += n; + # total = total_m; + println2('\n\n\nmalloc($n) total=$total') + print_backtrace() +#endif + ptr := C.malloc(n) + if isnil(ptr) { + panic('malloc($n) failed') + } + return ptr +} + +pub fn calloc(n int) byteptr { + if n < 0 { + panic('calloc(<0)') + } + return C.calloc(sizeof(float) * n, sizeof(float)) +} + +fn _strlen(s byteptr) int { + // TODO don't call a C function, implement it in V + return C.strlen(s) +} + +// `fn foo() ?Foo { return foo }` => `fn foo() ?Foo { return opt_ok(foo); }` +fn opt_ok(data voidptr) Option { + return Option { + data: data + ok: true + } +} + +fn memdup(src voidptr, sz int) voidptr { + mem := malloc(sz) + return C.memcpy(mem, src, sz) +} + +pub fn error(s string) Option { + return Option { + error: s + } +} + +// TODO this is not used anymore +fn range_int(start, end int) []int { + len := end - start + mut res := [0; len] + for i := 0; i < len; i++ { + res[i] = start + i + } + return res +} + diff --git a/builtin/int.v b/builtin/int.v new file mode 100644 index 0000000000..c4813aaab5 --- /dev/null +++ b/builtin/int.v @@ -0,0 +1,163 @@ +module builtin + +fn (d double) str() string { + buf := malloc(sizeof(double) * 5 + 1)// TODO + C.sprintf(buf, '%f', d) + return tos(buf, _strlen(buf)) +} + +fn (d float) str() string { + buf := malloc(sizeof(double) * 5 + 1)// TODO + C.sprintf(buf, '%f', d) + return tos(buf, _strlen(buf)) +} + +fn (d f64) str() string { + buf := malloc(sizeof(double) * 5 + 1)// TODO + C.sprintf(buf, '%f', d) + return tos(buf, _strlen(buf)) +} + +fn (d f32) str() string { + buf := malloc(sizeof(double) * 5 + 1)// TODO + C.sprintf(buf, '%f', d) + return tos(buf, _strlen(buf)) +} + +fn ptr_str(ptr voidptr) string { + buf := malloc(sizeof(double) * 5 + 1)// TODO + C.sprintf(buf, '%p', ptr) + return tos(buf, _strlen(buf)) +} + +// fn (nn i32) str() string { +// return i +// } +fn (nn int) str() string { + mut n = nn + if n == 0 { + return '0' + } + max := 16 + mut buf := malloc(max) + mut len := 0 + mut is_neg = false + if n < 0 { + n = -n + is_neg = true + } + // Fill the string from the end + for n > 0 { + d := n % 10 + buf[max - len - 1] = d + int(`0`) + len++ + n = n / 10 + } + // Prepend - if it's negative + if is_neg { + buf[max - len - 1] = `-` + len++ + } + return tos(buf + max - len, len) +} + +fn (nn u8) str() string { + mut n = nn + if n == u8(0) { + return '0' + } + max := 5 + mut buf := malloc(max) + mut len := 0 + mut is_neg = false + if n < u8(0) { + n = -n + is_neg = true + } + // Fill the string from the end + for n > u8(0) { + d := n % u8(10) + buf[max - len - 1] = d + u8(`0`) + len++ + n = n / u8(10) + } + // Prepend - if it's negative + if is_neg { + buf[max - len - 1] = `-` + len++ + } + return tos(buf + max - len, len) +} + +fn (nn i64) str() string { + mut n = nn + if n == i64(0) { + return '0' + } + max := 32 + mut buf := malloc(max) + mut len := 0 + mut is_neg = false + if n < i64(0) { + n = -n + is_neg = true + } + // Fill the string from the end + for n > i64(0) { + d := int(n % i64(10)) + buf[max - len - 1] = d + int(`0`) + len++ + n = n / i64(10) + } + // Prepend - if it's negative + if is_neg { + buf[max - len - 1] = `-` + len++ + } + return tos(buf + max - len, len) +} + +fn (b bool) str() string { + if b { + return 'true' + } + return 'false' +} + +fn (n int) hex() string { + s := n.str() + hex := malloc(s.len + 2) + C.sprintf(hex, '0x%x', n) + return tos(hex, s.len + 2) +} + +fn (n i64) hex() string { + s := n.str() + hex := malloc(s.len + 2) + C.sprintf(hex, '0x%x', n) + return tos(hex, s.len + 2) +} + +fn (a[]byte) contains(val byte) bool { + for aa in a { + if aa == val { + return true + } + } + return false +} + +/* TODO +fn (c rune) str() string { +} +*/ +fn (c byte) str() string { + mut str := string { + len: 1 + str: malloc(2) + } + str.str[0] = c + str.str[1] = `\0` + return str +} + diff --git a/builtin/map.v b/builtin/map.v new file mode 100644 index 0000000000..c1f919b400 --- /dev/null +++ b/builtin/map.v @@ -0,0 +1,159 @@ +module builtin + +struct map { + // cap int + // keys []string + // table byteptr + // keys_table *string + // table *Entry + element_size int + // collisions []Entry +pub: + entries []Entry + is_sorted bool +} + +struct Entry { +pub: + key string + val voidptr + // linked list for collisions + // next *Entry +} + +fn new_map(cap int, elm_size int) map { + res := map { + // len: len, + element_size: elm_size + // entries: + // keys: []string + } + return res +} + +fn (m &map) new_entry(key string, val voidptr) Entry { + new_e := Entry { + key: key + val: malloc(m.element_size) + // next: 0 + } + C.memcpy(new_e.val, val, m.element_size) + return new_e +} + +fn (m mut map) _set(key string, val voidptr) { + e := m.new_entry(key, val) + for i := 0; i < m.entries.len; i++ { + entry := m.entries[i] + if entry.key == key { + // e := Entry2{key: key, val: val} + m.entries[i] = e + return + } + } + m.entries << e// m.new_entry(key, val) + m.is_sorted = false +} + +fn volt_abs(n int) int { + // println('volt_abs($n)') + if n < 0 { + // println('< 0: -($n)') + return -n + } + return n +} + +fn (m map) bs(query string, start, end int, out voidptr) { + // println('bs "$query" $start -> $end') + mid := start + ((end - start) / 2) + if end - start == 0 { + last := m.entries[end] + C.memcpy(out, last.val, m.element_size) + return + } + if end - start == 1 { + first := m.entries[start] + C.memcpy(out, first.val, m.element_size) + return + } + if mid >= m.entries.len { + return + } + mid_msg := m.entries[mid] + // println('mid.key=$mid_msg.key') + if query < mid_msg.key { + m.bs(query, start, mid, out) + return + } + m.bs(query, mid, end, out) +} + +fn compare_map(a, b *Entry) int { + if a.key < b.key { + return -1 + } + if a.key > b.key { + return 1 + } + return 0 +} + +pub fn (m mut map) sort() { + m.entries.sort_with_compare(compare_map) + m.is_sorted = true +} + +fn (m map) get(key string, out voidptr) bool { + if m.is_sorted { + // println('\n\nget "$key" sorted') + m.bs(key, 0, m.entries.len, out) + return true + } + for i := 0; i < m.entries.len; i++ { + entry := m.entries[i] + if entry.key == key { + C.memcpy(out, entry.val, m.element_size) + return true + } + } + return false +} + +fn (m map) print() { + println('<<<<<<<<') + for i := 0; i < m.entries.len; i++ { + // entry := m.entries[i] + // println('$entry.key => $entry.val') + } + /* + for i := 0; i < m.cap * m.element_size; i++ { + b := m.table[i] + print('$i: ') + C.printf('%02x', b) + println('') + } +*/ + println('>>>>>>>>>>') +} + +fn (m map) free() { + // C.free(m.table) + // C.free(m.keys_table) +} + +fn (m map_string) str() string { + // return 'not impl' + if m.entries.len == 0 { + return '{}' + } + // TODO use bytes buffer + mut s := '{\n' + for entry in m.entries { + val := m[entry.key] + s += ' "$entry.key" => "$val"\n' + } + s += '}' + return s +} + diff --git a/builtin/option.v b/builtin/option.v new file mode 100644 index 0000000000..1377c90abf --- /dev/null +++ b/builtin/option.v @@ -0,0 +1,8 @@ +module builtin + +struct Option { + data voidptr + error string + ok bool +} + diff --git a/builtin/smap.v b/builtin/smap.v new file mode 100644 index 0000000000..d87d089f56 --- /dev/null +++ b/builtin/smap.v @@ -0,0 +1,97 @@ +module builtin + +struct Entry2 { + key string + val string +} + +struct smap { + entries []Entry2 + is_sorted bool +} + +fn new_smap() smap { + res := smap{} + return res +} + +fn (m mut smap) set(key string, val string) { + /* + for i := 0; i < m.entries.len; i++ { + entry := m.entries[i] + if entry.key == key { + e := Entry2{key: key, val: val} + m.entries[i] = e + return + } + } +*/ + e := Entry2{key: key, val: val} + m.entries << e +} + +fn (m smap) get(key string) string { + if m.is_sorted { + return m.bs(key, 0, m.entries.len) + } + for i := 0; i < m.entries.len; i++ { + entry := m.entries[i] + if entry.key == key { + return entry.val + } + } + return '' +} + +fn (m smap) bs(query string, start, end int) string { + mid := start + ((end - start) / 2) + if end - start == 0 { + last := m.entries[end] + return last.val + } + if end - start == 1 { + first := m.entries[start] + return first.val + } + if mid >= m.entries.len { + return '' + } + mid_msg := m.entries[mid] + if query < mid_msg.key { + return m.bs(query, start, mid) + } + return m.bs(query, mid, end) +} + +fn compare_smap(a, b *Entry2) int { + if a.key < b.key { + return -1 + } + if a.key > b.key { + return 1 + } + return 0 +} + +fn (m mut smap) sort() { + m.entries.sort_with_compare(compare_smap) + m.is_sorted = true +} + +fn (m smap) free() { + // m.entries.free() +} + +fn (m smap) str() string { + if m.entries.len == 0 { + return '{}' + } + // TODO use bytes buffer + mut s := '{\n' + for entry in m.entries { + s += ' "$entry.key" => "$entry.val"\n' + } + s += '}' + return s +} + diff --git a/builtin/string.v b/builtin/string.v new file mode 100644 index 0000000000..9a8ebe4458 --- /dev/null +++ b/builtin/string.v @@ -0,0 +1,814 @@ +module builtin + +// V strings are not null-terminated. +struct string { + str byteptr +pub: + len int +} + +struct ustring { +pub: + s string + runes []int + len int +} + +// For C strings only +fn C.strlen(s byteptr) int + +// Converts a C string to a V string +fn tos(s byteptr, len int) string { + // This should never happen. + if isnil(s) { + panic('tos(): nil string') + } + return string { + str: s + len: len + } +} + +fn tos_clone(s byteptr) string { + if isnil(s) { + panic('tos: nil string') + return string{} + } + len := strlen(s) + res := tos(s, len) + return res.clone() +} + +// Same as `tos`, but calculates the length itself. TODO bad name. +fn tos2(s byteptr) string { + if isnil(s) { + panic('tos2: nil string') + return string{} + } + len := C.strlen(s) + res := tos(s, len) + return res +} + +fn tos_no_len(s byteptr) string { + return tos2(s) +} + +fn (a string) clone() string { + mut b := string { + len: a.len + str: malloc(a.len + 1) + } + for i := 0; i < a.len; i++ { + b[i] = a[i] + } + b[a.len] = `\0` + return b +} + +fn (s string) cstr() byteptr { + clone := s.clone() + return clone.str +} + +pub fn (s string) replace(rep, with string) string { + if s.len == 0 || rep.len == 0 { + return '' + } + if !s.contains(rep) { + 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{} + // idxs := []int { + // 2, 8, 14 + // } + for i := 0; i < s.len; i++ { + // Do we have the string we are looking for (rep) starting at i? + // Go thru all chars in rep and compare. + mut rep_i := 0 + mut j := i + for rep_i < rep.len && j < s.len && s[j] == rep[rep_i] { + rep_i++ + j++ + } + if rep_i == rep.len { + idxs << i + } + } + // Dont change the string if there's nothing to replace + if idxs.len == 0 { + return s + } + // Now we know the number of replacements we need to do and we can calc the len of the new string + new_len := s.len + idxs.len * (with.len - rep.len) + mut b := malloc(new_len + 1)// add a newline just in case + // Fill the new string + mut idx_pos := 0 + mut cur_idx := idxs[idx_pos] + mut b_i = 0 + for i := 0; i < s.len; i++ { + // Reached the location of rep, replace it with "with" + if i == cur_idx { + for j := 0; j < with.len; j++ { + b[b_i] = with[j] + b_i++ + } + // Skip the length of rep, since we just replaced it with "with" + i += rep.len - 1 + // Go to the next index + idx_pos++ + if idx_pos < idxs.len { + cur_idx = idxs[idx_pos] + } + } + // Rep doesnt start here, just copy + else { + b[b_i] = s[i] + b_i++ + } + } + b[new_len] = `\0` + return tos(b, new_len) +} + +// TODO `.int()` ? +pub fn (s string) to_i() int { + return C.atoi(s.str) +} + +// TODO `.f32()` +fn (s string) to_float() float { + return C.atof(s.str) +} + +// == +fn (s string) eq(a string) bool { + if isnil(s.str) { + panic('string.eq(): nil string') + } + if s.len != a.len { + return false + } + for i := 0; i < s.len; i++ { + if s[i] != a[i] { + return false + } + } + return true +} + +// != +fn (s string) ne(a string) bool { + return !s.eq(a) +} + +// s >= a +fn (s string) ge(a string) bool { + mut j := 0 + for i := 0; i < s.len; i++ { + if i >= a.len { + return true + } + if int(s[i]) < int(a[j]) { + return false + } + else if int(s[i]) > int(a[j]) { + return true + } + j++ + } + return true +} + +// s <= a +fn (s string) le(a string) bool { + return !s.ge(a) || s == a +} + +// s < a +fn (s string) lt(a string) bool { + return s.le(a) && s != a +} + +// s > a +fn (s string) gt(a string) bool { + return s.ge(a) && s != a +} + +// TODO `fn (s string) + (a string)` ? To be consistent with operator overloading syntax. +fn (s string) add(a string) string { + new_len := a.len + s.len + mut res := string { + len: new_len + str: malloc(new_len + 1) + } + for j := 0; j < s.len; j++ { + res[j] = s[j] + } + for j := 0; j < a.len; j++ { + res[s.len + j] = a[j] + } + res[new_len] = `\0`// V strings are not null terminated, but just in case + return res +} + +pub fn (s string) split(delim string) []string { + // println('string split delim="$delim" s="$s"') + mut res := []string + if delim.len == 0 { + res << s + return res + } + if delim.len == 1 { + return s.split_single(delim[0]) + // println2('split 1 only') + // os.exit() + } + mut i := 0 + mut start := 0// - 1 + for i < s.len { + // printiln(i) + mut a := s[i] == delim[0] + mut j := 1 + for j < delim.len && a { + a = a && s[i + j] == delim[j] + j++ + } + last := i == s.len - 1 + if a || last { + if last { + i++ + } + mut val := s.substr(start, i) + // println('got it "$val" start=$start i=$i delim="$delim"') + if val.len > 0 { + // todo perf + // val now is '___VAL'. remove '___' from the start + if val.starts_with(delim) { + // println('!!') + val = val.right(delim.len) + } + res << val.trim_space() + } + start = i + } + i++ + } + return res +} + +fn (s string) split_single(delim byte) []string { + mut res := []string + if int(delim) == 0 { + res << s + return res + } + mut i := 0 + mut start := 0 + for i < s.len { + a := s[i] == delim + b := i == s.len - 1 + if a || b { + if i == s.len - 1 { + i++ + } + val := s.substr(start, i) + if val.len > 0 { + res << val.trim_space() + } + start = i + 1 + } + i++ + } + return res +} + +pub fn (s string) split_into_lines() []string { + mut res := []string + if s.len == 0 { + return res + } + mut start := 0 + for i := 0; i < s.len; i++ { + last := i == s.len - 1 + if int(s[i]) == 10 || last { + if last { + i++ + } + line := s.substr(start, i) + res << line + start = i + 1 + } + } + return res +} + +// 'hello'.left(2) => 'he' +pub fn (s string) left(n int) string { + if n >= s.len { + return s + } + return s.substr(0, n) +} + +pub fn (s string) right(n int) string { + if n >= s.len { + return '' + } + return s.substr(n, s.len) +} + +// Because the string is immutable, it is safe for multiple strings to share +// the same storage, so slicing s results in a new 2-word structure with a +// potentially different pointer and length that still refers to the same byte +// sequence. This means that slicing can be done without allocation or copying, +// making string slices as efficient as passing around explicit indexes. +// substr without allocations. Reuses memory and works great. BUT. This substring does not have +// a \0 at the end, and it's not possible to add it. So if we have s = 'privet' +// and substr := s.substr_fast(1, 4) ('riv') +// puts(substr.str) will print 'rivet' +// Avoid using C functions with these substrs! +pub fn (s string) substr(start, end int) string { + /* + if start > end || start >= s.len || end > s.len || start < 0 || end < 0 { + panic('substr($start, $end) out of bounds (len=$s.len)') + return '' + } +*/ + if start >= s.len { + return '' + } + len := end - start + res := string { + str: s.str + start + len: len + } + return res +} + +pub fn (s string) index(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 + } + i++ + } + return -1 +} + +pub fn (s string) last_index(p string) int { + if p.len > s.len { + return -1 + } + mut i := s.len - p.len + for i >= 0 { + mut j := 0 + for j < p.len && s[i + j] == p[j] { + j++ + } + if j == p.len { + return i + } + i-- + } + return -1 +} + +pub fn (s string) index_after(p string, start int) int { + if p.len > s.len { + return -1 + } + mut strt := start + if start < 0 { + strt = 0 + } + if start >= s.len { + return -1 + } + mut i := strt + 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 + } + i++ + } + return -1 +} + +pub fn (s string) contains(p string) bool { + res := s.index(p) > 0 - 1 + return res +} + +pub fn (s string) starts_with(p string) bool { + res := s.index(p) == 0 + return res +} + +pub fn (s string) ends_with(p string) bool { + if p.len > s.len { + return false + } + res := s.last_index(p) == s.len - p.len + return res +} + +// TODO only works with ASCII +pub fn (s string) to_lower() string { + mut b := malloc(s.len)// TODO + 1 ?? + for i := 0; i < s.len; i++ { + b[i] = C.tolower(s.str[i]) + } + return tos(b, s.len) +} + +pub fn (s string) to_upper() string { + mut b := malloc(s.len)// TODO + 1 ?? + for i := 0; i < s.len; i++ { + b[i] = C.toupper(s.str[i]) + } + return tos(b, s.len) +} + +// 'hey [man] how you doin' +// find_between('[', ']') == 'man' +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 +fn (ar[]string) contains(val string) bool { + for s in ar { + if s == val { + return true + } + } + return false +} + +// TODO generic +fn (ar[]int) contains(val int) bool { + for i, s in ar { + if s == val { + return true + } + } + return false +} + +fn (a[]string) to_c() voidptr { + # char ** res = malloc(sizeof(char*) * a.len); + for i := 0; i < a.len; i++ { + val := a[i] + # res[i] = val.str; + } + # return res; + return 0 +} + +fn is_space(c byte) bool { + return C.isspace(c) +} + +fn (c byte) is_space() bool { + return is_space(c) +} + +pub fn (s string) trim_space() string { + if s == '' { + return '' + } + // println('TRIM SPACE "$s"') + mut i := 0 + for i < s.len && is_space(s[i]) { + i++ + } + mut res := s.right(i) + mut end := res.len - 1 + for end >= 0 && is_space(res[end]) { + // C.printf('end=%d c=%d %c\n', end, res.str[end]) + end-- + } + res = res.left(end + 1) + // println('after SPACE "$res"') + return res +} + +pub fn (s string) trim(c byte) string { + if s == '' { + return '' + } + mut i := 0 + for i < s.len && c == s[i] { + i++ + } + mut res := s.right(i) + mut end := res.len - 1 + for end >= 0 && c == res[end] { + end-- + } + res = res.left(end + 1) + return res +} + +fn (s string) trim_left(cutset string) string { + mut start := s.index(cutset) + if start != 0 { + return s + } + for start < s.len - 1 && s[start] == cutset[0] { + start++ + } + return s.right(start) +} + +fn (s string) trim_right(cutset string) string { + return s + pos := s.last_index(cutset) + if pos == -1 { + return s + } + return s.left(pos) +} + +// fn print_cur_thread() { +// //C.printf("tid = %08x \n", pthread_self()); +// } +fn compare_strings(a, b *string) int { + if a.le(b) { + return -1 + } + if a.ge(b) { + return 1 + } + return 0 +} + +fn compare_strings_by_len(a, b *string) int { + if a.len < b.len { + return -1 + } + if a.len > b.len { + return 1 + } + return 0 +} + +fn compare_lower_strings(a, b *string) int { + aa := a.to_lower() + bb := a.to_lower() + return compare_strings(aa, bb) +} + +pub fn (s mut []string) sort() { + s.sort_with_compare(compare_strings) +} + +fn (s mut []string) sort_ignore_case() { + s.sort_with_compare(compare_lower_strings) +} + +fn (s mut []string) sort_by_len() { + s.sort_with_compare(compare_strings_by_len) +} + +fn (s string) ustring() ustring { + mut res := ustring { + s: s + // runes will have at least s.len elements, save reallocations + // TODO use VLA for small strings? + runes: new_array(0, s.len, sizeof(int)) + } + for i := 0; i < s.len; i++ { + char_len := 0 + # char_len =UTF8_CHAR_LEN(s.str[i]); + // println('cl=$char_len') + res.runes << i + i += char_len - 1 + res.len++ + } + return res +} + +// A hack that allows to create ustring without allocations. +// It's called from functions like draw_text() where we know that the string is going to be freed +// right away. Uses global buffer for storing runes []int array. +# array_int g_ustring_runes; +fn (s string) ustring_tmp() ustring { + mut res := ustring { + s: s + runes: 0 + } + # res.runes = g_ustring_runes ; + # res.runes.len = s.len ; + mut j := 0 + for i := 0; i < s.len; i++ { + char_len := 0 + # char_len =UTF8_CHAR_LEN(s.str[i]); + res.runes[j] = i + j++ + i += char_len - 1 + res.len++ + } + return res +} + +fn (u ustring) substr(start, end int) string { + // println('substr($start, $end)') + // println('runes=') + // println(u.runes) + start = u.runes[start] + // handle last char + if end >= u.runes.len { + end = u.s.len + } + else { + end = u.runes[end] + } + // println('fast $start, $end') + return u.s.substr(start, end) +} + +fn (u ustring) left(pos int) string { + return u.substr(0, pos) +} + +fn (u ustring) right(pos int) string { + return u.substr(pos, u.len) +} + +fn (s string) at(idx int) byte { + if idx < 0 || idx >= s.len { + panic('string index out of range: $idx / $s.len') + } + return s.str[idx] +} + +fn (u ustring) at(idx int) string { + return u.substr(idx, idx + 1) +} + +fn (u ustring) free() { + u.runes.free() +} + +fn abs(a int) int { + if a >= 0 { + return a + } + return -a +} + +fn (c byte) is_digit() bool { + return c >= `0` && c <= `9` +} + +fn (c byte) is_letter() bool { + return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) +} + +pub fn (s string) free() { + C.free(s.str) +} + +fn (arr[]string) free() { + for s in arr { + s.free() + } + C.free(arr.data) +} + +// all_before('23:34:45.234', '.') == '23:34:45' +fn (s string) all_before(dot string) string { + pos := s.index(dot) + if pos == -1 { + return s + } + return s.left(pos) +} + +fn (s string) all_before_last(dot string) string { + pos := s.last_index(dot) + if pos == -1 { + return s + } + return s.left(pos) +} + +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 { + if a.len == 0 { + return '' + } + mut len := 0 + for i, val in a { + len += val.len + del.len + } + len -= del.len + // Allocate enough memory + mut res := '' + res.len = len + res.str = malloc(res.len + 1) + mut idx := 0 + // Go thru every string and copy its every char one by one + for i, val in a { + for j := 0; j < val.len; j++ { + c := val[j] + res.str[idx] = val.str[j] + idx++ + } + // Add del if it's not last + if i != a.len - 1 { + for k := 0; k < del.len; k++ { + res.str[idx] = del.str[k] + idx++ + } + } + } + res.str[res.len] = `\0` + return res +} + +fn (s[]string) join_lines() string { + return s.join('\n') +} + +// 'hello'.limit(2) => 'he' +// 'hi'.limit(10) => 'hi' +fn (s string) limit(max int) string { + u := s.ustring() + if u.len <= max { + return s + } + return u.substr(0, max) +} + +// TODO is_white_space() +fn (c byte) is_white() bool { + i := int(c) + return i == 10 || i == 32 || i == 9 || i == 13 || c == `\r` +} + +// TODO move this to strings.repeat() +fn repeat_char(c byte, n int) string { + if n <= 0 { + return '' + } + mut arr := malloc(n + 1) + for i := 0; i < n; i++ { + arr[i] = c + } + arr[n] = `\0` + return tos(arr, n) +} + +pub fn (s string) hash() int { + mut hash := int(0) + for i := 0; i < s.len; i++ { + // if key == 'Content-Type' { + // println('$i) $hash') + // } + hash = hash * int(31) + int(s.str[i]) + } + return hash +} + diff --git a/builtin/string_builder.v b/builtin/string_builder.v new file mode 100644 index 0000000000..d3ee8bf4f1 --- /dev/null +++ b/builtin/string_builder.v @@ -0,0 +1,32 @@ +module builtin + +struct StringBuilder { + buf []byte + len int +} + +fn new_string_builder(initial_size int) StringBuilder { + return StringBuilder { + buf: new_array(0, initial_size, sizeof(byte)) + } +} + +fn (b mut StringBuilder) write(s string) { + b.buf._push_many(s.str, s.len) + b.len += s.len +} + +fn (b mut StringBuilder) writeln(s string) { + b.buf._push_many(s.str, s.len) + b.buf << `\n` + b.len += s.len + 1 +} + +fn (b StringBuilder) str() string { + return tos(b.buf.data, b.len) +} + +fn (b StringBuilder) cut(n int) { + b.len -= n +} + diff --git a/builtin/utf8.v b/builtin/utf8.v new file mode 100644 index 0000000000..cf4137f798 --- /dev/null +++ b/builtin/utf8.v @@ -0,0 +1,338 @@ +module builtin + +fn (s string) is_utf8() int { + faulty_bytes := 0 + len := s.len + i := 0 + // # size_t i = 0; + # byte * str = s.str; + # + # while (i < len) { + # if (str[i] <= 0x7F) /* 00..7F */ { + # i += 1; + # } +#else if (str[i] >= 0xC2 && str[i] <= 0xDF) /* C2..DF 80..BF */ { + # if (i + 1 < len) /* Expect a 2nd byte */ { + # if (str[i + 1] < 0x80 || str[i + 1] > 0xBF) { + # printf( "After a first byte between C2 and DF, expecting a 2nd byte between 80 and BF"); + # faulty_bytes = 2; + # goto end; + # } + # } +#else { + # printf( "After a first byte between C2 and DF, expecting a 2nd byte."); + # faulty_bytes = 1; + # goto end; + # } + # i += 2; + # } +#else if (str[i] == 0xE0) /* E0 A0..BF 80..BF */ { + # if (i + 2 < len) /* Expect a 2nd and 3rd byte */ { + # if (str[i + 1] < 0xA0 || str[i + 1] > 0xBF) { + # printf( "After a first byte of E0, expecting a 2nd byte between A0 and BF."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte of E0, expecting a 3nd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # } +#else { + # printf( "After a first byte of E0, expecting two following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 3; + # } +#else if (str[i] >= 0xE1 && str[i] <= 0xEC) /* E1..EC 80..BF 80..BF */ { + # if (i + 2 < len) /* Expect a 2nd and 3rd byte */ { + # if (str[i + 1] < 0x80 || str[i + 1] > 0xBF) { + # printf( "After a first byte between E1 and EC, expecting the 2nd byte between 80 and BF."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte between E1 and EC, expecting the 3rd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # } +#else { + # printf( "After a first byte between E1 and EC, expecting two following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 3; + # } +#else if (str[i] == 0xED) /* ED 80..9F 80..BF */ { + # if (i + 2 < len) /* Expect a 2nd and 3rd byte */ { + # if (str[i + 1] < 0x80 || str[i + 1] > 0x9F) { + # printf( "After a first byte of ED, expecting 2nd byte between 80 and 9F."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte of ED, expecting 3rd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # } +#else { + # printf( "After a first byte of ED, expecting two following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 3; + # } +#else if (str[i] >= 0xEE && str[i] <= 0xEF) /* EE..EF 80..BF 80..BF */ { + # if (i + 2 < len) /* Expect a 2nd and 3rd byte */ { + # if (str[i + 1] < 0x80 || str[i + 1] > 0xBF) { + # printf( "After a first byte between EE and EF, expecting 2nd byte between 80 and BF."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte between EE and EF, expecting 3rd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # } +#else { + # printf( "After a first byte between EE and EF, two following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 3; + # } +#else if (str[i] == 0xF0) /* F0 90..BF 80..BF 80..BF */ { + # if (i + 3 < len) /* Expect a 2nd, 3rd 3th byte */ { + # if (str[i + 1] < 0x90 || str[i + 1] > 0xBF) { + # printf( "After a first byte of F0, expecting 2nd byte between 90 and BF."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte of F0, expecting 3rd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # if (str[i + 3] < 0x80 || str[i + 3] > 0xBF) { + # printf( "After a first byte of F0, expecting 4th byte between 80 and BF."); + # faulty_bytes = 4; + # goto end; + # } + # } +#else { + # printf( "After a first byte of F0, expecting three following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 4; + # } +#else if (str[i] >= 0xF1 && str[i] <= 0xF3) /* F1..F3 80..BF 80..BF 80..BF */ { + # if (i + 3 < len) /* Expect a 2nd, 3rd 3th byte */ { + # if (str[i + 1] < 0x80 || str[i + 1] > 0xBF) { + # printf( "After a first byte of F1, F2, or F3, expecting a 2nd byte between 80 and BF."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte of F1, F2, or F3, expecting a 3rd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # if (str[i + 3] < 0x80 || str[i + 3] > 0xBF) { + # printf( "After a first byte of F1, F2, or F3, expecting a 4th byte between 80 and BF."); + # faulty_bytes = 4; + # goto end; + # } + # } +#else { + # printf( "After a first byte of F1, F2, or F3, expecting three following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 4; + # } +#else if (str[i] == 0xF4) /* F4 80..8F 80..BF 80..BF */ { + # if (i + 3 < len) /* Expect a 2nd, 3rd 3th byte */ { + # if (str[i + 1] < 0x80 || str[i + 1] > 0x8F) { + # printf( "After a first byte of F4, expecting 2nd byte between 80 and 8F."); + # faulty_bytes = 2; + # goto end; + # } + # if (str[i + 2] < 0x80 || str[i + 2] > 0xBF) { + # printf( "After a first byte of F4, expecting 3rd byte between 80 and BF."); + # faulty_bytes = 3; + # goto end; + # } + # if (str[i + 3] < 0x80 || str[i + 3] > 0xBF) { + # printf( "After a first byte of F4, expecting 4th byte between 80 and BF."); + # faulty_bytes = 4; + # goto end; + # } + # } +#else { + # printf( "After a first byte of F4, expecting three following bytes."); + # faulty_bytes = 1; + # goto end; + # } + # i += 4; + # } +#else { + # printf( "i=%d Expecting bytes in the following ranges: 00..7F C2..F4.", + # i); + # faulty_bytes = 1; + # goto end; + # } + # } + # + # end: ; + // println('faulty bytes=$faulty_bytes i=$i') + // # printf("c='%c'\n", str[i]); + ok := faulty_bytes == 0 + if ok { + return -1 + } + if !ok { + println2('utf is bad dalen=$len KEK $s sdf') + // s = s.left(i) + } + return i + // return ok +} + +/* +fn (s string) runes() []string { + res2 := []string{} + // res := new_empty_array_with_cap_string(s.len) + res := []string{} + if !s.is_utf8() { + mys := s + println2('string.me runes bad utf $mys HAHA') + return res + } + for i := 0; i < s.len; i++ { + char_len := 0 + # char_len =UTF8_CHAR_LEN(s.str[i]); + switch char_len { + case 1: + // println('ONE') + res <<(char2string(s[i])) + case 2: + // println('TWO') + rune2 := s.substr(i, i + 2) + res <<(rune2) + i++ + case 3: + // println('TWO') + rune3 := s.substr(i, i + 3) + res <<(rune3) + i++ + i++ + case 4: + // println('TWO') + rune4 := s.substr(i, i + 4) + res <<(rune4) + i++ + i++ + i++ + } + } + return res +} +*/ +// Convert utf32 to utf8 +// utf32 == Codepoint +fn utf32_to_str(code u32) string { + // println('code = $code') + buffer := malloc(5) + # if (code <= 0x7F) { + // println('!!!!!!!1') + # buffer[0] = code; + # return tos(buffer, 1); + # } + # if (code <= 0x7FF) { + // println('!!!!!!!2') + # buffer[0] = 0xC0 | (code >> 6); /* 110xxxxx */ + # buffer[1] = 0x80 | (code & 0x3F); /* 10xxxxxx */ + # return tos(buffer, 2); + # } + # if (code <= 0xFFFF) { + // println('!!!!!!!3') + # buffer[0] = 0xE0 | (code >> 12); /* 1110xxxx */ + # buffer[1] = 0x80 | ((code >> 6) & 0x3F); /* 10xxxxxx */ + # buffer[2] = 0x80 | (code & 0x3F); /* 10xxxxxx */ + # return tos(buffer, 3); + # } + # if (code <= 0x10FFFF) { + # buffer[0] = 0xF0 | (code >> 18); /* 11110xxx */ + # buffer[1] = 0x80 | ((code >> 12) & 0x3F); /* 10xxxxxx */ + # buffer[2] = 0x80 | ((code >> 6) & 0x3F); /* 10xxxxxx */ + # buffer[3] = 0x80 | (code & 0x3F); /* 10xxxxxx */ + # return tos(buffer, 4); + # } + return '' +} + +// TODO copypasta +fn utf32_to_str_no_malloc(code u32, buf voidptr) string { + // println('code = $code') + # char* buffer = buf; + # if (code <= 0x7F) { + // println('!!!!!!!1') + # buffer[0] = code; + # return tos(buffer, 1); + # } + # if (code <= 0x7FF) { + // println('!!!!!!!2') + # buffer[0] = 0xC0 | (code >> 6); /* 110xxxxx */ + # buffer[1] = 0x80 | (code & 0x3F); /* 10xxxxxx */ + # return tos(buffer, 2); + # } + # if (code <= 0xFFFF) { + // println('!!!!!!!3') + # buffer[0] = 0xE0 | (code >> 12); /* 1110xxxx */ + # buffer[1] = 0x80 | ((code >> 6) & 0x3F); /* 10xxxxxx */ + # buffer[2] = 0x80 | (code & 0x3F); /* 10xxxxxx */ + # return tos(buffer, 3); + # } + # if (code <= 0x10FFFF) { + # buffer[0] = 0xF0 | (code >> 18); /* 11110xxx */ + # buffer[1] = 0x80 | ((code >> 12) & 0x3F); /* 10xxxxxx */ + # buffer[2] = 0x80 | ((code >> 6) & 0x3F); /* 10xxxxxx */ + # buffer[3] = 0x80 | (code & 0x3F); /* 10xxxxxx */ + # return tos(buffer, 4); + # } + return '' +} + +// Convert utf8 to utf32 +fn (_rune string) utf32_code() int { + // println('utf 32 of $rune len=$rune.len') + if _rune.len == 0 { + return 0 + } + // save ASC symbol as is + if _rune.len == 1 { + return int(_rune[0]) + } + b := byte(int(_rune[0])) + // TODO should be + // res := int( rune[0] << rune.len) + # b <<= _rune.len; + res := int(b) + mut shift := 6 - _rune.len + for i := 1; i < _rune.len; i++ { + // println('c=$res') + c := int(_rune[i]) + # res <<= shift; + # res |= c & 0x3f; + shift = 6 + } + // println('!!!!!!!! utf32 $rune res = $res') + return res +} + diff --git a/compiler/cgen.v b/compiler/cgen.v new file mode 100644 index 0000000000..6de3c93441 --- /dev/null +++ b/compiler/cgen.v @@ -0,0 +1,290 @@ +module main + +struct CGen { + out os.File + out_path string + typedefs []string + type_aliases []string + includes []string + types []string + thread_args []string + thread_fns []string + consts []string + fns []string + so_fns []string + consts_init []string + // tmp_lines []string + // tmp_lines_pos int + lines []string + is_user bool +mut: + run Pass + nogen bool + tmp_line string + cur_line string + prev_line string + is_tmp bool + fn_main string + stash string + // st_start_pos int +} + +fn new_cgen(out_name_c string) *CGen { + // println('NEW CGENN($out_name_c)') + // println('$LANG_TMP/$out_name_c') + gen := &CGen { + out_path: '$TmpPath/$out_name_c' + out: os.create_file('$TmpPath/$out_name_c') + } + for i := 0; i < 10; i++ { + // gen.tmp_lines.push('') + } + return gen +} + +fn (g mut CGen) genln(s string) { + if g.nogen || g.run == RUN_DECLS { + return + } + if g.is_tmp { + // if g.tmp_lines_pos > 0 { + g.tmp_line = '$g.tmp_line $s\n' + return + } + g.cur_line = '$g.cur_line $s' + if g.cur_line != '' { + g.lines << g.cur_line + g.prev_line = g.cur_line + g.cur_line = '' + } + // g.lines << s +} + +fn (g mut CGen) gen(s string) { + // if g.nogen || g.run == RunType.RUN_DECLS { + if g.nogen || g.run == RUN_DECLS { + return + } + if g.is_tmp { + // if g.tmp_lines_pos > 0 { + g.tmp_line = '$g.tmp_line $s' + } + else { + g.cur_line = '$g.cur_line $s' + } +} + +fn (g mut CGen) save() { + s := g.lines.join('\n') + g.out.appendln(s) + g.out.close() + // os.system('clang-format -i $g.out_path') +} + +fn (g mut CGen) start_tmp() { + if g.is_tmp { + println(g.tmp_line) + os.exit('start_tmp() already started. cur_line="$g.cur_line"') + } + // kg.tmp_lines_pos++ + g.tmp_line = '' + // g.tmp_lines[g.tmp_lines_pos] = '' + // g.tmp_lines.set(g.tmp_lines_pos, '') + g.is_tmp = true +} + +fn (g mut CGen) end_tmp() string { + g.is_tmp = false + res := g.tmp_line + g.tmp_line = '' + // g.tmp_lines_pos-- + // g.tmp_line = g.tmp_lines[g.tmp_lines_pos] + return res +} + +fn (g mut CGen) add_placeholder() int { + // g.genln('/*placeholder*/') + // g.genln('') + // return g.lines.len - 1 + if g.is_tmp { + return g.tmp_line.len + } + return g.cur_line.len +} + +fn (g mut CGen) set_placeholder(pos int, val string) { + if g.nogen { + return + } + // g.lines.set(pos, val) + if g.is_tmp { + left := g.tmp_line.left(pos) + right := g.tmp_line.right(pos) + g.tmp_line = '${left}${val}${right}' + return + } + left := g.cur_line.left(pos) + right := g.cur_line.right(pos) + g.cur_line = '${left}${val}${right}' + // g.genln('') +} + +// ///////////////////// +fn (g mut CGen) add_placeholder2() int { + if g.is_tmp { + exit('tmp in addp2') + } + g.lines << '' + return g.lines.len - 1 +} + +fn (g mut CGen) set_placeholder2(pos int, val string) { + if g.nogen { + return + } + if g.is_tmp { + exit('tmp in setp2') + } + g.lines[pos] = val +} + +// ///////////////// +// fn (g mut CGen) cut_lines_after(pos int) string { +// end := g.lines.len +// lines := g.lines.slice_fast(pos, end) +// body := lines.join('\n') +// g.lines = g.lines.slice_fast(0, pos) +// return body +// } +// fn (g mut CGen) set_prev_line(val string) { +// g.lines.set(g.lines.len - 3, val) +// } +// ////fn (g mut CGen) go_back() { +// ////g.stash = g.prev_line + g.cur_line +// g.lines.set(g.lin +// ////} +// fn (g mut CGen) end_statement() { +// last_lines := g.lines.slice_fast(g.st_start_pos, g.lines.len - 1) +// mut merged := last_lines.join(' ') +// merged += '/* M $last_lines.len */' +// merged = merged.replace('\n', '') +// // zero last N lines instead of deleting them +// for i := g.st_start_pos; i < g.lines.len; i++ { +// g.lines.set(i, '') +// } +// g.lines.set(g.lines.len - 1, merged) +// // g.genln('') +// g.st_start_pos = g.lines.len - 1 +// // os.exitkmerged) +// } +// fn (g mut CGen) prepend_type(typ string) { +// g.cur_line = typ.add(g.cur_line) +// g.cur_line='!!!' +// } +fn (g mut CGen) insert_before(val string) { + // g.cur_line = val.add(g.cur_line) + // return + // val += '/*inserted*/' + g.lines.insert(g.lines.len - 1, val) +} + +// fn (g mut CGen) swap_last_lines() { +// return +// if g.run == RUN_DECLS { +// return +// } +// i := g.lines.len - 1 +// j := i - 1 +// tmp := g.lines[i] +// // println('lines i = $tmp') +// // println('lines j = ${g.lines[j]}') +// // // os.exit('') +// g.lines.set(i, g.lines[j]) +// g.lines.set(j, tmp) +// } +fn (g mut CGen) register_thread_fn(wrapper_name, wrapper_text, struct_text string) { + for arg in g.thread_args { + if arg.contains(wrapper_name) { + return + } + } + g.thread_args << struct_text + g.thread_args << wrapper_text +} + +/* +fn (g mut CGen) delete_all_after(pos int) { + if pos > g.cur_line.len - 1 { + return + } + g.cur_line = g.cur_line.substr(0, pos) +} +*/ +fn (c mut V) prof_counters() string { + mut res := []string + // Global fns + for f in c.table.fns { + res << 'double ${c.table.cgen_name(f)}_time;' + // println(f.name) + } + // Methods + for typ in c.table.types { + // println('') + for f in typ.methods { + // res << f.cgen_name() + res << 'double ${c.table.cgen_name(f)}_time;' + // println(f.cgen_name()) + } + } + return res.join(';\n') +} + +fn (p mut Parser) print_prof_counters() string { + mut res := []string + // Global fns + for f in p.table.fns { + counter := '${p.table.cgen_name(f)}_time' + res << 'if ($counter) printf("%%f : $f.name \\n", $counter);' + // println(f.name) + } + // Methods + for typ in p.table.types { + // println('') + for f in typ.methods { + counter := '${p.table.cgen_name(f)}_time' + res << 'if ($counter) printf("%%f : ${p.table.cgen_name(f)} \\n", $counter);' + // res << 'if ($counter) printf("$f.name : %%f\\n", $counter);' + // res << f.cgen_name() + // res << 'double ${f.cgen_name()}_time;' + // println(f.cgen_name()) + } + } + return res.join(';\n') +} + +fn (p mut Parser) gen_type(s string) { + if !p.first_run() { + return + } + p.cgen.types << s +} + +fn (p mut Parser) gen_typedef(s string) { + if !p.first_run() { + return + } + p.cgen.typedefs << s +} + +fn (p mut Parser) gen_type_alias(s string) { + if !p.first_run() { + return + } + p.cgen.type_aliases << s +} + +fn (g mut CGen) add_to_main(s string) { + println('add to main') + g.fn_main = g.fn_main + s +} + diff --git a/compiler/fn.v b/compiler/fn.v new file mode 100644 index 0000000000..cc7b8158a2 --- /dev/null +++ b/compiler/fn.v @@ -0,0 +1,848 @@ +module main + +const ( + MaxLocalVars = 50 +) + +struct Fn { + // addr int +mut: + pkg string + local_vars []Var + var_idx int + args []Var + is_interface bool + // called_fns []string + // idx int + scope_level int + typ string // return type + name string + is_c bool + receiver_typ string + is_private bool + is_method bool + returns_error bool + is_decl bool // type myfn fn(int, int) + defer string +} + +fn (f &Fn) find_var(name string) Var { + for i in 0 .. f.var_idx { + if f.local_vars[i].name == name { + return f.local_vars[i] + } + } + return Var{} +} + +fn (f mut Fn) open_scope() { + f.scope_level++ +} + +fn (f mut Fn) close_scope() { + // println('close_scope level=$f.scope_level var_idx=$f.var_idx') + // Move back `var_idx` (pointer to the end of the array) till we reach the previous scope level. + // This effectivly deletes (closes) current scope. + mut i := f.var_idx - 1 + for; i >= 0; i-- { + v := f.local_vars[i] + if v.scope_level != f.scope_level { + // println('breaking. "$v.name" v.scope_level=$v.scope_level') + break + } + } + f.var_idx = i + 1 + // println('close_scope new var_idx=$f.var_idx\n') + f.scope_level-- +} + +fn (f &Fn) mark_var_used(v Var) { + for i, vv in f.local_vars { + if vv.name == v.name { + mut ptr := &f.local_vars[i] + ptr.is_used = true + // / f.local_vars[i].is_used = true + // return + } + } +} + +fn (f &Fn) known_var(name string) bool { + v := f.find_var(name) + return v.name.len > 0 +} + +fn (f mut Fn) register_var(v Var) { + new_var := {v | scope_level: f.scope_level} + // Expand the array + if f.var_idx >= f.local_vars.len { + f.local_vars << new_var + } + else { + f.local_vars[f.var_idx] = new_var + f.var_idx++ + } +} + +// vlib header file? +fn (p mut Parser) is_sig() bool { + return (p.build_mode == DEFAULT_MODE || p.build_mode == BUILD) && + (p.file_path.contains(TmpPath)) +} + +fn new_fn(pkg string) *Fn { + mut f := &Fn { + pkg: pkg + local_vars: [Var{} + ; MaxLocalVars] + } + return f +} + +// Function signatures are added to the top of the .c file in the first run. +fn (p mut Parser) fn_decl() { + p.fgen('fn ') + is_pub := p.tok == PUB + if is_pub { + p.next() + } + p.returns = false + p.next() + mut f := new_fn(p.pkg) + // Method receiver + mut receiver_typ := '' + if p.tok == LPAR { + f.is_method = true + p.check(LPAR) + receiver_name := p.check_name() + is_mut := p.tok == MUT + is_amp := p.tok == AMP + if is_mut || is_amp { + p.next() + } + receiver_typ = p.get_type() + T := p.table.find_type(receiver_typ) + if T.is_interface { + p.error('invalid receiver type `$receiver_typ` (`$receiver_typ` is an interface)') + } + // Don't allow modifying types from a different module + if !p.first_run() && !p.builtin_pkg && T.pkg != p.pkg { + println('T.pkg=$T.pkg') + println('pkg=$p.pkg') + p.error('cannot define new methods on non-local type `$receiver_typ`') + } + // (a *Foo) instead of (a mut Foo) is a common mistake + if !p.builtin_pkg && receiver_typ.contains('*') { + t := receiver_typ.replace('*', '') + p.error('use `($receiver_name mut $t)` instead of `($receiver_name *$t)`') + } + f.receiver_typ = receiver_typ + if is_mut || is_amp { + receiver_typ += '*' + } + p.check(RPAR) + receiver := Var { + name: receiver_name + is_arg: true + typ: receiver_typ + is_mut: is_mut + ref: is_amp + ptr: is_mut + line_nr: p.scanner.line_nr + } + f.args << receiver + f.register_var(receiver) + } + if p.tok == PLUS || p.tok == MINUS || p.tok == MUL { + f.name = p.tok.str() + println('!!! $f.name') + p.next() + } + else { + f.name = p.check_name() + } + // C function header def? (fn C.NSMakeRect(int,int,int,int)) + is_c := f.name == 'C' && p.tok == DOT + // Just fn signature? only builtin.v + default build mode + // is_sig := p.builtin_pkg && p.build_mode == DEFAULT_MODE + // is_sig := p.build_mode == DEFAULT_MODE && (p.builtin_pkg || p.file.contains(LANG_TMP)) + is_sig := p.is_sig() + // println('\n\nfn decl !!is_sig=$is_sig name=$f.name $p.builtin_pkg') + if is_c { + p.check(DOT) + f.name = p.check_name() + f.is_c = true + } + else if !p.translated && !p.file_path.contains('view.v') { + if contains_capital(f.name) { + p.error('function names cannot contain uppercase letters, use snake_case instead') + } + if f.name.contains('__') { + p.error('function names cannot contain double underscores ("__"), use single underscores instead') + } + } + // simple_name := f.name + // println('!SIMPLE=$simple_name') + // user.register() => User_register() + has_receiver := receiver_typ.len > 0 + if receiver_typ != '' { + // f.name = '${receiver_typ}_${f.name}' + } + // full pkg function name + // os.exit ==> os__exit() + if !is_c && !p.builtin_pkg && p.pkg != 'main' && receiver_typ.len == 0 { + f.name = p.prepend_pkg(f.name) + } + if p.first_run() && p.table.known_fn(f.name) && receiver_typ.len == 0 { + existing_fn := p.table.find_fn(f.name) + // This existing function could be defined as C decl before (no body), then we don't need to throw an erro + if !existing_fn.is_decl { + p.error('redefinition of `$f.name`') + } + } + // Generic? + mut is_generic := false + if p.tok == LT { + p.next() + gen_type := p.check_name() + if gen_type != 'T' { + p.error('only `T` is allowed as a generic type for now') + } + p.check(GT) + is_generic = true + } + // Args (...) + p.fn_args(mut f) + // Returns an error? + if p.tok == NOT { + p.next() + f.returns_error = true + } + // Returns a type? + mut typ := 'void' + if p.tok == NAME || p.tok == MUL || p.tok == AMP || p.tok == LSBR || + p.tok == QUESTION { + p.fgen(' ') + // TODO In + // if p.tok in [ NAME, MUL, AMP, LSBR ] { + typ = p.get_type() + } + // Translated C code can have empty functions (just definitions) + is_fn_header := !is_c && !is_sig && (p.translated || p.is_test) && + (p.tok != LCBR)// || (p.tok == NAME && p.peek() != LCBR)) + if is_fn_header { + f.is_decl = true + // println('GOT fn header $f.name') + } + // { required only in normal function declarations + if !is_c && !is_sig && !is_fn_header { + p.fgen(' ') + p.check(LCBR) + } + // Register option ? type + if typ.starts_with('Option_') { + p.cgen.typedefs << 'typedef Option $typ;' + } + // Register function + f.typ = typ + mut str_args := f.str_args(p.table) + // println('FN DECL $f.name typ=$f.typ str_args="$str_args"') + // Special case for main() args + if f.name == 'main' && !has_receiver { + if str_args != '' { + p.error('fn main must have no arguments and no return values') + } + typ = 'int' + str_args = 'int argc, char** argv' + } + // Only in C code generate User_register() instead of register() + // Internally it's still stored as "register" in type User + // mut fn_name_cgen := f.name + // if receiver_typ != '' { + // fn_name_cgen = '${receiver_typ}_$f.name' + // fn_name_cgen = fn_name_cgen.replace(' ', '') + // fn_name_cgen = fn_name_cgen.replace('*', '') + // } + mut fn_name_cgen := p.table.cgen_name(f) + // Start generation of the function body + is_live := p.is_live && f.name != 'main' && f.name != 'reload_so' + skip_main_in_test := f.name == 'main' && p.is_test + if !is_c && !is_live && !is_sig && !is_fn_header && !skip_main_in_test { + if p.obfuscate { + p.genln('; // ${f.name}') + } + p.genln('$typ $fn_name_cgen($str_args) {') + // if f.name == 'WinMain' { + // typ = 'int' + // } + } + if is_fn_header { + p.genln('$typ $fn_name_cgen($str_args);') + p.fgenln('') + } + if is_c { + p.fgenln('\n') + } + p.cur_fn = f + // Register the method + if receiver_typ != '' { + mut receiver_T := p.table.find_type(receiver_typ) + // No such type yet? It could be defined later. Create a new type. + // struct declaration later will modify it instead of creating a new one. + if p.first_run() && receiver_T.name == '' { + // println('fn decl !!!!!!! REG PH $receiver_typ') + ttyp := Type { + name: receiver_typ.replace('*', '') + pkg: p.pkg + is_placeholder: true + } + p.table.register_type2(ttyp) + } + // f.idx = p.table.fn_cnt + receiver_T.add_method(f) + } + else { + // println('register_fn typ=$typ isg=$is_generic') + p.table.register_fn(f) + } + if is_sig || p.first_run() || is_live || is_fn_header || skip_main_in_test { + // First pass? Skip the body for now [BIG] + if !is_sig && !is_fn_header { + for { + p.next() + if p.tok.is_decl() { + break + } + } + } + // Live code reloading? Load all fns from .so + if is_live && p.first_run() { + // p.cgen.consts_init.push('$fn_name_cgen = dlsym(lib, "$fn_name_cgen");') + p.cgen.so_fns << fn_name_cgen + fn_name_cgen = '(* $fn_name_cgen )' + } + // Actual fn declaration! + mut fn_decl := '$typ $fn_name_cgen($str_args)' + if p.obfuscate { + fn_decl += '; // ${f.name}' + } + // Add function definition to the top + if !is_c && f.name != 'main' && p.first_run() { + // TODO hack to make Volt compile without -embed_vlib + if f.name == 'darwin__nsstring' && p.build_mode == DEFAULT_MODE { + return + } + p.cgen.fns << fn_decl + ';' + } + p.fgenln('\n')// TODO defer this instead of copy pasting + return + } + if f.name == 'main' || f.name == 'WinMain' { + p.genln('init_consts();') + if p.table.imports.contains('os') { + if f.name == 'main' { + p.genln('os__init_os_args(argc, argv);') + } + else if f.name == 'WinMain' { + p.genln('os__parse_windows_cmd_line(pCmdLine);') + } + } + // We are in live code reload mode, call the .so loader in bg + if p.is_live { + p.genln(' +load_so("bounce.so"); +pthread_t _thread_so; +pthread_create(&_thread_so , NULL, &reload_so, NULL); ') + } + if p.is_test && !p.scanner.file_path.contains('/volt') { + p.error('tests cannot have function `main`') + } + } + // println('is_c=$is_c name=$f.name') + if is_c || is_sig || is_fn_header { + // println('IS SIG RETURNING tok=${p.strtok()}') + p.fgenln('\n') + return + } + // We are in profile mode? Start counting at the beginning of the function (save current time). + if p.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) + f.defer = ' ${cgen_name}_time += time__ticks() - _PROF_START;' + } + p.statements_no_curly_end() + // Print counting result after all statements in main + if p.is_prof && f.name == 'main' { + p.genln(p.print_prof_counters()) + } + // Counting or not, always need to add defer before the end + p.genln(f.defer) + if typ != 'void' && !p.returns && f.name != 'main' && f.name != 'WinMain' { + p.error('$f.name must return "$typ"') + } + // {} closed correctly? scope_level should be 0 + if p.pkg == 'main' { + // println(p.cur_fn.scope_level) + } + if p.cur_fn.scope_level > 2 { + // p.error('unclosed {') + } + // Make sure all vars in this function are used (only in main for now) + // if p.builtin_pkg || p.pkg == 'os' ||p.pkg=='http'{ + if p.pkg != 'main' { + p.genln('}') + p.fgenln('\n') + return + } + for var in f.local_vars { + if var.name == '' { + break + } + if !var.is_used && !var.is_arg && !p.translated && var.name != '_' { + p.scanner.line_nr = var.line_nr - 1 + p.error('`$var.name` declared and not used') + } + // Very basic automatic memory management at the end of the function. + // This is inserted right before the final `}`, so if the object is being returned, + // the free method will not be called. + if p.is_test && var.typ.contains('array_') { + // p.genln('v_${var.typ}_free($var.name); // !!!! XAXA') + // p.genln('free(${var.name}.data); // !!!! XAXA') + } + } + // println('end of func decl') + // p.print_tok() + p.cur_fn = EmptyFn + p.fgenln('\n') + p.genln('}') +} + +// Important function with 5 args. +// user.say_hi() => "User_say_hi(user)" +// method_ph - where to insert "user_say_hi(" +// receiver_var - "user" (needed for pthreads) +// receiver_type - "User" +fn (p mut Parser) async_fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { + // println('\nfn_call $f.name is_method=$f.is_method receiver_type=$f.receiver_type') + // p.print_tok() + mut thread_name := '' + // Normal function => just its name, method => TYPE_FNNAME + mut fn_name := f.name + if f.is_method { + receiver_type = receiver_type.replace('*', '') + fn_name = '${receiver_type}_${f.name}' + } + // Generate tmp struct with args + arg_struct_name := 'thread_arg_$fn_name' + tmp_struct := p.get_tmp() + p.genln('$arg_struct_name * $tmp_struct = malloc(sizeof($arg_struct_name));') + mut arg_struct := 'typedef struct $arg_struct_name { ' + p.next() + p.check(LPAR) + // str_args contains the args for the wrapper function: + // wrapper(arg_struct * arg) { fn("arg->a, arg->b"); } + mut str_args := '' + 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' + if i == 0 && f.is_method { + p.genln('$tmp_struct -> $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.expression() + p.genln(';') + if i < f.args.len - 1 { + p.check(COMMA) + str_args += ',' + } + } + 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) + 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) + // Create thread object + tmp_nr := p.get_tmp_counter() + thread_name = '_thread$tmp_nr' + if p.os != WINDOWS { + p.genln('pthread_t $thread_name;') + } + tmp2 := p.get_tmp() + mut parg := 'NULL' + if f.args.len > 0 { + parg = ' $tmp_struct' + } + // Call the wrapper + if p.os == WINDOWS { + p.genln(' CreateThread(0,0, $wrapper_name, $parg, 0,0);') + } + else { + p.genln('int $tmp2 = pthread_create(& $thread_name, NULL, $wrapper_name, $parg);') + } + p.check(RPAR) +} + +fn (p mut Parser) fn_call(f Fn, method_ph int, receiver_var, receiver_type string) { + p.calling_c = f.is_c + is_print := p.is_prod &&// Hide prints only in prod + !p.is_test && + !p.builtin_pkg &&// Allow prints in builtin pkgs + (f.name == 'println' || (f.is_c && f.name == 'printf')) + if !p.cgen.nogen { + p.cgen.nogen = is_print + } + cgen_name := p.table.cgen_name(f) + // if p.is_prof { + // p.cur_fn.called_fns << cgen_name + // } + // Normal function call + if !f.is_method { + p.gen(cgen_name) + p.gen('(') + // p.fgen(f.name) + } + // If we have a method placeholder, + // we need to preappend "method(receiver, ...)" + else { + // C only knows about functions "array_get", "array_set" etc + // TODO I don't need this? + // mut cgen_typ := receiver_type.replace('*', '') + // if cgen_typ.starts_with('array_') { + // cgen_typ = 'array' + // } + // println('METHOD fn_call name=$cgen_name') + // mut method_call := '${cgen_typ}_${cgen_name}(' + mut method_call := '${cgen_name}(' + // println('GGGG $f.name') + receiver := f.args.first() + if receiver.is_mut && !p.expr_var.is_mut { + println('$method_call recv=$receiver.name recv_mut=$receiver.is_mut') + p.error('`$p.expr_var.name` is immutable') + } + // if receiver is mutable 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.next() + // p.check(LPAR) + p.fn_call_args(f) + p.gen(')') + // p.check(RPAR) + p.calling_c = false + if is_print { + p.cgen.nogen = false + } + // println('end of fn call typ=$f.typ') +} + +// for declaration +// return an updated Fn object with args[] field set +fn (p mut Parser) fn_args(f mut Fn) { + p.check(LPAR) + // TODO defer p.check(RPAR) + if f.is_interface { + int_arg := Var { + typ: f.receiver_typ + } + f.args << int_arg + } + // Just register fn arg types + types_only := p.tok == MUL || (p.peek() == COMMA && p.table.known_type(p.lit)) || p.peek() == RPAR// (int, string) + if types_only { + for p.tok != RPAR { + typ := p.get_type() + v := Var { + typ: typ + is_arg: true + // is_mut: is_mut + line_nr: p.scanner.line_nr + } + // f.register_var(v) + f.args << v + if p.tok == COMMA { + p.next() + } + } + } + // (a int, b,c string) syntax + for p.tok != RPAR { + mut names := [ + p.check_name() + ] + // a,b,c int syntax + for p.tok == COMMA { + p.check(COMMA) + p.fspace() + names << p.check_name() + } + p.fspace() + is_mut := p.tok == MUT + if is_mut { + p.next() + } + mut typ2 := p.get_type() + for name in names { + if !p.first_run() && !p.table.known_type(typ2) { + p.error('fn_args: unknown type $typ2') + } + if is_mut { + // && !typ2.starts_with('array_') { + typ2 += '*' + } + v := Var { + name: name + typ: typ2 + is_arg: true + is_mut: is_mut + ptr: is_mut + line_nr: p.scanner.line_nr + } + f.register_var(v) + f.args << v + } + if p.tok == COMMA { + p.next() + } + if p.tok == DOTDOT { + f.args << Var { + name: '..' + } + p.next() + } + } + p.check(RPAR) +} + +fn (p mut Parser) fn_call_args(f *Fn) *Fn { + // p.gen('(') + // println('fn_call_args() name=$f.name args.len=$f.args.len') + // C func. # of args is not known + // if f.name.starts_with('c_') { + p.check(LPAR) + if f.is_c { + for p.tok != RPAR { + // debug("LEX before EXP", p.tok) + p.bool_expression() + // debug("LEX AFTER EXP", p.tok) + if p.tok == COMMA { + p.gen(', ') + p.check(COMMA) + // debug("UUUUU C FUNC" + fnName) + // p.g.Gen("C FN " + fnName) + } + } + p.check(RPAR) + // p.gen(')') + return f + } + // Receiver - first arg + for i, arg in f.args { + // 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 { + p.gen(',') + } + continue + } + // Reached the final vararg? Quit + if i == f.args.len - 1 && arg.name == '..' { + break + } + amp_ph := p.cgen.add_placeholder() + // ) here means not enough args were supplied + if p.tok == RPAR { + str_args := f.str_args(p.table)// TODO this is C args + p.error('not enough arguments in call to `$f.name ($str_args)`') + } + // If `arg` is mutable, the caller needs to provide MUT: + // `arr := [1,2,3]; reverse(mut arr);` + if arg.is_mut { + if p.tok != MUT { + p.error('`$arg.name` is a mutable argument, you need to provide `mut`: `$f.name(...mut a...)`') + } + if p.peek() != NAME { + p.error('`$arg.name` is a mutable argument, you need to provide a variable to modify: `$f.name(... mut a...)`') + } + p.check(MUT) + } + typ := p.bool_expression() + // TODO temporary hack to allow println(777) + if i == 0 && f.name == 'println' && typ != 'string' + && typ != 'void' { + // If we dont check for void, then V will compile "println(procedure())" + if !p.is_prod { + T := p.table.find_type(typ) + if typ == 'u8' { + p.cgen.set_placeholder(amp_ph, 'u8_str(') + } + else if T.parent == 'int' { + p.cgen.set_placeholder(amp_ph, 'int_str(') + } + else if typ.ends_with('*') { + p.cgen.set_placeholder(amp_ph, 'ptr_str(') + } + else { + // Make sure this type has a `str()` method + if !T.has_method('str') { + p.error('`$typ` needs to have method `str() string` to be printable') + } + p.cgen.set_placeholder(amp_ph, '${typ}_str(') + } + p.gen(')') + } + continue + } + got := typ + expected := arg.typ + // println('fn arg got="$got" exp="$expected"') + if !p.check_types_no_throw(got, expected) { + mut err := 'Fn "$f.name" wrong arg #${i+1}. ' + err += 'Expected "$arg.typ" ($arg.name) but got "$typ"' + p.error(err) + } + is_interface := p.table.is_interface(arg.typ) + // Add & or * before arg? + if !is_interface { + // Dereference + if got.contains('*') && !expected.contains('*') { + p.cgen.set_placeholder(amp_ph, '*') + } + // Reference + // TODO ptr hacks. DOOM hacks, fix please. + if !got.contains('*') && expected.contains('*') && got != 'voidptr' { + // println('\ne:"$expected" got:"$got"') + if ! (expected == 'void*' && got == 'int') && + ! (expected == 'byte*' && got.contains(']byte')) && + ! (expected == 'byte*' && got == 'string') { + p.cgen.set_placeholder(amp_ph, '& /*11 EXP:"$expected" GOT:"$got" */') + } + } + } + // interface? + if is_interface { + if !got.contains('*') { + p.cgen.set_placeholder(amp_ph, '&') + } + // Pass all interface methods + interface_type := p.table.find_type(arg.typ) + for method in interface_type.methods { + p.gen(', ${typ}_${method.name} ') + } + } + // Check for commas + if i < f.args.len - 1 { + // Handle 0 args passed to varargs + is_vararg := i == f.args.len - 2 && f.args[i + 1].name == '..' + if p.tok != COMMA && !is_vararg { + p.error('wrong number of arguments for $i,$arg.name fn `$f.name`: expected $f.args.len, but got less') + } + if p.tok == COMMA { + p.fgen(', ') + } + if !is_vararg { + p.next() + p.gen(',') + } + } + } + // varargs + if f.args.len > 0 { + last_arg := f.args.last() + if last_arg.name == '..' { + println('GOT VAR ARGS AFTER') + for p.tok != RPAR { + if p.tok == COMMA { + p.gen(',') + p.check(COMMA) + } + p.bool_expression() + } + } + } + if p.tok == COMMA { + p.error('wrong number of arguments for fn `$f.name`: expected $f.args.len, but got more') + } + p.check(RPAR) + // p.gen(')') +} + +fn contains_capital(s string) bool { + // for c in s { + for i := 0; i < s.len; i++ { + c := s[i] + if c >= `A` && c <= `Z` { + return true + } + } + return false +} + +// "fn (int, string) int" +fn (f Fn) typ_str() string { + mut sb := new_string_builder(50) + sb.write('fn (') + for i, arg in f.args { + sb.write(arg.typ) + if i < f.args.len - 1 { + sb.write(',') + } + } + sb.write(')') + if f.typ != 'void' { + sb.write(' $f.typ') + } + return sb.str() +} + +// f.args => "int a, string b" +fn (f &Fn) str_args(table *Table) string { + mut s := '' + for i, arg in f.args { + // Interfaces are a special case. We need to pass the object + pointers + // to all methods: + // fn handle(r Runner) { => + // void handle(void *r, void (*Runner_run)(void*)) { + if table.is_interface(arg.typ) { + // First the object (same name as the interface argument) + s += ' void* $arg.name' + // Now all methods + interface_type := table.find_type(arg.typ) + for method in interface_type.methods { + s += ', $method.typ (*${arg.typ}_${method.name})(void*) ' + } + } + else if arg.name == '..' { + s += '...' + } + else { + // s += '$arg.typ $arg.name' + s += table.cgen_name_type_pair(arg.name, arg.typ)// '$arg.typ $arg.name' + } + if i < f.args.len - 1 { + s += ', ' + } + } + return s +} + diff --git a/compiler/jsgen.v b/compiler/jsgen.v new file mode 100644 index 0000000000..5831145d59 --- /dev/null +++ b/compiler/jsgen.v @@ -0,0 +1,158 @@ +// 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 main + +// TODO replace with comptime code generation. +// TODO remove cJSON dependency. +// OLD: User decode_User(string js) { +// now it's +// User decode_User(cJSON* root) { +// User res; +// res.name = decode_string(js_get(root, "name")); +// res.profile = decode_Profile(js_get(root, "profile")); +// return res; +// } +// Codegen json_decode/encode funcs +fn (p mut Parser) gen_json_for_type(typ Type) { + mut dec := '' + mut enc := '' + t := typ.name + if t == 'int' || t == 'string' || t == 'bool' { + return + } + if p.first_run() { + return + } + // println('gen_json_for_type( $typ.name )') + // Register decoder fn + mut dec_fn := Fn { + pkg: p.pkg + typ: 'Option_$typ.name' + name: js_dec_name(t) + } + // Already registered? Skip. + if p.table.known_fn(dec_fn.name) { + return + } + // decode_TYPE funcs receive an actual cJSON* object to decode + // cJSON_Parse(str) call is added by the compiler + arg := Var { + typ: 'cJSON*' + } + dec_fn.args << arg + p.table.register_fn(dec_fn) + // Register encoder fn + mut enc_fn := Fn { + pkg: p.pkg + typ: 'cJSON*' + name: js_enc_name(t) + } + // encode_TYPE funcs receive an object to encode + enc_arg := Var { + typ: t + } + enc_fn.args << enc_arg + p.table.register_fn(enc_fn) + // Code gen decoder + dec += ' +//$t $dec_fn.name(cJSON* root) { +Option $dec_fn.name(cJSON* root, $t* res) { +// $t res; + if (!root) { + const char *error_ptr = cJSON_GetErrorPtr(); + if (error_ptr != NULL) { + fprintf(stderr, "Error in decode() for $t error_ptr=: %%s\\n", error_ptr); +// printf("\\nbad js=%%s\\n", js.str); + return b_error(tos2(error_ptr)); + } + } +' + // Code gen encoder + enc += ' +cJSON* $enc_fn.name($t val) { +cJSON *o = cJSON_CreateObject(); +string res = tos2(""); +' + // Handle arrays + if t.starts_with('array_') { + dec += p.decode_array(t) + enc += p.encode_array(t) + } + // Range through fields + for field in typ.fields { + if field.attr == 'skip' { + continue + } + field_type := p.table.find_type(field.typ) + // Now generate decoders for all field types in this struct + // need to do it here so that these functions are generated first + p.gen_json_for_type(field_type) + name := field.name + _typ := field.typ.replace('*', '') + enc_name := js_enc_name(_typ) + dec_name := js_dec_name(_typ) + if is_js_prim(_typ) { + dec += ' /*prim*/ res->$name = $dec_name(js_get(root, "$field.name"))' + // dec += '.data' + } + else { + dec += ' /*!!*/ $dec_name(js_get(root, "$field.name"), & (res->$name))' + } + dec += ';\n' + enc += ' cJSON_AddItemToObject(o, "$name", $enc_name(val.$name)); \n' + } + // cJSON_delete + p.cgen.fns << '$dec return opt_ok(res); \n}' + p.cgen.fns << '/*enc start*/ $enc return o;}' +} + +fn is_js_prim(typ string) bool { + return typ == 'int' || typ == 'string' || typ == 'bool' +} + +fn (p mut Parser) decode_array(typ string) string { + typ = typ.replace('array_', '') + t := p.table.find_type(typ) + fn_name := js_dec_name(typ) + // If we have `[]Profile`, have to register a Profile en(de)coder first + p.gen_json_for_type(t) + mut s := '' + if is_js_prim(typ) { + s = '$typ val= $fn_name(jsval); ' + } + else { + s = ' $typ val; $fn_name(jsval, &val); ' + } + return ' +*res = new_array(0, 0, sizeof($typ)); +const cJSON *jsval = NULL; +cJSON_ArrayForEach(jsval, root) +{ +$s + array__push(res, &val); +} +' +} + +fn js_enc_name(typ string) string { + name := 'json__jsencode_$typ' + return name +} + +fn js_dec_name(typ string) string { + name := 'json__jsdecode_$typ' + return name +} + +fn (p &Parser) encode_array(typ string) string { + typ = typ.replace('array_', '') + fn_name := js_enc_name(typ) + return ' +o = cJSON_CreateArray(); +for (int i = 0; i < val.len; i++){ + cJSON_AddItemToArray(o, $fn_name( (($typ*)val.data)[i] )); +} +' +} + diff --git a/compiler/main.v b/compiler/main.v new file mode 100644 index 0000000000..af08efa6a2 --- /dev/null +++ b/compiler/main.v @@ -0,0 +1,845 @@ +module main + +import os +import time + +const ( + Version = '0.0.12' +) + +// TODO no caps +enum BuildMode { + // `v program.v' + // Build user code only, and add pre-compiled vlib (`cc program.o builtin.o os.o...`) + DEFAULT_MODE + // `v -embed_vlib program.v` + // vlib + user code in one file (slower compilation, but easier when working on vlib and cross-compiling) + EMBED_VLIB + // `v -lib ~/v/os` + // build any module (generate os.o + os.vh) + BUILD // TODO a better name would be smth like `.build_module` I think +} + +fn vtmp_path() string { + $if windows { + return os.home_dir() + '/.vlang$Version/' + } + return '/var/tmp/vlang$Version/' +} + +const ( + SupportedPlatforms = ['windows', 'mac', 'linux'] + TmpPath = vtmp_path() +) + +// TODO V was re-written in V before enums were implemented. Lots of consts need to be replaced with +// enums. +const ( + MAC = 0 + LINUX = 1 + WINDOWS = 2 +) + +enum Pass { + // A very short pass that only looks at imports in the begginning of each file + RUN_IMPORTS + // First pass, only parses and saves declarations (fn signatures, consts, types). + // Skips function bodies. + // We need this because in V things can be used before they are declared. + RUN_DECLS + // Second pass, parses function bodies and generates C or machine code. + RUN_MAIN +} + +/* +// TODO rename to: +enum Pass { + imports + decls + main +} +*/ +struct V { +mut: + build_mode BuildMode + os int // the OS to build for + nofmt bool // disable vfmt + out_name_c string // name of the temporary C file + files []string // all V files that need to be parsed and compiled + dir string // directory (or file) being compiled (TODO rename to path?) + table *Table // table with types, vars, functions etc + cgen *CGen // C code generator + is_test bool // `v test string_test.v` + is_script bool // single file mode (`v program.v`), `fn main(){}` can be skipped + is_so bool + is_live bool // for hot code reloading + is_prof bool // benchmark every function + translated bool // `v translated doom.v` are we running V code translated from C? allow globals, ++ expressions, etc + obfuscate bool // `v -obf program.v`, renames functions to "f_XXX" + lang_dir string // "~/code/v" + is_verbose bool // print extra information with `v.log()` + is_run bool // `v run program.v` + is_play bool // playground mode + show_c_cmd bool // `v -show_c_cmd` prints the C command to build program.v.c + sanitize bool // use Clang's new "-fsanitize" option + out_name string // "program.exe" + is_prod bool // use "-O2" and skip printlns (TODO I don't thik many people want printlns to disappear in prod buidls) + is_repl bool +} + +fn main() { + // There's no `flags` module yet, so args have to be parsed manually + args := os.args + // Print the version and exit. + if 'version' in args { + println2('V $Version') + return + } + if '-h' in args || '--help' in args || 'help' in args { + println(HelpText) + } + // u := os.file_last_mod_unix('/var/tmp/alex') + // t := time.unixn(u) + // println(t.clean()) + // If there's not tmp path with current version yet, the user must be using a pre-built package + // Copy the `vlib` directory to the tmp path. + if !os.file_exists(TmpPath) && os.file_exists('vlib') { + os.mkdir(TmpPath) + os.system2('cp -rf vlib $TmpPath/') + // os.system2('cp -rf json $TmpPath/') + } + // Just fmt and exit + if args.contains('fmt') { + file := args.last() + if !os.file_exists(file) { + os.exit1('"$file" does not exist') + } + if !file.ends_with('.v') { + os.exit1('v fmt can only be used on .v files') + } + println2('vfmt is temporarily disabled') + return + } + // V with no args? REPL + if args.len < 2 { + run_repl() + return + } + // Construct the V object from command line arguments + mut c := new_v(args) + if c.is_verbose { + println(args) + } + // Generate the docs and exit + if args.contains('doc') { + // c.gen_doc_html_for_module(args.last()) + os.exit('') + } + c.compile() +} + +fn (c mut V) compile() { + mut cgen := c.cgen + cgen.genln('// Generated by V') + // Add user files to compile + c.add_user_v_files() + if c.is_verbose { + println('all .v files:') + println(c.files) + } + // First pass (declarations) + for file in c.files { + mut p := c.new_parser(file, RUN_DECLS) + p.parse() + } + // Main pass + cgen.run = RUN_MAIN + if c.os == MAC { + cgen.genln('#define mac (1) ') + // cgen.genln('#include ') + } + if c.os == LINUX { + cgen.genln('#define linux (1) ') + cgen.genln('#include ') + } + if c.os == WINDOWS { + cgen.genln('#define windows (1) ') + // cgen.genln('#include ') + cgen.genln('#include ') + } + if c.is_play { + cgen.genln('#define VPLAY (1) ') + } + cgen.genln(' +#include // TODO remove all these includes, define all function signatures and types manually +#include +#include +#include // for va_list +#include // int64_t etc + +//================================== TYPEDEFS ================================*/ + +typedef unsigned char byte; +typedef unsigned int uint; +typedef int64_t i64; +typedef int32_t i32; +typedef int16_t i16; +typedef int8_t i8; +typedef uint64_t u64; +typedef uint32_t u32; +typedef uint16_t u16; +typedef uint8_t u8; +typedef uint32_t rune; +typedef float f32; +typedef double f64; +typedef unsigned char* byteptr; +typedef int* intptr; +typedef void* voidptr; +typedef struct array array; +typedef struct map map; +typedef array array_string; +typedef array array_int; +typedef array array_byte; +typedef array array_uint; +typedef array array_float; +typedef map map_int; +typedef map map_string; +#ifndef bool + typedef int bool; + #define true 1 + #define false 0 +#endif + +//============================== HELPER C MACROS =============================*/ + +#define _PUSH(arr, val, tmp, tmp_typ) {tmp_typ tmp = (val); array__push(arr, &tmp);} +#define _IN(typ, val, arr) array_##typ##_contains(arr, val) +#define ALLOC_INIT(type, ...) (type *)memdup((type[]){ __VA_ARGS__ }, sizeof(type)) +#define UTF8_CHAR_LEN( byte ) (( 0xE5000000 >> (( byte >> 3 ) & 0x1e )) & 3 ) + 1 + +//================================== GLOBALS =================================*/ +//int V_ZERO = 0; +byteptr g_str_buf; +int load_so(byteptr); +void reload_so(); +void init_consts();') + imports_json := c.table.imports.contains('json') + // TODO remove global UI hack + if c.os == MAC && ((c.build_mode == EMBED_VLIB && c.table.imports.contains('ui')) || + (c.build_mode == BUILD && c.dir.contains('/ui'))) { + cgen.genln('id defaultFont = 0; // main.v') + } + // TODO remove ugly .c include once V has its own json parser + // Embed cjson either in embedvlib or in json.o + if imports_json && c.build_mode == EMBED_VLIB || + (c.build_mode == BUILD && c.out_name.contains('json.o')) { + cgen.genln('#include "json/cJSON/cJSON.c" ') + } + // We need the cjson header for all the json decoding user will do in default mode + if c.build_mode == DEFAULT_MODE { + if imports_json { + cgen.genln('#include "json/cJSON/cJSON.h"') + } + } + if c.build_mode == EMBED_VLIB || c.build_mode == DEFAULT_MODE { + // If we declare these for all modes, then when running `v a.v` we'll get + // `/usr/bin/ld: multiple definition of 'total_m'` + cgen.genln('i64 total_m = 0; // For counting total RAM allocated') + cgen.genln('int g_test_ok = 1; ') + if c.table.imports.contains('json') { + cgen.genln(' +#define js_get(object, key) cJSON_GetObjectItemCaseSensitive((object), (key)) +') + } + } + if os.args.contains('-debug_alloc') { + cgen.genln('#define DEBUG_ALLOC 1') + } + cgen.genln('/*================================== FNS =================================*/') + cgen.genln('this line will be replaced with definitions') + defs_pos := cgen.lines.len - 1 + for file in c.files { + mut p := c.new_parser(file, RUN_MAIN) + p.parse() + // p.g.gen_x64() + // Format all files (don't format automatically generated vlib headers) + if !c.nofmt && !file.contains('/vlib/') { + // new vfmt is not ready yet + } + } + c.log('Done parsing.') + // Write everything + mut d := new_string_builder(10000)// Just to avoid some unnecessary allocations + d.writeln(cgen.includes.join_lines()) + d.writeln(cgen.typedefs.join_lines()) + d.writeln(cgen.types.join_lines()) + d.writeln('\nstring _STR(const char*, ...);\n') + d.writeln('\nstring _STR_TMP(const char*, ...);\n') + d.writeln(cgen.fns.join_lines()) + d.writeln(cgen.consts.join_lines()) + d.writeln(cgen.thread_args.join_lines()) + if c.is_prof { + d.writeln('; // Prof counters:') + d.writeln(c.prof_counters()) + } + dd := d.str() + cgen.lines.set(defs_pos, dd)// TODO `def.str()` doesn't compile + // if c.build_mode in [.default, .embed_vlib] { + if c.build_mode == DEFAULT_MODE || c.build_mode == EMBED_VLIB { + // vlib can't have `init_consts()` + cgen.genln('void init_consts() { g_str_buf=malloc(1000); ${cgen.consts_init.join_lines()} }') + // _STR function can't be defined in vlib + cgen.genln(' +string _STR(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + size_t len = vsnprintf(0, 0, fmt, argptr) + 1; + va_end(argptr); + byte* buf = malloc(len); + va_start(argptr, fmt); + vsprintf(buf, fmt, argptr); + va_end(argptr); +#ifdef DEBUG_ALLOC + puts("_STR:"); + puts(buf); +#endif + return tos2(buf); +} + +string _STR_TMP(const char *fmt, ...) { + va_list argptr; + va_start(argptr, fmt); + size_t len = vsnprintf(0, 0, fmt, argptr) + 1; + va_end(argptr); + va_start(argptr, fmt); + vsprintf(g_str_buf, fmt, argptr); + va_end(argptr); +#ifdef DEBUG_ALLOC + //puts("_STR_TMP:"); + //puts(g_str_buf); +#endif + return tos2(g_str_buf); +} + +') + } + // Make sure the main function exists + // Obviously we don't need it in libraries + if c.build_mode != BUILD { + if !c.table.main_exists() && !c.is_test { + // It can be skipped in single file programs + if c.is_script { + println('Generating main()...') + cgen.genln('int main() { $cgen.fn_main; return 0; }') + } + else { + println('panic: function `main` is undeclared in the main module') + } + } + // Generate `main` which calls every single test function + else if c.is_test { + cgen.genln('int main() { init_consts();') + for v in c.table.fns { + if v.name.starts_with('test_') { + cgen.genln('$v.name();') + } + } + cgen.genln('return g_test_ok == 0; }') + } + } + if c.is_live { + cgen.genln(' int load_so(byteptr path) { + printf("load_so %s\\n", path); dlclose(live_lib); live_lib = dlopen(path, RTLD_LAZY); + if (!live_lib) {puts("open failed"); exit(1); return 0;} + ') + for so_fn in cgen.so_fns { + cgen.genln('$so_fn = dlsym(live_lib, "$so_fn"); ') + } + cgen.genln('return 1; }') + } + cgen.save() + c.log('flags=') + if c.is_verbose { + println(c.table.flags) + } + c.cc() + if c.is_test || c.is_run { + if true || c.is_verbose { + println('============running $c.out_name==============================') + } + cmd := if c.out_name.starts_with('/') { + c.out_name + } + else { + './' + c.out_name + } + ret := os.system2(cmd) + if ret != 0 { + s := os.system(cmd) + println2(s) + os.exit1('ret not 0, exiting') + } + } +} + +fn (c mut V) cc() { + linux_host := os.user_os() == 'linux' + c.log('cc() isprod=$c.is_prod outname=$c.out_name') + mut a := ['-w']// arguments for the C compiler + flags := c.table.flags.join(' ') + /* + mut shared := '' + if c.is_so { + a << '-shared'// -Wl,-z,defs' + c.out_name = c.out_name + '.so' + } +*/ + if c.is_prod { + a << '-O2' + } + else { + a << '-g' + } + mut libs := ''// builtin.o os.o http.o etc + if c.build_mode == BUILD { + a << '-c' + } + else if c.build_mode == EMBED_VLIB { + // + } + else if c.build_mode == DEFAULT_MODE { + libs = '$TmpPath/vlib/builtin.o' + if !os.file_exists(libs) { + println2('`builtin.o` not found') + exit('') + } + for imp in c.table.imports { + if imp == 'webview' { + continue + } + libs += ' $TmpPath/vlib/${imp}.o' + } + } + // -I flags + /* +mut args := '' + for flag in c.table.flags { + if !flag.starts_with('-l') { + args += flag + args += ' ' + } + } +*/ + if c.sanitize { + a << '-fsanitize=leak' + } + // Cross compiling linux + sysroot := '/Users/alex/tmp/lld/linuxroot/' + if c.os == LINUX && !linux_host { + // Build file.o + a << '-c --sysroot=$sysroot -target x86_64-linux-gnu' + // Right now `out_name` can be `file`, not `file.o` + if !c.out_name.ends_with('.o') { + c.out_name = c.out_name + '.o' + } + } + // Cross compiling windows + // sysroot := '/Users/alex/tmp/lld/linuxroot/' + // Output executable name + // else { + a << '-o $c.out_name' + // } + // Min macos version is mandatory I think? + if c.os == MAC { + a << '-mmacosx-version-min=10.7' + } + a << flags + a << libs + // macOS code can include objective C TODO remove once objective C is replaced with C + if c.os == MAC { + a << '-x objective-c' + } + // The C file we are compiling + a << '$TmpPath/$c.out_name_c' + // Without these libs compilation will fail on Linux + if c.os == LINUX && c.build_mode != BUILD { + a << '-lm -ldl -lpthread' + } + // Find clang executable + fast_clang := '/usr/local/Cellar/llvm/8.0.0/bin/clang' + args := a.join(' ') + cmd := if os.file_exists(fast_clang) { + '$fast_clang -I. $args' + } + else { + 'clang -I. $args' + } + // Print the C command + if c.show_c_cmd || c.is_verbose { + println('\n==========\n$cmd\n=========\n') + } + // Run + res := os.system(cmd) + // println('C OUTPUT:') + if res.contains('error: ') { + println2(res) + panic('clang error') + } + // Link it if we are cross compiling and need an executable + if c.os == LINUX && !linux_host && c.build_mode != BUILD { + c.out_name = c.out_name.replace('.o', '') + obj_file := c.out_name + '.o' + println('linux obj_file=$obj_file out_name=$c.out_name') + ress := os.system('/usr/local/Cellar/llvm/8.0.0/bin/ld.lld --sysroot=$sysroot ' + + '-v -o $c.out_name ' + + '-m elf_x86_64 -dynamic-linker /lib64/ld-linux-x86-64.so.2 ' + + '/usr/lib/x86_64-linux-gnu/crt1.o ' + + '$sysroot/lib/x86_64-linux-gnu/libm-2.28.a ' + + '/usr/lib/x86_64-linux-gnu/crti.o ' + + obj_file + + ' /usr/lib/x86_64-linux-gnu/libc.so ' + + '/usr/lib/x86_64-linux-gnu/crtn.o') + println(ress) + if ress.contains('error:') { + os.exit1('') + } + println('linux cross compilation done. resulting binary: "$c.out_name"') + } + // print_time('after gcc') +} + +fn (c &V) v_files_from_dir(dir string) []string { + mut res := []string + mut files := os.ls(dir) + if !os.file_exists(dir) { + panic('$dir doesnt exist') + } + if c.is_verbose { + println('v_files_from_dir ("$dir")') + } + // println(files.len) + // println(files) + files.sort() + for file in files { + c.log('F=$file') + if !file.ends_with('.v') && !file.ends_with('.vh') { + continue + } + if file.ends_with('_test.v') { + continue + } + if file.ends_with('_win.v') && c.os != WINDOWS { + continue + } + if file.ends_with('_lin.v') && c.os != LINUX { + continue + } + if file.ends_with('_mac.v') && c.os != MAC { + lin_file := file.replace('_mac.v', '_lin.v') + // println('lin_file="$lin_file"') + // If there are both _mac.v and _lin.v, don't use _mac.v + if os.file_exists('$dir/$lin_file') { + continue + } + else if c.os == WINDOWS { + continue + } + else { + // If there's only _mac.v, then it can be used on Linux too + } + } + res << '$dir/$file' + } + return res +} + +// Parses imports, adds necessary libs, and then user files +fn (c mut V) add_user_v_files() { + mut dir := c.dir + c.log('add_v_files($dir)') + // Need to store user files separately, because they have to be added after libs, but we dont know + // which libs need to be added yet + mut user_files := []string + // v volt/slack_test.v: compile all .v files to get the environment + // I need to implement user packages! TODO + is_test_with_imports := dir.ends_with('_test.v') && + (dir.contains('/volt') || dir.contains('/c2volt'))// TODO + if is_test_with_imports { + user_files << dir + pos := dir.last_index('/') + dir = dir.left(pos) + '/'// TODO WHY IS THIS NEEDED? + } + if dir.ends_with('.v') { + // Just compile one file and get parent dir + user_files << dir + dir = dir.all_before('/') + } + else { + // Add files from the dir user is compiling (only .v files) + files := c.v_files_from_dir(dir) + for file in files { + user_files << file + } + } + if user_files.len == 0 { + exit('No input .v files') + } + if c.is_verbose { + c.log('user_files:') + println(user_files) + } + // Parse user imports + for file in user_files { + mut p := c.new_parser(file, RUN_IMPORTS) + p.parse() + } + // Parse lib imports + if c.build_mode == DEFAULT_MODE { + for i := 0; i < c.table.imports.len; i++ { + pkg := c.table.imports[i] + vfiles := c.v_files_from_dir('$TmpPath/vlib/$pkg') + // Add all imports referenced by these libs + for file in vfiles { + mut p := c.new_parser(file, RUN_IMPORTS) + p.parse() + } + } + } + else { + // TODO this used to crash compiler? + // for pkg in c.table.imports { + for i := 0; i < c.table.imports.len; i++ { + pkg := c.table.imports[i] + // mut import_path := '$c.lang_dir/$pkg' + vfiles := c.v_files_from_dir('$c.lang_dir/$pkg') + // Add all imports referenced by these libs + for file in vfiles { + mut p := c.new_parser(file, RUN_IMPORTS) + p.parse() + } + } + } + if c.is_verbose { + c.log('imports:') + println(c.table.imports) + } + // Only now add all combined lib files + for pkg in c.table.imports { + mut module_path := '$c.lang_dir/$pkg' + // If we are in default mode, we don't parse vlib .v files, but header .vh files in + // TmpPath/vlib + // These were generated by vfmt + if c.build_mode == DEFAULT_MODE || c.build_mode == BUILD { + module_path = '$TmpPath/vlib/$pkg' + } + vfiles := c.v_files_from_dir(module_path) + for vfile in vfiles { + c.files << vfile + } + // TODO c.files.append_array(vfiles) + } + // Add user code last + for file in user_files { + c.files << file + } + // c.files.append_array(user_files) +} + +fn get_arg(joined_args, arg, def string) string { + key := '-$arg ' + mut pos := joined_args.index(key) + if pos == -1 { + return def + } + pos += key.len + mut space := joined_args.index_after(' ', pos) + if space == -1 { + space = joined_args.len + } + res := joined_args.substr(pos, space) + // println('get_arg($arg) = "$res"') + return res +} + +fn (c &V) log(s string) { + if !c.is_verbose { + return + } + println(s) +} + +fn new_v(args[]string) *V { + mut dir := args.last() + // println('new compiler "$dir"') + if args.len < 2 { + dir = '' + } + joined_args := args.join(' ') + target_os := get_arg(joined_args, 'os', '') + mut out_name := get_arg(joined_args, 'o', 'a.out') + // build mode + mut build_mode := DEFAULT_MODE + if args.contains('-lib') { + build_mode = BUILD + // v -lib ~/v/os => os.o + base := dir.all_after('/') + println('Building module ${base}...') + out_name = '$TmpPath/vlib/${base}.o' + // Cross compiling? Use separate dirs for each os + if target_os != os.user_os() { + os.mkdir('$TmpPath/vlib/$target_os') + out_name = '$TmpPath/vlib/$target_os/${base}.o' + println('Cross compiling $out_name') + } + } + // TODO embed_vlib is temporarily the default mode. It's much slower. + else if !args.contains('-embed_vlib') { + build_mode = EMBED_VLIB + } + // + is_test := dir.ends_with('_test.v') + is_script := dir.ends_with('.v') + if is_script && !os.file_exists(dir) { + exit('`$dir` does not exist') + } + // No -o provided? foo.v => foo + if out_name == 'a.out' && dir.ends_with('.v') { + out_name = dir.left(dir.len - 2) + } + // if we are in `/foo` and run `v .`, the executable should be `foo` + if dir == '.' && out_name == 'a.out' { + base := os.getwd().all_after('/') + out_name = base.trim_space() + } + mut _os := MAC + // No OS specifed? Use current system + if target_os == '' { + $if linux { + _os = LINUX + } + $if mac { + _os = MAC + } + $if windows { + _os = WINDOWS + } + } + else { + switch target_os { + case 'linux': _os = LINUX + case 'windows': _os = WINDOWS + case 'mac': _os = MAC + } + } + builtins := [ + 'array.v', + 'string.v', + 'builtin.v', + 'int.v', + 'utf8.v', + 'map.v', + 'smap.v', + 'option.v', + 'string_builder.v', + ] + // Location of all vlib files TODO allow custom location + mut lang_dir = os.home_dir() + '/code/v/' + out_name_c := out_name.all_after('/') + '.c' + mut files := []string + // Add builtin files + if !out_name.contains('builtin.o') { + for builtin in builtins { + mut f := '$lang_dir/builtin/$builtin' + // In default mode we use precompiled vlib.o, point to .vh files with signatures + if build_mode == DEFAULT_MODE || build_mode == BUILD { + f = '$TmpPath/vlib/builtin/${builtin}h' + } + files << f + } + } + obfuscate := args.contains('-obf') + return &V { + os: _os + out_name: out_name + files: files + dir: dir + lang_dir: lang_dir + table: new_table(obfuscate) + out_name: out_name + out_name_c: out_name_c + is_test: is_test + is_script: is_script + is_so: args.contains('-shared') + is_play: args.contains('play') + is_prod: args.contains('-prod') + is_verbose: args.contains('-verbose') + obfuscate: obfuscate + is_prof: args.contains('-prof') + is_live: args.contains('-live') + sanitize: args.contains('-sanitize') + nofmt: args.contains('-nofmt') + show_c_cmd: args.contains('-show_c_cmd') + translated: args.contains('translated') + cgen: new_cgen(out_name_c) + build_mode: build_mode + is_run: args.contains('run') + is_repl: args.contains('-repl') + } +} + +fn run_repl() []string { + println2('V $Version') + println2('Use Ctrl-D to exit') + println2('For now you have to use println() to print values, this will be fixed soon\n') + file := TmpPath + '/vrepl.v' + mut lines := []string + for { + print('>>> ') + mut line := os.get_line().trim_space() + if line == '' { + break + } + // Save the source only if the user is printing something, + // but don't add this print call to the `lines` array, + // so that it doesn't get called during the next print. + if line.starts_with('print') { + // TODO remove this once files without main compile correctly + source_code := 'fn main(){' + lines.join('\n') + '\n' + line + '}' + os.write_file(file, source_code) + mut v := new_v( ['v', '-repl', file]) + v.compile() + s := os.system(TmpPath + '/vrepl') + println2(s) + } + else { + lines << line + } + } + return lines +} + +// This definitely needs to be better :) +const ( + HelpText = ' +- To build a V program: +v file.v + +- To get current V version: +v version + +- To build an optimized executable: +v -prod file.v + +- To specify the executable\'s name: +v -o program file.v +' +) + +/* +- To disable automatic formatting: +v -nofmt file.v + +- To build a program with an embedded vlib (use this if you do not have prebuilt vlib libraries or if you +are working on vlib) +v -embed_vlib file.v +*/ diff --git a/compiler/parser.v b/compiler/parser.v new file mode 100644 index 0000000000..9e698b8931 --- /dev/null +++ b/compiler/parser.v @@ -0,0 +1,3216 @@ +module main + +import rand + +struct Var { +mut: + typ string + name string + is_arg bool + is_const bool + is_import_const bool // TODO remove import consts entirely + args []Var // function args + attr string // [json] etc + is_mut bool + ptr bool + ref bool + parent_fn string // Variables can only be defined in functions + pkg string // module where this var is stored TODO rename to `mod` + line_nr int + access_mod AccessMod + is_global bool // __global (translated from C only) + is_used bool + scope_level int +} + +struct Parser { + file_path string // "/home/user/hello.v" + file_name string // "hello.v" +mut: + scanner *Scanner + // tokens []Token // TODO cache all tokens, right now they have to be scanned twice + token_idx int + tok Token + prev_tok Token + prev_tok2 Token // TODO remove these once the tokens are cached + lit string + cgen *CGen + table *Table + run Pass // TODO rename `run` to `pass` + os int + pkg string + inside_const bool + expr_var Var + assigned_type string + tmp_cnt int + // TODO all these options are copy-pasted from the V struct. Create a Settings struct instead? + is_test bool + is_script bool + is_live bool + is_so bool + is_prof bool + translated bool + is_prod bool + is_verbose bool + obfuscate bool + is_play bool + is_repl bool + builtin_pkg bool + build_mode BuildMode + vh_lines []string + inside_if_expr bool + is_struct_init bool + if_expr_cnt int + for_expr_cnt int // to detect whether `continue` can be used + ptr_cast bool + calling_c bool + cur_fn *Fn + returns bool +} + +const ( + EmptyFn = &Fn { } +) + +fn (c mut V) new_parser(path string, run Pass) Parser { + c.log('new_parser("$path")') + c.cgen.run = run + mut p := Parser { + file_path: path + file_name: path.all_after('/') + scanner: new_scanner(path) + table: c.table + cur_fn: EmptyFn + cgen: c.cgen + is_test: c.is_test + is_script: (c.is_script && path == c.dir) + is_so: c.is_so + os: c.os + is_prof: c.is_prof + is_prod: c.is_prod + is_play: c.is_play + translated: c.translated + obfuscate: c.obfuscate + is_verbose: c.is_verbose + build_mode: c.build_mode + is_repl: c.is_repl + run: run + } + p.next() + // p.scanner.debug_tokens() + return p +} + +fn (p mut Parser) next() { + p.prev_tok2 = p.prev_tok + p.prev_tok = p.tok + res := p.scanner.scan() + p.tok = res.tok + p.lit = res.lit +} + +fn (p &Parser) log(s string) { + if !p.is_verbose { + return + } + println(s) +} + +fn (p mut Parser) parse() { + p.log('\nparse() run=$p.run file=$p.file_name tok=${p.strtok()}')// , "script_file=", script_file) + // `module main` is not required if it's a single file program + if p.is_script || p.is_test { + p.pkg = 'main' + // User may still specify `module main` + if p.tok == PACKAGE { + p.next() + p.fgen('module ') + p.pkg = p.check_name() + } + } + else { + p.check(PACKAGE) + p.pkg = p.check_name() + } + p.fgenln('\n') + p.builtin_pkg = p.pkg == 'builtin' + // Import pass - the first and the smallest pass that only analyzes imports + p.table.register_package(p.pkg) + if p.run == RUN_IMPORTS { + for p.tok == IMPORT && p.peek() != CONST { + p.import_statement() + } + return + } + // Go through every top level token or throw a compilation error if a non-top level token is met + for { + switch p.tok { + case IMPORT: + if p.peek() == CONST { + p.const_decl() + } + else { + // TODO remove imported consts from the language + p.import_statement() + } + case AT: + p.at() + case ENUM: + p.next() + if p.tok == NAME { + p.fgen('enum ') + name := p.check_name() + p.fgen(' ') + p.enum_decl(name) + } + // enum without a name, only allowed in code, translated from C + // it's a very bad practice in C as well, but is used unfortunately (for example, by DOOM) + // such fields are basically int consts + else if p.translated { + p.enum_decl('int') + } + else { + p.check(NAME) + } + case PUB: + if p.peek() == FUNC { + p.fn_decl() + } + // TODO public structs + case FUNC: + p.fn_decl() + case TIP: + p.type_decl() + case STRUCT, INTERFACE, UNION, LSBR:// `[` can only mean an [attribute] before the struct definition + p.struct_decl() + case CONST: + p.const_decl() + case HASH: + // insert C code, TODO this is going to be removed ASAP + // some libraries (like UI) still have lots of C code + // # puts("hello"); + p.chash() + case DOLLAR: + // $if, $else + p.comp_time() + case GLOBAL: + if !p.translated { + p.error('__global is only allowed in translated code') + } + p.next() + name := p.check_name() + typ := p.get_type() + p.register_global(name, typ) + // p.genln(p.table.cgen_name_type_pair(name, typ)) + mut g := p.table.cgen_name_type_pair(name, typ) + if p.tok == ASSIGN { + p.next() + // p.gen(' = ') + g += ' = ' + p.cgen.start_tmp() + p.bool_expression() + // g += '<<< ' + p.cgen.end_tmp() + '>>>' + g += p.cgen.end_tmp() + } + // p.genln('; // global') + g += ('; // global') + p.cgen.consts << g + case EOF: + p.log('end of parse()') + if true && !p.first_run() && p.fileis('test') { + out := os.create('/var/tmp/fmt.v') + out.appendln(p.scanner.fmt_out.str()) + out.close() + } + return + default: + // no `fn main`, add this "global" statement to cgen.fn_main + if p.is_script && !p.is_test { + if p.cur_fn.scope_level == 0 { + // p.cur_fn.scope_level++ + } + // println('is script') + p.print_tok() + start := p.cgen.lines.len + p.statement(true) + end := p.cgen.lines.len + lines := p.cgen.lines.slice(start, end) + // p.cgen.fn_main << p.cgen.prev_line + // println('fn line:') + // println(p.cgen.fn_main + lines.join('\n')) + p.cgen.fn_main = p.cgen.fn_main + lines.join('\n') + p.cgen.cur_line = '' + for i := start; i < end; i++ { + // p.cgen.lines[p.cgen.lines.len - 1] = '' + p.cgen.lines[i] = '' + } + // exit('') + } + else { + p.error('unexpected token `${p.strtok()}`') + } + } + } +} + +fn (p mut Parser) import_statement() { + p.check(IMPORT) + // `import ()` + if p.tok == LPAR { + p.check(LPAR) + for p.tok != RPAR && p.tok != EOF { + pkg := p.lit.trim_space() + p.next() + if p.table.imports.contains(pkg) { + continue + } + p.table.imports << pkg + p.table.register_package(pkg) + } + p.check(RPAR) + return + } + // `import foo` + if p.tok != NAME { + p.error('bad import format') + } + pkg := p.lit.trim_space() + p.next() + p.fgenln(' ' + pkg) + // Make sure there are no duplicate imports + if p.table.imports.contains(pkg) { + return + } + p.log('adding import $pkg') + p.table.imports << pkg + p.table.register_package(pkg) +} + +fn (p mut Parser) const_decl() { + is_import := p.tok == IMPORT + p.inside_const = true + if is_import { + p.next() + } + p.check(CONST) + p.fspace() + p.check(LPAR) + p.fgenln('') + p.scanner.fmt_indent++ + for p.tok == NAME { + // `Age = 20` + mut name := p.check_name() + if p.is_play && ! (name[0] >= `A` && name[0] <= `Z`) { + p.error('const name must be capitalized') + } + // Imported consts (like GL_TRIANGLES) dont need pkg prepended (gl__GL_TRIANGLES) + if !is_import { + name = p.prepend_pkg(name) + } + mut typ := 'int' + if !is_import { + p.check_space(ASSIGN) + typ = p.expression() + } + if p.first_run() && !is_import && p.table.known_const(name) { + p.error('redefinition of `$name`') + } + p.table.register_const(name, typ, p.pkg, is_import) + if p.run == RUN_MAIN && !is_import { + // TODO hack + // cur_line has const's value right now. if it's just a number, then optimize generation: + // output a #define so that we don't pollute the binary with unnecessary global vars + if is_compile_time_const(p.cgen.cur_line) { + p.cgen.consts << '#define $name $p.cgen.cur_line' + p.cgen.cur_line = '' + p.fgenln('') + continue + } + if typ.starts_with('[') { + p.cgen.consts << p.table.cgen_name_type_pair(name, typ) + + ' = $p.cgen.cur_line;' + } + else { + p.cgen.consts << p.table.cgen_name_type_pair(name, typ) + ';' + p.cgen.consts_init << '$name = $p.cgen.cur_line;' + } + p.cgen.cur_line = '' + } + p.fgenln('') + } + p.scanner.fmt_indent-- + p.check(RPAR) + p.fgenln('\n') + p.inside_const = false +} + +// `type myint int` +// `type onclickfn fn(voidptr) int` +fn (p mut Parser) type_decl() { + p.check(TIP) + name := p.check_name() + parent := p.get_type() + nt_pair := p.table.cgen_name_type_pair(name, parent) + // TODO dirty C typedef hacks for DOOM + // Unknown type probably means it's a struct, and it's used before the struct is defined, + // so specify "struct" + _struct := if !parent.contains('[') && !parent.starts_with('fn ') && !p.table.known_type(parent){'struct'} else { ''} + p.gen_typedef('typedef $_struct $nt_pair; // type alias name="$name" parent="$parent"') + p.table.register_type_with_parent(name, parent) +} + +// also unions and interfaces +fn (p mut Parser) struct_decl() { + // Attribute before type? + mut objc_parent := '' + mut is_objc := false// V can generate Objective C for integration with Cocoa + // [attr] + if p.tok == LSBR { + p.check(LSBR) + // `[interface:ParentInterface]` + is_objc = p.tok == INTERFACE + p.next() + if is_objc { + p.check(COLON) + objc_parent = p.check_name() + } + p.check(RSBR) + } + is_interface := p.tok == INTERFACE + is_union := p.tok == UNION + is_struct := p.tok == STRUCT + p.fgen(p.tok.str() + ' ') + // Get type name + p.next() + mut name := p.check_name() + if name.contains('_') && !p.translated { + p.error('type names cannot contain `_`') + } + if is_interface && !name.ends_with('er') { + p.error('interface names temporarily have to end with `er` (e.g. `Speaker`, `Reader`)') + } + is_c := name == 'C' && p.tok == DOT + if is_c { + p.check(DOT) + name = p.check_name() + } + // Specify full type name + if !is_c && !p.builtin_pkg && p.pkg != 'main' { + name = p.prepend_pkg(name) + } + if p.run == RUN_DECLS && p.table.known_type(name) { + p.error('`$name` redeclared') + } + // Generate type definitions + if is_objc { + p.gen_type('@interface $name : $objc_parent { @public') + } + else { + // type alias is generated later + if !is_c { + kind := if is_union{'union'} else { 'struct'} + p.gen_typedef('typedef $kind $name $name;') + p.gen_type('$kind $name {') + } + } + // V used to have 'type Foo struct', many Go users might use this syntax + if p.tok == STRUCT { + p.error('use `struct $name {` instead of `type $name struct {`') + } + // Register the type + mut typ := p.table.find_type(name) + mut is_ph := false + if typ.is_placeholder { + is_ph = true + typ.name = name + typ.pkg = p.pkg + typ.is_c = is_c + typ.is_placeholder = false + } + else { + typ = &Type { + name: name + pkg: p.pkg + is_c: is_c + is_interface: is_interface + } + } + // Struct `C.Foo` declaration, no body + // println('EEEE $is_c $is_struct') + if is_c && is_struct && p.tok != LCBR { + // println('skipping struct header $name') + p.table.register_type2(typ) + return + } + p.fgen(' ') + p.check(LCBR) + // Struct fields + mut is_pub := false + mut is_mut := false + mut names := []string// to avoid dup names TODO alloc perf + // mut is_mut_mut := false + for p.tok != RCBR { + if p.tok == PUB { + if is_pub { + p.error('structs can only have one `pub:`, all public fields have to be grouped') + } + is_pub = true + is_mut = false + p.scanner.fmt_indent-- + p.check(PUB) + p.check(COLON) + p.scanner.fmt_indent++ + p.fgenln('') + } + if p.tok == MUT { + if is_mut { + p.error('structs can only have one `mut:`, all private mutable fields have to be grouped') + } + is_mut = true + is_pub = false + p.scanner.fmt_indent-- + p.check(MUT) + p.check(COLON) + p.scanner.fmt_indent++ + p.fgenln('') + } + // if is_pub { + // } + // (mut) user *User + // if p.tok == PLUS { + // p.next() + // } + // Check dups + field_name := p.check_name() + if field_name in names { + p.error('duplicate field `$field_name`') + } + names << field_name + // We are in an interface? + // `run() string` => run is a method, not a struct field + if is_interface { + mut interface_method := &Fn { + name: field_name + is_interface: true + is_method: true + receiver_typ: name + } + println('is interfaace. field=$field_name run=$p.run') + p.fn_args(mut interface_method) + p.fspace() + interface_method.typ = p.get_type()// method return type + typ.add_method(interface_method) + p.fgenln('') + continue + } + // `pub` access mod + access_mod := if is_pub{PUBLIC} else { PRIVATE} + if typ.name == 'Userf' { + println('$field_name $access_mod mut=$is_mut') + } + p.fgen(' ') + field_type := p.get_type() + is_atomic := p.tok == ATOMIC + if is_atomic { + p.next() + p.gen_type('_Atomic ') + } + if !is_c { + p.gen_type(p.table.cgen_name_type_pair(field_name, field_type) + ';') + } + // [ATTR] + mut attr := '' + if p.tok == LSBR { + p.next() + attr = p.check_name() + p.check(RSBR) + } + typ.add_field(field_name, field_type, is_mut, attr, access_mod) + p.fgenln('') + } + if !is_ph && p.first_run() { + p.table.register_type2(typ) + } + p.check(RCBR) + if !is_c { + p.gen_type('}; ') + } + if is_objc { + p.gen_type('@end') + } + p.fgenln('\n') +} + +fn (p mut Parser) enum_decl(_enum_name string) { + mut enum_name := _enum_name + // Specify full type name + if !p.builtin_pkg && p.pkg != 'main' { + enum_name = p.prepend_pkg(enum_name) + } + p.table.register_type2(&Type { + name: enum_name + pkg: p.pkg + parent: 'int' + is_enum: true + }) + // Skip empty enums + if enum_name != 'int' { + p.cgen.typedefs << 'typedef int $enum_name ;\n' + } + p.check(LCBR) + mut val := 0 + for p.tok == NAME { + field := p.check_name() + // name := '${p.pkg}__${enum_name}_$field' + // name := '${enum_name}_$field' + name := '$field' + p.fgenln('') + if p.run == RUN_MAIN { + p.cgen.consts << '#define $name $val \n' + } + if p.tok == COMMA { + p.next() + } + p.table.register_const(name, enum_name, p.pkg, false) + val++ + } + p.check(RCBR) + p.fgenln('\n') +} + +// check_name checks for a name token and returns its literal +fn (p mut Parser) check_name() string { + name := p.lit + p.check(NAME) + return name +} + +fn (p mut Parser) check_string() string { + s := p.lit + p.check(STRING) + return s +} + +fn (p &Parser) strtok() string { + if p.tok == NAME { + return p.lit + } + if p.tok == STRING { + return '"$p.lit"' + } + res := p.tok.str() + if res == '' { + n := int(p.tok) + return n.str() + } + return res +} + +// same as check(), but addes a space to the formatter output +// TODO bad name +fn (p mut Parser) check_space(expected Token) { + p.fspace() + p.check(expected) + p.fspace() +} + +fn (p mut Parser) check(expected Token) { + if p.tok != expected { + println('check()') + mut s := 'expected `${expected.str()}` but got `${p.strtok()}`' + p.next() + println('next token = `${p.strtok()}`') + print_backtrace() + p.error(s) + } + if expected == RCBR { + p.scanner.fmt_indent-- + } + p.fgen(p.strtok()) + // vfmt: increase indentation on `{` unless it's `{}` + if expected == LCBR && p.scanner.text[p.scanner.pos + 1] != `}` { + p.fgenln('') + p.scanner.fmt_indent++ + } + p.next() +} + +fn (p mut Parser) error(s string) { + // Dump all vars and types for debugging + if false { + file_types := os.create_file('$TmpPath/types') + file_vars := os.create_file('$TmpPath/vars') + // ////debug("ALL T", q.J(p.table.types)) + // os.write_to_file('/var/tmp/lang.types', '')//pes(p.table.types)) + // //debug("ALL V", q.J(p.table.vars)) + // os.write_to_file('/var/tmp/lang.vars', q.J(p.table.vars)) + file_types.close() + file_vars.close() + } + if !p.is_repl { + println('pass=$p.run fn=`$p.cur_fn.name`') + } + p.cgen.save() + // p.scanner.debug_tokens() + // Print `[]int` instead of `array_int` in errors + p.scanner.error(s.replace('array_', '[]').replace('__', '.')) +} + +fn (p &Parser) first_run() bool { + return p.run == RUN_DECLS +} + +// TODO return Type instead of string? +fn (p mut Parser) get_type() string { + debug := p.fileis('fn_test') && false + mut mul := false + mut nr_muls := 0 + mut typ = '' + // fn type + if p.tok == FUNC { + if debug { + println('\nget_type() GOT FN TYP line=$p.scanner.line_nr') + } + mut f := Fn{name: '_', pkg: p.pkg} + p.next() + line_nr := p.scanner.line_nr + p.fn_args(mut f) + // Same line, it's a return type + if p.scanner.line_nr == line_nr { + if debug { + println('same line getting type') + } + f.typ = p.get_type() + // println('fn return typ=$f.typ') + } + else { + if debug { + println('new line!!') + } + f.typ = 'void' + } + // Register anon fn type + fn_typ := Type { + name: f.typ_str()// 'fn (int, int) string' + pkg: p.pkg + func: f + } + p.table.register_type2(fn_typ) + // println('!!!registerd typ_str="' + f.typ_str() + '" f.typ=$f.typ') + return f.typ_str() + } + // arrays ([]int) + mut is_arr := false + mut is_arr2 := false// [][]int TODO remove this and allow unlimited levels of arrays + is_question := p.tok == QUESTION + if is_question { + p.check(QUESTION) + } + if p.tok == LSBR { + p.check(LSBR) + // [10]int + if p.tok == INT { + typ = '[$p.lit]' + p.next() + } + else { + is_arr = true + } + p.check(RSBR) + // [10][3]int + if p.tok == LSBR { + p.next() + if p.tok == INT { + typ += '[$p.lit]' + p.check(INT) + } + else { + is_arr2 = true + } + p.check(RSBR) + } + } + for p.tok == MUL { + mul = true + nr_muls++ + p.next() + } + if p.tok == AMP { + mul = true + nr_muls++ + p.next() + } + typ += p.lit + if !p.is_struct_init { + // Otherwise we get `foo := FooFoo{` because `Foo` was already generated in name_expr() + p.fgen(p.lit) + } + // C.Struct import + if p.lit == 'C' && p.peek() == DOT { + p.next() + p.check(DOT) + typ = p.lit + } + else { + // Package specified? (e.g. gx.Image) + if p.peek() == DOT { + p.next() + p.check(DOT) + typ += '__$p.lit' + } + mut t := p.table.find_type(typ) + // "typ" not found? try "pkg__typ" + if t.name == '' && !p.builtin_pkg { + // && !p.first_run() { + if !typ.contains('array_') && p.pkg != 'main' && !typ.contains('__') { + typ = p.prepend_pkg(typ) + } + t = p.table.find_type(typ) + if t.name == '' && !p.translated && !p.first_run() && !typ.starts_with('[') { + println('get_type() bad type') + // println('all registered types:') + // for q in p.table.types { + // println(q.name) + // } + p.error('unknown type `$typ`') + } + } + } + if typ == 'void' { + p.error('unknown type `$typ`') + } + if mul { + typ += repeat_char(`*`, nr_muls) + } + // Register an []array type + if is_arr2 { + typ = 'array_array_$typ' + p.register_array(typ) + } + else if is_arr { + typ = 'array_$typ' + // p.log('ARR TYPE="$typ" run=$p.run') + // We come across "[]User" etc ? + p.register_array(typ) + } + p.next() + if p.tok == QUESTION || is_question { + typ = 'Option_$typ' + p.table.register_type_with_parent(typ, 'Option') + if p.tok == QUESTION { + p.next() + } + } + // Because the code uses * to see if it's a pointer + if typ == 'byteptr' { + return 'byte*' + } + if typ == 'voidptr' { + //if !p.builtin_pkg && p.pkg != 'os' && p.pkg != 'gx' && p.pkg != 'gg' && !p.translated { + //p.error('voidptr can only be used in unsafe code') + //} + return 'void*' + } + if typ.last_index('__') > typ.index('__') { + p.error('2 __ in gettype(): typ="$typ"') + } + return typ +} + +fn (p &Parser) print_tok() { + if p.tok == NAME { + println(p.lit) + return + } + if p.tok == STRING { + println('"$p.lit"') + return + } + println(p.tok.str()) +} + +// statements() returns the type of the last statement +fn (p mut Parser) statements() string { + p.log('statements()') + typ := p.statements_no_curly_end() + if !p.inside_if_expr { + p.genln('}') + } + if p.fileis('if_expr') { + println('statements() ret=$typ line=$p.scanner.line_nr') + } + return typ +} + +fn (p mut Parser) statements_no_curly_end() string { + p.cur_fn.open_scope() + // p.genln('/*sts()*/') + // p.gen('/* 999 */') + if !p.inside_if_expr { + p.genln('') + } + mut i := 0 + mut last_st_typ := '' + for p.tok != RCBR && p.tok != EOF && p.tok != CASE && p.tok != DEFAULT { + // println(p.tok.str()) + // p.print_tok() + last_st_typ = p.statement(true) + // println('last st typ=$last_st_typ') + if !p.inside_if_expr { + p.genln('')// // end st tok= ${p.strtok()}') + p.fgenln('') + } + i++ + if i > 50000 { + p.cgen.save() + p.error('more than 50 000 statements in function `$p.cur_fn.name`') + } + } + if p.tok != CASE && p.tok != DEFAULT { + // p.next() + p.check(RCBR) + } + else { + // p.check(RCBR) + } + p.scanner.fmt_indent-- + // println('close scope line=$p.scanner.line_nr') + p.cur_fn.close_scope() + return last_st_typ +} + +fn (p mut Parser) genln(s string) { + p.cgen.genln(s) +} + +fn (p mut Parser) gen(s string) { + p.cgen.gen(s) +} + +// Generate V header from V source +fn (p mut Parser) vh_genln(s string) { + p.vh_lines << s +} + +fn (p mut Parser) fmt_inc() { + p.scanner.fmt_indent++ +} + +fn (p mut Parser) fmt_dec() { + p.scanner.fmt_indent-- +} + +fn (p mut Parser) statement(add_semi bool) string { + p.cgen.is_tmp = false + tok := p.tok + mut q := '' + switch tok { + case NAME: + next := p.peek() + if p.is_verbose { + println(next.str()) + } + // goto_label: + if p.peek() == COLON { + p.fmt_dec() + label := p.check_name() + p.fmt_inc() + p.genln(label + ':') + p.check(COLON) + return '' + } + else if p.peek() == DECL_ASSIGN { + p.log('var decl') + p.var_decl() + } + else if p.lit == 'jsdecode' { + p.js_decode() + } + else { + // "a + 3", "a(7)" or maybe just "a" + q = p.bool_expression() + } + case GOTO: + p.check(GOTO) + p.fgen(' ') + label := p.check_name() + p.genln('goto $label;') + return '' + case HASH: + p.chash() + return '' + case DOLLAR: + p.comp_time() + case IF: + p.if_st(false) + case FOR: + p.for_st() + case SWITCH, MATCH: + p.switch_statement() + case MUT, STATIC: + p.var_decl() + case RETURN: + p.return_st() + case LCBR:// {} block + p.next() + p.genln('{') + p.statements() + return '' + case CONTINUE: + if p.for_expr_cnt == 0 { + p.error('`continue` statement outside `for`') + } + p.genln('continue') + p.next() + case BREAK: + if p.for_expr_cnt == 0 { + p.error('`break` statement outside `for`') + } + p.genln('break') + p.next() + case GO: + p.go_statement() + case ASSERT: + p.assert_statement() + default: + // An expression as a statement + typ := p.expression() + if p.inside_if_expr { + } + else { + p.genln('; ') + } + return typ + } + // ? : uses , as statement separators + if p.inside_if_expr && p.tok != RCBR { + p.gen(', ') + } + if add_semi && !p.inside_if_expr { + p.genln(';') + } + return q + // p.cgen.end_statement() +} + +// is_map: are we in map assignment? (m[key] = val) if yes, dont generate '=' +// this can be `user = ...` or `user.field = ...`, in both cases `v` is `user` +fn (p mut Parser) assign_statement(v Var, ph int, is_map bool) { + p.log('assign_statement() name=$v.name tok=') + // p.print_tok() + // p.gen(name) + // p.assigned_var = name + tok := p.tok + if !v.is_mut && !v.is_arg && !p.translated { + p.error('`$v.name` is immutable') + } + if !v.is_mut && p.is_play && !p.builtin_pkg && !p.translated { + // no mutable args in play + p.error('`$v.name` is immutable') + } + is_str := v.typ == 'string' + switch tok { + case ASSIGN: + if !is_map { + p.gen(' = ') + } + case PLUS_ASSIGN: + if is_str { + p.gen('= string_add($v.name, ')// TODO can't do `foo.bar += '!'` + } + else { + p.gen(' += ') + } + default: p.gen(' ' + p.tok.str() + ' ') + } + p.fgen(' ' + p.tok.str() + ' ') + p.next() + pos := p.cgen.cur_line.len + expr_type := p.bool_expression() + // Allow `num = 4` where `num` is an `?int` + if p.assigned_type.starts_with('Option_') && expr_type == p.assigned_type.right('Option_'.len) { + println('allowing option asss') + expr := p.cgen.cur_line.right(pos) + left := p.cgen.cur_line.left(pos) + p.cgen.cur_line = left + 'opt_ok($expr)' + } + else if !p.builtin_pkg && !p.check_types_no_throw(expr_type, p.assigned_type) { + p.scanner.line_nr-- + p.error('cannot use type `$expr_type` as type `$p.assigned_type` in assignment') + } + if is_str && tok == PLUS_ASSIGN { + p.gen(')') + } + // p.assigned_var = '' + p.assigned_type = '' + if !v.is_used { + p.cur_fn.mark_var_used(v) + } + // p.cgen.set_placeholder(ph, '/* KEK */') +} + +fn (p mut Parser) var_decl() { + is_mut := p.tok == MUT || p.prev_tok == FOR + is_static := p.tok == STATIC + if p.tok == MUT { + p.check(MUT) + p.fspace() + } + if p.tok == STATIC { + p.check(STATIC) + p.fspace() + } + // println('var decl tok=${p.strtok()} ismut=$is_mut') + name := p.check_name() + p.fgen(' := ') + // Don't allow declaring a variable with the same name. Even in a child scope + // (shadowing is not allowed) + if !p.builtin_pkg && p.cur_fn.known_var(name) { + v := p.cur_fn.find_var(name) + p.error('redefinition of `$name`') + // Check if this variable has already been declared only in the first run. + // Otherwise the is_script code outside main will run in the first run + // since we can't skip the function body since there's no function. + // And the variable will be registered twice. + if p.is_play && p.first_run() && !p.builtin_pkg { + p.error('variable `$name` is already declared.') + } + } + // println('var_decl $name') + // p.assigned_var = name + p.next()// := + // Generate expression to tmp because we need its type first + // [TYP NAME =] bool_expression() + pos := p.cgen.add_placeholder() + // p.gen('typ $name = ') + // p.gen('/*^^^*/') + mut typ := p.bool_expression() + // p.gen('/*VVV*/') + // Option check ? or { + or_else := p.tok == OR_ELSE + tmp := p.get_tmp() + // assigned_var_copy := p.assigned_var + 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.gen(';') + typ = typ.replace('Option_', '') + p.next() + p.check(LCBR) + p.genln('if (!$tmp .ok) {') + p.statements() + p.genln('$typ $name = *($typ*) $tmp . data;') + if !p.returns && p.prev_tok2 != CONTINUE && p.prev_tok2 != BREAK { + println(p.prev_tok2) + p.error('`or` statement must return/continue/break') + } + // p.assigned_var = assigned_var_copy + } + p.register_var(Var { + name: name + typ: typ + is_mut: is_mut + }) + mut cgen_typ := typ + if !or_else { + gen_name := p.table.var_cgen_name(name) + // p.cgen.set_placeholder(pos, '$cgen_typ $gen_name = ') + mut nt_gen := p.table.cgen_name_type_pair(gen_name, cgen_typ) + '=' + if is_static { + nt_gen = 'static $nt_gen' + // p.gen('static ') + } + p.cgen.set_placeholder(pos, nt_gen) + } +} + +fn (p mut Parser) bool_expression() string { + tok := p.tok + typ := p.bterm() + for p.tok == AND || p.tok == OR { + p.gen(' ${p.tok.str()} ') + p.next() + p.check_types(p.bterm(), typ) + } + if typ == '' { + println('curline:') + println(p.cgen.cur_line) + println(tok.str()) + p.error('expr() returns empty type') + } + return typ +} + +fn (p mut Parser) bterm() string { + ph := p.cgen.add_placeholder() + mut typ = p.expression() + is_str := typ.eq('string') + tok := p.tok + // 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 { + p.gen(',') + } + else { + p.gen(tok.str()) + } + p.next() + p.check_types(p.expression(), typ) + typ = 'bool' + if is_str { + p.gen(')/*8*/') + switch tok { + case EQ: p.cgen.set_placeholder(ph, 'string_eq(') + case NE: p.cgen.set_placeholder(ph, 'string_ne(') + case LE: p.cgen.set_placeholder(ph, 'string_le(') + case GE: p.cgen.set_placeholder(ph, 'string_ge(') + case GT: p.cgen.set_placeholder(ph, 'string_gt(') + case LT: p.cgen.set_placeholder(ph, 'string_lt(') + } + } + } + return typ +} + +// also called on *, & +fn (p mut Parser) name_expr() string { + p.log('\nname expr() pass=$p.run tok=${p.tok.str()} $p.lit') + // print('known type:') + // println(p.table.known_type(p.lit)) + // hack for struct_init TODO + hack_pos := p.scanner.pos + hack_tok := p.tok + hack_lit := p.lit + // amp + ptr := p.tok == AMP + deref := p.tok == MUL + if ptr || deref { + p.next() + } + if deref { + if p.is_play && !p.builtin_pkg { + p.error('dereferencing is temporarily disabled on the playground, will be fixed soon') + } + // IVE BEEN LOOKING FOR YOU FOR 20 MINUTES + // p.gen('*/*!!!!*/') + } + mut name := p.lit + p.fgen(name) + // known_type := p.table.known_type(name) + orig_name := name + // println('\n\n!!name_expr() name=$name cur_fn = $p.cur_fn.name') + is_c := name == 'C' && p.peek() == DOT + mut is_c_struct_init := is_c && ptr// a := &C.mycstruct{} + if is_c { + p.next() + p.check(DOT) + name = p.lit + p.fgen(name) + // Currently struct init is set to true only we have `&C.Foo{}`, handle `C.Foo{}`: + if !is_c_struct_init && p.peek() == LCBR { + is_c_struct_init = true + } + } + // ////////////////////////// + // module ? + // Allow shadowing (gg = gg.newcontext(); gg.draw_triangle()) + if p.table.known_pkg(name) && !p.cur_fn.known_var(name) { + // println('"$name" is a known pkg') + pkg := name + p.next() + p.check(DOT) + name = p.lit + p.fgen(name) + name = prepend_pkg(pkg, name) + } + else if !p.table.known_type(name) && !p.cur_fn.known_var(name) && + !p.table.known_fn(name) && !p.table.known_const(name) && !is_c { + name = p.prepend_pkg(name) + } + // Variable + v := p.cur_fn.find_var(name) + if v.name.len != 0 { + if ptr { + p.gen('& /*vvar*/ ') + } + else if deref { + p.gen('*') + } + mut typ := p.var_expr(v) + // *var + if deref { + if !typ.contains('*') && !typ.ends_with('ptr') { + println('name="$name", t=$v.typ') + p.error('dereferencing requires a pointer, but got `$typ`') + } + typ = typ.replace('ptr', '')// TODO + typ = typ.replace('*', '')// TODO + } + // &var + else if ptr { + typ += '*' + } + return typ + } + // if known_type || is_c_struct_init || (p.first_run() && p.peek() == LCBR) { + // known type? int(4.5) or Color.green (enum) + if p.table.known_type(name) { + // float(5), byte(0), (*int)(ptr) etc + if p.peek() == LPAR || (deref && p.peek() == RPAR) { + // println('CASTT $name') + if deref { + // p.check(RPAR) + // p.next() + name += '*' + } + else if ptr { + name += '*' + } + p.gen('(/*casttt*/') + mut typ := p.cast(name) + p.gen(')') + for p.tok == DOT { + typ = p.dot(typ, 0) + } + return typ + } + // Color.green + else if p.peek() == DOT { + // println('$name enum init!!') + enum_type := p.table.find_type(name) + if !enum_type.is_enum { + p.error('`$name` is not an enum') + } + p.next() + p.check(DOT) + val := p.lit + // println('enum val $val') + p.gen(p.pkg + '__' + enum_type.name + '_' + val)// `color = main__Color_green` + p.next() + return enum_type.name + } + else { + // go back to name start (pkg.name) + p.scanner.pos = hack_pos + p.tok = hack_tok + p.lit = hack_lit + // 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 && name != 'tm' { + p.cgen.insert_before('struct ') + } + return p.struct_init(is_c_struct_init) + } + } + // C fn + if is_c { + f := Fn { + name: name// .replace('c_', '') + is_c: true + } + p.fn_call(f, 0, '', '') + // Try looking it up. Maybe its defined with "C.fn_name() fn_type", + // then we know what type it returns + cfn := p.table.find_fn(name) + // Not Found? Return 'void*' + if cfn.name == '' { + return 'void*' + } + return cfn.typ + } + // Constant + mut c := p.table.find_const(name) + if c.name != '' && ptr && !c.is_global { + p.error('cannot take the address of constant `$c.name`') + } + if c.name.len != 0 { + if ptr { + // c.ptr = true + p.gen('& /*const*/ ') + } + p.log('calling var expr') + mut typ := p.var_expr(c) + if ptr { + typ += '*' + } + return typ + } + // Function (not method btw, methods are handled in dot()) + f := p.table.find_fn(name) + if f.name == '' { + println(p.cur_fn.name) + println(p.cur_fn.args.len) + // if !p.first_run() && !p.translated { + if !p.first_run() { + // println('name_expr():') + // If orig_name is a pkg, then printing undefined: `pkg` tells us nothing + if p.table.known_pkg(orig_name) { + name = name.replace('__', '.') + p.error('undefined: `$name`') + } + else { + p.error('undefined: `$orig_name`') + } + } + p.next() + return 'void' + } + // no () after func, so func is an argument, just gen its name + // TODO verify this and handle errors + if p.peek() != LPAR { + p.gen(p.table.cgen_name(f)) + p.next() + return 'void*' + } + // TODO bring back + if f.typ == 'void' && !p.inside_if_expr { + // p.error('`$f.name` used as value') + } + p.log('calling function') + p.fn_call(f, 0, '', '') + // dot after a function call: `get_user().age` + if p.tok == DOT { + mut typ := '' + for p.tok == DOT { + // println('dot #$dc') + typ = p.dot(f.typ, 0) + } + return typ + } + p.log('end of name_expr') + return f.typ +} + +fn (p mut Parser) var_expr(v Var) string { + p.log('\nvar_expr() v.name="$v.name" v.typ="$v.typ"') + // println('var expr is_tmp=$p.cgen.is_tmp\n') + // p.gen('VAR EXPR ') + p.cur_fn.mark_var_used(v) + fn_ph := p.cgen.add_placeholder() + p.expr_var = v + p.gen(p.table.var_cgen_name(v.name)) + p.next() + mut typ := v.typ + // fn_pointer() + if typ.starts_with('fn ') { + println('CALLING FN PTR') + p.print_tok() + T := p.table.find_type(typ) + p.gen('(') + p.fn_call_args(T.func) + p.gen(')') + typ = T.func.typ + } + // users[0] before dot so that we can have + // users[0].name + if p.tok == LSBR { + typ = p.index_expr(typ, fn_ph) + // ////println('QQQQ KEK $typ') + } + // a.b.c().d chain + // mut dc := 0 + for p.tok == DOT { + // println('dot #$dc') + typ = p.dot(typ, fn_ph) + p.log('typ after dot=$typ') + // print('tok after dot()') + // p.print_tok() + // dc++ + if p.tok == LSBR { + // typ = p.index_expr(typ, fn_ph, v) + } + } + // a++ and a-- + if p.tok == INC || p.tok == DEC { + if !v.is_mut && !v.is_arg && !p.translated { + p.error('`$v.name` is immutable') + } + if typ != 'int' { + if !p.translated && !is_number_type(typ) { + // if T.parent != 'int' { + p.error('cannot ++/-- value of type `$typ`') + } + } + p.gen(p.tok.str()) + p.fgen(p.tok.str()) + p.next()// ++ + // allow a := c++ in translated + if p.translated { + return p.index_expr(typ, fn_ph) + // return typ + } + else { + return 'void' + } + } + typ = p.index_expr(typ, fn_ph) + return typ +} + +fn (p &Parser) fileis(s string) bool { + return p.scanner.file_path.contains(s) +} + +// user.name => `str_typ` is `User` +// user.company.name => `str_typ` is `Company` +fn (p mut Parser) dot(str_typ string, method_ph int) string { + p.check(DOT) + field_name := p.lit + p.fgen(field_name) + p.log('dot() field_name=$field_name typ=$str_typ') + if p.fileis('hi_test') { + println('dot() field_name=$field_name typ=$str_typ') + } + typ := p.find_type(str_typ) + if typ.name.len == 0 { + p.error('dot(): cannot find type `$str_typ`') + } + has_field := p.table.type_has_field(typ, field_name) + has_method := p.table.type_has_method(typ, field_name) + if !typ.is_c && !has_field && !has_method && !p.first_run() { + // println(typ.str()) + if typ.name.starts_with('Option_') { + opt_type := typ.name.substr(7, typ.name.len) + p.error('unhandled option type: $opt_type?') + } + println('dot():') + println('fields:') + for field in typ.fields { + println(field.name) + } + println('methods:') + for field in typ.methods { + println(field.name) + } + println('str_typ=="$str_typ"') + p.error('type `$typ.name` has no field or method `$field_name`') + } + mut dot := '.' + if str_typ.contains('*') { + dot = '->' + } + // field + if has_field { + field := p.table.find_field(typ, field_name) + if field.name == 'age' { + println('!! $field.name $field.is_mut') + } + // Is the next token `=`, `+=` etc? (Are we modifying the field?) + next := p.peek() + modifying := next.is_assign() || next == INC || next == DEC + is_vi := p.fileis('vi') + if !p.builtin_pkg && !p.translated && modifying && !field.is_mut && !is_vi { + p.error('cannot modify immutable field `$field_name` (type `$typ.name`)') + } + if !p.builtin_pkg && p.pkg != typ.pkg { + } + // if p.is_play && field.access_mod == PRIVATE && !p.builtin_pkg && p.pkg != typ.pkg { + // Don't allow `arr.data` + if field.access_mod == PRIVATE && !p.builtin_pkg && !p.translated && p.pkg != typ.pkg { + // println('$typ.name :: $field.name ') + // println(field.access_mod) + p.error('cannot refer to unexported field `$field_name` (type `$typ.name`)') + } + // if field.access_mod == PUBLIC && p.peek() == ASSIGN && !p.builtin_pkg && p.pkg != typ.pkg { + // Don't allow `str.len = 0` + if field.access_mod == PUBLIC && !p.builtin_pkg && p.pkg != typ.pkg { + // if field.name == 'age' { + // println('HOHOH') + // println(next.str()) + // } + if !field.is_mut && !p.translated && modifying { + p.error('cannot modify public immutable field `$field_name` (type `$typ.name`)') + } + } + p.gen('${dot}${field_name}') + // p.gen(dot + field_name) + p.next() + return field.typ + } + // method + // mut method := typ.find_method(field_name) + mut method := p.table.find_method(typ, field_name) + p.fn_call(method, method_ph, '', str_typ) + // Methods returning "array" (like slice_fast) should return "array_string" + if method.typ == 'array' && typ.name.starts_with('array_') { + return typ.name + } + // Array Methods returning `voidptr` (like `last()`) should return element type + if method.typ == 'void*' && typ.name.starts_with('array_') { + // return typ.name.replace('array_', '') + return typ.name.right(6) + } + if false && p.tok == LSBR { + println('!!!!!!!! [ in DOT') + // if is_indexer { + return p.index_expr(method.typ, method_ph) + } + return method.typ +} + +fn (p mut Parser) index_expr(typ string, fn_ph int) string { + if p.fileis('int_test') { + println('index expr typ=$typ') + } + // a[0] + v := p.expr_var + is_map := typ.starts_with('map_') + is_str := typ == 'string' + is_arr0 := typ.starts_with('array_') + is_arr := is_arr0 || typ == 'array' + is_ptr := typ == 'byte*' || typ == 'byteptr' || typ.contains('*') + is_indexer := p.tok == LSBR + mut close_bracket := false + if is_indexer { + // println('!!! GOT []')p + is_fixed_arr := typ[0] == `[` + if !is_str && !is_arr && !is_map && !is_ptr && !is_fixed_arr { + p.error('Cant [] non-array/string/map. Got type "$typ"') + } + p.check(LSBR) + // Get element type (set `typ` to it) + if is_str { + typ = 'byte' + p.fgen('[') + // Direct faster access to .str[i] in builtin package + if p.builtin_pkg { + p.gen('.str[') + close_bracket = true + } + else { + // Bounds check everywhere else + p.gen(',') + } + } + if is_fixed_arr { + // `[10]int` => `int`, `[10][3]int` => `[3]int` + if typ.contains('][') { + pos := typ.index_after('[', 1) + typ = typ.right(pos) + } + else { + typ = typ.all_after(']') + } + p.gen('[') + close_bracket = true + } + else if is_ptr { + // typ = 'byte' + typ = typ.replace('*', '') + // modify(mut []string) fix + if !is_arr { + p.gen('[/*ptr*/') + close_bracket = true + } + } + 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.translated { + // 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(',') + } + } + // map is tricky + // need to replace "m[key] = val" with "tmp = val; map_set(&m, key, &tmp)" + // need to replace "m[key]" with "tmp = val; map_get(&m, key, &tmp)" + // can only do that later once we know whether there's an "=" or not + if is_map { + typ = typ.replace('map_', '') + if typ == 'map' { + typ = 'void*' + } + p.gen(',') + } + // expression inside [ ] + if is_arr { + T := p.table.find_type(p.expression()) + if T.parent != 'int' { + p.check_types(T.name, 'int') + } + } + else { + p.expression() + } + p.check(RSBR) + // if (is_str && p.builtin_pkg) || is_ptr || is_fixed_arr && ! (is_ptr && is_arr) { + if close_bracket { + p.gen(']/*r$typ $v.is_mut*/') + } + } + // TODO if p.tok in ... + // if p.tok in [ASSIGN, PLUS_ASSIGN, MINUS_ASSIGN] + if p.tok == ASSIGN || p.tok == PLUS_ASSIGN || p.tok == MINUS_ASSIGN || + p.tok == MULT_ASSIGN || p.tok == DIV_ASSIGN || p.tok == XOR_ASSIGN || p.tok == MOD_ASSIGN || + p.tok == OR_ASSIGN || p.tok == AND_ASSIGN || p.tok == RIGHT_SHIFT_ASSIGN || + p.tok == LEFT_SHIFT_ASSIGN { + if is_map || is_arr { + // Don't generate indexer right away, but assign it to tmp + // p.cgen.start_tmp() + } + if is_indexer && is_str && !p.builtin_pkg { + p.error('strings are immutable') + } + // println('111 "$p.cgen.cur_line"') + assign_pos := p.cgen.cur_line.len + p.assigned_type = typ + p.assign_statement(v, fn_ph, is_indexer && (is_map || is_arr)) + // m[key] = val + if is_indexer && (is_map || is_arr) { + // a[0] = 7 + // curline right now: "a , 0 = 7" + // println('222 "$p.cgen.cur_line"') + // Cant have &7, so need a tmp + tmp := p.get_tmp() + tmp_val := p.cgen.cur_line.right(assign_pos) + p.cgen.cur_line = p.cgen.cur_line.left(assign_pos) + // val := p.cgen.end_tmp() + if is_map { + p.cgen.set_placeholder(fn_ph, 'map__set(&') + } + else { + if is_ptr { + p.cgen.set_placeholder(fn_ph, 'array_set(') + } + else { + p.cgen.set_placeholder(fn_ph, 'array_set(&/*q*/') + } + } + p.gen(', & $tmp)') + p.cgen.insert_before('$typ $tmp = $tmp_val;') + } + return typ + return 'void' + } + // else if p.is_verbose && p.assigned_var != '' { + // p.error('didnt assign') + // } + // m[key]. no =, just a getter + else if (is_map || is_arr || (is_str && !p.builtin_pkg)) && 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() + index_expr := p.cgen.cur_line.right(fn_ph) + p.cgen.cur_line = 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.translated { + p.gen('$index_expr ]') + } + else { + p.gen('( *($typ*) array__get($index_expr) )') + } + } + else if is_str && !p.builtin_pkg { + 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("", 0);') + } + } + // else if is_arr && is_indexer{} + p.log('!!indexer typ=$typ') + return typ +} + +// returns resulting type +fn (p mut Parser) expression() string { + if p.scanner.file_path.contains('test_test') { + println('epxression() pass=$p.run tok=') + p.print_tok() + } + p.cgen('/* expr start*/') + ph := p.cgen.add_placeholder() + mut typ := p.term() + is_str := typ.eq('string') + // a << b ==> array2_push(&a, b) + if p.tok == LEFT_SHIFT { + if typ.contains('array_') { + // Can't pass integer literal, because push requires a void* + // a << 7 => int tmp = 7; array_push(&a, &tmp); + // _PUSH(&a, expression(), tmp, string) + tmp := p.get_tmp() + tmp_typ := typ.right(6)// skip "array_" + p.next() + // Get the value we are pushing + p.gen(', (') + // Immutable? Can we push? + if !p.expr_var.is_mut && !p.translated { + p.error('`$p.expr_var.name` is immutable (can\'t <<)') + } + p.check_types(p.expression(), tmp_typ) + // Pass tmp var info to the _PUSH macro + p.gen('), $tmp, $tmp_typ)') + // 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(&') + p.cgen.set_placeholder(ph, push_call) + return 'void' + } + else { + p.next() + p.gen(' << ') + p.check_types(p.expression(), typ) + return 'int' + } + } + // a in [1,2,3] + if p.tok == IN { + p.fgen(' ') + p.check(IN) + p.fgen(' ') + p.gen(', ') + arr_typ := p.expression() + if !arr_typ.starts_with('array_') { + p.error('`in` requires an array') + } + T := p.table.find_type(arr_typ) + if !T.has_method('contains') { + p.error('$arr_typ has no method `contains`') + } + // `typ` is element type + p.cgen.set_placeholder(ph, '_IN($typ, ') + p.gen(')') + return 'bool' + } + if p.tok == RIGHT_SHIFT { + p.next() + p.gen(' >> ') + p.check_types(p.expression(), typ) + return 'int' + } + if p.tok == DOT { + for p.tok == DOT { + typ = p.dot(typ, 0) + } + } + // + - | + for p.tok == PLUS || p.tok == MINUS || p.tok == PIPE || p.tok == AMP || p.tok == XOR { + // for p.tok in [PLUS, MINUS, PIPE, AMP, XOR] { + tok_op := p.tok + is_num := typ == 'void*' || typ == 'byte*' || is_number_type(typ) + p.next() + if is_str && tok_op == PLUS { + p.cgen.set_placeholder(ph, 'string_add(') + p.gen(',') + } + // 3 + 4 + else if is_num { + p.gen(tok_op.str()) + } + // Vec + Vec + else { + if p.translated { + p.gen(tok_op.str() + ' /*doom hack*/')// TODO hack to fix DOOM's angle_t + } + else { + p.gen(',') + } + } + p.check_types(p.term(), typ) + if is_str && tok_op == PLUS { + p.gen(')') + } + // Make sure operators are used with correct types + if !p.translated && !is_str && !is_num { + T := p.table.find_type(typ) + if tok_op == PLUS { + if T.has_method('+') { + p.cgen.set_placeholder(ph, typ + '_plus(') + p.gen(')') + } + else { + p.error('operator + not defined on `$typ`') + } + } + else if tok_op == MINUS { + if T.has_method('-') { + p.cgen.set_placeholder(ph, '${typ}_minus(') + p.gen(')') + } + else { + p.error('operator - not defined on `$typ`') + } + } + } + } + return typ +} + +fn (p mut Parser) term() string { + line_nr := p.scanner.line_nr + if p.fileis('fn_test') { + println('\nterm() $line_nr') + } + typ := p.unary() + if p.fileis('fn_test') { + println('2: $line_nr') + } + // `*` on a newline? Can't be multiplication, only dereference + if p.tok == MUL && line_nr != p.scanner.line_nr { + return typ + } + for p.tok == MUL || p.tok == DIV || p.tok == MOD { + tok := p.tok + is_div := tok == DIV + is_mod := tok == MOD + // is_mul := tok == MOD + p.next() + p.gen(tok.str())// + ' /*op2*/ ') + p.fgen(' ' + tok.str() + ' ') + if is_div && p.tok == INT && p.lit == '0' { + p.error('division by zero') + } + if is_mod && (is_float_type(typ) || !is_number_type(typ)) { + p.error('operator MOD requires integer types') + } + p.check_types(p.unary(), typ) + } + return typ +} + +fn (p mut Parser) unary() string { + mut typ := '' + tok := p.tok + switch tok { + case NOT: + p.gen('!') + p.next() + typ = 'bool' + p.bool_expression() + case BIT_NOT: + p.gen('~') + p.next() + typ = p.bool_expression() + default: + typ = p.factor() + } + return typ +} + +fn (p mut Parser) factor() string { + // p.cgen('/* fact start */') + mut typ := '' + // if p.file.contains('test') { + // print('factor() line=$p.scanner.line_nr tok= ') + // p.print_tok() + // } + tok := p.tok + switch tok { + case INT: + // p.g.Gen(q.Str(q.Int(p.lit))) + p.gen(p.lit) + p.fgen(p.lit) + typ = 'int' + // typ = 'number' + if p.lit.starts_with('u') { + typ = 'long' + } + if p.lit.contains('.') || p.lit.contains('e') { + // typ = 'f64' + typ = 'float' + } + case FLOAT: + typ = 'float' + // typ = 'f64' + // p.gen('(f64)$p.lit') + p.gen('$p.lit') + p.fgen(p.lit) + case MINUS: + p.gen('-') + p.fgen('-') + p.next() + return p.factor() + // Variable + case SIZEOF: + p.gen('sizeof(') + p.fgen('sizeof(') + p.next() + p.check(LPAR) + mut sizeof_typ := p.get_type() + if sizeof_typ.ends_with('*') { + // Move * from the end to the beginning, as C requires + sizeof_typ = '*' + sizeof_typ.left(sizeof_typ.len - 1) + } + p.check(RPAR) + p.gen('$sizeof_typ)') + p.fgen('$sizeof_typ)') + return 'int' + case AMP: + return p.name_expr() + case DOT: + return p.name_expr()// `.green` (enum) + case MUL: + return p.name_expr() + case NAME: + // map[string]int + if p.lit == 'map' && p.peek() == LSBR { + return p.map_init() + } + if p.lit == 'json' && p.peek() == DOT { + return p.js_decode() + } + typ = p.name_expr() + // debug("TOK AFTER NAME E", p.strtok()) + return typ + // case TYPEOF: + // p.next() + // p.next() + // name := p.checkName() + // if name != "T" { + // p.Error("type of needs T") + // } + // p.g.Gen("typeof(T)") + // p.next() + // return "string" + case DEFAULT: + p.next() + p.next() + name := p.check_name() + if name != 'T' { + p.error('default needs T') + } + p.gen('default(T)') + p.next() + return 'T' + case LPAR: + p.gen('(/*lpar*/') + p.next()// ( + typ = p.bool_expression() + // Hack. If this `)` referes to a ptr cast `(*int__)__`, it was already checked + // TODO: fix parser so that it doesn't think it's a par expression when it sees `(` in + // __(__*int)( + if !p.ptr_cast { + p.check(RPAR) + } + p.ptr_cast = false + p.gen(')') + return typ + case CHAR: + p.char_expr() + typ = 'byte' + return typ + case STRING: + p.string_expr() + typ = 'string' + return typ + case FALSE: + typ = 'bool' + p.gen('0') + p.fgen('false') + case TRUE: + typ = 'bool' + p.gen('1') + p.fgen('true') + case LSBR: + // `[1,2,3]` or `[]` or `[20]byte` + // TODO have to return because arrayInit does next() + // everything should do next() + return p.array_init() + case LCBR: + // { user | name :'new name' } + return p.assoc() + case IF: + typ = p.if_st(true) + return typ + default: + next := p.peek() + println('PREV=${p.prev_tok.str()}') + println('NEXT=${next.str()}') + p.error('unexpected token: `${p.tok.str()}`') + } + p.next()// TODO everything should next() + return typ +} + +// { user | name: 'new name' } +fn (p mut Parser) assoc() string { + // println('assoc()') + p.next() + name := p.check_name() + if !p.cur_fn.known_var(name) { + p.error('unknown variable `$name`') + } + var := p.cur_fn.find_var(name) + p.check(PIPE) + p.gen('($var.typ){') + mut fields := []string// track the fields user is setting, the rest will be copied from the old object + for p.tok != RCBR { + field := p.check_name() + fields << field + p.gen('.$field = ') + p.check(COLON) + p.bool_expression() + p.gen(',') + if p.tok != RCBR { + p.check(COMMA) + } + } + // Copy the rest of the fields + T := p.table.find_type(var.typ) + for ffield in T.fields { + f := ffield.name + if f in fields { + continue + } + p.gen('.$f = $name . $f,') + } + p.check(RCBR) + p.gen('}') + return var.typ +} + +fn (p mut Parser) char_expr() { + p.gen('\'$p.lit\'') + p.next() +} + +fn format_str(str string) string { + str = str.replace('"', '\\"') + str = str.replace('\n', '\\n') + return str +} + +fn (p mut Parser) typ_to_fmt(typ string) string { + t := p.table.find_type(typ) + if t.parent == 'int' { + return '%d' + } + switch typ { + case 'string': return '%.*s' + case 'ustring': return '%.*s' + case 'long': return '%ld' + case 'byte': return '%d' + case 'int': return '%d' + case 'char': return '%d' + case 'byte': return '%d' + case 'bool': return '%d' + case 'u32': return '%d' + case 'float': return '%f' + case 'double', 'f64': return '%f' + case 'i64': return '%lld' + case 'byte*': return '%s' + // case 'array_string': return '%s' + // case 'array_int': return '%s' + case 'void': p.error('cannot interpolate this value') + default: + p.error('unhandled sprintf format "$typ" ') + } + return '' +} + +fn (p mut Parser) string_expr() { + // println('STRING EXPR') + str := p.lit + p.fgen('\'$str\'') + // No ${}, just return simple string + if p.peek() != DOLLAR { + // println('before format: "$str"') + f := format_str(str) + // println('after format: "$str"') + if p.calling_c || p.translated { + p.gen('"$f"') + } + else { + p.gen('tos2("$f")')// TODO dont call strlen here + } + p.next() + return + } + // tmp := p.get_tmp() + mut args := '"' + mut format := '"' + for p.tok == STRING { + // Add the string between %d's + format += format_str(p.lit) + p.next()// skip $ + if p.tok != DOLLAR { + continue + } + // Handle DOLLAR + p.next() + // Get bool expr inside a temp var + p.cgen.start_tmp() + typ := p.bool_expression() + mut val := p.cgen.end_tmp() + val = val.trim_space() + // array_string_str(val) + /* + T := p.table.find_type(typ) + if T.has_method('str') && !typ.ends_with('*') { + args += ', ${typ}_str($val).str' + } + else { + args += ', $val' + } +*/ + args += ', $val' + if typ == 'string' { + // args += '.str' + // printf("%.*s", a.len, a.str) syntax + args += '.len, ${val}.str' + } + if typ == 'ustring' { + args += '.len, ${val}.s.str' + } + // Custom format? ${t.hour:02d} + custom := p.tok == COLON + if custom { + format += '%' + p.next() + if p.tok == DOT { + format += '.' + p.next() + } + format += p.lit// 02 + p.next() + format += p.lit// f + // println('custom str F=$format') + p.next() + } + else { + format += p.typ_to_fmt(typ) + } + } + // println("hello %d", num) optimization. + if p.cgen.nogen { + return + } + // Don't allocate a new string, just print it . TODO HACK PRINT OPT + cur_line := p.cgen.cur_line.trim_space() + if cur_line.contains('println(') && p.tok != PLUS && !p.is_prod && !cur_line.contains('string_add') { + p.cgen.cur_line = cur_line.replace('println(', '/*opt hack*/printf(') + p.gen('$format\\n$args') + return + } + // '$age'! means the user wants this to be a tmp string (uses global buffer, no allocation, + // won't be used again) + if p.tok == NOT { + p.next() + p.gen('_STR_TMP($format$args)') + } + else { + // Otherwise do ugly len counting + allocation + sprintf + p.gen('_STR($format$args)') + } +} + +// m := map[string]int{} +fn (p mut Parser) map_init() string { + p.next() + p.check(LSBR) + key_type := p.check_name() + if key_type != 'string' { + p.error('only string key maps allowed for now') + } + p.check(RSBR) + val_type := p.check_name() + if !p.table.known_type(val_type) { + p.error('map init unknown type "$val_type"') + } + p.gen('new_map(1, sizeof($val_type))') + p.check(LCBR) + p.check(RCBR) + return 'map_$val_type' +} + +// [1,2,3] +fn (p mut Parser) array_init() string { + p.check(LSBR) + is_integer := p.tok == INT + lit := p.lit + 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] + for p.tok != RSBR { + val_typ := p.bool_expression() + // Get type of the first expression + if i == 0 { + typ = val_typ + // fixed width array initialization? (`arr := [20]byte`) + if is_integer && p.tok == RSBR && p.peek() == NAME { + nextc := p.scanner.text[p.scanner.pos + 1] + // TODO whitespace hack + // Make sure there's no space in `[10]byte` + if !nextc.is_space() { + p.check(RSBR) + name := p.check_name() + if p.table.known_type(name) { + p.cgen.cur_line = '' + p.gen('{} /* arkek init*/') + return '[$lit]$name' + } + else { + p.error('bad type `$name`') + } + } + } + } + if val_typ != typ { + if !p.check_types_no_throw(val_typ, typ) { + p.error('bad array element type `$val_typ` instead of `$typ`') + } + } + if p.tok != RSBR && p.tok != SEMICOLON { + p.gen(',') + p.check(COMMA) + } + i++ + // Repeat (a = [0;5] ) + if i == 1 && p.tok == SEMICOLON { + p.check_space(SEMICOLON) + val := p.cgen.cur_line.right(pos) + // p.cgen.cur_line = '' + p.cgen.cur_line = p.cgen.cur_line.left(pos) + // Special case for zero + if false && val.trim_space() == '0' { + p.gen('array_repeat( & V_ZERO, ') + } + else { + tmp := p.get_tmp() + p.cgen.insert_before('/* arr init tmp*/ $typ $tmp = $val;') + p.gen('array_repeat(&$tmp, ') + } + p.check_types(p.bool_expression(), 'int') + p.gen(', sizeof($typ) )') + p.check(RSBR) + return 'array_$typ' + } + } + p.check(RSBR) + // type after `]`? (e.g. "[]string") + if p.tok != NAME && i == 0 { + p.error('specify array type: `[]typ` instead of `[]`') + } + if p.tok == NAME && i == 0 { + // vals.len == 0 { + typ = p.get_type() + // println('GOT TYP after [] $typ') + } + // ! after array => no malloc and no copy + no_copy := p.tok == NOT + if no_copy { + p.next() + } + // [1,2,3]!! => [3]int{1,2,3} + is_fixed_size := p.tok == NOT + if is_fixed_size { + p.next() + p.gen(' }') + if !p.first_run() { + // If we are defining a const array, we don't need to specify the type: + // `a = {1,2,3}`, not `a = (int[]) {1,2,3}` + if p.inside_const { + p.cgen.set_placeholder(new_arr_ph, '{ ') + } + else { + p.cgen.set_placeholder(new_arr_ph, '($typ[]) { ') + } + } + return '[$i]$typ' + } + // if ptr { + // typ += '_ptr" + // } + mut new_arr := '/*$new_arr_ph*/ new_array_from_c_array' + if no_copy { + new_arr += '_no_copy' + } + p.gen(' })') + // p.gen('$new_arr($vals.len, $vals.len, sizeof($typ), ($typ[]) $c_arr );') + // TODO why need !first_run()?? Otherwise it goes to the very top of the out.c file + if !p.first_run() { + p.cgen.set_placeholder(new_arr_ph, '$new_arr($i, $i, sizeof($typ), ($typ[]) { ') + } + typ = 'array_$typ' + p.register_array(typ) + return typ +} + +fn (p mut Parser) register_array(typ string) { + if typ.contains('*') { + println('bad arr $typ') + return + } + if !p.table.known_type(typ) { + p.register_type_with_parent(typ, 'array') + p.cgen.typedefs << 'typedef array $typ;' + } +} + +// name == 'User' +fn (p mut Parser) struct_init(is_c_struct_init bool) string { + p.is_struct_init = true + // print('0struct init() tok=') + // p.print_tok() + mut typ := p.get_type() + p.scanner.fmt_out.cut(typ.len) + ptr := typ.contains('*') + // println('struct init() $typ') + p.gen('/* S INIT */') + p.check(LCBR) + // tmp := p.get_tmp() + if !ptr { + if typ == 'tm' { + p.gen('(struct tm) {')// TODO struct tm hack, handle all C structs + } + 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 typ + } + mut type_gen := typ.replace('*', '') + // All V types are typedef'ed, C structs aren't, so we need to prepend "struct " + if is_c_struct_init { + type_gen = 'struct $type_gen' + } + // p.gen('malloc(sizeof($type_gen)); \n') + no_star := typ.replace('*', '') + p.gen('ALLOC_INIT($no_star, {') + } + // Loop thru all struct init keys and assign values + // u := User{age:20, name:'bob'} + // Remember which fields were set, so that we dont have to zero them later + mut inited_fields := []string + peek := p.peek() + if peek == COLON || p.tok == RCBR { + t := p.table.find_type(typ) + for p.tok != RCBR { + field := p.check_name() + if !t.has_field(field) { + p.error('`$t.name` has no field `$field`') + } + inited_fields << field + p.gen('.$field = ') + p.check(COLON) + p.fspace() + p.expression() + if p.tok == COMMA { + p.next() + } + if p.tok != RCBR { + p.gen(',') + } + p.fgenln('') + } + // If we already set some fields, need to prepend a comma + if t.fields.len != inited_fields.len && inited_fields.len > 0 { + p.gen(',') + } + // Zero values: init all fields (ints to 0, strings to '' etc) + for i, field in t.fields { + // println('### field.name') + // Skip if this field has already been assigned to + if inited_fields.contains(field.name) { + continue + } + field_typ := field.typ + if !p.builtin_pkg && field_typ.ends_with('*') && field_typ.contains('Cfg') { + p.error('pointer field `${typ}.${field.name}` must be initialized') + } + def_val := type_default(field_typ) + if def_val != '' { + p.gen('.$field.name = $def_val') + if i != t.fields.len - 1 { + p.gen(',') + } + } + } + } + // Point{3,4} syntax + else { + mut T := p.table.find_type(typ) + // Aliases (TODO Hack, implement proper aliases) + if T.fields.len == 0 && T.parent != '' { + T = p.table.find_type(T.parent) + } + for i, ffield in T.fields { + expr_typ := p.bool_expression() + if !p.check_types_no_throw(expr_typ, ffield.typ) { + p.error('field value #${i+1} `$ffield.name` has type `$ffield.typ`, got `$expr_typ` ') + } + if i < T.fields.len - 1 { + if p.tok != COMMA { + p.error('too few values in `$typ` literal (${i+1} instead of $T.fields.len)') + } + p.gen(',') + p.next() + } + } + // Allow `user := User{1,2,3,}` + // The final comma will be removed by vfmt, since we are not calling `p.fgen()` + if p.tok == COMMA { + p.next() + } + if p.tok != RCBR { + p.error('too many fields initialized: `$typ` has $T.fields.len field(s)') + } + } + p.gen('}') + if ptr { + p.gen(')') + } + p.check(RCBR) + p.is_struct_init = false + // println('struct init typ=$typ') + return typ +} + +// `f32(3)` +// tok is `f32` or `)` if `(*int)(ptr)` +fn (p mut Parser) cast(typ string) string { + // typ := p.lit + if p.file_path.contains('test') { + println('CAST TYP=$typ tok=') + p.print_tok() + } + p.gen('($typ)(') + // p.fgen(typ) + p.next() + if p.tok == RPAR { + // skip `)` if it's `(*int)(ptr)`, not `int(a)` + p.ptr_cast = true + p.next() + } + p.check(LPAR) + p.gen('/*77*/') + expr_typ := p.bool_expression() + p.check(RPAR) + p.gen(')') + if typ == 'string' && expr_typ == 'int' { + p.error('cannot convert `$expr_typ` to `$typ`') + } + return typ +} + +fn (p mut Parser) get_tmp() string { + p.tmp_cnt++ + return 'tmp$p.tmp_cnt' +} + +fn (p mut Parser) get_tmp_counter() int { + p.tmp_cnt++ + return p.tmp_cnt +} + +fn (p mut Parser) comp_time() { + p.next() + if p.tok == IF { + p.next() + not := p.tok == NOT + if not { + p.next() + } + name := p.check_name() + if name in SupportedPlatforms { + if not { + p.genln('#ifndef $name') + } + else { + p.genln('#ifdef $name') + } + p.check(LCBR) + p.statements_no_curly_end() + if ! (p.tok == DOLLAR && p.peek() == ELSE) { + p.genln('#endif') + } + } + else { + println('Supported platforms:') + println(SupportedPlatforms) + p.error('unknown platform `$name`') + } + } + else if p.tok == FOR { + p.next() + name := p.check_name() + if name != 'field' { + p.error('for field only') + } + p.check(IN) + p.check_name() + p.check(DOT) + p.check_name()// fields + p.check(LCBR) + // for p.tok != RCBR && p.tok != EOF { + res_name := p.check_name() + println(res_name) + p.check(DOT) + p.check(DOLLAR) + p.check(NAME) + p.check(ASSIGN) + p.cgen.start_tmp() + p.bool_expression() + val := p.cgen.end_tmp() + println(val) + p.check(RCBR) + // } + } + else if p.tok == ELSE { + p.next() + p.check(LCBR) + p.genln('#else') + p.statements_no_curly_end() + p.genln('#endif') + } + else { + p.error('bad comptime expr') + } +} + +fn (p mut Parser) chash() { + hash := p.lit.trim_space() + // println('chsh() file=$p.file is_sig=${p.is_sig()} hash="$hash"') + p.next() + is_sig := p.is_sig() + if is_sig { + // p.cgen.nogen = true + } + if hash == 'live' { + if p.is_so { + return + } + p.is_live = true + return + } + if hash.starts_with('flag ') { + mut flag := hash.right(5) + // println('FLAG!!! $flag OS=$p.os') + // No the right os? Skip! + // mut ok := true + if hash.contains('linux') && p.os != LINUX { + return + } + else if hash.contains('darwin') && p.os != MAC { + return + } + else if hash.contains('windows') && p.os != WINDOWS { + return + } + // Remove "linux" etc from flag + if flag.contains('linux') || flag.contains('darwin') || flag.contains('windows') { + pos := flag.index(' ') + flag = flag.right(pos) + } + flag = flag.trim_space() + if p.table.flags.contains(flag) { + return + } + p.log('adding flag "$flag"') + p.table.flags << flag// .all_after(' ')) + // } + return + } + if hash.starts_with('include') { + if p.first_run() && !is_sig { + p.cgen.includes << '#$hash' + return + } + } + else if hash.starts_with('typedef') { + if p.first_run() { + p.cgen.typedefs << '$hash' + } + } + // TODO remove after ui_mac.m is removed + else if hash.contains('embed') { + pos := hash.index('embed') + 5 + file := hash.right(pos) + if p.build_mode != DEFAULT_MODE { + p.genln('#include $file') + } + } + else if is_c_pre(hash) { + // Skip not current OS hack + if hash.starts_with('ifdef') { + os := hash.right(6).trim_space() + // println('ifdef "$os" $p.scanner.line_nr') + if os == 'linux' && p.os != LINUX { + // println('linux ifdef skip') + for p.tok != EOF { + if p.tok == HASH && p.lit.contains('else') || p.lit.contains('endif') { + break + } + // println('skipping') + p.next() + } + } + } + // Move defines on top (like old gdefine) + if hash.contains('define') { + p.cgen.includes << '#$hash' + } + else { + p.genln('#$hash') + } + } + else { + if p.cur_fn.name == '' { + // p.error('# outside of fn') + } + p.genln(hash) + } + // p.cgen.nogen = false + // println('HASH=$hash') +} + +fn is_c_pre(hash string) bool { + return hash.contains('ifdef') || hash.contains('define') || + hash.contains('endif') || hash.contains('elif') || + hash.contains('ifndef') || (hash.contains('else') && !hash.contains('{')) +} + +fn (p mut Parser) if_st(is_expr bool) string { + if is_expr { + if p.fileis('if_expr') { + println('IF EXPR') + } + p.inside_if_expr = true + p.gen('(') + } + else { + p.gen('if (') + p.fgen('if ') + } + p.next() + p.check_types(p.bool_expression(), 'bool') + if is_expr { + p.gen(') ? (') + } + else { + p.genln(') {') + p.fgenln('{') + p.genln('/*if*/') + } + p.fgen(' ') + p.check(LCBR) + mut typ := '' + // if { if hack + if p.tok == IF && p.inside_if_expr { + println('AAAWWFAFAF') + typ = p.factor() + println('QWEWQE typ=$typ') + p.next() + } + else { + typ = p.statements() + } + // println('IF TYp=$typ') + if p.tok == ELSE { + p.next() + if p.tok == IF { + p.gen(' else ') + return p.if_st(is_expr) + // return '' + } + if is_expr { + p.gen(') : (') + } + else { + p.genln(' else { ') + p.genln('/*else if*/') + } + p.check(LCBR) + // statements() returns the type of the last statement + typ = p.statements() + p.inside_if_expr = false + if is_expr { + p.gen(')') + } + return typ + } + p.inside_if_expr = false + if p.fileis('test_test') { + println('if ret typ="$typ" line=$p.scanner.line_nr') + } + return typ +} + +fn (p mut Parser) for_st() { + p.check(FOR) + p.fgen(' ') + p.for_expr_cnt++ + next_tok := p.peek() + debug := p.scanner.file_path.contains('r_draw') + if debug { + println('\n\nFOR {') + } + p.cur_fn.open_scope() + if p.tok == LCBR { + // Infinite loop + p.gen('while (1) {') + } + else if p.tok == MUT { + p.error('`mut` is not required in for loops') + } + // for i := 0; i < 10; i++ { + else if next_tok == DECL_ASSIGN || next_tok == ASSIGN || p.tok == SEMICOLON { + if debug { + println('for 1') + } + p.genln('for (') + if next_tok == DECL_ASSIGN { + p.var_decl() + } + else if p.tok != SEMICOLON { + // allow `for ;; i++ {` + // Allow `for i = 0; i < ...` + p.statement(false) + } + if debug { + println('for 2') + } + p.check(SEMICOLON) + p.gen(' ; ') + p.fgen(' ') + if p.tok != SEMICOLON { + p.bool_expression() + } + if debug { + println('for 3') + } + p.check(SEMICOLON) + p.gen(' ; ') + p.fgen(' ') + if p.tok != LCBR { + p.statement(false) + } + if debug { + println('for 4') + } + p.fgen(' ') + p.genln(') { ') + } + // 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]; + i := p.check_name() + p.check(COMMA) + val := p.check_name() + p.fgen(' ') + p.check(IN) + p.fgen(' ') + tmp := p.get_tmp() + p.cgen.start_tmp() + typ := p.bool_expression() + expr := p.cgen.end_tmp() + p.genln('$typ $tmp = $expr ;') + var_typ := typ.right(6) + // typ = strings.Replace(typ, "_ptr", "*", -1) + // Register temp var + val_var := Var { + name: val + typ: var_typ + // parent_fn: p.cur_fn + ptr: typ.contains('*') + } + p.register_var(val_var) + i_var := Var { + name: i + typ: 'int' + // parent_fn: p.cur_fn + is_mut: true + } + 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];') + } + // `for val in vals` + else if p.peek() == IN { + val := p.check_name() + p.fgen(' ') + p.check(IN) + p.fspace() + tmp := p.get_tmp() + p.cgen.start_tmp() + typ := p.bool_expression() + expr := p.cgen.end_tmp() + // println('if in:') + // println(p.strtok()) + is_range := p.tok == DOTDOT + mut range_end := '' + if is_range { + p.check_types(typ, 'int') + p.check_space(DOTDOT) + p.cgen.start_tmp() + p.check_types(p.bool_expression(), 'int') + range_end = p.cgen.end_tmp() + } + is_arr := typ.contains('array') + is_str := typ == 'string' + // ////if !typ.contains('array') && typ != 'string' { + if !is_arr && !is_str && !is_range { + p.error('`for in` requires an array or a string but got `$typ`') + } + p.genln('$typ $tmp = $expr;') + // TODO var_type := if... + mut var_type := '' + if is_arr { + var_type = typ.right(6)// all after `array_` + } + else if is_str { + var_type = 'byte' + } + else if is_range { + var_type = 'int' + } + // println('for typ=$typ vartyp=$var_typ') + // Register temp var + val_var := Var { + name: val + typ: var_type + ptr: typ.contains('*') + } + 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];') + } + else if is_str { + p.genln('$var_type $val = (($var_type *) ${tmp}.str)[$i];') + } + else if is_range { + p.genln('$var_type $val = $i;') + } + } + else { + // `for a < b {` + p.gen('while (') + p.check_types(p.bool_expression(), 'bool') + p.genln(') {') + } + p.check(LCBR) + p.statements() + p.cur_fn.close_scope() + p.for_expr_cnt-- +} + +fn (p mut Parser) switch_statement() { + p.next() + p.cgen.start_tmp() + typ := p.bool_expression() + expr := p.cgen.end_tmp() + p.check(LCBR) + mut i := 0 + for p.tok == CASE || p.tok == DEFAULT { + if p.tok == DEFAULT { + p.genln('else { // default:') + p.next() + p.check(COLON) + p.statements() + break + } + if i > 0 { + p.gen('else ') + } + p.gen('if (') + // Multiple checks separated by comma + mut got_comma := false + for { + if got_comma { + p.gen(') || ') + } + if typ == 'string' { + p.gen('string_eq($expr, ') + } + else { + p.gen('($expr == ') + } + if p.tok == CASE || p.tok == DEFAULT { + p.next() + } + p.bool_expression() + if p.tok != COMMA { + break + } + p.check(COMMA) + got_comma = true + } + p.check(COLON) + p.gen(')) {') + p.genln('/* case */') + p.statements() + i++ + } +} + +fn (p mut Parser) assert_statement() { + p.check(ASSERT) + p.fspace() + tmp := p.get_tmp() + p.gen('bool $tmp = ') + p.check_types(p.bool_expression(), 'bool') + // TODO print "expected: got" for failed tests + filename := p.file_path + p.genln(';\n +if (!$tmp) { + puts("\\x1B[31mFAILED: $p.cur_fn.name() in $filename:$p.scanner.line_nr\\x1B[0m"); +g_test_ok = 0 ; + // TODO + // Maybe print all vars in a test function if it fails? +} +else { + puts("\\x1B[32mPASSED: $p.cur_fn.name()\\x1B[0m"); +}') +} + +fn (p mut Parser) return_st() { + p.cgen.insert_before(p.cur_fn.defer) + p.gen('return ') + if p.cur_fn.name == 'main' { + p.gen(' 0') + } + p.check(RETURN) + p.fgen(' ') + fn_returns := p.cur_fn.typ != 'void' + if fn_returns { + if p.tok == RCBR { + p.error('`$p.cur_fn.name` needs to return `$p.cur_fn.typ`') + } + else { + ph := p.cgen.add_placeholder() + expr_type := p.bool_expression() + // Automatically wrap an object inside an option if the function returns an option + if p.cur_fn.typ.ends_with(expr_type) && p.cur_fn.typ.starts_with('Option_') { + p.cgen.set_placeholder(ph, 'opt_ok(& ') + p.gen(')') + } + p.check_types(expr_type, p.cur_fn.typ) + } + } + else { + // Don't allow `return val` in functions that don't return anything + // if p.tok != RCBR && p.tok != HASH { + if false && p.tok == NAME || p.tok == INT { + p.error('function `$p.cur_fn.name` does not return a value') + } + } + p.returns = true +} + +fn (p mut Parser) at() { + vals := p.lit.split(' ') + if vals.len != 2 { + p.error('Bad @ syntax (type name)') + } + typ := vals[0] + // name := vals[1] + if !p.table.known_type(typ) { + p.table.register_type(typ) + } + // if p.fn == "" { + // p.table.registerVar(&Var{ + // Name: name, + // Type: typ, + // Cat: CatFunc, + // }) + // } else { + // + // fn := p.table.findVar(p.fn) + // p.table.registerFnVar(&Var{ + // Name: name, + // Type: typ, + // Cat: CatVar, + // }, fn) + // } + p.next() +} + +fn prepend_pkg(pkg, name string) string { + return '${pkg}__${name}' +} + +fn (p &Parser) prepend_pkg(name string) string { + return prepend_pkg(p.pkg, name) +} + +fn (p mut Parser) go_statement() { + p.check(GO) + // TODO copypasta of name_expr() ? + // Method + if p.peek() == DOT { + var_name := p.lit + v := p.cur_fn.find_var(var_name) + p.cur_fn.mark_var_used(v) + p.next() + p.check(DOT) + typ := p.table.find_type(v.typ) + mut method := p.table.find_method(typ, p.lit) + p.async_fn_call(method, 0, var_name, v.typ) + } + // Normal function + else { + f := p.table.find_fn(p.lit) + if f.name == 'println' { + p.error('`go` cannot be used with `println`') + } + // println(f.name) + p.async_fn_call(f, 0, '', '') + } +} + +fn (p mut Parser) register_var(v Var) { + if v.line_nr == 0 { + v.line_nr = p.scanner.line_nr + } + p.cur_fn.register_var(v) +} + +// user:=jsdecode(User, user_json_string) +fn (p mut Parser) js_decode() string { + p.check(NAME)// json + p.check(DOT) + op := p.check_name() + if op == 'decode' { + // User tmp2; tmp2.foo = 0; tmp2.bar = 0;// I forgot to zero vals before => huge bug + // Option_User tmp3 = jsdecode_User(json_parse( s), &tmp2); ; + // if (!tmp3 .ok) { + // return + // } + // User u = *(User*) tmp3 . data; // TODO remove this (generated in or {} block handler) + p.check(LPAR) + typ := p.get_type() + p.check(COMMA) + p.cgen.start_tmp() + p.check_types(p.bool_expression(), 'string') + expr := p.cgen.end_tmp() + p.check(RPAR) + tmp := p.get_tmp() + cjson_tmp := p.get_tmp() + mut decl := '$typ $tmp; ' + // Init the struct + T := p.table.find_type(typ) + for field in T.fields { + def_val := type_default(field.typ) + if def_val != '' { + decl += '$tmp . $field.name = $def_val;\n' + } + } + p.gen_json_for_type(T) + decl += 'cJSON* $cjson_tmp = json__json_parse($expr);' + p.cgen.insert_before(decl) + // p.gen('jsdecode_$typ(json_parse($expr), &$tmp);') + p.gen('json__jsdecode_$typ($cjson_tmp, &$tmp); cJSON_Delete($cjson_tmp);') + opt_type := 'Option_$typ' + p.cgen.typedefs << 'typedef Option $opt_type;' + p.table.register_type(opt_type) + return opt_type + } + else if op == 'encode' { + p.check(LPAR) + p.cgen.start_tmp() + typ := p.bool_expression() + T := p.table.find_type(typ) + p.gen_json_for_type(T) + expr := p.cgen.end_tmp() + p.check(RPAR) + p.gen('json__json_print(json__jsencode_$typ($expr))') + return 'string' + } + else { + p.error('bad json op "$op"') + } + return '' +} + +fn is_compile_time_const(s string) bool { + s = s.trim_space() + if s == '' { + return false + } + if s.contains('\'') { + return true + } + for c in s { + if ! ((c >= `0` && c <= `9`) || c == `.`) { + return false + } + } + return true +} + +// fmt helpers +fn (scanner mut Scanner) fgen(s string) { + if scanner.fmt_line_empty { + s = repeat_char(`\t`, scanner.fmt_indent) + s + } + scanner.fmt_out.write(s) + scanner.fmt_line_empty = false +} + +fn (scanner mut Scanner) fgenln(s string) { + if scanner.fmt_line_empty { + s = repeat_char(`\t`, scanner.fmt_indent) + s + } + scanner.fmt_out.writeln(s) + scanner.fmt_line_empty = true +} + +fn (p mut Parser) fgen(s string) { + p.scanner.fgen(s) +} + +fn (p mut Parser) fspace() { + p.fgen(' ') +} + +fn (p mut Parser) fgenln(s string) { + p.scanner.fgenln(s) +} + diff --git a/compiler/scanner.v b/compiler/scanner.v new file mode 100644 index 0000000000..6244e4d197 --- /dev/null +++ b/compiler/scanner.v @@ -0,0 +1,630 @@ +module main + +struct Scanner { +mut: + file_path string + text string + pos int + line_nr int + inside_string bool + dollar_start bool // for hacky string interpolation TODO simplify + dollar_end bool + debug bool + line_comment string + started bool + is_fmt bool + // vfmt fields + fmt_out StringBuilder + fmt_indent int + fmt_line_empty bool +} + +const ( + SINGLE_QUOTE = `\'` + QUOTE = `"` +) + +fn new_scanner(file_path string) *Scanner { + if !os.file_exists(file_path) { + panic('"$file_path" doesnt exist') + } + scanner := &Scanner { + file_path: file_path + text: os.read_file(file_path) + fmt_out: new_string_builder(1000) + } + // println('new scanner "$file_path" txt.len=$scanner.text.len') + return scanner +} + +// TODO remove once multiple return values are implemented +struct ScanRes { + tok Token + lit string +} + +fn scan_res(tok Token, lit string) ScanRes { + return ScanRes{tok, lit} +} + +fn is_white(c byte) bool { + return c.is_white() +} + +fn is_nl(c byte) bool { + i := int(c) + return i == 12 || i == 10 +} + +fn (s mut Scanner) ident_name() string { + start := s.pos + for { + s.pos++ + c := s.text[s.pos] + if !is_name_char(c) && !c.is_digit() { + break + } + } + name := s.text.substr(start, s.pos) + s.pos-- + return name +} + +fn (s mut Scanner) ident_number() string { + start := s.pos + is_hex := s.text[s.pos] == `0` && s.text[s.pos + 1] == `x` + is_oct := !is_hex && s.text[s.pos] == `0` + mut is_float := false + for { + s.pos++ + c := s.text[s.pos] + if c == `.` { + is_float = true + } + is_good_hex := is_hex && (c == `x` || c == `u` || (c >= `a` && c <= `f`)) + // 1e+3, 1e-3, 1e3 + if !is_hex && c == `e` { + next := s.text[s.pos + 1] + if next == `+` || next == `-` || next.is_digit() { + s.pos++ + continue + } + } + if !c.is_digit() && c != `.` && !is_good_hex { + break + } + // 1..9 + if c == `.` && s.text[s.pos + 1] == `.` { + break + } + if is_oct && c >= `8` && !is_float { + s.error('malformed octal constant') + } + } + number := s.text.substr(start, s.pos) + s.pos-- + return number +} + +fn (s mut Scanner) skip_whitespace() { + for s.pos < s.text.len && is_white(s.text[s.pos]) { + if is_nl(s.text[s.pos]) { + s.line_nr++ + if s.is_fmt { + return + } + } + s.pos++ + } + // if s.pos == s.text.len { + // return scan_res(EOF, '') + // } +} + +fn (s mut Scanner) scan() ScanRes { + // if s.file_path == 'd.v' { + // println('\nscan()') + // } + // if s.started { + if s.pos > 0 { + // || (s.pos == 0 && s.text.len > 0 && s.text[s.pos] == `\n`) { + s.pos++ + } + s.started = true + if s.pos >= s.text.len { + return scan_res(EOF, '') + } + // skip whitespace + if !s.inside_string { + s.skip_whitespace() + } + if s.is_fmt && s.text[s.pos] == `\n` { + return scan_res(NL, '') + } + // End of $var, start next string + if !s.is_fmt && s.dollar_end { + // fmt.Println("end of $var, get string", s.pos, string(s.text[s.pos])) + if s.text[s.pos] == SINGLE_QUOTE { + // fmt.Println("ENDDD") + s.dollar_end = false + return scan_res(STRING, '') + } + s.dollar_end = false + return scan_res(STRING, s.ident_string()) + } + s.skip_whitespace() + // println('ws skipped') + // end of file + if s.pos >= s.text.len { + // println('scan(): returning EOF (pos >= len)') + return scan_res(EOF, '') + } + // println('!!!!! HANDLE CHAR pos=$s.pos') + // handle each char + c := s.text[s.pos] + mut nextc := `\0` + if s.pos + 1 < s.text.len { + nextc = s.text[s.pos + 1] + } + // name or keyword + if is_name_char(c) { + name := s.ident_name() + next_char := s.text[s.pos + 1]// tmp hack to detect . in ${} + // println('!!! got name=$name next_char=$next_char') + if is_key(name) { + // println('IS KEY') + // tok := (key_to_token(name)) + // println(tok.str()) + return scan_res(key_to_token(name), '') + } + // 'asdf $b' => "b" is the last name in the string, dont start parsing string + // at the next ', skip it + if s.inside_string { + // println('is_letter inside string! nextc=${nextc.str()}') + if s.text[s.pos + 1] == SINGLE_QUOTE { + // println('var is last before QUOTE') + s.pos++ + s.dollar_start = false + s.inside_string = false + } + } + if s.dollar_start && next_char != `.` { + // println('INSIDE STRING .dollar var=$name') + s.dollar_end = true + s.dollar_start = false + } + return scan_res(NAME, name) + } + // number, `.123` + else if c.is_digit() || c == `.` && nextc.is_digit() { + num := s.ident_number() + return scan_res(INT, num) + } + // all other tokens + switch c { + case `+`: + if nextc == `+` { + s.pos++ + return scan_res(INC, '') + } + else if nextc == `=` { + s.pos++ + return scan_res(PLUS_ASSIGN, '') + } + return scan_res(PLUS, '') + case `-`: + if nextc == `-` { + s.pos++ + return scan_res(DEC, '') + } + else if nextc == `=` { + s.pos++ + return scan_res(MINUS_ASSIGN, '') + } + return scan_res(MINUS, '') + case `*`: + if nextc == `=` { + s.pos++ + return scan_res(MULT_ASSIGN, '') + } + return scan_res(MUL, '') + case `^`: + if nextc == `=` { + s.pos++ + return scan_res(XOR_ASSIGN, '') + } + return scan_res(XOR, '') + case `%`: + if nextc == `=` { + s.pos++ + return scan_res(MOD_ASSIGN, '') + } + return scan_res(MOD, '') + case `?`: + return scan_res(QUESTION, '') + case SINGLE_QUOTE: + return scan_res(STRING, s.ident_string()) + // TODO allow double quotes + // case QUOTE: + // return scan_res(STRING, s.ident_string()) + case `\``: + return scan_res(CHAR, s.ident_char()) + case `(`: + return scan_res(LPAR, '') + case `)`: + return scan_res(RPAR, '') + case `[`: + return scan_res(LSBR, '') + case `]`: + return scan_res(RSBR, '') + case `{`: + // Skip { in ${ in strings + if s.inside_string { + return s.scan() + } + return scan_res(LCBR, '') + case `$`: + return scan_res(DOLLAR, '') + case `}`: + // s = `hello $name kek` + // s = `hello ${name} kek` + if s.inside_string { + s.pos++ + // TODO UNNEEDED? + if s.text[s.pos] == SINGLE_QUOTE { + s.inside_string = false + return scan_res(STRING, '') + } + return scan_res(STRING, s.ident_string()) + } + else { + return scan_res(RCBR, '') + } + case `&`: + if nextc == `=` { + s.pos++ + return scan_res(AND_ASSIGN, '') + } + if s.text[s.pos + 1] == `&` { + s.pos++ + return scan_res(AND, '') + } + return scan_res(AMP, '') + case `|`: + if s.text[s.pos + 1] == `|` { + s.pos++ + return scan_res(OR, '') + } + if nextc == `=` { + s.pos++ + return scan_res(OR_ASSIGN, '') + } + return scan_res(PIPE, '') + case `,`: + return scan_res(COMMA, '') + case `\n`: + return scan_res(NL, '') + case `.`: + if s.text[s.pos + 1] == `.` { + s.pos++ + return scan_res(DOTDOT, '') + } + return scan_res(DOT, '') + case `#`: + start := s.pos + 1 + for s.text[s.pos] != `\n` { + s.pos++ + } + s.line_nr++ + hash := s.text.substr(start, s.pos) + if s.is_fmt { + // fmt needs NL after # + s.pos-- + } + return scan_res(HASH, hash.trim_space()) + case `@`: + start := s.pos + 1 + for s.text[s.pos] != `\n` { + s.pos++ + } + s.line_nr++ + at := s.text.substr(start, s.pos) + return scan_res(AT, at.trim_space()) + case `>`: + if s.text[s.pos + 1] == `=` { + s.pos++ + return scan_res(GE, '') + } + else if s.text[s.pos + 1] == `>` { + if s.text[s.pos + 2] == `=` { + s.pos += 2 + return scan_res(RIGHT_SHIFT_ASSIGN, '') + } + s.pos++ + return scan_res(RIGHT_SHIFT, '') + } + else { + return scan_res(GT, '') + } + case `<`: + if s.text[s.pos + 1] == `=` { + s.pos++ + return scan_res(LE, '') + } + else if s.text[s.pos + 1] == `<` { + if s.text[s.pos + 2] == `=` { + s.pos += 2 + return scan_res(LEFT_SHIFT_ASSIGN, '') + } + s.pos++ + return scan_res(LEFT_SHIFT, '') + } + else { + return scan_res(LT, '') + } + case `=`: + if s.text[s.pos + 1] == `=` { + s.pos++ + return scan_res(EQ, '') + } + else { + return scan_res(ASSIGN, '') + } + case `:`: + if s.text[s.pos + 1] == `=` { + s.pos++ + return scan_res(DECL_ASSIGN, '') + } + else { + return scan_res(COLON, '') + } + case `;`: + return scan_res(SEMICOLON, '') + case `!`: + if s.text[s.pos + 1] == `=` { + s.pos++ + return scan_res(NE, '') + } + else { + return scan_res(NOT, '') + } + case `~`: + return scan_res(BIT_NOT, '') + case `/`: + if nextc == `=` { + s.pos++ + return scan_res(DIV_ASSIGN, '') + } + if s.text[s.pos + 1] == `/` { + // debug("!!!!!!GOT LINE COM") + start := s.pos + 1 + for s.text[s.pos] != `\n` { + s.pos++ + } + s.line_nr++ + s.line_comment = s.text.substr(start + 1, s.pos) + s.line_comment = s.line_comment.trim_space() + s.fgenln('// $s.line_comment') + if s.is_fmt { + // fmt needs NL after comment + s.pos-- + } + else { + // Skip comment + return s.scan() + } + return scan_res(LINE_COM, s.line_comment) + } + // Multiline comments + if s.text[s.pos + 1] == `*` { + start := s.pos + // Skip comment + for ! (s.text[s.pos] == `*` && s.text[s.pos + 1] == `/`) { + s.pos++ + if s.pos >= s.text.len { + s.line_nr-- + s.error('comment not terminated') + } + if s.text[s.pos] == `\n` { + s.line_nr++ + } + } + s.pos++ + end := s.pos + 1 + comm := s.text.substr(start, end) + s.fgenln(comm) + if s.is_fmt { + return scan_res(MLINE_COM, comm) + } + // Skip if not in fmt mode + return s.scan() + } + return scan_res(DIV, '') + } + println2('(char code=$c) pos=$s.pos len=$s.text.len') + s.error('invalid character `${c.str()}`') + return scan_res(EOF, '') +} + +fn (s &Scanner) error(msg string) { + // println('!! SCANNER ERROR: $msg') + file := s.file_path.all_after('/') + println2('panic: $file:${s.line_nr + 1}') + println2(msg) + // os.print_backtrace() + // println(file) + // println(s.file_path) + os.exit1(' ') +} + +// println2('array out of bounds $idx len=$a.len') +// This is really bad. It needs a major clean up +fn (s mut Scanner) ident_string() string { + // println("\nidentString() at char=", string(s.text[s.pos]), + // "chard=", s.text[s.pos], " pos=", s.pos, "txt=", s.text[s.pos:s.pos+7]) + debug := s.file_path.contains('test_test') + if debug { + println('identStr() $s.file_path line=$s.line_nr pos=$s.pos') + } + mut start := s.pos + s.inside_string = false + slash := `\\` + for { + s.pos++ + if s.pos >= s.text.len { + break + } + c := s.text[s.pos] + if debug { + println(c.str()) + } + prevc := s.text[s.pos - 1] + // end of string + if c == SINGLE_QUOTE && (prevc != slash || (prevc == slash && s.text[s.pos - 2] == slash)) { + // handle '123\\' slash at the end + break + } + if c == `\n` { + s.line_nr++ + } + // Don't allow \0 + if c == `0` && s.pos > 2 && s.text[s.pos - 1] == `\\` { + s.error('0 character in a string literal') + } + // Don't allow \x00 + if c == `0` && s.pos > 5 && s.text[s.pos - 1] == `0` && s.text[s.pos - 2] == `x` && + s.text[s.pos - 3] == `\\` { + s.error('0 character in a string literal') + } + // ${var} + if !s.is_fmt && c == `{` && prevc == `$` { + s.inside_string = true + // fmt.Println("breaking out of is()") + // so that s.pos points to $ at the next step + s.pos -= 2 + // fmt.Println("break pos=", s.pos, "c=", string(s.text[s.pos]), "d=", s.text[s.pos]) + break + } + // $var + // if !s.is_fmt && c != `{` && c != ` ` && ! (c >= `0` && c <= `9`) && prevc == `$` { + if !s.is_fmt && (c.is_letter() || c == `_`) && prevc == `$` { + s.inside_string = true + s.dollar_start = true + // println('setting s.dollar=true pos=$s.pos') + s.pos -= 2 + break + } + } + mut lit := '' + if s.text[start] == SINGLE_QUOTE { + start++ + } + mut end := s.pos + if s.inside_string { + end++ + } + if start > s.pos{} + else { + lit = s.text.substr(start, end) + } + // if lit.contains('\n') { + // println('\nstring lit="$lit" pos=$s.pos line=$s.line_nr') + // } + /* + for c in lit { + if s.file_path.contains('range_test') { + println('!') + println(c) + } + } +*/ + return lit +} + +fn (s mut Scanner) ident_char() string { + start := s.pos + slash := `\\` + mut len := 0 + for { + s.pos++ + if s.pos >= s.text.len { + break + } + if s.text[s.pos] != slash { + len++ + } + double_slash := s.text[s.pos - 1] == slash && s.text[s.pos - 2] == slash + if s.text[s.pos] == `\`` && (s.text[s.pos - 1] != slash || double_slash) { + if double_slash { + len++ + } + break + } + } + len-- + c := s.text.substr(start + 1, s.pos) + if len != 1 { + s.error('invalid character literal (more than one character: $len)') + } + return c +} + +fn (p mut Parser) peek() Token { + for { + tok := p.scanner.peek() + if tok != NL { + return tok + } + } +} + +fn (s mut Scanner) peek() Token { + pos := s.pos + line := s.line_nr + inside_string := s.inside_string + dollar_start := s.dollar_start + dollar_end := s.dollar_end + // ///// + res := s.scan() + tok := res.tok + s.pos = pos + s.line_nr = line + s.inside_string = inside_string + s.dollar_start = dollar_start + s.dollar_end = dollar_end + return tok +} + +fn (s mut Scanner) debug_tokens() { + s.pos = 0 + fname := s.file_path.all_after('/') + println('\n===DEBUG TOKENS $fname ============') + // allToks := '' + s.debug = true + for { + res := s.scan() + tok := res.tok + lit := res.lit + // printiln(tok) + print(tok.str()) + // allToks += tok.String() + if lit != '' { + println(' `$lit`') + // allToks += " `" + lit + "`" + } + else { + println('') + } + // allToks += "\n" + if tok == EOF { + println('============ END OF DEBUG TOKENS ==================') + // fmt.Println("========"+s.file+"========\n", allToks) + break + } + } +} + +fn is_name_char(c byte) bool { + return c.is_letter() || c == `_` +} + diff --git a/compiler/table.v b/compiler/table.v new file mode 100644 index 0000000000..b789724327 --- /dev/null +++ b/compiler/table.v @@ -0,0 +1,644 @@ +module main + +struct Table { +mut: + types []Type + consts []Var + fns []Fn + obf_ids map_int // obf_ids 'myfunction'] == 23 + packages []string // List of all modules registered by the application + imports []string // List of all imports + flags []string // ['-framework Cocoa', '-lglfw3'] + fn_cnt int atomic + obfuscate bool +} + +enum AccessMod { + PRIVATE // private immutable + PRIVET_MUT // private mutable + PUBLIC // public immmutable (readonly) + PUBLIC_MUT // public, but mutable only in this module + PUBLIC_MUT_MUT // public and mutable both inside and outside (not recommended to use, that's why it's so verbose) +} + +enum TypeCategory { + TYPE_STRUCT + T_CAT_FN +} + +struct Type { +mut: + pkg string + name string + fields []Var + methods []Fn + parent string + cat TypeCategory + gen_types []string + func Fn // For cat == FN (type kek fn()) + is_c bool // C.FILE + is_interface bool + is_enum bool + // This field is used for types that are not defined yet but are known to exist. + // It allows having things like `fn (f Foo) bar()` before `Foo` is defined. + // This information is needed in the first pass. + is_placeholder bool +} + +// For debugging types +fn (t Type) str() string { + mut s := 'type "$t.name" {' + if t.fields.len > 0 { + // s += '\n $t.fields.len fields:\n' + for field in t.fields { + s += '\n $field.name $field.typ' + } + s += '\n' + } + if t.methods.len > 0 { + // s += '\n $t.methods.len methods:\n' + for method in t.methods { + s += '\n ${method.str()}' + } + s += '\n' + } + s += '}\n' + return s +} + +const ( + CReserved = [ + 'exit', + 'unix', + 'print', + // 'ok', + 'error', + 'malloc', + 'calloc', + 'char', + 'free', + 'panic', + 'register' + ] + +) + +// This is used in generated C code +fn (f Fn) str() string { + t := Table{} + str_args := f.str_args(t) + return '$f.name($str_args) $f.typ' +} + +// fn (types array_Type) print_to_file(f string) { +// } +const ( + NUMBER_TYPES = ['number', 'int', 'i8', 'u8', 'i16', 'u16', 'i32', 'u32', 'byte', 'i64', 'u64', 'long', 'double', 'float', 'f32', 'f64'] + FLOAT_TYPES = ['double', 'float', 'f32', 'f64'] +) + +fn is_number_type(typ string) bool { + return NUMBER_TYPES.contains(typ) +} + +fn is_float_type(typ string) bool { + return FLOAT_TYPES.contains(typ) +} + +fn new_table(obfuscate bool) *Table { + mut t := &Table { + obf_ids: map[string]int{} + obfuscate: obfuscate + } + t.register_type('int') + t.register_type('size_t') + t.register_type_with_parent('i8', 'int') + t.register_type_with_parent('u8', 'int') + t.register_type_with_parent('i16', 'int') + t.register_type_with_parent('u16', 'int') + t.register_type_with_parent('i32', 'int') + t.register_type_with_parent('u32', 'int') + t.register_type_with_parent('byte', 'int') + // t.register_type_with_parent('i64', 'int') + t.register_type('i64') + t.register_type_with_parent('u64', 'int') + t.register_type('long') + t.register_type('byteptr') + t.register_type('intptr') + t.register_type('double')// TODO remove + t.register_type('float')// TODO remove + t.register_type('f32') + t.register_type('f64') + t.register_type('rune') + t.register_type('bool') + t.register_type('void') + t.register_type('voidptr') + t.register_type('va_list') + t.register_const('stdin', 'int', 'main', false) + t.register_const('stderr', 'int', 'main', false) + t.register_type_with_parent('map_string', 'map') + t.register_type_with_parent('map_int', 'map') + return t +} + +// If `name` is a reserved C keyword, returns `v_name` instead. +fn (t mut Table) var_cgen_name(name string) string { + if CReserved.contains(name) { + return 'v_$name' + } + else { + return name + } +} + +fn (t mut Table) register_package(pkg string) { + if t.packages.contains(pkg) { + return + } + t.packages << pkg +} + +fn (table &Table) known_pkg(pkg string) bool { + return pkg in table.packages +} + +fn (t mut Table) register_const(name, typ string, pkg string, is_imported bool) { + t.consts << Var { + name: name + typ: typ + is_const: true + is_import_const: is_imported + pkg: pkg + } +} + +// Only for translated code +fn (p mut Parser) register_global(name, typ string) { + p.table.consts << Var { + name: name + typ: typ + is_const: true + is_global: true + pkg: p.pkg + } +} + +// TODO PERF O(N) this slows down the comiler a lot! +fn (t mut Table) register_fn(f Fn) { + // Avoid duplicate fn names TODO why? the name should already be unique? + for ff in t.fns { + if ff.name == f.name { + return + } + } + t.fns << f +} + +fn (table &Table) known_type(typ string) bool { + // 'byte*' => look up 'byte', but don't mess up fns + if typ.ends_with('*') && !typ.contains(' ') { + typ = typ.left(typ.len - 1) + } + for t in table.types { + if t.name == typ && !t.is_placeholder { + return true + } + } + return false +} + +// TODO PERF O(N) this slows down the comiler a lot! +fn (t &Table) find_fn(name string) Fn { + for f in t.fns { + if f.name == name { + return f + } + } + return Fn{} +} + +// TODO PERF O(N) this slows down the comiler a lot! +fn (t &Table) known_fn(name string) bool { + for f in t.fns { + if f.name == name { + return true + } + } + return false +} + +fn (t &Table) known_const(name string) bool { + v := t.find_const(name) + // TODO use optional + return v.name.len > 0 +} + +fn (t mut Table) register_type(typ string) { + if typ.len == 0 { + return + } + // println('REGISTER TYPE $typ') + for typ2 in t.types { + if typ2.name == typ { + return + } + } + // if t.types.filter( _.name == typ.name).len > 0 { + // return + // } + datyp := Type { + name: typ + } + t.types << datyp +} + +fn (p mut Parser) register_type_with_parent(strtyp, parent string) { + typ := Type { + name: strtyp + parent: parent + pkg: p.pkg + } + p.table.register_type2(typ) +} + +fn (t mut Table) register_type_with_parent(typ, parent string) { + if typ.len == 0 { + return + } + // if t.types.filter(_.name == typ) > 0 + for typ2 in t.types { + if typ2.name == typ { + return + } + } + /* +mut pkg := '' +if parent == 'array' { +pkg = 'builtin' +} +*/ + datyp := Type { + name: typ + parent: parent + } + t.types << datyp +} + +fn (t mut Table) register_type2(typ Type) { + if typ.name.len == 0 { + return + } + // println('register type2 $typ.name') + for typ2 in t.types { + if typ2.name == typ.name { + return + } + } + t.types << typ +} + +fn (t mut Type) add_field(name, typ string, is_mut bool, attr string, access_mod AccessMod) { + // if t.name == 'Parser' { + // println('adding field $name') + // } + v := Var { + name: name + typ: typ + is_mut: is_mut + attr: attr + access_mod: access_mod + } + t.fields << v +} + +fn (t &Type) has_field(name string) bool { + field := t.find_field(name) + return (field.name != '') +} + +fn (t &Type) find_field(name string) Var { + for field in t.fields { + if field.name == name { + return field + } + } + return Var{} +} + +fn (table &Table) type_has_field(typ &Type, name string) bool { + field := table.find_field(typ, name) + return (field.name != '') +} + +fn (table &Table) find_field(typ &Type, name string) Var { + field := typ.find_field(name) + if field.name.len == 0 && typ.parent.len > 0 { + parent := table.find_type(typ.parent) + return parent.find_field(name) + } + return field +} + +fn (t mut Type) add_method(f Fn) { + // if t.name.contains('Parser') { + // println('!!!add_method() $f.name to $t.name len=$t.methods.len cap=$t.methods.cap') + // } + t.methods << f + // println('end add_method()') +} + +fn (t &Type) has_method(name string) bool { + method := t.find_method(name) + return (method.name != '') +} + +fn (table &Table) type_has_method(typ &Type, name string) bool { + method := table.find_method(typ, name) + return (method.name != '') +} + +// TODO use `?Fn` +fn (table &Table) find_method(typ &Type, name string) Fn { + // println('TYPE HAS METHOD $name') + method := typ.find_method(name) + if method.name.len == 0 && typ.parent.len > 0 { + parent := table.find_type(typ.parent) + return parent.find_method(name) + // println('parent = $parent.name $res') + // return res + } + return method +} + +fn (t &Type) find_method(name string) Fn { + // println('$t.name find_method($name) methods.len=$t.methods.len') + for method in t.methods { + // println('method=$method.name') + if method.name == name { + return method + } + } + return Fn{} +} + +fn (t mut Type) add_gen_type(type_name string) { + // println('add_gen_type($s)') + if t.gen_types.contains(type_name) { + return + } + t.gen_types << type_name +} + +fn (p &Parser) find_type(name string) *Type { + typ := p.table.find_type(name) + if typ.name.len == 0 { + return p.table.find_type(p.prepend_pkg(name)) + } + return typ +} + +fn (t &Table) find_type(name string) *Type { + if name.ends_with('*') && !name.contains(' ') { + name = name.left(name.len - 1) + } + // TODO PERF use map + for i, typ in t.types { + if typ.name == name { + return &t.types[i] + } + } + return &Type{} +} + +fn (p mut Parser) _check_types(got, expected string, throw bool) bool { + p.log('check types got="$got" exp="$expected" ') + if p.translated { + return true + } + // Allow ints to be used as floats + if got.eq('int') && expected.eq('float') { + return true + } + if got.eq('int') && expected.eq('f64') { + return true + } + if got == 'f64' && expected == 'float' { + return true + } + if got == 'float' && expected == 'f64' { + return true + } + // Allow ints to be used as longs + if got.eq('int') && expected.eq('long') { + return true + } + if got == 'void*' && expected.starts_with('fn ') { + return true + } + if got.starts_with('[') && expected == 'byte*' { + return true + } + // Todo void* allows everything right now + if got.eq('void*') || expected.eq('void*') { + // if !p.builtin_pkg { + if p.is_play { + return false + } + return true + } + // TODO only allow numeric consts to be assigned to bytes, and + // throw an error if they are bigger than 255 + if got.eq('int') && expected.eq('byte') { + return true + } + if got.eq('int') && expected.eq('byte*') { + return true + } + // byteptr += int + if got.eq('int') && expected.eq('byteptr') { + return true + } + if got == 'Option' && expected.starts_with('Option_') { + return true + } + // lines := new_array + if got == 'array' && expected.starts_with('array_') { + return true + } + // Expected type "Option_os__File", got "os__File" + if expected.starts_with('Option_') && expected.ends_with(got) { + return true + } + // NsColor* return 0 + if !p.is_play { + if expected.ends_with('*') && got == 'int' { + return true + } + // if got == 'T' || got.contains('') { + // return true + // } + // if expected == 'T' || expected.contains('') { + // return true + // } + // Allow pointer arithmetic + if expected.eq('void*') && got.eq('int') { + return true + } + } + expected = expected.replace('*', '') + got = got.replace('*', '') + if got != expected { + // Interface check + if expected.ends_with('er') { + if p.satisfies_interface(expected, got, throw) { + return true + } + } + if !throw { + return false + } + else { + p.error('expected type `$expected`, but got `$got`') + } + } + return true +} + +// throw by default +fn (p mut Parser) check_types(got, expected string) bool { + return p._check_types(got, expected, true) +} + +fn (p mut Parser) check_types_no_throw(got, expected string) bool { + return p._check_types(got, expected, false) +} + +fn (p mut Parser) satisfies_interface(interface_name, _typ string, throw bool) bool { + int_typ := p.table.find_type(interface_name) + typ := p.table.find_type(_typ) + for method in int_typ.methods { + if !typ.has_method(method.name) { + // if throw { + p.error('Type "$_typ" doesnt satisfy interface "$interface_name" (method "$method.name" is not implemented)') + // } + return false + } + } + return true +} + +fn type_default(typ string) string { + if typ.starts_with('array_') { + typ = typ.right(6) + return 'new_array(0, 1, sizeof($typ))' + } + // Always set pointers to 0 + if typ.ends_with('*') { + return '0' + } + // ? + if typ.contains('__') { + return '' + } + // Default values for other types are not needed because of mandatory initialization + switch typ { + case 'int': return '0' + case 'string': return 'tos("", 0)' + case 'void*': return '0' + case 'byte*': return '0' + case 'bool': return '0' + } + return '' +} + +// TODO PERF O(n) +fn (t &Table) is_interface(name string) bool { + for typ in t.types { + if typ.is_interface && typ.name == name { + return true + } + } + return false +} + +// Do we have fn main()? +fn (t &Table) main_exists() bool { + for f in t.fns { + if f.name == 'main' { + return true + } + } + return false +} + +// TODO use `?Var` +fn (t &Table) find_const(name string) Var { + for c in t.consts { + if c.name == name { + return c + } + } + 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.pkg == 'builtin' && CReserved.contains(f.name) { + return 'v_$name' + } + // Obfuscate but skip certain names + // TODO ugly, fix + if table.obfuscate && f.name != 'main' && f.name != 'WinMain' && f.pkg != 'builtin' && !f.is_c && + f.pkg != 'darwin' && f.pkg != 'os' && !f.name.contains('window_proc') && f.name != 'gg__vec2' && + f.name != 'build_token_str' && f.name != 'build_keys' && f.pkg != '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' + println2('$old ==> $name') + } + return name +} + +// ('s', 'string') => 'string s' +// ('nums', '[20]byte') => 'byte nums[20]' +// ('myfn', 'fn(int) string') => 'string (*myfn)(int)' +fn (table &Table) cgen_name_type_pair(name, typ string) string { + // Special case for [10]int + if typ.len > 0 && typ[0] == `[` { + tmp := typ.all_after(']') + size := typ.all_before(']') + return '$tmp $name $size ]' + } + // fn() + else if typ.starts_with('fn (') { + T := table.find_type(typ) + if T.name == '' { + os.exit1('this should never happen') + } + str_args := T.func.str_args(table) + return '$T.func.typ (*$name)( $str_args /*FFF*/ )' + } + // TODO tm hack, do this for all C struct args + else if typ == 'tm' { + return 'struct tm $name' + } + return '$typ $name' +} + diff --git a/compiler/token.v b/compiler/token.v new file mode 100644 index 0000000000..516ad65428 --- /dev/null +++ b/compiler/token.v @@ -0,0 +1,265 @@ +module main + +enum Token { + EOF + NAME + INT + STRING + CHAR + FLOAT + PLUS + MINUS + MUL + DIV + MOD + XOR + PIPE + INC + DEC + AND + OR + NOT + BIT_NOT + QUESTION + COMMA + SEMICOLON + COLON + AMP + HASH + AT + DOLLAR + LEFT_SHIFT + RIGHT_SHIFT + // = := += -= + ASSIGN + DECL_ASSIGN + PLUS_ASSIGN + MINUS_ASSIGN + DIV_ASSIGN + MULT_ASSIGN + XOR_ASSIGN + MOD_ASSIGN + OR_ASSIGN + AND_ASSIGN + RIGHT_SHIFT_ASSIGN + LEFT_SHIFT_ASSIGN + // {} () [] + LCBR + RCBR + LPAR + RPAR + LSBR + RSBR + // == != <= < >= > + EQ + NE + GT + LT + GE + LE + // comments + LINE_COM + MLINE_COM + NL + DOT + DOTDOT + // keywords + keyword_beg + PACKAGE + // MODULE + STRUCT + IF + ELSE + RETURN + GO + CONST + IMPORT_CONST + MUT + TIP + ENUM + FOR + SWITCH + MATCH + CASE + FUNC + TRUE + FALSE + CONTINUE + BREAK + EMBED + IMPORT + TYPEOF + DEFAULT + ENDIF + ASSERT + SIZEOF + IN + ATOMIC + INTERFACE + OR_ELSE + GLOBAL + UNION + PUB + GOTO + STATIC + keyword_end +} + +// build_keys genereates a map with keywords' string values: +// Keywords['return'] == .return +fn build_keys() map_int { + mut res := map[string]int{} + for t := int(keyword_beg) + 1; t < int(keyword_end); t++ { + key := TOKENSTR[t] + res[key] = int(t) + } + return res +} + +fn build_token_str() []string { + mut s := [''; 140]// TODO define a const + s[keyword_beg] = '' + s[keyword_end] = '' + s[EOF] = 'EOF' + s[NAME] = 'NAME' + s[INT] = 'INT' + s[STRING] = 'STR' + s[CHAR] = 'CHAR' + s[FLOAT] = 'FLOAT' + s[PLUS] = '+' + s[MINUS] = '-' + s[MUL] = '*' + s[DIV] = '/' + s[MOD] = '%' + s[XOR] = '^' + s[BIT_NOT] = '~' + s[PIPE] = '|' + s[HASH] = '#' + s[AMP] = '&' + s[AT] = '@' + s[INC] = '++' + s[DEC] = '--' + s[AND] = '&&' + s[OR] = '||' + s[NOT] = '!' + s[DOT] = '.' + s[DOTDOT] = '..' + s[COMMA] = ',' + s[SEMICOLON] = ';' + s[COLON] = ':' + s[ASSIGN] = '=' + s[DECL_ASSIGN] = ':=' + s[PLUS_ASSIGN] = '+=' + s[MINUS_ASSIGN] = '-=' + s[MULT_ASSIGN] = '*=' + s[DIV_ASSIGN] = '/=' + s[XOR_ASSIGN] = '^=' + s[MOD_ASSIGN] = '%=' + s[OR_ASSIGN] = '|=' + s[AND_ASSIGN] = '&=' + s[RIGHT_SHIFT_ASSIGN] = '>>=' + s[LEFT_SHIFT_ASSIGN] = '<<=' + s[LCBR] = '{' + s[RCBR] = '}' + s[LPAR] = '(' + s[RPAR] = ')' + s[LSBR] = '[' + s[RSBR] = ']' + s[EQ] = '==' + s[NE] = '!=' + s[GT] = '>' + s[LT] = '<' + s[GE] = '>=' + s[LE] = '<=' + s[QUESTION] = '?' + s[LEFT_SHIFT] = '<<' + s[RIGHT_SHIFT] = '>>' + s[LINE_COM] = '//' + s[NL] = 'NLL' + s[DOLLAR] = '$' + s[ASSERT] = 'assert' + s[STRUCT] = 'struct' + s[IF] = 'if' + s[ELSE] = 'else' + s[RETURN] = 'return' + s[PACKAGE] = 'module' + s[SIZEOF] = 'sizeof' + s[GO] = 'go' + s[GOTO] = 'goto' + s[CONST] = 'const' + s[MUT] = 'mut' + s[TIP] = 'type' + s[FOR] = 'for' + s[SWITCH] = 'switch' + s[MATCH] = 'match' + s[CASE] = 'case' + s[FUNC] = 'fn' + s[TRUE] = 'true' + s[FALSE] = 'false' + s[CONTINUE] = 'continue' + s[BREAK] = 'break' + s[IMPORT] = 'import' + s[EMBED] = 'embed' + s[TYPEOF] = 'typeof' + s[DEFAULT] = 'default' + s[ENDIF] = 'endif' + s[ENUM] = 'enum' + s[INTERFACE] = 'interface' + s[PUB] = 'pub' + s[IMPORT_CONST] = 'import_const' + s[IN] = 'in' + s[ATOMIC] = 'atomic' + s[OR_ELSE] = 'or' + s[GLOBAL] = '__global' + s[UNION] = 'union' + s[STATIC] = 'static' + return s +} + +const ( + TOKENSTR = build_token_str() + KEYWORDS = build_keys() +) + +fn key_to_token(key string) Token { + a := Token(KEYWORDS[key]) + return a +} + +fn is_key(key string) bool { + return int(key_to_token(key)) > 0 +} + +fn (t Token) str() string { + return TOKENSTR[int(t)] +} + +fn (t Token) is_decl() bool { + // TODO return t in [FUNC ,TIP, CONST, IMPORT_CONST ,AT ,EOF] + return t == ENUM || t == INTERFACE || t == FUNC || t == STRUCT || t == TIP || + t == CONST || t == IMPORT_CONST || t == AT || t == EOF +} + +const ( + AssignTokens = [ + ASSIGN, PLUS_ASSIGN, MINUS_ASSIGN, + MULT_ASSIGN, DIV_ASSIGN, XOR_ASSIGN, MOD_ASSIGN, + OR_ASSIGN, AND_ASSIGN, RIGHT_SHIFT_ASSIGN, + LEFT_SHIFT_ASSIGN + ] + +) + +fn (t Token) is_assign() bool { + return t in AssignTokens +} + +fn (t[]Token) contains(val Token) bool { + for tt in t { + if tt == val { + return true + } + } + return false +} + diff --git a/examples/concurrent_news_fetcher.v b/examples/concurrent_news_fetcher.v new file mode 100644 index 0000000000..faca945e15 --- /dev/null +++ b/examples/concurrent_news_fetcher.v @@ -0,0 +1,35 @@ +// Please share your thoughts, suggestions, questions, etc here: +// https://github.com/vlang-io/V/issues/3 +// I'm very interested in your feedback. +import http +import json +import runtime + +struct Story { + title string +} + +// Fetches top HN stories in 8 coroutines +fn main() { + resp := http.get('https://hacker-news.firebaseio.com/v0/topstories.json')? + ids := json.decode([]int, resp.body)? + mut cursor := 0 + for _ in 0..8 { + go fn() { + for { + lock { // Without this lock the program will not compile + if cursor >= ids.len { + break + } + id := ids[cursor] + cursor++ + } + url := 'https://hacker-news.firebaseio.com/v0/item/$id.json' + resp := http.get(url)? + story := json.decode(Story, resp.body)? + println(story.title) + } + }() + } + runtime.wait() // Waits for all coroutines to finish +} diff --git a/examples/links_scraper.v b/examples/links_scraper.v new file mode 100644 index 0000000000..f72d09365a --- /dev/null +++ b/examples/links_scraper.v @@ -0,0 +1,15 @@ +import http + +fn main() { + html := http.get('https://news.ycombinator.com') + mut pos := 0 + for { + pos = html.index_after('https://', pos + 1) + if pos == -1 { + break + } + end := html.index_after('"', pos) + println(html.substr(pos, end)) + } +} + diff --git a/examples/tetris/tetris.v b/examples/tetris/tetris.v new file mode 100644 index 0000000000..73c5a33dfa --- /dev/null +++ b/examples/tetris/tetris.v @@ -0,0 +1,339 @@ +import rand +import time +import gx +import gl +import gg +import glfw +import math + +const ( + BlockSize = 20 // pixels + FieldHeight = 20 // # of blocks + FieldWidth = 10 + TetroSize = 4 + WinWidth = BlockSize * FieldWidth + WinHeight = BlockSize * FieldHeight + TimerPeriod = 250 // ms +) + +const ( + // Tetros' 4 possible states are encoded in binaries + BTetros = [ + // 0000 0 + // 0000 0 + // 0110 6 + // 0110 6 + [66, 66, 66, 66], + // 0000 0 + // 0000 0 + // 0010 2 + // 0111 7 + [27, 131, 72, 232], + // 0000 0 + // 0000 0 + // 0011 3 + // 0110 6 + [36, 231, 36, 231], + // 0000 0 + // 0000 0 + // 0110 6 + // 0011 3 + [63, 132, 63, 132], + // 0000 0 + // 0011 3 + // 0001 1 + // 0001 1 + [311, 17, 223, 74], + // 0000 0 + // 0011 3 + // 0010 2 + // 0010 2 + [322, 71, 113, 47], + // Special case since 15 can't be used + // 1111 + [1111, 9, 1111, 9], + ] + // Each tetro has its unique color + Colors = [ + gx.rgb(0, 0, 0), + gx.rgb(253, 32, 47), + gx.rgb(0, 110, 194), + gx.rgb(34, 169, 16), + gx.rgb(170, 0, 170), + gx.rgb(0, 0, 170), + gx.rgb(0, 170, 0), + gx.rgb(170, 85, 0), + gx.rgb(0, 170, 170), + ] +) + +// TODO: type Tetro [TetroSize]struct{ x, y int } +struct Block { + mut: + x int + y int +} + +struct Game { + mut: + // Position of the current tetro + pos_x int + pos_y int + // field[y][x] contains the color of the block with (x,y) coordinates + // "-1" border is to avoid bounds checking. + // -1 -1 -1 -1 + // -1 0 0 -1 + // -1 0 0 -1 + // -1 -1 -1 -1 + // TODO: field [][]int + field array_array_int + // TODO: tetro Tetro + tetro []Block + // TODO: tetros_cache []Tetro + tetros_cache []Block + // Index of the current tetro. Refers to its color. + tetro_idx int + // Index of the rotation (0-3) + rotation_idx int + // gg context for drawing + gg *gg.GG +} + +fn main() { + glfw.init() + mut game := &Game{gg: 0} // TODO + game.parse_tetros() + game.init_game() + mut window := glfw.create_window(glfw.WinCfg { + width: WinWidth + height: WinHeight + title: 'V Tetris' + ptr: game // glfw user pointer + }) + window.make_context_current() + window.onkeydown(key_down) + gg.init() + game.gg = gg.new_context(gg.Cfg { + width: WinWidth + height: WinHeight + use_ortho: true // This is needed for 2D drawing + }) + go game.run() // Run the game loop in a new thread + gl.clear() // For some reason this is necessary to avoid an intial flickering + gl.clear_color(255, 255, 255, 255) + for { + gl.clear() + gl.clear_color(255, 255, 255, 255) + game.draw_scene() + window.swap_buffers() + glfw.wait_events() + } +} + +fn (g mut Game) init_game() { + rand.seed() + g.generate_tetro() + g.field = []array_int // TODO: g.field = [][]int + // Generate the field, fill it with 0's, add -1's on each edge + for i := 0; i < FieldHeight + 2; i++ { + mut row := [0; FieldWidth + 2] + row[0] = - 1 + row[FieldWidth + 1] = - 1 + g.field << row + } + mut first_row := g.field[0] + mut last_row := g.field[FieldHeight + 1] + for j := 0; j < FieldWidth + 2; j++ { + first_row[j] = - 1 + last_row[j] = - 1 + } +} + +fn (g mut Game) parse_tetros() { + for b_tetros in BTetros { + for b_tetro in b_tetros { + for t in parse_binary_tetro(b_tetro) { + g.tetros_cache << t + } + } + } +} + +fn (g mut Game) run() { + for { + g.move_tetro() + g.delete_completed_lines() + glfw.post_empty_event() // force window redraw + time.sleep_ms(TimerPeriod) + } +} + +fn (g mut Game) move_tetro() { + // Check each block in current tetro + for block in g.tetro { + y := block.y + g.pos_y + 1 + x := block.x + g.pos_x + // Reached the bottom of the screen or another block? + // TODO: if g.field[y][x] != 0 + row := g.field[y] + if row[x] != 0 { + // The new tetro has no space to drop => end of the game + if g.pos_y < 2 { + g.init_game() + return + } + // Drop it and generate a new one + g.drop_tetro() + g.generate_tetro() + return + } + } + g.pos_y++ +} + +fn (g mut Game) move_right(dx int) { + // Reached left/right edge or another tetro? + for i := 0; i < TetroSize; i++ { + tetro := g.tetro[i] + y := tetro.y + g.pos_y + x := tetro.x + g.pos_x + dx + row := g.field[y] + if row[x] != 0 { + // Do not move + return + } + } + g.pos_x += dx +} + +fn (g mut Game) delete_completed_lines() { + for y := FieldHeight; y >= 1; y-- { + g.delete_completed_line(y) + } +} + +fn (g mut Game) delete_completed_line(y int) { + for x := 1; x <= FieldWidth; x++ { + f := g.field[y] + if f[x] == 0 { + return + } + } + // Move everything down by 1 position + for yy := y - 1; yy >= 1; yy-- { + for x := 1; x <= FieldWidth; x++ { + mut a := g.field[yy + 1] + mut b := g.field[yy] + a[x] = b[x] + } + } +} + +// Place a new tetro on top +fn (g mut Game) generate_tetro() { + g.pos_y = 0 + g.pos_x = FieldWidth / 2 - TetroSize / 2 + g.tetro_idx = rand.next(BTetros.len) + g.rotation_idx = 0 + g.get_tetro() +} + +// Get the right tetro from cache +fn (g mut Game) get_tetro() { + idx := g.tetro_idx * TetroSize * TetroSize + g.rotation_idx * TetroSize + g.tetro = g.tetros_cache.slice(idx, idx + TetroSize) +} + +fn (g mut Game) drop_tetro() { + for i := 0; i < TetroSize; i++ { + tetro := g.tetro[i] + x := tetro.x + g.pos_x + y := tetro.y + g.pos_y + // Remember the color of each block + // TODO: g.field[y][x] = g.tetro_idx + 1 + mut row := g.field[y] + row[x] = g.tetro_idx + 1 + } +} + +fn (g &Game) draw_tetro() { + for i := 0; i < TetroSize; i++ { + tetro := g.tetro[i] + g.draw_block(g.pos_y + tetro.y, g.pos_x + tetro.x, g.tetro_idx + 1) + } +} + +fn (g &Game) draw_block(i, j int, color_idx int) { + g.gg.draw_rect((j - 1) * BlockSize, (i - 1) * BlockSize, + BlockSize - 1, BlockSize - 1, Colors[color_idx]) +} + +fn (g &Game) draw_field() { + for i := 1; i < FieldHeight + 1; i++ { + for j := 1; j < FieldWidth + 1; j++ { + f := g.field[i] + if f[j] > 0 { + g.draw_block(i, j, f[j]) + } + } + } +} + +fn (g &Game) draw_scene() { + g.draw_tetro() + g.draw_field() +} + +fn parse_binary_tetro(t int) []Block { + res := [Block{} ; 4] + mut cnt := 0 + horizontal := t == 9// special case for the horizontal line + for i := 0; i <= 3; i++ { + // Get ith digit of t + p := int(math.pow(10, 3 - i)) + mut digit := int(t / p) + t %= p + // Convert the digit to binary + for j := 3; j >= 0; j-- { + bin := digit % 2 + digit /= 2 + if bin == 1 || (horizontal && i == TetroSize - 1) { + // TODO: res[cnt].x = j + // res[cnt].y = i + mut point := &res[cnt] + point.x = j + point.y = i + cnt++ + } + } + } + return res +} + +// TODO: this exposes the unsafe C interface, clean up +fn key_down(wnd voidptr, key int, code int, action, mods int) { + if action != 2 && action != 1 { + return + } + // Fetch the game object stored in the user pointer + mut game := &Game(glfw.get_window_user_pointer(wnd)) + switch key { + case glfw.KeyUp: + // Rotate the tetro + game.rotation_idx++ + if game.rotation_idx == TetroSize { + game.rotation_idx = 0 + } + game.get_tetro() + if game.pos_x < 0 { + game.pos_x = 1 + } + case glfw.KeyLeft: + game.move_right(-1) + case glfw.KeyRight: + game.move_right(1) + case glfw.KeyDown: + game.move_tetro() // drop faster when the player presses + } +} + diff --git a/examples/word_counter/word_counter.v b/examples/word_counter/word_counter.v new file mode 100644 index 0000000000..a7a7417b14 --- /dev/null +++ b/examples/word_counter/word_counter.v @@ -0,0 +1,58 @@ +import os + +fn main() { + mut path = 'cinderella.txt' + if os.args.len != 2 { + println('usage: word_counter [text_file]') + println('using $path') + } + else { + path = os.args[1] + } + lines := os.read_file_lines(path.trim_space()) + mut m := map[string]int{} + for line in lines { + words := line.to_lower().split(' ') + for word in words { + key := filter_word(word) + if key == '' { + continue + } + m[key] = m[key] + 1// TODO m[key]++ + } + } + // Sort the keys + mut keys := []string + for e in m.entries { + keys << e.key + } + keys.sort() + // Print the map + for key in keys { + val := m[key] + println('$key => $val') + } +} + +// Removes punctuation +fn filter_word(word string) string { + if word == '' || word == ' ' { + return '' + } + mut i := 0 + for i < word.len && !is_letter(word[i]) { + i++ + } + start := i + for i < word.len && is_letter(word[i]) { + i++ + } + end := i + return word.substr(start, end) +} + +// TODO remove once it's possible to call word[i].is_letter() +fn is_letter(c byte) bool { + return c.is_letter() +} + diff --git a/gg/gg.v b/gg/gg.v new file mode 100644 index 0000000000..cd43b8d895 --- /dev/null +++ b/gg/gg.v @@ -0,0 +1,712 @@ +module gg + +import stbi +import glm +import gl + +#flag darwin -I/usr/local/Cellar/freetype/2.10.0/include/freetype2 +#flag -lfreetype +#flag linux -I/usr/include/freetype2 +#flag linux -I. +#include "ft2build.h" +#include FT_FREETYPE_H +#include "glad.h" +struct Vec2 { + x int + y int +} + +import const ( + GL_STATIC_DRAW + GL_FLOAT + GL_FALSE + GL_UNSIGNED_INT + GL_INT +) + +const ( + DEFAULT_FONT_SIZE = 12 +) + +pub fn vec2(x, y int) Vec2 { + res := Vec2 { + x: x, + y: y, + } + return res +} + +struct Character { + texture_id u32 + size Vec2 + bearing Vec2 + advance u32 +} + +fn init() { + println(gl.TEXT_VERT) + gl.init_glad() +} + +struct Face { + cobj voidptr + kek int +} + +struct Cfg { + width int + height int + use_ortho int + retina bool + font_size int +} + +struct GG { + shader gl.Shader + // use_ortho bool + width int + height int + VAO u32 + rect_vao u32 + rect_vbo u32 + line_vao u32 + line_vbo u32 + VBO u32 + chars []gg.Character + utf_runes []string + utf_chars []gg.Character + text_ctx *GG + face Face + scale int // retina = 2 , normal = 1 +} + +// fn new_context(width, height int, use_ortho bool, font_size int) *GG { +pub fn new_context(cfg Cfg) *GG { + // println('new context orhto=$cfg.use_ortho') + // # glScissor(0,0,300,300); + shader := gl.new_shader('simple') + shader.use() + if cfg.use_ortho > 0 { + projection := glm.ortho(0, cfg.width, cfg.height, 0) + /* + // for debugging broken tetris in gg.o + # projection.data[0]=0.010000; + # projection.data[1]=0.000000; + # projection.data[2]=0.000000; + # projection.data[3]=0.000000; + # projection.data[4]=0.000000; + # projection.data[5]=-0.005000; + # projection.data[6]=0.000000; + # projection.data[7]=0.000000; + # projection.data[8]=0.000000; + # projection.data[9]=0.000000; + # projection.data[10]=1.000000; + # projection.data[11]=0.000000; + # projection.data[12]=-1.000000; + # projection.data[13]=1.000000; + # projection.data[14]=0.000000; + # projection.data[15]=1.000000; +*/ + // projection_new := ortho(0, width, height, 0) + // println('\nORTHO OLD=') + # for (int i=0;i<16;i++) printf("%d=%f ",i, projection.data[i]); + // println('\n\n!ORTHO NEW=') + // # for (int i=0;i<16;i++) printf("%d=%f ",i, projection_new[i]); + // println('\n\n') + println('setting o') + shader.set_mat4('projection', projection) + } + else { + // TODO move to function (allow volt functions to return arrrays without allocations) + // i := glm.identity3() + shader.set_mat4('projection', glm.identity()) + } + VAO := gl.gen_vertex_array() + println('new gg context VAO=$VAO') + VBO := gl.gen_buffer() + mut scale := 1 + if cfg.retina { + scale = 2 + } + mut ctx := &GG { + shader: shader, + width: cfg.width, + height: cfg.height, + VAO: VAO, + VBO: VBO, + // /line_vao: gl.gen_vertex_array() + // /line_vbo: gl.gen_buffer() + text_ctx: new_context_text(cfg, scale), + scale: scale + // use_ortho: use_ortho + } + // ctx.init_rect_vao() + return ctx +} + +pub fn (ctx &GG) draw_triangle(x1, y1, x2, y2, x3, y3 float, c gx.Color) { + // println('draw_triangle $x1,$y1 $x2,$y2 $x3,$y3') + ctx.shader.use() + ctx.shader.set_color('color', c) + vertices := [ + x1, y1, 0, + x2, y2, 0, + x3, y3, 0, + ] ! + // bind the Vertex Array Object first, then bind and set vertex buffer(s), + // and then configure vertex attributes(s). + gl.bind_vao(ctx.VAO) + gl.set_vbo(ctx.VBO, vertices, GL_STATIC_DRAW) + gl.vertex_attrib_pointer(0, 3, GL_FLOAT, false, 3, 0) + gl.enable_vertex_attrib_array(0) + // gl.bind_buffer(GL_ARRAY_BUFFER, uint(0)) + // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, + // but this rarely happens. Modifying other + // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs + // (nor VBOs) when it's not directly necessary. + // gl.bind_vertex_array(uint(0)) + // gl.bind_vertex_array(ctx.VAO) + gl.draw_arrays(GL_TRIANGLES, 0, 3) +} + +pub fn (ctx &GG) draw_triangle_tex(x1, y1, x2, y2, x3, y3 float, c gx.Color) { + ctx.shader.use() + ctx.shader.set_color('color', c) + ctx.shader.set_int('has_texture', 1) + vertices := [ + x1, y1, 0, 0, 0, 0, 1, 1, + x2, y2, 0, 0, 0, 0, 1, 0, + x3, y3, 0, 0, 0, 0, 0, 0, + ] ! + gl.bind_vao(ctx.VAO) + gl.set_vbo(ctx.VBO, vertices, GL_STATIC_DRAW) + // position attribute + gl.vertex_attrib_pointer(0, 3, GL_FLOAT, false, 3, 0) + gl.enable_vertex_attrib_array(0) + // color attribute + gl.vertex_attrib_pointer(1, 3, GL_FLOAT, false, 8, 3) + gl.enable_vertex_attrib_array(1) + // texture attribute + gl.vertex_attrib_pointer(2, 2, GL_FLOAT, false, 8, 6) + gl.enable_vertex_attrib_array(2) + // / + // gl.draw_arrays(GL_TRIANGLES, 0, 3) + gl.draw_elements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0) +} + +fn (ctx &GG) draw_rect(x, y, w, h float, c gx.Color) { + // println('gg.draw_rect($x,$y,$w,$h)') + // wrong order + // // ctx.draw_triangle(x, y, x + w, y, x + w, y + h, c) + // // ctx.draw_triangle(x, y, x, y + h, x + w, y + h, c) + // good order. counter clock wise + // ctx.draw_triangle(x, y, x, y + h, x + w, y + h, c) + // ctx.draw_triangle(x, y, x + w, y + h, x + w, y, c) + ctx.draw_rect2(x, y, w, h, c) +} + +/* +fn (ctx mut GG) init_rect_vao() { + + ctx.rect_vao = gl.gen_vertex_array() + ctx.rect_vbo = gl.gen_buffer() + vertices := [ + x + w, y, 0, + x + w, y + h, 0, + x, y + h, 0, + x, y, 0, + ] ! + indices := [ + 0, 1, 3,// first triangle + 1, 2, 3// second triangle + ] ! + gl.bind_vao(ctx.rect_vao) + gl.set_vbo(ctx.rect_vbo, vertices, GL_STATIC_DRAW) + ebo := gl.gen_buffer() + // /////// + gl.set_ebo(ebo, indices, GL_STATIC_DRAW) +} +*/ +fn (ctx &GG) draw_rect2(x, y, w, h float, c gx.Color) { + C.glDeleteBuffers(1, &ctx.VAO) + C.glDeleteBuffers(1, &ctx.VBO) + ctx.shader.use() + ctx.shader.set_color('color', c) + ctx.shader.set_int('has_texture', 0) + // 4--1 + // 3--2 +#ifdef linux + // y += h +#endif + vertices := [ + x + w, y, 0, + x + w, y + h, 0, + x, y + h, 0, + x, y, 0, + ] ! + indices := [ + 0, 1, 3,// first triangle + 1, 2, 3// second triangle + ] ! + gl.bind_vao(ctx.VAO) + gl.set_vbo(ctx.VBO, vertices, GL_STATIC_DRAW) + ebo := gl.gen_buffer() + // /////// + gl.set_ebo(ebo, indices, GL_STATIC_DRAW)// !!! LEAKS + // ///// + gl.vertex_attrib_pointer(0, 3, GL_FLOAT, false, 3, 0) + gl.enable_vertex_attrib_array(0) + // gl.bind_vao(ctx.rect_vao) + gl.bind_vao(ctx.VAO) + gl.draw_elements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0) + C.glDeleteBuffers(1, &ebo) +} + +// jfn ft_load_char(face FT_Face, code FT_ULong) Character { +// fn ft_load_char(_face voidptr, _code voidptr) Character { +fn ft_load_char(_face Face, code long) Character { + // #FT_Face face = *(FT_Face*)(_face); FT_ULong code = *(FT_ULong*)(code); + # FT_Face face = *((FT_Face*)_face.cobj); + # if (FT_Load_Char(face, code, FT_LOAD_RENDER)) + { + os.exit('ERROR::FREETYTPE: Failed to load Glyph') + } + // Generate texture + # GLuint texture; + # glGenTextures(1, &texture); + # glBindTexture(GL_TEXTURE_2D, texture); + # glTexImage2D( + # GL_TEXTURE_2D, + # 0, + # GL_RED, + # face->glyph->bitmap.width, + # face->glyph->bitmap.rows, + # 0, + # GL_RED, + # GL_UNSIGNED_BYTE, + # face->glyph->bitmap.buffer + # ); + // Set texture options + # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); + # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); + # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + # glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + // Now store character for later use + ch := Character{} + # ch.texture_id=texture ; + # ch.size = gg__vec2(face->glyph->bitmap.width, face->glyph->bitmap.rows); + # ch.bearing = gg__vec2(face->glyph->bitmap_left, face->glyph->bitmap_top), + # ch.advance = face->glyph->advance.x; + return ch +} + +fn new_context_text(cfg Cfg, scale int) *GG { + // Can only have text in ortho mode + if !cfg.use_ortho { + return &GG{text_ctx: 0} + } + mut width := cfg.width * scale + mut height := cfg.height * scale + font_size := cfg.font_size * scale + // exit('fs=$font_size') + // if false { + // retina + // width = width * 2// scale// 2 + // height = height * 2// scale// 2 + // font_size *= scale// 2 + // } + /* + gl.viewport(0, 0, width, height) +*/ + // gl.enable(GL_CULL_FACE) // TODO NEED CULL? MEANS SHIT IS BROKEN? + gl.enable(GL_BLEND) + // return &GG{} + # glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + shader := gl.new_shader('text') + shader.use() + projection := glm.ortho(0, width, 0, height)// 0 at BOT + // projection_new := ortho(0, width, 0, height)// 0 at BOT + // projection := gl.ortho(0, width,height,0) // 0 at TOP + shader.set_mat4('projection', projection) + // FREETYPE + # FT_Library ft; + // All functions return a value different than 0 whenever an error occurred + # if (FT_Init_FreeType(&ft)) + println('ERROR::FREETYPE: Could not init FreeType Library') + // Load font as face + // face := FT_Face{} + mut font_path := 'RobotoMono-Regular.ttf' + if !os.file_exists(font_path) { + font_path = '/var/tmp/RobotoMono-Regular.ttf' + } + if !os.file_exists(font_path) { + println2('failed to load RobotoMono-Regular.ttf') + exit('') + } + # FT_Face face; + # if (FT_New_Face(ft, font_path.str, 0, &face)) + // # if (FT_New_Face(ft, "/Library/Fonts/Courier New.ttf", 0, &face)) + // # if (FT_New_Face(ft, "/System/Library/Fonts/Apple Color Emoji.ttc", 0, &face)) + { + exit('ERROR::FREETYPE: Failed to load font') + } + // Set size to load glyphs as + # FT_Set_Pixel_Sizes(face, 0, font_size) ; + // Disable byte-alignment restriction + # glPixelStorei(GL_UNPACK_ALIGNMENT, 1); + // Gen texture + // Load first 128 characters of ASCII set + mut chars := []gg.Character{} + f := Face { + cobj: 0 + kek: 0 + } + # f.cobj = &face; + // # for (GLubyte c = 0; c < 128; c++) + for c := 0; c < 128; c++ { + // ch := Character{} + // ch:=ft_load_char(face, c) + // # ch =gg__ft_load_char(&face, &c); + // //////////////////////////////// + mut ch := ft_load_char(f, long(c)) + // s := utf32_to_str(uint(0x043f)) + // s := 'п' + // ch = ft_load_char(f, s.utf32_code()) + // # ch = gg__ft_load_char(f, 0x043f); // RUS P + // # unsigned long c = FT_Get_Char_Index(face, 0x043f ); + // # printf("!!!!!!!!! %lu\n", c); + // # c = FT_Get_Char_Index(face, 0xd0bf ); + // # printf("!!!!!!!!! %lu\n", c); + // # ch = gg__ft_load_char(f, 0xd0bf) ; // UTF 8 + chars << ch + } + ch := Character{} + // # ch = gg__ft_load_char(f, 0x0000043f); + // # ch = gg__ft_load_char(f, 128169); + // chars.push(ch) + // Configure VAO + VAO := gl.gen_vertex_array() + println('new gg text context VAO=$VAO') + VBO := gl.gen_buffer() + gl.bind_vao(VAO) + gl.bind_buffer(GL_ARRAY_BUFFER, VBO) + // # glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * 6 * 4, NULL, GL_DYNAMIC_DRAW); + gl.enable_vertex_attrib_array(0) + gl.vertex_attrib_pointer(0, 4, GL_FLOAT, false, 4, 0) + // # glVertexAttribPointer(0, 4, GL_FLOAT,false, 4 * sizeof(GLfloat), 0); + // gl.bind_buffer(GL_ARRAY_BUFFER, uint(0)) + // # glBindVertexArray(0); + mut ctx := &GG { + shader: shader, + width: width, + height: height, + scale: scale + VAO: VAO, + VBO: VBO, + chars: chars, + face: f + text_ctx: 0 + } + ctx.init_utf8_runes() + return ctx +} + +// A dirty hack to implement rendering of cyrillic letters. +// All UTF-8 must be supported. +fn (ctx mut GG) init_utf8_runes() { + s := '≈йцукенгшщзхъфывапролджэячсмитьбюЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЯЧСМИТЬБЮ' + println(s) + us := s.ustring() + for i := 0; i < us.len; i++ { + _rune := us.at(i) + ch := ft_load_char(ctx.face, _rune.utf32_code()) + // ctx.utf_rune_map.set(rune, ch) + ctx.utf_runes << _rune + ctx.utf_chars << ch + } +} + +// fn (ctx &GG) render_text(text string, x, y, scale float, color gx.Color) { +pub fn (ctx &GG) draw_text(_x, _y int, text string, cfg gx.TextCfg) { + // dont draw non ascii for now + /* + for i := 0; i < text.len; i++ { + c := text[i] + if int(c) > 128 { + // ctx.text_ctx._draw_text(_x, _y, '[NON ASCII]', cfg) + // return + } + } +*/ + // # glScissor(0,0,300,300); + utext := text.ustring_tmp() + // utext := text.ustring() + ctx.text_ctx._draw_text(_x, _y, utext, cfg) + // utext.free() + // # glScissor(0,0,ctx->width*2,ctx->height*2); + // gl.disable(GL_SCISSOR_TEST)// TODO + // #free(text.str); +} + +fn (ctx &GG) draw_text_fast(_x, _y int, text ustring, cfg gx.TextCfg) { + ctx.text_ctx._draw_text(_x, _y, text, cfg) +} + +// TODO HACK with second text context +// fn (ctx &GG) _draw_text(_x, _y int, text string, cfg gx.TextCfg) { +fn (ctx &GG) _draw_text(_x, _y int, utext ustring, cfg gx.TextCfg) { + /* + if utext.s.contains('on_seg') { + println('\nat(0)') + println(utext.runes) + firstc := utext.at(0) + println('drawtext "$utext.s" len=$utext.s.len ulen=$utext.len x=$_x firstc=$firstc') + if firstc != ' ' { + exit('') + } + } +*/ + // println('scale=$ctx.scale size=$cfg.size') + if cfg.align == gx.ALIGN_RIGHT { + width := utext.len * 7 + _x -= width + 10 + } + x := float(_x) * ctx.scale// float(2) + // println('y=$_y height=$ctx.height') + // _y = _y * int(ctx.scale) //+ 26 + _y = _y * int(ctx.scale) + ((cfg.size * ctx.scale) / 2) + 5 * ctx.scale + y := float(ctx.height - _y) + color := cfg.color + // Activate corresponding render state + ctx.shader.use() + ctx.shader.set_color('textColor', color) + # glActiveTexture(GL_TEXTURE0); + gl.bind_vao(ctx.VAO) + // Iterate through all characters + // utext := text.ustring() + for i := 0; i < utext.len; i++ { + _rune := utext.at(i) + // println('$i => $_rune') + mut ch := Character{} + if _rune.len == 1 { + idx := _rune[0] + if idx < 0 || idx >= ctx.chars.len { + println('BADE RUNE $_rune') + continue + } + ch = ctx.chars[_rune[0]] + } + else if _rune.len > 1 { + // TODO O(1) use map + for j := 0; j < ctx.utf_runes.len; j++ { + rune_j := ctx.utf_runes[j] + // if string_eq(ctx.utf_runes[j], rune) { + if rune_j.eq(_rune) { + ch = ctx.utf_chars[j] + break + } + } + } + if ch.size.x == 0 { + // continue + } + // mut c := int(text[i]) + // c = 128 + // s := 'A' + // c := int(s[0]) + // ch := ctx.chars[c] + xpos := x + float(ch.bearing.x) * 1 + ypos := y - float(ch.size.y - ch.bearing.y) * 1 + w := float(ch.size.x) * 1 + h := float(ch.size.y) * 1 + // Update VBO for each character + # GLfloat vertices[6][4] = { + # { xpos, ypos + h, 0.0, 0.0 }, + # { xpos, ypos, 0.0, 1.0 }, + # { xpos + w, ypos, 1.0, 1.0 }, + # { xpos, ypos + h, 0.0, 0.0 }, + # { xpos + w, ypos, 1.0, 1.0 }, + # { xpos + w, ypos + h, 1.0, 0.0 } + # }; + // t := glfw.get_time() + // Render glyph texture over quad + // t1 := glfw.get_time() + # glBindTexture(GL_TEXTURE_2D, ch.texture_id); + // Update content of VBO memory + gl.bind_buffer(GL_ARRAY_BUFFER, ctx.VBO) + // t2 := glfw.get_time() + // # glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(vertices), vertices); // Be sure to use glBufferSubData and not glBufferData + # glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW); + // t3 := glfw.get_time() + // gl.bind_buffer(GL_ARRAY_BUFFER, uint(0)) + // t4 := glfw.get_time() + // Render quad + gl.draw_arrays(GL_TRIANGLES, 0, 6) + // t5 := glfw.get_time() + // # if (glfw__get_time() - t > 0.001) + // { + // # printf("do_text = %f '%s' \n", glfw__get_time() - t, text.str); + // # printf("t1=%f, t2=%f, t3=%f, t4=%f, t5=%f\n\n\n", t1-t, t2-t1, t3-t2, t4-t3, t5-t4); + // } + // Now advance cursors for next glyph (note that advance is number of 1/64 pixels) + // Bitshift by 6 to get value in pixels (2^6 = 64 (divide amount of 1/64th pixels by 64 to get amount of pixels)) + # x += (ch.advance >> 6) * 1; + } + gl.bind_vao(u32(0)) + # glBindTexture(GL_TEXTURE_2D, 0); + // runes.free() + // #free(runes.data); +} + +fn (ctx &GG) draw_text_def(x, y int, text string) { + cfg := gx.TextCfg { + color: gx.BLACK, + size: DEFAULT_FONT_SIZE, + align: gx.ALIGN_LEFT, + } + ctx.draw_text(x, y, text, cfg) +} + +fn update() { + // # ui__post_empty_event(); +} + +pub fn (c GG) circle(x, y, r int) { +} + +fn (c GG) fill_color(color gx.Color) { +} + +fn (c GG) fill() { +} + +fn (c GG) move_to(x, y int) { +} + +fn (c GG) line_to(x, y int) { +} + +fn (c GG) stroke_width(size int) { +} + +fn (c GG) stroke_color(color gx.Color) { +} + +fn (c GG) stroke() { +} + +fn (c GG) save() { +} + +fn (c GG) restore() { +} + +fn (c GG) intersect_scissor(x, y, w, h int) { +} + +fn (c GG) translate(x, y int) { +} + +fn (c GG) create_font(name, file string) int { + return 0 +} + +fn (c GG) text(x, y int, text string) { +} + +fn (c GG) text_box(x, y, max int, text string) { +} + +fn (c GG) font_face(f string) { +} + +fn (c GG) font_size(size int) { +} + +fn (c GG) text_align(a int) { +} + +pub fn create_image(file string) u32 { + println('gg create image "$file"') + if file.contains('twitch') { + return u32(0)// TODO + } + if !os.file_exists(file) { + println('gg create image no such file "$file"') + return u32(0) + } + texture := gl.gen_texture() + img := stbi.load(file) + gl.bind_2d_texture(texture) + img.tex_image_2d() + gl.generate_mipmap(GL_TEXTURE_2D) + img.free() + // println('gg end') + return texture +} + +pub fn (ctx &GG) draw_line_c(x, y, x2, y2 int, color gx.Color) { + C.glDeleteBuffers(1, &ctx.VAO) + C.glDeleteBuffers(1, &ctx.VBO) + ctx.shader.use() + ctx.shader.set_color('color', color) + vertices := [float(x), float(y), float(x2), float(y2)] ! + gl.bind_vao(ctx.VAO) + gl.set_vbo(ctx.VBO, vertices, GL_STATIC_DRAW) + gl.vertex_attrib_pointer(0, 2, GL_FLOAT, false, 2, 0) + gl.enable_vertex_attrib_array(0) + gl.bind_vao(ctx.VAO) + gl.draw_arrays(GL_LINES, 0, 2) +} + +pub fn (c &GG) draw_line(x, y, x2, y2 int) { + c.draw_line_c(x, y, x2, y2, gx.GRAY) +} + +pub fn (c &GG) draw_vertical(x, y, height int) { + c.draw_line(x, y, x, y + height) +} + +// fn (ctx &GG) draw_image(x, y, w, h float, img stbi.Image) { +pub fn (ctx &GG) draw_image(x, y, w, h float, tex_id u32) { + // println('DRAW IMAGE $x $y $w $h $tex_id') + ctx.shader.use() + // ctx.shader.set_color('color', c) + ctx.shader.set_int('has_texture', 1) + // 4--1 + // | | + // 3--2 + vertices := [ + x + w, y, 0, 1, 0, 0, 1, 1, + x + w, y + h, 0, 0, 1, 0, 1, 0, + x, y + h, 0, 0, 0, 1, 0, 0, + x, y, 0, 1, 1, 0, 0, 1, + ] ! + indices := [ + 0, 1, 3,// first triangle + 1, 2, 3// second triangle + ] ! + // VAO := gl.gen_vertex_array() + // VBO := gl.gen_buffer() + gl.bind_vao(ctx.VAO) + gl.set_vbo(ctx.VBO, vertices, GL_STATIC_DRAW) + ebo := gl.gen_buffer() + gl.set_ebo(ebo, indices, GL_STATIC_DRAW) + gl.vertex_attrib_pointer(0, 3, GL_FLOAT, false, 8, 0) + gl.enable_vertex_attrib_array(0) + gl.vertex_attrib_pointer(1, 3, GL_FLOAT, false, 8, 3) + gl.enable_vertex_attrib_array(1) + gl.vertex_attrib_pointer(2, 2, GL_FLOAT, false, 8, 6) + gl.enable_vertex_attrib_array(2) + gl.bind_2d_texture(u32(tex_id)) + gl.bind_vao(ctx.VAO) + gl.draw_elements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0) +} + +pub fn (c &GG) draw_empty_rect(x, y, w, h int, color gx.Color) { + c.draw_line_c(x, y, x + w, y, color) + c.draw_line_c(x, y, x, y + h, color) + c.draw_line_c(x, y + h, x + w, y + h, color) + c.draw_line_c(x + w, y, x + w, y + h, color) +} + diff --git a/glfw/glfw.v b/glfw/glfw.v new file mode 100644 index 0000000000..d8bf32752d --- /dev/null +++ b/glfw/glfw.v @@ -0,0 +1,303 @@ +module glfw + +// Debugging a custom build +#flag darwin -L/var/tmp/glfw/src/ +#flag darwin -lglfw +#flag linux -lglfw +#flag windows -I/usr/local/Cellar/glfw/3.2.1/include/ +// #include // !gen.go include GLFW.v +#include +// #flag darwin -framework Carbon +// #flag darwin -framework Cocoa +// #flag darwin -framework CoreVideo +// #flag darwin -framework IOKit +// struct C.GL +// @GLFWwindow* C.glfwCreateWindow +// #int gconst_init = 0; +const ( + RESIZABLE = 1 + DECORATED = 2 +) + +import const ( + GLFW_RESIZABLE + GLFW_DECORATED +) + +import const ( + GLFW_KEY_ENTER + GLFW_KEY_A + GLFW_KEY_B + GLFW_KEY_P + GLFW_KEY_F + GLFW_KEY_M + GLFW_KEY_L + GLFW_KEY_V + GLFW_KEY_R + GLFW_KEY_D + GLFW_KEY_7 + GLFW_KEY_Z + GLFW_KEY_UP + GLFW_KEY_DOWN + GLFW_KEY_UP + GLFW_KEY_LEFT + GLFW_KEY_RIGHT + GLFW_KEY_BACKSPACE + GLFW_KEY_ENTER + GLFW_KEY_ESCAPE + GLFW_KEY_N + GLFW_KEY_PERIOD + GLFW_KEY_SLASH + GLFW_KEY_F5 + GLFW_KEY_F6 + GLFW_KEY_MINUS + GLFW_KEY_EQUAL + GLFW_KEY_C + GLFW_KEY_G + GLFW_KEY_I + GLFW_KEY_J + GLFW_KEY_E + GLFW_KEY_K + GLFW_KEY_O + GLFW_KEY_T + GLFW_KEY_H + GLFW_KEY_L + GLFW_KEY_N + GLFW_KEY_U + GLFW_KEY_X + GLFW_KEY_W + GLFW_KEY_Y + GLFW_KEY_Q + GLFW_KEY_RIGHT_BRACKET + GLFW_KEY_LEFT_BRACKET + GLFW_KEY_8 + GLFW_KEY_TAB + GLFW_KEY_COMMA + GLFW_KEY_QUESTION +) + +const ( + KEY_ESCAPE = 256 + KEY_LEFT_SUPER = 343 +) + +const ( + KeyUp = 265 + KeyLeft = 263 + KeyRight = 262 + KeyDown = 264 +) + +// TODO COPY PASTA +struct WinCfg { + width int + height int + title string + ptr voidptr + borderless bool + is_modal int + is_browser bool + url string +} + +// data *C.GLFWwindow +// TODO change data to cobj +struct Window { + data voidptr + title string + mx int + my int +} + +struct Size { +pub: + width int + height int +} + +struct Pos { + x int + y int +} + +// type clickfn fn (window * GLFWwindow, button, action, mods int) +type clickfn fn (window voidptr, button, action, mods int) + +fn init() { + C.glfwInit() + # glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3); + # glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3); + # glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); + # glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); +} + +// fn mouse_move(w * GLFWwindow, x, y double) { +fn mouse_move(w voidptr, x, y double) { + // #printf("%f : %f => %d \n", x,y); +} + +// fn create_window(title string, w, h int) * Window { +fn window_hint(key, val int) { + C.glfwWindowHint(key, val) +} + +fn create_window(c WinCfg) *Window { + // TODO why i need this in stdlib? extern? + // # if (!gconst_init) { init_consts(); gconst_init = 1; } + // ChatsRepo + if c.borderless { + window_hint(GLFW_RESIZABLE, 0) + window_hint(GLFW_DECORATED, 0) + } + cwindow := C.glfwCreateWindow(c.width, c.height, c.title.str, 0, 0) + # if (!cwindow) + // if cwindow == 0 + { + println('failed to credate glfw window') + C.glfwTerminate() + } + // # glfwSetCursorPosCallback(cwindow, glfw__mouse_move) ; + // C.glfwSetCursorPosCallback(cwindow, mouse_move) + C.printf('create window wnd=%p ptr==%p\n', cwindow, c.ptr) + C.glfwSetWindowUserPointer(cwindow, c.ptr) + // # void *a =glfwGetWindowUserPointer(cwindow); + // # printf("aaaaaa=%p d=%d\n", a,a); + window := &Window { + data: cwindow, + title: c.title, + } + // user_ptr: ptr, + // repo: repo, + // for !C.glfwWindowShouldClose(cwindow) { + // C.glfwPollEvents() + // wait_events() + // } + // C.glfwTerminate() + return window +} + +fn (w &Window) set_title(title string) { + C.glfwSetWindowTitle(w.data, title.str) +} + +fn (w &Window) make_context_current() { + // ChatsRepo + kkk := 0 + // println('making context current' ) + C.glfwMakeContextCurrent(w.data) +} + +fn swap_interval(interval int) { + C.glfwSwapInterval(interval) +} + +fn wait_events() { + C.glfwWaitEvents() +} + +fn poll_events() { + C.glfwPollEvents() +} + +fn (w &Window) should_close() bool { + // ChatsRepo + return C.glfwWindowShouldClose(w.data) +} + +fn (w &Window) swap_buffers() { + C.glfwSwapBuffers(w.data) +} + +fn (w mut Window) onmousemove(cb voidptr) { + C.glfwSetCursorPosCallback(w.data, cb) +} + +fn (w mut Window) set_mouse_button_callback(cb voidptr) { + C.glfwSetMouseButtonCallback(w.data, cb) +} + +fn (w mut Window) on_click(cb voidptr) { + C.glfwSetMouseButtonCallback(w.data, cb) +} + +fn (w &Window) set_scroll_callback(cb voidptr) { + C.glfwSetScrollCallback(w.data, cb) +} + +fn (w &Window) on_scroll(cb voidptr) { + C.glfwSetScrollCallback(w.data, cb) +} + +fn post_empty_event() { + C.glfwPostEmptyEvent() +} + +fn (w mut Window) onkeydown(cb voidptr) { + C.glfwSetKeyCallback(w.data, cb) +} + +fn (w mut Window) onchar(cb voidptr) { + C.glfwSetCharModsCallback(w.data, cb) +} + +fn get_time() double { + return C.glfwGetTime() +} + +fn key_pressed(wnd voidptr, key int) bool { + # return glfwGetKey(wnd, key) == GLFW_PRESS; + return false +} + +// TODO not mut +fn (w mut Window) get_clipboard_text() string { + return tos2(C.glfwGetClipboardString(w.data)) + // # char *c = glfwGetClipboardString(w->data); + // # return tos_no_len(c); + // return '' +} + +fn (w &Window) set_clipboard_text(s string) { + C.glfwSetClipboardString(w.data, s.str) +} + +fn (w &Window) get_cursor_pos() Pos { + x := double(0) + y := double(0) + C.glfwGetCursorPos(w.data, &x, &y) + return Pos { + x: int(x) + y: int(y) + } +} + +fn (w &Window) user_ptr() voidptr { + return C.glfwGetWindowUserPointer(w.data) +} + +fn (w &Window) set_user_ptr(ptr voidptr) { + C.glfwSetWindowUserPointer(w.data, ptr) +} + +fn C.glfwGetVideoMode() C.GLFWvideoMode + +fn get_monitor_size() Size { + # GLFWvidmode* mode = glfwGetVideoMode(glfwGetPrimaryMonitor()); + // window_width = mode->width; + // window_height = mode->height; + // monitor := C.glfwGetPrimaryMonitor() + res := Size{} + # res.width=mode->width; + # res.height=mode->height; + // C.glfwGetMonitorPhysicalSize(monitor, &res.width, &res.height) + return res +} + +fn (size Size) str() string { + return '{$size.width, $size.height}' +} + +fn get_window_user_pointer(gwnd voidptr) voidptr { + return C.glfwGetWindowUserPointer(gwnd) +} + diff --git a/glm/glm.v b/glm/glm.v new file mode 100644 index 0000000000..4560829c4e --- /dev/null +++ b/glm/glm.v @@ -0,0 +1,314 @@ +module glm + +import math + +/* +#flag -lmyglm +# float* myglm_ortho(float, float, float, float); +# float* myglm_translate(float, float, float); +*/ +// # float* myglm_rotate(float *m, float angle, float, float, float); +// # float* myglm_perspective(float, float, float, float); +// # float* myglm_look_at(glm__Vec3, glm__Vec3, glm__Vec3); +// # glm__Vec3 myglm_mult(glm__Vec3, glm__Vec3); +// # glm__Vec3 myglm_cross(glm__Vec3, glm__Vec3); +// # glm__Vec3 myglm_normalize(glm__Vec3); +struct Mat4 { +pub: + data *float +} + +struct Vec2 { + x float + y float +} + +struct Vec3 { + x float + y float + z float +} + +fn vec3(x, y, z float) Vec3 { + res := Vec3 { + x: x, + y: y, + z: z, + } + return res +} + +fn mat4(f *float) Mat4 { + res := Mat4 { + data: f + } + return res +} + +fn (v Vec3) str() string { + return 'Vec3{ $v.x, $v.y, $v.z }' +} + +fn (v Vec2) str() string { + return 'Vec3{ $v.x, $v.y }' +} + +fn (m Mat4) str() string { + mut s := '[ ' + for i := 0; i < 4; i++ { + if i != 0 { + s += ' ' + } + for j := 0; j < 4; j++ { + val := m.data[i * 4 + j] + s += '${val:.2f} ' + } + if i != 3 { + s += '\n' + } + } + s += ']' + return s +} + +fn vec2(x, y int) Vec2 { + res := Vec2 { + x: x, + y: y, + } + return res +} + +fn (a Vec3) add(b Vec3) Vec3 { + res := Vec3 { + x: a.x + b.x, + y: a.y + b.y, + z: a.z + b.z, + } + return res +} + +fn (a Vec3) sub(b Vec3) Vec3 { + res := Vec3 { + x: a.x - b.x, + y: a.y - b.y, + z: a.z - b.z, + } + return res +} + +// fn (a Vec3) mult(b Vec3) Vec3 { +// # return myglm_mult(a,b); +// } +fn (a Vec3) mult_scalar(b float) Vec3 { + res := Vec3 { + x: a.x * b, + y: a.y * b, + z: a.z * b, + } + return res +} + +fn (a Vec3) print() { + x := a.x + y := a.y + z := a.z + # printf("vec3{%f,%f,%f}\n",x,y,z); + // println('vec3{$x,$y,$z}') +} + +/* +fn rotate(m Mat4, angle float, vec Vec3) Mat4 { + // # t_mat4 m; + // println('rotate done') + # return glm__mat4( myglm_rotate(m.data, angle, vec.x,vec.y,vec.z) ); + return Mat4{} +} +*/ +// fn translate(vec Vec3) *float { +fn translate(m Mat4, v Vec3) Mat4 { + // # return glm__mat4(myglm_translate(vec.x,vec.y,vec.z) ); + a := m.data + mut out := float_calloc(16) + x := v.x + y := v.y + z := v.z + a00 := a[0]a01 := a[1]a02 := a[2]a03 := a[3] + a10 := a[4]a11 := a[5]a12 := a[6]a13 := a[7] + a20 := a[8]a21 := a[9]a22 := a[10]a23 := a[11] + out[0] = a00 out[1] = a01 out[2] = a02 out[3] = a03 + out[4] = a10 out[5] = a11 out[6] = a12 out[7] = a13 + out[8] = a20 out[9] = a21 out[10] = a22 out[11] = a23 + out[12] = a00 * x + a10 * y + a20 * z + a[12] + out[13] = a01 * x + a11 * y + a21 * z + a[13] + out[14] = a02 * x + a12 * y + a22 * z + a[14] + out[15] = a03 * x + a13 * y + a23 * z + a[15] + return mat4(out) +} + +/* +fn normalize(vec Vec3) Vec3 { + # return myglm_normalize(vec); + return Vec3{} +} +*/ +// https://github.com/g-truc/glm/blob/0ceb2b755fb155d593854aefe3e45d416ce153a4/glm/ext/matrix_clip_space.inl +fn ortho(left, right, bottom, top float) Mat4 { + println('glm ortho($left, $right, $bottom, $top)') + // mat<4, 4, T, defaultp> Result(static_cast(1)); + n := 16 + mut res := float_calloc(n) + # res[0] = 2 / (right - left) ; + # res[5] = 2.0 / (top - bottom); + # res[10] = (1); + # res[12] = - (right + left) / (right - left); + # res[13] = - (top + bottom) / (top - bottom); + res[15] = 1 + return mat4(res) +} + +// fn scale(a *float, v Vec3) *float { +fn scale(m Mat4, v Vec3) Mat4 { + a := m.data + mut out := float_calloc(16) + x := v.x + y := v.y + z := v.z + out[0] = a[0] * v.x + out[1] = a[1] * x + out[2] = a[2] * x + out[3] = a[3] * x + out[4] = a[4] * y + out[5] = a[5] * y + out[6] = a[6] * y + out[7] = a[7] * y + out[8] = a[8] * z + out[9] = a[9] * z + out[10] = a[10] * z + out[11] = a[11] * z + out[12] = a[12] + out[13] = a[13] + out[14] = a[14] + out[15] = a[15] + return mat4(out) +} + +// fn rotate_z(a *float, rad float) *float { +fn rotate_z(m Mat4, rad float) Mat4 { + a := m.data + mut out := float_calloc(16) + s := math.sin(rad) + c := math.cos(rad) + a00 := a[0] + a01 := a[1] + a02 := a[2] + a03 := a[3] + a10 := a[4] + a11 := a[5] + a12 := a[6] + a13 := a[7] + out[8] = a[8] + out[9] = a[9] + out[10] = a[10] + out[11] = a[11] + out[12] = a[12] + out[13] = a[13] + out[14] = a[14] + out[15] = a[15] + // Perform axis-specific matrix multiplication + out[0] = a00 * c + a10 * s + out[1] = a01 * c + a11 * s + out[2] = a02 * c + a12 * s + out[3] = a03 * c + a13 * s + out[4] = a10 * c - a00 * s + out[5] = a11 * c - a01 * s + out[6] = a12 * c - a02 * s + out[7] = a13 * c - a03 * s + return mat4(out) +} + +fn identity() Mat4 { + // 1 0 0 0 + // 0 1 0 0 + // 0 0 1 0 + // 0 0 0 1 + n := 16 + mut res := float_calloc(sizeof(float) * n) + res[0] = 1 + res[5] = 1 + res[10] = 1 + res[15] = 1 + return mat4(res) +} + +// returns *float without allocation +fn identity2(res *float) { + res[0] = 1 + res[5] = 1 + res[10] = 1 + res[15] = 1 + // # float f[16]={0};// for (int i =0;i<16;i++) + // # printf("!!%d\n", f[0]); + // # glm__identity2(&f); + // # gl__Shader_set_mat4(shader, tos2("projection"), f) ; +} + +fn identity3() []float { + res := [1.0, 0, 0, 0, + 0, 1, 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1, + ] ! + return res +} + +// https://github.com/toji/gl-matrix/blob/1549cf21dfa14a2bc845993485343d519cf064fe/src/gl-matrix/mat4.js +fn ortho_js(left, right, bottom, top float) *float { + mynear := 1 + myfar := 1 + lr := 1.0 / (left - right) + bt := 1.0 / (bottom - top) + nf := 1.0 / 1.0// (mynear -myfar) + # float* out = malloc (sizeof(float) * 16); + # out[0] = -2 * lr; + # out[1] = 0; + # out[2] = 0; + # out[3] = 0; + # out[4] = 0; + # out[5] = -2 * bt; + # out[6] = 0; + # out[7] = 0; + # out[8] = 0; + # out[9] = 0; + # out[10] = 2 * nf; + # out[11] = 0; + # out[12] = (left + right) * lr; + # out[13] = (top + bottom) * bt; + # out[14] = 1 * nf;//(far + near) * nf; + # out[15] = 1; + # return out; + f := 0.0 + return &f +} + +// fn ortho_old(a, b, c, d float) *float { +// # return myglm_ortho(a,b,c,d); +// } +fn cross(a, b Vec3) Vec3 { + // # return myglm_cross(a,b); + return Vec3{} +} + +/* +fn perspective(degrees float, ratio float, a, b float) Mat4 { + // println('lang per degrees=$degrees ratio=$ratio a=$a b=$b') + // # printf("lang pers degrees=%f ratio=%f a=%f b=%f\n", degrees, ratio, a,b); + # return glm__mat4( myglm_perspective(degrees, ratio, a,b) ) ; + return Mat4{} +} + +fn look_at(eye, center, up Vec3) Mat4 { + # return glm__mat4( myglm_look_at(eye, center, up) ) ; + return Mat4{} +} +*/ diff --git a/gx/gx.v b/gx/gx.v new file mode 100644 index 0000000000..98e60580ab --- /dev/null +++ b/gx/gx.v @@ -0,0 +1,77 @@ +module gx + +struct Color { +pub: + r int + g int + b int +} + +const ( + BLUE = Color { r: 60, g: 126, b: 197 } + Blue = Color { r: 60, g: 126, b: 197 } + BlueLite = Color { r: 226, g: 233, b: 241 } + BLACK = Color { r: 0, g: 0, b: 0 } + WHITE = Color { r: 255, g: 255, b: 255 } + GRAY = Color { r: 223, g: 223, b: 223 } + GRAY_DARK = Color { r: 150, g: 150, b: 150 } + GRAY_LITE = Color { r: 245, g: 245, b: 245 } + BLUE_LITE = Color { r: 226, g: 233, b: 241 } + ORANGE = Color { r: 255, g: 140, b: 0 } + GREEN = Color { r: 0, g: 140, b: 0 } + RED = Color { r: 140, g: 0, b: 0 } + YELLOW = Color { r: 255, g: 255, b: 0 } +) + +const ( + ALIGN_LEFT = 1 + ALIGN_RIGHT = 4 +) + +struct TextCfg { +pub: + color Color + size int + align int + max_width int + family string + bold bool + mono bool +} + +struct Image { +mut: + obj voidptr +pub: + id int + width int + height int +} + +pub fn (img Image) is_empty() bool { + return isnil(img.obj) +} + +pub fn (c Color) str() string { + return '{$c.r, $c.g, $c.b}' +} + +pub fn (a Color) eq(b Color) bool { + return a.r == b.r && + a.g == b.g && + a.b == b.b +} + +pub fn rgb(r, g, b int) Color { + res := Color { + r: r, + g: g, + b: b, + } + return res +} + +// fn text_width_char(c char) int { +// return text_width(char2string(c)) +// // return C.text_width_char(c) +// } diff --git a/http/download_lin.v b/http/download_lin.v new file mode 100644 index 0000000000..2422214b3d --- /dev/null +++ b/http/download_lin.v @@ -0,0 +1,59 @@ +module http + +import os + +@size_t kek +type downloadfn fn (written int) + +struct DownloadStruct { + stream voidptr + written int + cb downloadfn +} + +fn download_cb(ptr voidptr, size, nmemb size_t, userp voidptr) int { + // # struct http__MemoryStruct *mem = (struct http__MemoryStruct *)userp; + data := &DownloadStruct(userp) + # size_t written = fwrite(ptr, size, nmemb, (FILE*)(data->stream)); + // # printf("!!!%d\n", written); + # data->written += written; + if !isnil(data.cb) { + # data->cb(data->written); + } + # return written; + return 0 +} + +fn download_file_with_progress(url, out string, cb, cb_finished voidptr) { + curl := C.curl_easy_init() + if isnil(curl) { + return + } + # FILE* fp = fopen(out.str,"wb"); + # curl_easy_setopt(curl, CURLOPT_URL, url.str); + C.curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_cb) + // # curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http__download_cb); + data := &DownloadStruct { + // stream:fp + cb: cb + } + # data->stream = fp; + # curl_easy_setopt(curl, CURLOPT_WRITEDATA, data); + # double d = 0; + # curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); + # CURLcode res = curl_easy_perform(curl); + println('DONE!') + # curl_easy_cleanup(curl); + # fclose(fp); + # void (*finished)() =cb_finished; finished(); +} + +fn download_file(url, out string) { + // println('\nDOWNLOAD FILE $out url=$url') + // -L follow redirects + // println('curl -L -o "$out" "$url"') + res := os.system('curl -s -L -o "$out" "$url"') + // res := os.system('curl -s -L -o "$out" "$url"') + // println(res) +} + diff --git a/http/download_mac.v b/http/download_mac.v new file mode 100644 index 0000000000..1df58873a1 --- /dev/null +++ b/http/download_mac.v @@ -0,0 +1,64 @@ +module http + +import os + +struct LUEL { + age int +} + +type downloadfn fn (written int) + +struct DownloadStruct { + stream voidptr + written int + cb downloadfn +} + +fn download_cb(ptr voidptr, size, nmemb size_t, userp voidptr) int { + // # struct http__MemoryStruct *mem = (struct http__MemoryStruct *)userp; + data := &DownloadStruct(userp) + # size_t written = fwrite(ptr, size, nmemb, (FILE*)(data->stream)); + // # printf("!!!%d\n", written); + # data->written += written; + if !isnil(data.cb) { + # data->cb(data->written); + } + # return written; + return 0 +} + +fn download_file_with_progress(url, out string, cb, cb_finished voidptr) { + /* + curl := C.curl_easy_init() + if isnil(curl) { + return + } + # FILE* fp = fopen(out.str,"wb"); + # curl_easy_setopt(curl, CURLOPT_URL, url.str); + C.curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, download_cb) + // # curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http__download_cb); + data := &DownloadStruct { + // stream:fp + cb: cb + } + # data->stream = fp; + # curl_easy_setopt(curl, CURLOPT_WRITEDATA, data); + # double d = 0; + # curl_easy_getinfo(curl, CURLINFO_CONTENT_LENGTH_DOWNLOAD, &d); + # CURLcode res = curl_easy_perform(curl); + println('DONE!') + # curl_easy_cleanup(curl); + # fclose(fp); + # void (*finished)() =cb_finished; finished(); +*/ +} + +fn download_file(url, out string) { + // println('\nDOWNLOAD FILE $out url=$url') + // -L follow redirects + // println('curl -L -o "$out" "$url"') + os.system2('curl -s -L -o "$out" "$url"') + // res := os.system('curl -s -L -o "$out" "$url"') + // println(res) +} + diff --git a/http/download_win.v b/http/download_win.v new file mode 100644 index 0000000000..4af2fcc6e6 --- /dev/null +++ b/http/download_win.v @@ -0,0 +1,18 @@ +module http + +fn download_file_with_progress(url, out string, cb, cb_finished voidptr) { +} + +fn download_file(url, out string) { + # HRESULT res = URLDownloadToFile(NULL, url.str, out.str, 0, NULL); + # if(res == S_OK) { + println('Download Ok') + # } else if(res == E_OUTOFMEMORY) { + println('Buffer length invalid, or insufficient memory') + # } else if(res == INET_E_DOWNLOAD_FAILURE) { + println('URL is invalid') + # } else { + # printf("Download error: %d\n", res); + # } +} + diff --git a/http/http.v b/http/http.v new file mode 100644 index 0000000000..8408a55e93 --- /dev/null +++ b/http/http.v @@ -0,0 +1,94 @@ +module http + +struct Request { +pub: + // headers []string + headers map_string + method string + // cookies map[string]string + h string + cmd string + typ string // GET POST + data string + url string + ws_func voidptr + user_ptr voidptr + verbose bool +} + +struct Response { +pub: + body string + headers map_string + status_code int +} + +// embed 'http' +fn get(url string) string { + if url == '' { + println2('http: empty get url') + return '' + } + mut req := new_request('GET', url, '') + resp := req.do() + return resp.body +} + +fn get2(url string) string { + return '' +} + +fn post(url, data string) string { + req := new_request('POST', url, data) + resp := req.do() + return resp.body +} + +fn new_request(typ, _url, _data string) *Request { + mut url := _url + mut data := _data + // req.headers['User-Agent'] = 'V $VERSION' + if typ == 'GET' && !url.contains('?') && data != '' { + println('zeroing data, to url') + url = '$url?$data' + data = '' + } + // req.headers = new_map(0, sizeof(string))// []string{} + return &Request { + typ: typ + url: _url + data: _data + ws_func: 0 + user_ptr: 0 + headers: new_map(0, sizeof(string)) + } +} + +/* +fn (req &Request) do() Response { + mut resp := Response{} + return resp +} +*/ +fn (req mut Request) free() { + req.headers.free() +} + +fn (resp mut Response) free() { + resp.headers.free() +} + +fn (req mut Request) add_header(key, val string) { + // println('start add header') + // println('add header "$key" "$val"') + // println(key) + // println(val) + // h := '$key: $val' + // println('SET H') + // req.headers << h + req.headers[key] = val + // mut h := req.h + // h += ' -H "${key}: ${val}" ' + // req.h = h +} + diff --git a/http/http_mac.v b/http/http_mac.v new file mode 100644 index 0000000000..82956d40ee --- /dev/null +++ b/http/http_mac.v @@ -0,0 +1,202 @@ +module http + +#include +#flag windows -I/usr/local/opt/curl/include +#flag darwin -lcurl +#flag windows -lcurl +#flag linux -lcurl +@size_t kek +@CURL* curl_easy_init +type wsfn fn (s string, ptr voidptr) + +struct MemoryStruct { + size size_t + ws_func wsfn + user_ptr voidptr // for wsfn + strings []string +} + +import const ( + CURLOPT_WRITEFUNCTION + CURLOPT_SSL_VERIFYPEER + CURLOPT_HEADERFUNCTION + CURLOPT_WRITEDATA + CURLOPT_HEADERDATA + CURLOPT_FOLLOWLOCATION + CURLOPT_URL + CURLOPT_VERBOSE + CURLOPT_HTTP_VERSION + CURL_HTTP_VERSION_1_1 + CURLOPT_HTTPHEADER + CURLOPT_POSTFIELDS + CURLOPT_CUSTOMREQUEST + CURLOPT_TCP_KEEPALIVE + CURLE_OK +) + +// type C.CURLcode { +// } +fn C.curl_easy_strerror(curl voidptr) byteptr + +fn C.curl_easy_perform(curl voidptr) C.CURLcode + +fn write_fn(contents byteptr, size, nmemb int, _mem *MemoryStruct) int { + mut mem = _mem + // # printf("size =%d nmemb=%d contents=%s\n", size, nmemb, contents); + realsize := size * nmemb// TODO size_t ? + // if !isnil(mem.ws_func) { + # if (mem->ws_func) + { + C.printf('\n\nhttp_mac.m: GOT WS FUNC. size=%d\n', realsize) + // Skip negative and 0 junk chars in the WS string + mut start := 0 + for i := 0; i < realsize; i++ { + // printf("char=%d %c\n", s[i], s[i]); + if contents[i] == 0 && start == 0 { + start = i + break + } + } + contents += start + 1 + // printf("GOOD CONTEnTS=%s\n", contents); + s := tos_no_len(contents) + // mem.ws_func('kek', 0) + # mem->ws_func(s, mem->user_ptr); + } + mut c := tos_no_len(contents) + c = c.trim_space() + // Need to clone because libcurl reuses this memory + mem.strings << c.clone() + return realsize +} + +struct C.curl_slist { } + +fn (req &Request) do() Response { + println('req.do() mac/linux url="$req.url" data="$req.data"') + // println('req.do() url="$req.url"') + /* + mut resp := Response { + headers: map[string]string{} + } +*/ + mut headers := map[string]string{} + // no data at this point + chunk := MemoryStruct { + ws_func: req.ws_func + user_ptr: req.user_ptr + } + // header chunk + hchunk := MemoryStruct { + ws_func: 0 + user_ptr: 0 + } + // init curl + curl := C.curl_easy_init() + if isnil(curl) { + println2('curl init failed') + return Response{} + } + // options + // url2 := req.url.clone() + C.curl_easy_setopt(curl, CURLOPT_URL, req.url.cstr())// ..clone()) + // C.curl_easy_setopt(curl, CURLOPT_URL, 'http://example.com') + // return resp + // curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0); + $if windows { + C.curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0) + } + C.curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_fn) + C.curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, write_fn) + C.curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk) + C.curl_easy_setopt(curl, CURLOPT_HEADERDATA, &hchunk) + C.curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1) + if req.typ == 'POST' { + C.curl_easy_setopt(curl, CURLOPT_POSTFIELDS, req.data.cstr()) + C.curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, 'POST') + // req.headers << 'Content-Type: application/x-www-form-urlencoded' + } + // Add request headers + mut hlist := &C.curl_slist{!} + // for i, h := range req.headers { + for entry in req.headers.entries { + key := entry.key + val := req.headers[key] + h := '$key: $val' + hlist = C.curl_slist_append(hlist, h.cstr()) + } + // curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, // (long)CURL_HTTP_VERSION_2TLS);`Cʀ9 + C.curl_easy_setopt(curl, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1) + if req.verbose { + C.curl_easy_setopt(curl, CURLOPT_VERBOSE, 1) + } + C.curl_easy_setopt(curl, CURLOPT_HTTPHEADER, hlist) + C.curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1) + C.curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1) + println('bef easy()') + res := C.curl_easy_perform(curl) + println('after easy()') + # if (res != CURLE_OK ) + { + err := C.curl_easy_strerror(res) + println('curl_easy_perform() failed: $err') + } + body := chunk.strings.join('')// tos_no_len(chunk.memory) + // chunk.strings.free() + // resp.headers = hchunk.strings + if hchunk.strings.len == 0 { + return Response{} + } + first_header := hchunk.strings.first() + mut status_code := 0 + if first_header.contains('HTTP/') { + val := first_header.find_between(' ', ' ') + status_code = val.to_i() + } + // Build resp headers map + // println('building resp headers hchunk.strings.len') + for h in hchunk.strings { + // break + // println(h) + vals := h.split(':') + pos := h.index(':') + if pos == -1 { + continue + } + if h.contains('Content-Type') { + continue + } + key := h.left(pos) + val := h.right(pos + 1) + // println('"$key" *** "$val"') + // val2 := val.trim_space() + // println('val2="$val2"') + headers[key] = val// val.trim_space() + } + // println('done') + // j.println(resp.status_code) + // println('body=') + // j.println(resp.body) + // j.println('headers=') + // j.println(hchunk.strings) + C.curl_easy_cleanup(curl) + println('end of req.do() url="$req.url"') + return Response { + body: body + } +} + +fn unescape(s string) string { + return tos2(C.curl_unescape(s.cstr(), s.len)) +} + +fn escape(s string) string { + return tos2(C.curl_escape(s.cstr(), s.len)) +} + +// //////////////// +fn (req &Request) do2() Response { + mut resp := Response{} + return resp +} + diff --git a/http/http_win.v b/http/http_win.v new file mode 100644 index 0000000000..c9cf0f2a7b --- /dev/null +++ b/http/http_win.v @@ -0,0 +1,203 @@ +module http + +#flag -lwininet +#flag -lurlmon +// #include +#include "urlmon.h" +#include +// #LPWSTR winstring(string s); +// # bool ok = InternetReadFile(request, buf, BUF_MAX, &nr_read); +import const ( + INTERNET_OPEN_TYPE_PRECONFIG + INTERNET_DEFAULT_HTTP_PORT + INTERNET_DEFAULT_HTTPS_PORT + INTERNET_SERVICE_HTTP +) + +fn (req &Request) do() Response { + mut s := '' + emptyresp := Response{} + mut url := req.url + println('\n\nhttp.do() WIN URL="$url" TYP=$req.typ data="$req.data" headers.len=req.headers.len"') + println(req.headers) + is_ssl := req.url.starts_with('https://') + println('is ssl=$is_ssl') + mut pos := url.index('/') + url = url.right(pos + 2) + mut host := url + mut path := '/' + pos = url.index('/') + if pos > -1 { + host = url.left(pos) + host = host.clone() + path = url.right(pos) + } + // println('HOST="$host"') + // println('PATH="$path"') + mut headers := '' + mut resp_headers := '' + // for header in req.headers { + for entry in req.headers.entries { + // headers += '$header\r\n' + key := entry.key + val := req.headers[key] + headers += '$key: $val\r\n' + } + if req.typ == 'POST' { + headers += 'Content-Type: application/x-www-form-urlencoded' + } + // headers = headers.trim_space() + // println('!!! OLO REQ HEADERS WIN="$headers"') + data := req.data + // Retrieve default http user agent + // char httpUseragent[512]; + // # char httpUseragent []= ""; + user_agent := '' + // DWORD szhttpUserAgent = sizeof(httpUseragent); + // ObtainUserAgentString(0, httpUseragent, &szhttpUserAgent); + // # HINTERNET internet = InternetOpenA(httpUseragent, INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + internet := C.InternetOpenA(user_agent.str, INTERNET_OPEN_TYPE_PRECONFIG, 0, 0, 0) + // # if (!internet) + if isnil(internet) { + println('InternetOpen() failed') + return emptyresp + } + // # INTERNET_PORT port = INTERNET_DEFAULT_HTTP_PORT; + port := int(if is_ssl{INTERNET_DEFAULT_HTTPS_PORT} else { INTERNET_DEFAULT_HTTP_PORT}) + // if is_ssl { + // # port = INTERNET_DEFAULT_HTTPS_PORT; + // } + connect := C.InternetConnectA(internet, host.str, port, 0, 0, INTERNET_SERVICE_HTTP, 0, 0) + // # HINTERNET connect = InternetConnectA(internet, host.str, port, NULL, NULL, + // # INTERNET_SERVICE_HTTP, 0, 0); + # if (!connect) + if isnil(connect) { + e := C.GetLastError() + println('[windows] InternetConnect() failed') + C.printf('err=%d\n', e) + return emptyresp + } + flags := 0 + // # DWORD flags = + #flags = + # INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_CN_INVALID | + # INTERNET_FLAG_IGNORE_CERT_DATE_INVALID | + # INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTP | + # INTERNET_FLAG_IGNORE_REDIRECT_TO_HTTPS | INTERNET_FLAG_NO_AUTH | + # INTERNET_FLAG_NO_CACHE_WRITE | INTERNET_FLAG_NO_UI | + # INTERNET_FLAG_NO_COOKIES | // FUCK YOU MICROSOFT + # INTERNET_FLAG_KEEP_CONNECTION | + # INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD ; + if is_ssl { + #flags = flags | INTERNET_FLAG_SECURE; + } + request := C.HttpOpenRequest(connect, req.typ.str, path.str, 'HTTP/1.1', 0, 0, flags, 0) + // request := C.InternetOpenUrl(connect, req.typ.str, path.str, 'HTTP/1.1', 0, 0, flags, 0) + // # HINTERNET request = HttpOpenRequest(connect, req->typ.str, path.str, "HTTP/1.1", + // # NULL, NULL, flags, NULL); + // # if (!request) + if isnil(request) { + println('HttpOpenRequest() failed') + return emptyresp + } + // println('LEN BEFORE SEND=$headers.len ; $headers') + # bool ret =HttpSendRequest(request, headers.str, -1, data.str, data.len); + # printf("RET=%d\n", ret); + # int e = GetLastError(); + # printf("e=%d\n", e); + // Get response headers + // Todo call twice to get len + # LPSTR h_buf = malloc(1024); + # DWORD dwSize = 1024; + // LPVOID lpOutBuffer=malloc(dwSize); + # HttpQueryInfo(request, HTTP_QUERY_RAW_HEADERS_CRLF, + # h_buf,&dwSize,NULL); + # printf(" resp HEADERS %s\n", h_buf); + // Get response body + // # const int BUF_MAX = 1024; + // # TCHAR buf[BUF_MAX + 1]; + mut buf := [1025]byte + mut nr_read := 0 + BUF_MAX := 1024 + // ok := C.InternetReadFile(request, buf, BUF_MAX, &nr_read) + // # DWORD dwRead = 0; + // /println('calling InternetReadFile()') + // # bool ok = InternetReadFile(request, buf, BUF_MAX, &nr_read); + // # if (!ok) + // { + // println('read not ok') + // # int e = GetLastError(); + // # printf("%d\n", e); + // } + // # printf("dwread=%d\n", dwRead); + // # while ((InternetReadFile(request, buf, BUF_MAX, &nr_read)) && nr_read > 0) + for + { + println('111') + ok := C.InternetReadFile(request, buf, BUF_MAX, &nr_read) + println('222') + if !ok { + println('InternetReadFile() not ok ') + } + if ok && nr_read == 0 { + println('ok && nr read == 0, breaking') + C.printf('buf broken="%s"\n', buf) + if req.url.contains('websocket') { + println('win sleeping 2') + time.sleep(2) + continue + } + break + } + println('ireadfile()') + buf[nr_read] = 0 + C.printf('buf="%s"\n', buf) + s += tos2(buf)// TODO perf + nr_read = 0 + } + C.InternetCloseHandle(request) + C.InternetCloseHandle(connect) + C.InternetCloseHandle(internet) + # resp_headers = tos2(h_buf); + hh := resp_headers.split('\n') + mut resp := Response { + body: s + headers: map[string]string{} + // headers: resp_headers + } + // println('gen hh') + for h in hh { + // println('\n!') + // println(h) + vals := h.split(':') + pos := h.index(':') + if pos == -1 { + continue + } + key := h.left(pos) + val := h.right(pos + 1) + // println('$key => $val') + resp.headers[key] = val.trim_space() + } + println('END OF WIN req.do($req.url)') + return resp +} + +fn escape(s string) string { + # DWORD size=1; + # char *escaped = NULL; + # char *empty_string = NULL; + # HRESULT res = UrlEscapeA(s.str, empty_string, &size, URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY); + # if (res == E_POINTER) + { + # escaped = HeapAlloc(GetProcessHeap(), 0, size); + # if (!escaped) + # return s; + # UrlEscapeA(s.str, escaped, &size, URL_ESCAPE_PERCENT | URL_ESCAPE_SEGMENT_ONLY); + # return tos2(escaped); + } + return '' +} + +fn C.InternetReadFile(voidptr, voidptr, int, intptr) bool + diff --git a/json/json_primitives.v b/json/json_primitives.v new file mode 100644 index 0000000000..b9b54574dc --- /dev/null +++ b/json/json_primitives.v @@ -0,0 +1,66 @@ +module json + +// #include "json/cJSON/cJSON.c" +#include "json/cJSON/cJSON.h" +struct C.cJSON { + valueint int + valuestring byteptr +} + +fn jsdecode_int(root *C.cJSON) int { + if isnil(root) { + return 0 + } + return root.valueint +} + +fn jsdecode_string(root *C.cJSON) string { + if isnil(root) { + return '' + } + if isnil(root.valuestring) { + return '' + } + // println('jsdecode string valuestring="$root.valuestring"') + // return tos(root.valuestring, _strlen(root.valuestring)) + return tos_clone(root.valuestring)// , _strlen(root.valuestring)) +} + +fn jsdecode_bool(root *C.cJSON) bool { + if isnil(root) { + return false + } + return C.cJSON_IsTrue(root) +} + +// /////////////////// +fn jsencode_int(val int) *C.cJSON { + return C.cJSON_CreateNumber(val) +} + +fn jsencode_bool(val bool) *C.cJSON { + return C.cJSON_CreateBool(val) +} + +fn jsencode_string(val string) *C.cJSON { + clone := val.clone() + return C.cJSON_CreateString(clone.str) + // return C.cJSON_CreateString2(val.str, val.len) +} + +// /////////////////////// +// user := decode_User(json_parse(js_string_var)) +fn json_parse(s string) *C.cJSON { + return C.cJSON_Parse(s.str) +} + +// json_string := json_print(encode_User(user)) +fn json_print(json *C.cJSON) string { + s := C.cJSON_PrintUnformatted(json) + return tos(s, _strlen(s)) +} + +// / cjson wrappers +// fn json_array_for_each(val, root *C.cJSON) { +// #cJSON_ArrayForEach (val ,root) +// } diff --git a/math/math.v b/math/math.v new file mode 100644 index 0000000000..63cabc2f28 --- /dev/null +++ b/math/math.v @@ -0,0 +1,55 @@ +// 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 math + +const ( + PI = 3.14159265358979323846264338327950288419716939937510582097494459 +) + +fn abs(a f64) f64 { + if a < 0 { + return -a + } + return a +} + +fn cos(a f64) f64 { + return C.cos(a) +} + +fn max(a, b f64) f64 { + if a > b { + return a + } + return b +} + +fn min(a, b f64) f64 { + if a < b { + return a + } + return b +} + +fn pow(a, b f64) f64 { + return C.pow(a, b) +} + +fn radians(degrees f64) f64 { + return degrees * (PI / 180.0) +} + +fn round(f f64) f64 { + return C.round(f) +} + +fn sin(a f64) f64 { + return C.sin(a) +} + +fn sqrt(a f64) f64 { + return C.sqrt(a) +} + diff --git a/os/os.v b/os/os.v new file mode 100644 index 0000000000..367b359a31 --- /dev/null +++ b/os/os.v @@ -0,0 +1,490 @@ +module os + +#include +const ( + args = []string +) + +struct FILE { +} + +struct File { + cfile *FILE +} + +import const ( + SEEK_SET + SEEK_END +) + +fn init_os_args(argc int, c voidptr) []string { + mut args := []string + # char** argv = (char**) c; + for i := 0; i < argc; i++ { + // # printf("ARG %d = '%s'\n", i, argv[i]); + arg := '' + # arg = tos(argv[i], strlen(argv[i])); + args << arg + } + # os__args = args; + return args +} + +fn parse_windows_cmd_line(cmd byteptr) { + s := tos2(cmd) + vals := s.split(' ') + println(vals) + # os__args = vals; +} + +// read_file reads the file in `path` and returns the contents. +// TODO return `?string` +pub fn read_file(path string) string { + res := '' + # FILE *f = fopen(path.str, "r"); + # if (!f) return tos("", 0); + # fseek(f, 0, SEEK_END); + # long fsize = ftell(f); + // # fseek(f, 0, SEEK_SET); //same as rewind(f); + # rewind(f); + # char *string = malloc(fsize + 1); + # fread(string, fsize, 1, f); + # fclose(f); + # string[fsize] = 0; + // # printf("RFILE= %s\n", string); + # res = tos(string, fsize); + return res +} + +fn (f File) read_rune() string { + # if (!f.cfile) return tos("", 0); + c := malloc(1) + C.fread(c, 1, 1, f.cfile) + return tos(c, 1) +} + +// `file_size` returns the size of the file located in `path`. +pub fn file_size(path string) int { + # struct stat s; + # stat(path.str, &s); + // # if (S_ISLNK(s.st_mode)) return -1; + # return s.st_size; + // ////////////////////// + # FILE *f = fopen(path.str, "r"); + # if (!f) return 0; + # fseek(f, 0, SEEK_END); + # long fsize = ftell(f); + // # fseek(f, 0, SEEK_SET); //same as rewind(f); + # rewind(f); + # return fsize; + return 0 +} + +pub fn file_last_mod_unix(path string) int { + # struct stat attr; + # stat(path.str, &attr); + # return attr.st_mtime ; + return 0 +} + +/* +pub fn file_last_mod_time(path string) time.Time { + return time.now() + q := C.tm{} + # struct stat attr; + # stat(path.str, &attr); + // # q = attr.st_mtime; + # struct tm * now = localtime(&attr.st_mtime); + # q = *now; + # printf("Last modified time: %s", ctime(&attr.st_mtime)); + return time.convert_ctime(q) +} +*/ +// `read_lines` reads the file in `path` into an array of lines. +pub fn read_lines(path string) []string { + return read_file_lines(path) +} + +fn read_file_into_lines(path string) []string { + return read_file_lines(path) +} + +fn read_file_into_ulines(path string) []ustring { + lines := read_file_into_lines(path) + // mut ulines := new_array(0, lines.len, sizeof(ustring)) + mut ulines := []ustring + for myline in lines { + // ulines[i] = ustr + ulines << myline.ustring() + } + return ulines +} + +const ( + BUF_SIZE = 5000 +) + +fn read_file_lines(path string) []string { + // println('read file $path into lines') + mut res := []string + # char buf[os__BUF_SIZE]; + # FILE *fp = fopen(path.str, "rb"); + # if (!fp) + { + println('failed to open file "$path"') + return res + } + # while (fgets(buf, os__BUF_SIZE, fp) != NULL) + { + mut val := '' + # buf[strlen(buf) - 1] = '\0'; // eat the newline fgets() stores + #ifdef windows + # if (buf[strlen(buf)-2] == 13) + # buf[strlen(buf) - 2] = '\0'; // eat the newline fgets() stores + #endif + // # printf("%s\n", buf); + # val=tos_clone(buf) ; + // for i := 0; i < val.len; i++ { + // C.printf('%d) %c %d\n', i, val.str[i], val.str[i]) + // } + #ifdef windows + // if val.str[val.len - 1] == 13 { + if val[val.len - 1] == 13 { + // TODO + // val.len-- + } + #endif + // println('QQQ read line="$val"') + res << val + } + # fclose(fp); + return res +} + +fn append_to_file(file, s string) { + # FILE* fp = fopen(file.str, "a"); + # fputs(s.str, fp); + # fputs("\n", fp); + # fclose(fp); +} + +struct Reader { + fp *FILE +} + +struct FileInfo { + name string + size int +} + +// fn open(file string) File? { +// return open_file(file) +// } +pub fn open(path string) File { + return open_file(path) +} + +fn open_file(file string) File { + return create_file2(file, 'r') +} + +// `create` creates a file at a specified location and returns a writable `File` object. +pub fn create(path string) File { + return create_file(path) +} + +pub fn open_append(path string) File { + return create_file(path) +} + +fn create_file(file string) File { + return create_file2(file, 'w') +} + +fn create_file_a(file string) File { + return create_file2(file, 'a') +} + +fn open_file_a(file string) File { + return create_file2(file, 'a') +} + +fn create_file2(file string, mode string) File { + res := File { + cfile: C.fopen(file.cstr(), mode.cstr()) + } + if isnil(res.cfile) { + println('coudlnt create file "$file"') + } + return res +} + +fn (f File) append(s string) { + ss := s.clone() + C.fputs(ss.cstr(), f.cfile) + // ss.free() + // C.fwrite(s.str, 1, s.len, f.cfile) +} + +// convert any value to []byte (LittleEndian) and write it +// for example if we have write(7, 4), "07 00 00 00" gets written +// write(0x1234, 2) => "34 12" +fn (f File) write(data voidptr, size int) { + C.fwrite(data, 1, size, f.cfile) +} + +fn (f File) write_at(data voidptr, size, pos int) { + C.fseek(f.cfile, pos, SEEK_SET) + C.fwrite(data, 1, size, f.cfile) + C.fseek(f.cfile, 0, SEEK_END) +} + +fn (f File) appendln(s string) { + // C.fwrite(s.str, 1, s.len, f.cfile) + // ss := s.clone() + // TODO perf + C.fputs(s.cstr(), f.cfile) + // ss.free() + C.fputs('\n', f.cfile) +} + +fn (f File) close() { + C.fclose(f.cfile) +} + +fn close_file(fp *FILE) { + $if windows { + } + # if (fp) + C.fclose(fp) +} + +// `system2` starts the specified command, waits for it to complete, and returns its code. +pub fn system2(cmd string) int { + cstr := cmd.clone() + ret := int(C.system(cstr.cstr())) + // println(' system2 ret=$ret cmd="$s"') + if ret == -1 { + os.print_c_errno() + } + return ret +} + +fn popen(path string) *FILE { + cpath := path.cstr() + $if windows { + return C._popen(cpath, 'r') + } + $else { + return C.popen(cpath, 'r') + } +} + +// TODO rename to run or exec (system doesnt return a string) +// `system` starts the specified command, waits for it to complete, and returns its output. +// TODO merge the two functions. +pub fn system(cmd string) string { + // println('OS SYSTEM($s)') + res := '' + ss := '$cmd 2>&1' + _ := 0// TODO DOLLAR TOKEN + f := popen(ss)// cmd) + // # if (!f) + if isnil(f) { + println('popen $cmd failed') + } + #define MAX 1000 + # char buf[MAX]; + // # char* buf = malloc(MAX); + // j# sleep(1); + // # if (!fgets(buf, MAX, f)) { + // jprintln('first get failed') + // jos.print_c_errno() + // j# } + # while (fgets(buf, MAX, f) != NULL) { + // # printf("popen buf=%s\n", buf); + # res = string_add(res, tos(buf, strlen(buf))); + # } + // println(res) + return res.trim_space() +} + +fn system_into_lines(s string) []string { + mut res := []string + cmd := '$s 2>&1' +#ifdef windows + # FILE* f = _popen(cmd.str, "r"); +#else + # FILE* f = popen(cmd.str, "r"); +#endif + #define MAX 5000 + // # char buf[MAX]; + # char * buf = malloc(sizeof(char) * MAX); + # while (fgets(buf, MAX, f) != NULL) + { + val := '' + # buf[strlen(buf) - 1] = '\0'; // eat the newline fgets() stores + # val=tos_clone(buf); + res << val + } + return res +} + +// `getenv` returns the value of the environment variable named by the key. +pub fn getenv(key string) string { + s := C.getenv(key.cstr()) + if isnil(s) { + return '' + } + return tos2(s) +} + +fn exit(reason string) { + println2('exit(): $reason') + log(reason) + C.exit(0) +} + +fn exit1(reason string) { + println2('exit(): $reason') + C.exit(1) +} + +// `file_exists` returns true if `path` exists. +pub fn file_exists(path string) bool { + // # return access( path.str, F_OK ) != -1 ; + res := false +#ifdef windows + # res = _access( path.str, 0 ) != -1 ; +#else + # res = access( path.str, 0 ) != -1 ; +#endif + return res +} + +// `mkdir` creates a new directory with the specified path. +pub fn mkdir(path string) { + $if windows { + path = path.replace('/', '\\') + C.CreateDirectory(path.cstr(), 0) + } + $else { + println('AAAAAAAA $$ "$path"') + C.mkdir(path.cstr(), 511)// S_IRWXU | S_IRWXG | S_IRWXO + // os.system2('mkdir -p $path') + } +} + +// `rm` removes file in `path`. +pub fn rm(path string) { + $if windows { + // os.system2('del /f $path') + } + $else { + C.remove(path.cstr()) + } + // C.unlink(path.cstr()) +} + +fn rmdir(path string, guard string) { + if !path.contains(guard) { + println('rmdir canceled because the path doesnt contain $guard') + return + } + println2('rmdir "$path"') +#ifndef windows + os.system('rm -rf "$path"') +#else + os.system('rmdir /s /q "$path"') +#endif +} + +pub fn unzip(path, out string) { + $if windows { + // TODO native string + // TODO handle older Windows + // The only way to unzip a file without installing dependencies is to use PowerShell + .NET + # char *s="powershell.exe -nologo -noprofile -command \"& { Add-Type -A 'System.IO.Compression.FileSystem'; [IO.Compression.ZipFile]::ExtractToDirectory('PATH', 'OUT'); }\" "; + mut cmd := '' + # cmd = tos(s, strlen(s)); + cmd = cmd.replace('PATH', path) + cmd = cmd.replace('OUT', out) + os.system(cmd) + } + $else { + os.system('unzip -o -d "$out" "$path"') + } +} + +fn print_c_errno() { + # printf("errno=%d err='%s'\n", errno, strerror(errno)); +} + +pub fn basedir(path string) string { + pos := path.last_index('/') + if pos == -1 { + return path + } + return path.left(pos + 1) +} + +pub fn filename(path string) string { + return path.all_after('/') +} + +fn C.getline(voidptr, voidptr, voidptr) int + +pub fn get_line() string { + max := 256 + buf := malloc(max) + nr_chars := C.getline(&buf, &max, stdin) + if nr_chars == 0 { + return '' + } + return tos(buf, nr_chars - 1) +} + +pub fn user_os() string { + $if linux { + return 'linux' + } + $if mac { + return 'mac' + } + $if windows { + return 'windows' + } + return 'unknown' +} + +// `home_dir` returns path to user's home directory. +pub fn home_dir() string { + mut home := os.getenv('HOME') + $if windows { + home = os.getenv('HOMEDRIVE') + home += os.getenv('HOMEPATH') + } + home += '/' + return home +} + +pub fn write_file(path, text string) { + f := os.create(path) + f.appendln(text) + f.close() +} + +fn on_segfault(f voidptr) { +#ifdef windows + return +#endif +#ifdef mac + # struct sigaction sa; + # memset(&sa, 0, sizeof(struct sigaction)); + # sigemptyset(&sa.sa_mask); + # sa.sa_sigaction = f; + # sa.sa_flags = SA_SIGINFO; + # sigaction(SIGSEGV, &sa, 0); +#endif +} + diff --git a/os/os_mac.v b/os/os_mac.v new file mode 100644 index 0000000000..1b066245f7 --- /dev/null +++ b/os/os_mac.v @@ -0,0 +1,67 @@ +module os + +#include +#include +#include +#include +#include +// import darwin +fn log(s string) { +} + +fn is_dir(path string) bool { + # struct stat statbuf; + cstr := path.cstr() + # if (stat(cstr, &statbuf) != 0) + { + return false + } + # return S_ISDIR(statbuf.st_mode); + return false +} + +fn chdir(path string) { + C.chdir(path.cstr()) +} + +fn getwd() string { + cwd := malloc(1024) + # if (getcwd(cwd, 1024)) return tos2(cwd); + return '' +} + +fn ls(path string) []string { + mut res := []string + # DIR *dir; + # struct dirent *ent; + # if ((dir = opendir (path.str)) == NULL) + { + println('ls() couldnt open dir "$path"') + print_c_errno() + return res + } + // print all the files and directories within directory */ + # while ((ent = readdir (dir)) != NULL) { + name := '' + # name = tos_clone(ent->d_name);//, strlen(ent->d_name)); + // # printf ("printf ls() %s\n", ent->d_name); + // println(name) + if name != '.' && name != '..' && name != '' { + res << name + } + # } + # closedir (dir); + // res.sort() + // println('sorted res') + // print_strings(res) + return res +} + +fn print_backtrace() { + # void *buffer[100]; + nptrs := 0 + # nptrs = backtrace(buffer, 100); + # printf("%d!!\n", nptrs); + # backtrace_symbols_fd(buffer, nptrs, STDOUT_FILENO) ; +} + diff --git a/os/os_win.v b/os/os_win.v new file mode 100644 index 0000000000..9f888b62b9 --- /dev/null +++ b/os/os_win.v @@ -0,0 +1,28 @@ +module os + +fn ls(path string) []string { + mut res := []string + return res +} + +const ( + FILE_ATTRIBUTE_DIRECTORY = 16 +) + +fn is_dir(path string) bool { + val := int(C.GetFileAttributes(path.cstr())) + return val &FILE_ATTRIBUTE_DIRECTORY > 0 +} + +fn chdir(path string) { + C._chdir(path.cstr()) +} + +fn getwd() string { + panic('getwd() not impl') + return '' +} + +fn log(s string) { +} + diff --git a/rand/rand.v b/rand/rand.v new file mode 100644 index 0000000000..fb43fad830 --- /dev/null +++ b/rand/rand.v @@ -0,0 +1,14 @@ +module rand + +#include +// #include +fn seed() { + # time_t t; + # srand((unsigned) time(&t)); +} + +fn next(max int) int { + # return rand() % max; + return 0 +} + diff --git a/stbi/stbi.v b/stbi/stbi.v new file mode 100644 index 0000000000..3f3e605110 --- /dev/null +++ b/stbi/stbi.v @@ -0,0 +1,61 @@ +module stbi + +#include "glad.h" +import gl + +#define STB_IMAGE_IMPLEMENTATION +#include +struct Image { +mut: + width int + height int + nr_channels int + ok bool + data voidptr + ext string +} + +import const ( + GL_RGBA + GL_RGB + GL_UNSIGNED_BYTE + GL_TEXTURE_2D + STBI_rgb_alpha +) + +fn load(path string) Image { + ext := path.all_after('.') + mut res := Image { + ok: true + ext: ext + data: 0 + } + if ext == 'png' { + res.data = C.stbi_load(path.str, &res.width, &res.height, &res.nr_channels, STBI_rgb_alpha) + } + else { + res.data = C.stbi_load(path.str, &res.width, &res.height, &res.nr_channels, 0) + } + if isnil(res.data) { + exit('stbi cant load') + } + return res +} + +fn (img Image) free() { + C.stbi_image_free(img.data) +} + +fn (img Image) tex_image_2d() { + mut rgb_flag := GL_RGB + if img.ext == 'png' { + rgb_flag = GL_RGBA + } + C.glTexImage2D(GL_TEXTURE_2D, 0, rgb_flag, img.width, img.height, 0, rgb_flag, GL_UNSIGNED_BYTE, + img.data) +} + +fn set_flip_vertically_on_load(val bool) { + C.stbi_set_flip_vertically_on_load(val) +} + diff --git a/time/time.v b/time/time.v new file mode 100644 index 0000000000..37572edb89 --- /dev/null +++ b/time/time.v @@ -0,0 +1,330 @@ +module time + +import rand + +#include +struct Time { +pub: + year int + day int + month int + hour int + minute int + second int + uni int // TODO it's safe to use "unix" now +} + +fn asfd() { +} + +struct C.tm { + tm_year int + tm_mon int + tm_mday int + tm_hour int + tm_min int + tm_sec int +} + +pub fn now() Time { + # time_t t = time(0); + // t := C.time(0) + # struct tm * now = localtime(&t); + res := Time{} + # res.year = now->tm_year + 1900; + # res.month = now->tm_mon + 1; + # res.day = now->tm_mday; + # res.hour = now->tm_hour; + # res.minute = now->tm_min; + # res.second = now->tm_sec; + # res.uni = (int)t; + // # res.ms = now->tm_msec; + return res +} + +// fn now() Time { +// t := C.time(0) +// now := localtime(&t) +// return Time{ +// year: now.tm_year + 1900 +// month : now.tm_mon + 1 +// day : now.tm_mday +// hour : now.tm_hour +// minute : now.tm_min +// second : now.tm_sec +// uni : int(t) +// } +// } +pub fn random() Time { + return Time { + year: rand.next(2) + 201 + month: rand.next(12) + 1 + day: rand.next(30) + 1 + hour: rand.next(24) + minute: rand.next(60) + second: rand.next(60) + } +} + +pub fn unix(u string) Time { + // println('unix time($u)') + // # int aa = atoi(u.str); + // #printf("!!!! %d\n", aa); + # int uni = atoi(u.str); + # time_t t = (time_t)uni; + # struct tm * now = localtime(&t); + // println('got tm') + // TODO COPY PASTA + res := Time{} + # res.year = now->tm_year + 1900; + # res.month = now->tm_mon + 1; + # res.day = now->tm_mday; + # res.hour = now->tm_hour; + # res.minute = now->tm_min; + # res.second = now->tm_sec; + # res.uni = uni; + // println('end unix') + return res +} + +pub fn convert_ctime(t tm) Time { + return Time { + year: t.tm_year + 1900 + month: t.tm_mon + 1 + day: t.tm_mday + hour: t.tm_hour + minute: t.tm_min + second: t.tm_sec + } + // uni = uni; +} + +pub fn unixn(uni int) Time { + // println('unix time($u)') + // # int aa = atoi(u.str); + // #printf("!!!! %d\n", aa); + # time_t t = (time_t)uni; + # struct tm * now = localtime(&t); + // println('got tm') + // TODO COPY PASTA + res := Time{} + # res.year = now->tm_year + 1900; + # res.month = now->tm_mon + 1; + # res.day = now->tm_mday; + # res.hour = now->tm_hour; + # res.minute = now->tm_min; + # res.second = now->tm_sec; + # res.uni = uni; + // println('end unix') + return res +} + +fn (t Time) format_ss() string { + return '${t.year}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}:${t.second:02d}' +} + +pub fn (t Time) format() string { + return '${t.year}-${t.month:02d}-${t.day:02d} ${t.hour:02d}:${t.minute:02d}' +} + +const ( + Months = 'JanFebMarAprMayJunJulAugSepOctNovDec' +) + +pub fn (t Time) smonth() string { + i := t.month - 1 + return Months.substr(i * 3, (i + 1) * 3) +} + +// 21:04 +pub fn (t Time) hhmm() string { + return '${t.hour:02d}:${t.minute:02d}' +} + +fn (t Time) hhmm_tmp() string { + return '${t.hour:02d}:${t.minute:02d}' +} + +// 21:04 +pub fn (t Time) hhmm12() string { + mut am := 'am' + mut hour = t.hour + if t.hour > 11 { + am = 'pm' + } + if t.hour > 12 { + hour = hour - 12 + } + if t.hour == 0 { + hour = 12 + } + return '$hour:${t.minute:02d} $am' +} + +// 21:04:03 +fn (t Time) hhmmss() string { + return '${t.hour:02d}:${t.minute:02d}:${t.second:02d}' +} + +// 2012-01-05 +fn (t Time) ymmdd() string { + return '${t.year}-${t.month:02d}-${t.day:02d}' +} + +// Jul 3 +fn (t Time) md() string { + // jl := t.smonth() + s := '${t.smonth()} $t.day' + return s +} + +fn (t Time) clean() string { + nowe := time.now() + // if amtime { + // hm = t.Format("3:04 pm") + // } + // Today + if t.month == nowe.month && t.year == nowe.year && t.day == nowe.day { + return t.hhmm() + } + // This week + // if time.Since(t) < 24*7*time.Hour { + // return t.Weekday().String()[:3] + " " + hm + // } + // This year + if t.year == nowe.year { + return '${t.smonth()} ${t.day} ${t.hhmm()}' + } + return t.format() + // return fmt.Sprintf("%4d/%02d/%02d", t.Year(), t.Month(), t.Day()) + " " + hm +} + +fn (t Time) clean12() string { + nowe := time.now() + // if amtime { + // hm = t.Format("3:04 pm") + // } + // Today + if t.month == nowe.month && t.year == nowe.year && t.day == nowe.day { + return t.hhmm12() + } + // This week + // if time.Since(t) < 24*7*time.Hour { + // return t.Weekday().String()[:3] + " " + hm + // } + // This year + if t.year == nowe.year { + return '${t.smonth()} ${t.day} ${t.hhmm12()}' + } + return t.format() + // return fmt.Sprintf("%4d/%02d/%02d", t.Year(), t.Month(), t.Day()) + " " + hm +} + +/* +// in ms +fn ticks() double { + # struct timeval tv; + # gettimeofday(&tv, NULL); + # double time_in_mill = (tv.tv_sec) * 1000 + (tv.tv_usec) / 1000 ; // convert tv_sec & tv_usec to millisecond + // # printf("!!!%f\n", time_in_mill); + // # return (int)time_in_mill; + // # return (int)(time_in_mill - 1521561736529); + # return (long)(time_in_mill - 1523777913000); + return double(0) + // return int64(0) +} +*/ +// `parse` parses time in the following format: "2018-01-27 12:48:34" +pub fn parse(s string) Time { + // println('parse="$s"') + pos := s.index(' ') + if pos <= 0 { + println('bad time format') + return now() + } + symd := s.left(pos) + ymd := symd.split('-') + if ymd.len != 3 { + println('bad time format') + return now() + } + shms := s.right(pos) + hms := shms.split(':') + hour := hms[0] + minute := hms[1] + second := hms[2] + // ////////// + return new_time(Time { + year: ymd[0].to_i() + month: ymd[1].to_i() + day: ymd[2].to_i() + hour: hour.to_i() + minute: minute.to_i() + second: second.to_i() + }) +} + +fn new_time(t Time) Time { + return{t | uni: t.calc_unix()} +} + +fn (t &Time) calc_unix() int { + # struct tm lDate; + # lDate.tm_sec = t->second; + # lDate.tm_min = t->minute; + # lDate.tm_hour = t->hour; + # lDate.tm_mday = t->day; + # lDate.tm_mon = t->month-1; + # lDate.tm_year = t->year - 1900; + # time_t kek = mktime(&lDate); + // # t->uni = (int)kek; + # return (int)kek; + return 0 +} + +// TODO add(d time.Duration) +pub fn (t Time) add_seconds(seconds int) Time { + return unixn(t.uni + seconds) +} + +// TODO use time.Duration instead of seconds +fn since(t Time) int { + return 0 +} + +pub fn (t Time) relative() string { + now := time.now() + secs := now.uni - t.uni + if secs <= 30 { + // right now or in the future + // TODO handle time in the future + return 'now' + } + if secs < 60 { + return '1m' + } + if secs < 3600 { + return '${secs/60}m' + } + if secs < 3600 * 24 { + return '${secs/3600}h' + } + if secs < 3600 * 24 * 5 { + return '${secs/3600/24}d' + } + if secs > 3600 * 24 * 10000 { + return '' + } + return t.md() +} + +fn day_of_week(y, m, d int) int { + // TODO please no + # return (d += m < 3 ? y-- : y - 2, 23*m/9 + d + 4 + y/4- y/100 + y/400)%7; + return 0 +} + +pub fn (t Time) day_of_week() int { + return day_of_week(t.year, t.month, t.day) +} + diff --git a/time/time_lin.v b/time/time_lin.v new file mode 100644 index 0000000000..1c2dba293e --- /dev/null +++ b/time/time_lin.v @@ -0,0 +1,15 @@ +module time + +// in ms +fn ticks() double { + return double(0) +} + +fn sleep(seconds int) { + C.sleep(seconds) +} + +fn sleep_ms(seconds int) { + C.usleep(seconds * 1000) +} + diff --git a/time/time_mac.v b/time/time_mac.v new file mode 100644 index 0000000000..5054ff6104 --- /dev/null +++ b/time/time_mac.v @@ -0,0 +1,28 @@ +module time + +#flag -framework CoreServices +#include +#include +#include +// in ms +fn ticks() double { + // #return glfwGetTime() * 1000.0; + // return glfw.get_time() * double(1000.0) + t := i64(C.mach_absolute_time()) + # Nanoseconds elapsedNano = AbsoluteToNanoseconds( *(AbsoluteTime *) &t ); + # return (double)(* (uint64_t *) &elapsedNano) / 1000000; + return double(0) +} + +fn sleep(seconds int) { + C.sleep(seconds) +} + +fn usleep(seconds int) { + C.usleep(seconds) +} + +fn sleep_ms(seconds int) { + C.usleep(seconds * 1000) +} + diff --git a/time/time_win.v b/time/time_win.v new file mode 100644 index 0000000000..f701b4d69e --- /dev/null +++ b/time/time_win.v @@ -0,0 +1,20 @@ +module time + +// in ms +fn ticks() double { + return C.GetTickCount() +} + +fn sleep(seconds int) { + C._sleep(seconds * 1000) +} + +fn usleep(seconds int) { + panic('usleep not impl') + // C._usleep(seconds) +} + +fn sleep_ms(n int) { + C.Sleep(n) +} +