Browse Source

V 0.0.12 open-source release

master
Alexander Medvednikov 2 years ago
commit
d32e538073
  1. 49
      base64/base64.v
  2. 223
      builtin/array.v
  3. 127
      builtin/builtin.v
  4. 163
      builtin/int.v
  5. 159
      builtin/map.v
  6. 8
      builtin/option.v
  7. 97
      builtin/smap.v
  8. 814
      builtin/string.v
  9. 32
      builtin/string_builder.v
  10. 338
      builtin/utf8.v
  11. 290
      compiler/cgen.v
  12. 848
      compiler/fn.v
  13. 158
      compiler/jsgen.v
  14. 845
      compiler/main.v
  15. 3216
      compiler/parser.v
  16. 630
      compiler/scanner.v
  17. 644
      compiler/table.v
  18. 265
      compiler/token.v
  19. 35
      examples/concurrent_news_fetcher.v
  20. 15
      examples/links_scraper.v
  21. 339
      examples/tetris/tetris.v
  22. 58
      examples/word_counter/word_counter.v
  23. 712
      gg/gg.v
  24. 303
      glfw/glfw.v
  25. 314
      glm/glm.v
  26. 77
      gx/gx.v
  27. 59
      http/download_lin.v
  28. 64
      http/download_mac.v
  29. 18
      http/download_win.v
  30. 94
      http/http.v
  31. 202
      http/http_mac.v
  32. 203
      http/http_win.v
  33. 66
      json/json_primitives.v
  34. 55
      math/math.v
  35. 490
      os/os.v
  36. 67
      os/os_mac.v
  37. 28
      os/os_win.v
  38. 14
      rand/rand.v
  39. 61
      stbi/stbi.v
  40. 330
      time/time.v
  41. 15
      time/time_lin.v
  42. 28
      time/time_mac.v
  43. 20
      time/time_win.v

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

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

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

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

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

8
builtin/option.v

@ -0,0 +1,8 @@
module builtin
struct Option {
data voidptr
error string
ok bool
}

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

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