Initial project import

This commit is contained in:
Agila Forge Importer committed 2026-07-13 15:20:32 +00:00
commit 23f2d29e32
11957 files changed
+1754577

No files matched your search

+10
View File
@@ -0,0 +1,10 @@
builtin/js
builtin/wasm
builtin/linux_bare
builtin/wasm_bare
os/bare
# Experimental and not part of the 0.5.1 release.
goroutines
v/slow_tests/
v/preludes_js/
v/preludes
+21
View File
@@ -0,0 +1,21 @@
---
page: index
title: V standard library documentation
index: V stdlib
---
This site contains the documentation for the standard library of modules
included with the [V language](https://vlang.io). Also commonly referred
to as `vlib`, as that is the root directory for these modules in the V
repository.
If you were looking for documentation for the language itself, the
builtin types, operators, et. al., please use the
[V documentation](https://docs.vlang.io/introduction.html) link.
Some included modules depend on third party libraries, and these are kept
separate in the `thirdparty` directory at the root level of the source
repository.
Note that the [builtin](https://modules.vlang.io/builtin.html) module is
implicitly imported by default in V, so you do not need to have a specific
`import` statement to use any of the features of that module.
+3
View File
@@ -0,0 +1,3 @@
## Description
`archive` is a namespace for different archive formats like `tar` or `zip`.
+34
View File
@@ -0,0 +1,34 @@
## Description
`tar` is a module to access tar archives.
Tape archives (tar) are a file format for storing a sequence of files that can be read and written
as streams. This module covers the reading of the basic sections of archives produced by GNU tools
like Linux command `tar -xvf` but in memory instead modifing the filesystem. Parses directories,
files, and file's content and manage paths longer than 100 chars.
### Read Efficiency
An entire tar file can be read in memory or by chunks. Keeps in memory a single decompressed
[chunk](https://modules.vlang.io/compress.gzip.html#decompress_with_callback) of 32 KB at a time
and also keeps in memory a single tar block of 512 bytes at a time. Convert paths to strings until
needed and the user reader implementation can stop early the reading process.
### Read Example
The tar blocks are parsed and some fields are passed to `Reader` implemented methods.
```v
import os
import archive.tar
fn main() {
os.chdir(@VMODROOT) or {}
path := 'archive/tar/testdata/life.tar.gz'
reader := tar.new_debug_reader()
tar.read_tar_gz_file(path, reader)!
}
```
For plain archives, use `tar.read_tar_file(path, reader)!`.
Look also in `examples` folder the `tar_gz_reader.v` program.
+292
View File
@@ -0,0 +1,292 @@
module tar
import compress.gzip
import os
// read_tar_file reads a given local .tar file and parses all blocks with a
// given reader.
pub fn read_tar_file(path string, reader Reader) ! {
all_blocks := os.read_bytes(path)!
read_tar_blocks(all_blocks, reader)!
}
// read_tar_gz_file decompresses a given local file and reads all the blocks
// with a given reader.
pub fn read_tar_gz_file(path string, reader Reader) ! {
tar_gz := os.read_bytes(path)!
all_blocks := gzip.decompress(tar_gz)!
read_tar_blocks(all_blocks, reader)!
}
fn read_tar_blocks(all_blocks []u8, reader Reader) ! {
mut untar := Untar{
reader: reader
}
untar.read_all_blocks(all_blocks)!
}
// Read is used by Untar to call Reader implemented methods.
// The implementor can read the block's `get_block_number()` and `get_path()`
// and can set the field `stop_early` to true to suspend the reading.
pub struct Read {
mut:
block_number int
special BlockSpecial
prefix_len int
prefix_buf [131]u8
separator bool
path_len int
path_buf [100]u8
long_path &LongPath = unsafe { nil }
pub mut:
stop_early bool
}
// set_short_path sets Read path with the tar block strings `prefix` and `path`.
// Block's `prefix` C string max length is 131 but most of the time is 0.
// Block's `path` C string max length is 100. Both `prefix` and `path` are
// linked to a V string but converted until is needed, see `get_path()`.
fn (mut b Read) set_short_path(buffer [512]u8, separator_after_prefix bool) {
// first check if TAR block has a prefix string (0 to 131 chars). The
// prefix will be other than '' the TAR block filepath len is > 100.
b.prefix_len = 0
for i := 345; i < 345 + 131; i++ {
letter := buffer[i]
if letter == 0 {
break // first 0 found means prefix C string is complete.
}
b.prefix_buf[b.prefix_len] = letter
b.prefix_len++
}
b.separator = separator_after_prefix
// most of the time there is path for blocks like dirs and regular files:
b.path_len = 0
for i := 0; i < 100; i++ {
letter := buffer[i]
if letter == 0 {
break // first 0 found means path C string is complete.
}
b.path_buf[b.path_len] = letter
b.path_len++
}
}
// set_long_path sets Read path with the long path reference.
fn (mut b Read) set_long_path(long_path &LongPath) {
b.long_path = unsafe { long_path }
}
// get_path returns the path of this read. The path is valid for blocks of types
// directory, file and file data.
pub fn (b Read) get_path() string {
if b.long_path != unsafe { nil } {
return b.long_path.get_path()
}
mut str := []u8{cap: b.prefix_len + b.path_len + 1}
if b.prefix_len > 0 {
for i in 0 .. b.prefix_len {
str << b.prefix_buf[i]
}
}
if b.prefix_len > 0 && b.separator {
str << `/`
}
if b.path_len > 0 {
for i in 0 .. b.path_len {
str << b.path_buf[i]
}
}
return str.bytestr()
}
// get_block_number returns the consecutive number of this read.
pub fn (b Read) get_block_number() int {
return b.block_number
}
// get_special returns the special type of the Read.
pub fn (b Read) get_special() BlockSpecial {
return b.special
}
// str returns a string representation with block number, path, special type and stop early.
pub fn (r Read) str() string {
return '(block_number:${r.block_number} path:${r.get_path()} special:${r.special} stop_early:${r.stop_early})'
}
// Reader is used to read by Untar to parse the blocks.
pub interface Reader {
mut:
// dir_block is called when untar reads a block of type directory.
// Call `Read.get_path()` to get the full name of the directory.
// `size` field is zero for directories.
// The implementor can set Read's field `stop_early` to suspend the reader.
dir_block(mut read Read, size u64)
// file_block is called when untar reads a block of type filename.
// Call `Read.get_path()` to get the full name of the file.
// `size` is the expected file size in bytes to be read later.
// The implementor can set Read's field `stop_early` to suspend the reader.
file_block(mut read Read, size u64)
// file_block is called when untar reads a block of type filedata.
// Call `Read.get_path()` to get the full name of the file data belongs to.
// The `data` size is 512 bytes or less. `pending` indicates how many bytes are left to read.
// The implementor can inspect the data and use the pending value
// to set Read's field `stop_early` to suspend the reader.
data_block(mut read Read, data []u8, pending int)
// other_block is called when untar reads a block type other than directory,
// filename or filedata. `Read.get_header()` and 'details' give more info about the block.
// `block device` or `FIFO`.
// The implementor can set Read's field `stop_early` to suspend the reader.
other_block(mut read Read, details string)
}
// DebugReader implements a Reader and prints rows for blocks read
// as directories, files, file data blocks and special blocks.
pub struct DebugReader implements Reader {
}
// new_debug_reader returns a DebugReader
pub fn new_debug_reader() &DebugReader {
return &DebugReader{}
}
fn (mut t DebugReader) dir_block(mut read Read, _size u64) {
println('DIR #${read.get_block_number()} ${read.get_path()}')
}
fn (mut t DebugReader) file_block(mut read Read, size u64) {
println('FILE #${read.get_block_number()} path:${read.get_path()} size:${size}')
}
fn (mut t DebugReader) data_block(mut read Read, data []u8, pending int) {
println('DATA #${read.get_block_number()} ${read.get_path()} size:${data.len} pending:${pending}')
}
fn (mut t DebugReader) other_block(mut read Read, details string) {
println('OTHER #${read.get_block_number()} special:${read.special} ${details}')
}
// ReadResult is returned by ReadResultFn
pub enum ReadResult {
@continue
stop_early
end_of_file
end_archive
overflow
}
type ReadResultFn = fn (block []u8) !ReadResult
@[heap]
pub struct Decompressor {
mut:
untar &Untar
}
// new_decompressor returns a Decompressor to decompress a tar.gz file
// A given Untar with a registered Reader will read the blocks.
pub fn new_decompressor(untar &Untar) &Decompressor {
return &Decompressor{
untar: untar
}
}
// read_all decompresses the given `tar_gz` array with all the tar blocks.
// Then calls untar method `read_all` to read all the blocks at once.
// A read result is returned which can be of the type stop early or an error.
pub fn (mut d Decompressor) read_all(tar_gz []u8) !ReadResult {
all_blocks := gzip.decompress(tar_gz)!
return d.untar.read_all_blocks(all_blocks)!
}
// read_chunks decompresses the given `tar_gz` array by chunks of
// 32768 bytes which can hold up to 64 tar blocks of 512 bytes each.
// Then calls untar method read_block with ChunksReader dispatcher.
// A read result is returned which can be of the type stop early or an error.
pub fn (mut d Decompressor) read_chunks(tar_gz []u8) !ReadResult {
mut reader := &ChunksReader{
read_block_fn: d.untar.read_single_block
}
callback := fn (chunk []u8, mut reader ChunksReader) int {
result := reader.read_blocks(chunk)
if result == .continue {
return chunk.len // go for more
}
return 0 // suspend
}
gzip.decompress_with_callback(tar_gz, callback, reader) or {
if reader.result == .continue {
return err
}
return reader.result
}
return reader.result
}
// ChunkReader has a reusable fixed buffer with maximum length of decompressed chunk
// of 32768 bytes plus a maximum previous pending tar block of 512 bytes.
struct ChunksReader {
mut:
read_block_fn ReadResultFn = unsafe { nil }
buffer [32768 + 512]u8
chunks_counter int
pending int // position of the last not sent buffer byte
result ReadResult
}
// read_blocks receives a chunk like those of 32k from a gzip decompressor. The chunk is
// assumed to be a TAR archive section and is cut in 512 bytes blocks that are sent to
// the untar reader one by one. The untar reader result informs this process to continue or
// stop early. This process can keep in the buffer the remaining bytes of an incomplete
// block and will be send to the untar reader prepended to a next chunk cuts.
fn (mut d ChunksReader) read_blocks(chunk []u8) ReadResult {
d.chunks_counter++
total := d.pending + chunk.len
if total > d.buffer.len {
assert false, 'Should not occur buffer overflow ${total}'
return .overflow
}
// append new chunk after previous incomplete block bytes not sent yet
for i, ch in chunk {
d.buffer[i + d.pending] = ch
}
d.pending += chunk.len
mut cut := 0
for {
if cut + 512 > d.pending {
// after sending all complete blocks move the remaining not sent bytes
// to the start of the reused buffer to be prepended before next chunk
for i := cut; i < d.pending; i++ {
d.buffer[i - cut] = d.buffer[i]
}
d.pending -= cut
return .continue
}
// send a complete block
block := d.buffer[cut..cut + 512]
cut += 512
d.result = d.read_block_fn(block) or {
assert false, 'Should not occur buffer overflow'
return .overflow
}
match d.result {
.continue {
// try next cut or leave a remaining
}
else {
break // untar error or stop_early
}
}
}
return d.result
}
+219
View File
@@ -0,0 +1,219 @@
@[has_globals]
module tar
import os
fn testsuite_begin() {
os.chdir(@VMODROOT) or {}
}
const testdata = 'vlib/archive/tar/testdata'
__global (
test_chunk_blocks [][]u8
)
fn collect_test_block(block []u8) !ReadResult {
test_chunk_blocks << block.clone()
return .continue
}
// files copied from golang: https://github.com/golang/go/blob/master/src/archive/tar/testdata/file-and-dir.tar
fn test_golang_testdata() {
// [ ] dir | 0 bytes | folder
// [ ] small.txt | 5 bytes | file
r1 := new_test_reader('file-and-dir.tar', false)!
assert r1.dirs[0] == 'dir/'
assert r1.files['small.txt'] == 5
assert r1.data['small.txt'] == 'Kilts'.bytes()
assert r1.other[0] == 'block:4 special:blank_1 continue'
assert r1.other[1] == 'block:5 special:blank_2 end_archive'
// [ ] small.txt | 5 bytes | file
// [ ] small2.txt | 11 bytes | file
r2 := new_test_reader('gnu.tar', false)!
assert r2.dirs.len == 0
assert r2.files['small.txt'] == 5
assert r2.files['small2.txt'] == 11
assert r2.data['small.txt'] == 'Kilts'.bytes()
assert r2.data['small2.txt'] == 'Google.com\n'.bytes()
// [ ] h1<?><?><?><?>bye | 0 bytes
r3 := new_test_reader('gnu-not-utf8.tar', false)!
r3_filename := [u8(`h`), `i`, 0x80, 0x81, 0x82, 0x83, `b`, `y`, `e`].bytestr()
r3_file_len := r3.files[r3_filename] or { assert false, 'file not found: ${r3_filename}' }
assert r3_file_len == 0
assert r3.other.len == 2
// [ ] 0123456789 | 0 bytes
r4 := new_test_reader('gnu-long-nul.tar', false)!
assert r4.dirs.len == 0
r4_filename := '0123456789'
r4_file_len := r4.files[r4_filename] or {
assert false, 'file ${r4_filename} not found in ${r4.files.keys()}'
}
assert r4_file_len == 0
assert r4.other[0] == 'block:1 special:long_name size:161'
assert r4.other[1] == 'block:2 special:long_name data_part:161'
// [ ] ☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹ | 0 bytes
r5 := new_test_reader('gnu-utf8.tar', false)!
r5_filename := '☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹☺☻☹'
r5_file_len := r5.files[r5_filename] or { assert false, 'file not found: ${r5_filename}' }
assert r5_file_len == 0
assert r5.other[0] == 'block:1 special:long_name size:163'
assert r5.other[1] == 'block:2 special:long_name data_part:163'
}
fn test_long_long_short() {
// test long path (human) substitute another long path (chimp) then a normal path (cat)
r1 := new_test_reader_gz('life.tar.gz', false)!
mammal := 'life/Animalia/Chordata/Mammalia'
human := '${mammal}/Primates_Haplorhini_Simiiformes/Hominidae_Homininae_Hominini/Homo/Homo sapiens.txt'
chimp := '${mammal}/Primates_Haplorhini_Simiiformes/Hominidae_Homininae_Hominini/Pan/Pan troglodytes.txt'
cat := '${mammal}/Carnivora_Feliformia/Felidae_Felinae/Felis/Felis catus.txt'
assert human.len > 100
assert chimp.len > 100
assert cat.len <= 100
assert r1.files[human] == 35
assert r1.files[chimp] == 40
assert r1.files[cat] == 33
assert r1.texts[human] == 'https://en.wikipedia.org/wiki/Human'
assert r1.texts[chimp] == 'https://en.wikipedia.org/wiki/Chimpanzee'
assert r1.texts[cat] == 'https://en.wikipedia.org/wiki/Cat'
}
@[heap]
struct ChunksCollector {
mut:
blocks [][]u8
}
fn test_chunks_reader_keeps_pending_bytes_between_chunks() {
mut collector := &ChunksCollector{}
read_block := fn [mut collector] (block []u8) !ReadResult {
collector.blocks << block.clone()
return .continue
}
mut block1 := []u8{len: 512}
mut block2 := []u8{len: 512}
for i in 0 .. 512 {
block1[i] = u8((i * 3) % 251)
block2[i] = u8((i * 7) % 251)
}
mut data := []u8{}
data << block1
data << block2
mut chunks := ChunksReader{
read_block_fn: read_block
}
assert chunks.read_blocks(data[..700]) == .continue
assert collector.blocks.len == 1
assert collector.blocks[0] == block1
assert chunks.pending == 188
assert chunks.read_blocks(data[700..900]) == .continue
assert collector.blocks.len == 1
assert chunks.pending == 388
assert chunks.read_blocks(data[900..]) == .continue
assert collector.blocks.len == 2
assert collector.blocks[1] == block2
assert chunks.pending == 0
}
struct TestReader {
debug bool
mut:
dirs []string
files map[string]u64
data map[string][]u8
texts map[string]string
other []string
last_file string
last_data []u8
}
// new_test_reader reads files *.tar
fn new_test_reader(tar_file string, debug bool) !&TestReader {
mut reader := &TestReader{
debug: debug
}
read_tar_file('${testdata}/${tar_file}', reader)!
return reader
}
// new_test_reader_gz reads files *.tar.gz
fn new_test_reader_gz(tar_gz_file string, debug bool) !&TestReader {
mut reader := &TestReader{
debug: debug
}
read_tar_gz_file('${testdata}/${tar_gz_file}', reader)!
return reader
}
fn (mut t TestReader) dir_block(mut read Read, size u64) {
t.dirs << read.get_path()
if t.debug {
println('DIR #${read.get_block_number()} ${read.get_path()}')
}
}
fn (mut t TestReader) file_block(mut read Read, size u64) {
t.last_file = read.get_path()
t.files[t.last_file] = size
if t.debug {
println('FILE #${read.get_block_number()} ${read.get_path()}')
}
}
fn (mut t TestReader) data_block(mut read Read, data []u8, pending int) {
path := read.get_path()
if t.debug {
println('DATA #${read.get_block_number()} ${path}')
}
if t.last_file == path {
t.last_data << data
if pending == 0 {
t.data[t.last_file] = t.last_data.clone()
t.texts[path] = t.last_data.bytestr()
if t.debug {
println('TEXT #${read.get_block_number()} ${t.last_data.bytestr()}')
}
t.last_file = ''
t.last_data.clear()
}
}
}
fn (mut t TestReader) other_block(mut read Read, details string) {
t.other << 'block:${read.block_number} special:${read.special} ${details}'
if t.debug {
println('OTHER #${read.get_block_number()} special:${read.special} ${details}')
}
}
fn test_chunks_reader_moves_pending_bytes_in_order() {
mut all := []u8{len: 1024}
for i := 0; i < all.len; i++ {
all[i] = u8(i % 251)
}
test_chunk_blocks = [][]u8{}
mut chunk_reader := ChunksReader{
read_block_fn: collect_test_block
}
assert chunk_reader.read_blocks(all[0..700]) == .continue
assert chunk_reader.pending == 188
assert chunk_reader.read_blocks(all[700..]) == .continue
assert chunk_reader.pending == 0
assert test_chunk_blocks.len == 2
assert test_chunk_blocks[0] == all[0..512]
assert test_chunk_blocks[1] == all[512..]
}
+43
View File
@@ -0,0 +1,43 @@
module tar
// ustart header block octets
// Field | Offset | Length
// --------------------------
// name | 0 | 100
// mode | 100 | 8
// uid | 108 | 8
// gid | 116 | 8
// size | 124 | 12
// mtime | 136 | 12
// chksum | 148 | 8
// typeflag | 156 | 1
// linkname | 157 | 100
// magic | 257 | 6
// version | 263 | 2
// uname | 265 | 32
// gname | 297 | 32
// devmajor | 329 | 8
// devminor | 337 | 8
// prefix | 345 | 155
pub enum BlockHeader as u8 {
file = u8(`0`) // 0x30
hard_link = u8(`1`) // 0x31
sym_link = u8(`2`) // 0x32
char_dev = u8(`3`) // 0x33
block_dev = u8(`4`) // 0x34
dir = u8(`5`) // 0x35
fifo = u8(`6`) // 0x36
long_name = u8(`L`) // 0x4c = 76 dec
global = u8(`g`) // 0x67 pax
}
pub enum BlockSpecial {
no // for headers `0`,`5` or data blocks
blank_1 // first blank block: continue
blank_2 // second blank block: end of archiv
ignore // for headers `1`, `2`, `3`, `4`, `6`
long_name // for header `L`
global // for header `g`
unknown // for not header defined
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+291
View File
@@ -0,0 +1,291 @@
module tar
// Untar uses a reader to parse the contents of a unix tar file.
// Reuses a fixed array of 512 bytes to parse each TAR block.
@[heap]
pub struct Untar {
mut:
reader Reader
max_blocks int
buffer [512]u8 // data to parse block
read Read // last read to send/receive to/from reader implementation
state State // true when reading data blocks or long names
size int // remaining data size during state_data
long_path &LongPath = unsafe { nil } // not nil to hold a file long_name
blank_block int = -1 // last no-data block with all-zeros
}
enum State {
header
data
long_path
}
// new_untar builds a untar with a given Reader.
pub fn new_untar(reader Reader) &Untar {
return &Untar{
reader: reader
}
}
// str returns a string representation with max_blocks and last read.
pub fn (u Untar) str() string {
return 'max_blocks:${u.max_blocks} last_read:${u.read}'
}
// read_all_blocks parses the data blocks of any decompressed *.tar.gz array.
// The data blocks length must be divisible by 512.
pub fn (mut u Untar) read_all_blocks(blocks []u8) !ReadResult {
if blocks.len % 512 != 0 {
return error('data_blocks size is not a multiple of 512')
}
u.max_blocks = blocks.len / 512
for i := 0; i < blocks.len; i += 512 {
result := u.read_single_block(blocks[i..i + 512])!
if result != .continue {
return result
}
}
return .end_of_file
}
// read_single_block parses one data block at a time.
// The data block length must be 512. Two consecutive no data blocks
// have 512 zeroes returns a .end_archive result.
pub fn (mut u Untar) read_single_block(block []u8) !ReadResult {
if block.len != 512 {
return error('data_block size is not 512')
}
u.read.block_number++ // 1,2,3...
mut is_blank_block := true
for i in 0 .. 512 {
u.buffer[i] = block[i]
if block[i] != 0 {
is_blank_block = false
}
}
match u.state {
.header {
if is_blank_block {
// current non-data block is a blank block
prev_block := u.read.block_number - 1
result := if u.blank_block == prev_block {
// two consecutive blank blocks
u.read.special = .blank_2
ReadResult.end_archive
} else {
// first blank block
u.read.special = .blank_1
ReadResult.continue
}
u.read.path_len = 0
u.reader.other_block(mut u.read, '${result}')
u.blank_block = u.read.block_number
return result
}
u.read_header()!
}
.data {
u.read_data()
}
.long_path {
u.read_long_path()
}
}
return if u.read.stop_early {
.stop_early
} else {
.continue
}
}
fn (mut u Untar) read_header() ! {
u.size = int(u.extract_octal(124, 12))
header := u.buffer[156] // pos 0x9c
block_header := BlockHeader.from(header) or {
u.read.special = .unknown
u.read.path_len = 0
u.reader.other_block(mut u.read, 'size:${u.size}')
return
}
match block_header {
.dir {
if !u.checksum_ok() {
return error('Checksum error: directory reading:${u.read}')
}
u.read.special = .no
u.read.set_short_path(u.buffer, false)
u.reader.dir_block(mut u.read, u64(u.size))
// u.state = .header
}
.file {
if !u.checksum_ok() {
return error('Checksum error file reading:${u.read}')
}
u.read.special = .no
if u.long_path != unsafe { nil } {
u.read.set_long_path(u.long_path)
if u.size > 0 {
u.state = .data
}
} else {
u.read.set_short_path(u.buffer, true)
if u.size > 0 {
u.state = .data
}
}
u.reader.file_block(mut u.read, u64(u.size))
}
.long_name {
u.read.special = .long_name
u.reader.other_block(mut u.read, 'size:${u.size}')
if u.size > 0 {
u.state = .long_path
u.long_path = new_long_path(u.size)
}
}
.hard_link, .sym_link, .char_dev, .block_dev, .fifo {
u.read.special = .ignore
u.reader.other_block(mut u.read, block_header.str())
}
.global {
u.read.special = .global
u.read.set_short_path(u.buffer, false)
u.reader.other_block(mut u.read, 'size:${u.size}')
if u.size > 0 {
u.state = .data
}
}
}
}
// reader_data calls Reader.data_block for implementor to collect data parts as file content
fn (mut u Untar) read_data() {
if u.size > 0 {
part := if u.size > 512 { 512 } else { u.size }
u.size -= 512
pending := if u.size > 0 { u.size } else { 0 }
data_part := u.buffer[0..part]
u.reader.data_block(mut u.read, data_part, pending)
}
if u.size <= 0 {
u.long_path = unsafe { nil }
u.read.long_path = unsafe { nil } // real clear
u.state = .header
}
}
fn (mut u Untar) read_long_path() {
if u.size > 0 {
part := if u.size > 512 { 512 } else { u.size }
u.size -= 512
data_part := u.buffer[0..part]
if u.long_path != unsafe { nil } {
// this long path field collects the data parts as file long name
u.long_path.append(data_part)
u.reader.other_block(mut u.read, 'data_part:${data_part.len}')
}
}
if u.size <= 0 {
u.state = .header
}
}
// extract_path returns the block path for directories and files.
fn (mut u Untar) extract_path() string {
mut name := []u8{}
mut i := 0
for {
if i >= u.buffer.len {
break
}
letter := u.buffer[i]
if letter == 0 {
break
}
name << letter
i++
}
return name.bytestr()
}
// checksum_ok verifies the validity for dir and files blocks.
fn (mut u Untar) checksum_ok() bool {
mut v := u64(0)
for n := 0; n < 512; n++ {
if n < 148 || n > 155 {
v += u.buffer[n]
} else {
v += 0x20
}
}
parse := u.extract_octal(148, 8)
return v == parse
}
// extract_octal reads an octal number at block position `pos` with a given number of `digits`.
fn (mut u Untar) extract_octal(pos int, digits int) u64 {
mut i := u64(0)
mut p := pos
mut n := digits
for {
if (u.buffer[p] < `0` || u.buffer[p] > `7`) && n > 0 {
p++
n--
} else {
break
}
}
for {
if u.buffer[p] >= `0` && u.buffer[p] <= `7` && n > 0 {
i *= 8
i += u8(u.buffer[p] - `0`)
p++
n--
} else {
break
}
}
return i
}
@[heap]
struct LongPath {
mut:
name []u8
last_pos int
}
// new_long_path builds a LongPath with a fixed maximum name size
fn new_long_path(size int) &LongPath {
return &LongPath{
name: []u8{len: size}
}
}
// appends copies the data to the
fn (mut l LongPath) append(data []u8) {
if l.name.len >= l.last_pos + data.len {
for i, d in data {
l.name[l.last_pos + i] = d
}
l.last_pos += data.len
}
}
// get_path returns the string from name appended as C string.
fn (l LongPath) get_path() string {
mut s := []u8{}
for n in l.name {
if n == 0 {
break
}
s << n
}
return s.bytestr()
}
+16
View File
@@ -0,0 +1,16 @@
## Description
`arrays` is a module that provides utility functions to make working with arrays easier.
## Examples
```v
import arrays
fn main() {
a := [1, 5, 7, 0, 9]
assert arrays.min(a)! == 0
assert arrays.max(a)! == 9
assert arrays.idx_min(a)! == 3
}
```
+783
View File
@@ -0,0 +1,783 @@
module arrays
import strings
// Common arrays functions:
// - min / max - return the value of the minimum / maximum
// - idx_min / idx_max - return the index of the first minimum / maximum
// - merge - combine two sorted arrays and maintain sorted order
// - append - combine two arrays, by appending the second array to the first
// - chunk - chunk array to arrays with n elements
// - window - get snapshots of the window of the given size sliding along array with the given step, where each snapshot is an array
// - group - merge two arrays by interleaving e.g. arrays.group([1,3,5], [2,4,6]) => [[1,2],[3,4],[5,6]]
// - flatten - reduce dimensionality of array by one. e.g. arrays.flatten([[1,2],[3,4],[5,6]]) => [1,2,3,4,5,6]
// - each - call a callback fn, for each element of the array, similar to a.map(), but unlike it, the callback should not return anything
// min returns the minimum value in the array.
// Example: arrays.min([1, 2, 3, 0, 9])! // => 0
pub fn min[T](array []T) !T {
if array.len == 0 {
return error('.min called on an empty array')
}
mut val := array[0]
for e in array {
if e < val {
val = e
}
}
return val
}
// max returns the maximum value in the array.
// Example: arrays.max([1, 2, 3, 0, 9])! // => 9
pub fn max[T](array []T) !T {
if array.len == 0 {
return error('.max called on an empty array')
}
mut val := array[0]
for e in array {
if e > val {
val = e
}
}
return val
}
// idx_min returns the index of the minimum value in the array.
// Example: arrays.idx_min([1, 2, 3, 0, 9])! // => 3
pub fn idx_min[T](array []T) !int {
if array.len == 0 {
return error('.idx_min called on an empty array')
}
mut idx := 0
mut val := array[0]
for i, e in array {
if e < val {
val = e
idx = i
}
}
return idx
}
// idx_max returns the index of the maximum value in the array.
// Example: arrays.idx_max([1, 2, 3, 0, 9])! // => 4
pub fn idx_max[T](array []T) !int {
if array.len == 0 {
return error('.idx_max called on an empty array')
}
mut idx := 0
mut val := array[0]
for i, e in array {
if e > val {
val = e
idx = i
}
}
return idx
}
// merge two sorted arrays (ascending) and maintain sorted order.
// Example: arrays.merge([1, 3, 5, 7], [2, 4, 6, 8]) // => [1, 2, 3, 4, 5, 6, 7, 8]
@[direct_array_access]
pub fn merge[T](a []T, b []T) []T {
mut m := []T{len: a.len + b.len}
mut ia := 0
mut ib := 0
mut j := 0
// TODO: efficient approach to merge_desc where: a[ia] >= b[ib]
for ia < a.len && ib < b.len {
if a[ia] <= b[ib] {
m[j] = a[ia]
ia++
} else {
m[j] = b[ib]
ib++
}
j++
}
// a leftovers
for ia < a.len {
m[j] = a[ia]
ia++
j++
}
// b leftovers
for ib < b.len {
m[j] = b[ib]
ib++
j++
}
return m
}
// append the second array `b` to the first array `a`, and return the result.
// Note, that unlike arrays.concat, arrays.append is less flexible, but more efficient,
// since it does not require you to use ...a for the second parameter.
// Example: arrays.append([1, 3, 5, 7], [2, 4, 6, 8]) // => [1, 3, 5, 7, 2, 4, 6, 8]
pub fn append[T](a []T, b []T) []T {
mut m := []T{cap: a.len + b.len}
m << a
m << b
return m
}
// group n arrays into a single array of arrays with n elements.
// This function is analogous to the "zip" function of other languages.
// To fully interleave two arrays, follow this function with a call to `flatten`.
// NOTE: An error will be generated if the type annotation is omitted.
// Example: arrays.group[int]([1, 2, 3], [4, 5, 6]) // => [[1, 4], [2, 5], [3, 6]]
pub fn group[T](arrs ...[]T) [][]T {
mut length := if arrs.len > 0 { arrs[0].len } else { 0 }
// calculate length of output by finding shortest input array
for ndx in 1 .. arrs.len {
if arrs[ndx].len < length {
length = arrs[ndx].len
}
}
if length > 0 {
mut arr := [][]T{cap: length}
// append all combined arrays into the resultant array
for ndx in 0 .. length {
mut grouped := []T{cap: arrs.len}
// combine each list item for the ndx position into one array
for arr_ndx in 0 .. arrs.len {
grouped << arrs[arr_ndx][ndx]
}
arr << grouped
}
return arr
}
return [][]T{}
}
// chunk array into a single array of arrays where each element is the next `size` elements of the original.
// Example: arrays.chunk([1, 2, 3, 4, 5, 6, 7, 8, 9], 2) // => [[1, 2], [3, 4], [5, 6], [7, 8], [9]]
pub fn chunk[T](array []T, size int) [][]T {
// allocate chunk array
mut chunks := [][]T{cap: array.len / size + if array.len % size == 0 { 0 } else { 1 }}
for i := 0; true; {
// check chunk size is greater than remaining element size
if array.len < i + size {
// check if there's no more element to chunk
if array.len <= i {
break
}
chunks << array[i..]
break
}
chunks << array[i..i + size]
i += size
}
return chunks
}
// chunk_while splits the input array `a` into chunks of varying length, using the `predicate`, passing to it pairs of adjacent elements `before` and `after`.
// Each chunk, will contain all ajdacent elements, for which the `predicate` returned true.
// The chunks are split *between* the `before` and `after` elements, for which the `predicate` returned false.
// Example: assert arrays.chunk_while([0,9,2,2,3,2,7,5,9,5],fn(x int,y int)bool{return x<=y})==[[0,9],[2,2,3],[2,7],[5,9],[5]]
// Example: assert arrays.chunk_while('aaaabbbcca'.runes(),fn(x rune,y rune)bool{return x==y})==[[`a`,`a`,`a`,`a`],[`b`,`b`,`b`],[`c`,`c`],[`a`]]
// Example: assert arrays.chunk_while('aaaabbbcca'.runes(),fn(x rune,y rune)bool{return x==y}).map({it[0]:it.len})==[{`a`:4},{`b`:3},{`c`:2},{`a`:1}]
pub fn chunk_while[T](a []T, predicate fn (before T, after T) bool) [][]T {
if a.len == 0 {
return []
}
mut chunks := [][]T{}
mut current := [a[0]]
mut i := 0
for i = 1; i < a.len; i++ {
if predicate(a[i - 1], a[i]) {
current << a[i]
continue
}
if current.len > 0 {
chunks << current
}
current = [a[i]]
}
if current.len > 0 {
chunks << current
}
return chunks
}
pub struct WindowAttribute {
pub:
size int
step int = 1
}
// get snapshots of the window of the given size sliding along array with the given step, where each snapshot is an array.
// - `size` - snapshot size
// - `step` - gap size between each snapshot, default is 1.
//
// Example: arrays.window([1, 2, 3, 4], size: 2) // => [[1, 2], [2, 3], [3, 4]]
// Example: arrays.window([1, 2, 3, 4, 5, 6, 7, 8, 9, 10], size: 3, step: 2) // => [[1, 2, 3], [3, 4, 5], [5, 6, 7], [7, 8, 9]]
pub fn window[T](array []T, attr WindowAttribute) [][]T {
if array.len == 0 {
return [][]T{}
}
// allocate snapshot array
mut windows := [][]T{cap: array.len - attr.size + 1}
for i := 0; true; {
// check remaining elements size is less than snapshot size
if array.len < i + attr.size {
break
}
windows << array[i..i + attr.size]
i += attr.step
}
return windows
}
// sum up array, return an error, when the array has no elements.
// Example: arrays.sum([1, 2, 3, 4, 5])! // => 15
pub fn sum[T](array []T) !T {
if array.len == 0 {
return error('Cannot sum up array of nothing.')
} else {
mut head := array[0]
for e in array[1..] {
head += e
}
return head
}
}
// reduce sets `acc = array[0]`, then successively calls `acc = reduce_op(acc, elem)` for each remaining element in `array`.
// returns the accumulated value in `acc`.
// returns an error if the array is empty.
// See also: [fold](#fold).
// Example: arrays.reduce([1, 2, 3, 4, 5], fn (t1 int, t2 int) int { return t1 * t2 })! // => 120
pub fn reduce[T](array []T, reduce_op fn (acc T, elem T) T) !T {
if array.len == 0 {
return error('Cannot reduce array of nothing.')
} else {
mut value := array[0]
for e in array[1..] {
value = reduce_op(value, e)
}
return value
}
}
// reduce_indexed sets `acc = array[0]`, then successively calls `acc = reduce_op(idx, acc, elem)` for each remaining element in `array`.
// returns the accumulated value in `acc`.
// returns an error if the array is empty.
// See also: [fold_indexed](#fold_indexed).
pub fn reduce_indexed[T](array []T, reduce_op fn (idx int, acc T, elem T) T) !T {
if array.len == 0 {
return error('Cannot reduce array of nothing.')
} else {
mut value := array[0]
for i, e in array {
if i == 0 {
continue
} else {
value = reduce_op(i, value, e)
}
}
return value
}
}
// filter_indexed filters elements based on `predicate` function being invoked on each element with its index in the original array.
pub fn filter_indexed[T](array []T, predicate fn (idx int, elem T) bool) []T {
mut result := []T{cap: array.len}
for i, e in array {
if predicate(i, e) {
result << e
}
}
return result
}
// fold sets `acc = init`, then successively calls `acc = fold_op(acc, elem)` for each element in `array`.
// returns `acc`.
// Example:
// ```v
// // Sum the length of each string in an array
// a := ['Hi', 'all']
// r := arrays.fold[string, int](a, 0,
// fn (r int, t string) int { return r + t.len })
// assert r == 5
// ```
pub fn fold[T, R](array []T, init R, fold_op fn (acc R, elem T) R) R {
$if R is $array {
mut value := init.clone()
for e in array {
value = fold_op(value, e)
}
return value
} $else {
mut value := init
for e in array {
value = fold_op(value, e)
}
return value
}
}
// fold_indexed sets `acc = init`, then successively calls `acc = fold_op(idx, acc, elem)` for each element in `array`.
// returns `acc`.
pub fn fold_indexed[T, R](array []T, init R, fold_op fn (idx int, acc R, elem T) R) R {
mut value := init
for i, e in array {
value = fold_op(i, value, e)
}
return value
}
// flatten flattens n + 1 dimensional array into n dimensional array.
// Example: arrays.flatten[int]([[1, 2, 3], [4, 5]]) // => [1, 2, 3, 4, 5]
pub fn flatten[T](array [][]T) []T {
// calculate required capacity
mut required_size := 0
for e1 in array {
for _ in e1 {
required_size += 1
}
}
// allocate flattened array
mut result := []T{cap: required_size}
for e1 in array {
for e2 in e1 {
result << e2
}
}
return result
}
// flat_map creates a new array populated with the flattened result of calling transform function being invoked on each element of `list`.
pub fn flat_map[T, R](array []T, transform fn (elem T) []R) []R {
mut result := [][]R{cap: array.len}
for v in array {
result << transform(v)
}
return flatten(result)
}
// flat_map_indexed creates a new array with the flattened result of calling the `transform` fn, invoked on each idx,elem pair from the original.
pub fn flat_map_indexed[T, R](array []T, transform fn (idx int, elem T) []R) []R {
mut result := [][]R{cap: array.len}
for i, v in array {
result << transform(i, v)
}
return flatten(result)
}
// map_indexed creates a new array with the result of calling the `transform` fn, invoked on each idx,elem pair from the original.
pub fn map_indexed[T, R](array []T, transform fn (idx int, elem T) R) []R {
mut result := []R{cap: array.len}
for i, v in array {
result << transform(i, v)
}
return result
}
// group_by groups together elements, for which the `grouping_op` callback produced the same result.
// Example: arrays.group_by[int, string](['H', 'el', 'lo'], fn (v string) int { return v.len }) // => {1: ['H'], 2: ['el', 'lo']}
pub fn group_by[K, V](array []V, grouping_op fn (val V) K) map[K][]V {
mut result := map[K][]V{}
for v in array {
key := grouping_op(v)
// check if key exists, if not, then create a new array with matched value, otherwise append.
if key in result {
result[key] << v
} else {
result[key] = [v]
}
}
return result
}
// concatenate an array with an arbitrary number of additional values.
// NOTE: if you have two arrays, you should simply use the `<<` operator directly.
// Example: assert arrays.concat([1, 2, 3], 4, 5, 6) == [1, 2, 3, 4, 5, 6]
// Example: assert arrays.concat([1, 2, 3], ...[4, 5, 6]) == [1, 2, 3, 4, 5, 6]
// Example: mut arr := arrays.concat([1, 2, 3], 4); arr << [10,20]; assert arr == [1,2,3,4,10,20] // note: arr is mutable
pub fn concat[T](a []T, b ...T) []T {
mut m := []T{cap: a.len + b.len}
m << a
m << b
return m
}
// returns the smallest element >= val, requires `array` to be sorted.
// Example: arrays.lower_bound([2, 4, 6, 8], 3)! // => 4
pub fn lower_bound[T](array []T, val T) !T {
if array.len == 0 {
return error('.lower_bound called on an empty array')
}
mut left, mut right := 0, array.len - 1
for ; left <= right; {
idx := (left + right) / 2
elem := array[idx]
if elem < val {
left = idx + 1
} else {
right = idx - 1
}
}
if left >= array.len {
return error('')
} else {
return array[left]
}
}
// returns the largest element <= val, requires `array` to be sorted.
// Example: arrays.upper_bound([2, 4, 6, 8], 3)! // => 2
pub fn upper_bound[T](array []T, val T) !T {
if array.len == 0 {
return error('.upper_bound called on an empty array')
}
mut left, mut right := 0, array.len - 1
for ; left <= right; {
idx := (left + right) / 2
elem := array[idx]
if elem > val {
right = idx - 1
} else {
left = idx + 1
}
}
if right < 0 {
return error('')
} else {
return array[right]
}
}
// binary_search, requires `array` to be sorted, returns index of found item or error.
// Binary searches on sorted lists can be faster than other array searches because at maximum
// the algorithm only has to traverse log N elements
// Example: arrays.binary_search([1, 2, 3, 4], 4)! // => 3
pub fn binary_search[T](array []T, target T) !int {
mut left := 0
mut right := array.len - 1
for ; left <= right; {
idx := (left + right) / 2
elem := array[idx]
if elem == target {
return idx
}
if elem < target {
left = idx + 1
} else {
right = idx - 1
}
}
return error('')
}
// rotate_left rotates the array in-place.
// It does it in such a way, that the first `mid` elements of the array, move to the end,
// while the last `array.len - mid` elements move to the front.
// After calling `rotate_left`, the element previously at index `mid` will become the first element in the array.
// Example:
// ```v
// mut x := [1,2,3,4,5,6]
// arrays.rotate_left(mut x, 2)
// println(x) // [3, 4, 5, 6, 1, 2]
// ```
pub fn rotate_left[T](mut array []T, mid int) {
assert mid <= array.len && mid >= 0
k := array.len - mid
p := unsafe { &T(array.data) }
unsafe {
ptr_rotate[T](mid, &T(usize(voidptr(p)) + usize(sizeof(T)) * usize(mid)), k)
}
}
// rotate_right rotates the array in-place.
// It does it in such a way, that the first `array.len - k` elements of the array, move to the end,
// while the last `k` elements move to the front.
// After calling `rotate_right`, the element previously at index `array.len - k` will become the first element in the array.
// Example:
// ```v
// mut x := [1,2,3,4,5,6]
// arrays.rotate_right(mut x, 2)
// println(x) // [5, 6, 1, 2, 3, 4]
// ```
pub fn rotate_right[T](mut array []T, k int) {
assert k <= array.len && k >= 0
mid := array.len - k
p := unsafe { &T(array.data) }
unsafe {
ptr_rotate[T](mid, &T(usize(voidptr(p)) + usize(sizeof(T)) * usize(mid)), k)
}
}
@[unsafe]
fn ptr_rotate[T](left_ int, mid &T, right_ int) {
sz := usize(sizeof(T))
mut left := usize(left_)
mut right := usize(right_)
limit := raw_array_cap[T]()
for {
delta := if left < right { left } else { right }
if delta <= usize(limit) {
break
}
unsafe {
swap_nonoverlapping[T](&T(usize(voidptr(mid)) - left * sz), &T(usize(voidptr(mid)) +
usize(right - delta) * sz), int(delta))
}
if left <= right {
right -= delta
} else {
left -= delta
}
}
unsafe {
rawarray := malloc(raw_array_malloc_size[T]())
defer {
free(rawarray)
}
dim := &T(usize(voidptr(mid)) - left * sz + right * sz)
if left <= right {
vmemcpy(rawarray, voidptr(usize(voidptr(mid)) - left * sz), isize(left * sz))
vmemmove(voidptr(usize(voidptr(mid)) - left * sz), voidptr(mid), isize(right * sz))
vmemcpy(voidptr(dim), rawarray, isize(left * sz))
} else {
vmemcpy(rawarray, voidptr(mid), isize(right * sz))
vmemmove(voidptr(dim), voidptr(usize(voidptr(mid)) - left * sz), isize(left * sz))
vmemcpy(voidptr(usize(voidptr(mid)) - left * sz), rawarray, isize(right * sz))
}
}
}
struct Block {
mut:
x u64
y u64
z u64
w u64
}
struct UnalignedBlock {
mut:
x u64
y u64
z u64
w u64
}
const extra_size = 32 * isize(sizeof(usize))
fn raw_array_cap[T]() isize {
size := isize(sizeof(T))
if size > extra_size {
return 1
} else {
return extra_size / size
}
}
fn raw_array_malloc_size[T]() isize {
size := isize(sizeof(T))
if size > extra_size {
return size * 2
} else {
return extra_size
}
}
@[unsafe]
fn memswap(x voidptr, y voidptr, len usize) {
block_size := isize(sizeof(Block))
mut i := usize(0)
for i + usize(block_size) <= len {
mut t_ := Block{}
t := voidptr(&t_)
xi := usize(x) + i
yi := usize(y) + i
unsafe {
vmemcpy(t, voidptr(xi), block_size)
vmemcpy(voidptr(xi), voidptr(yi), block_size)
vmemcpy(voidptr(yi), t, block_size)
}
i += usize(block_size)
}
if i < len {
mut t_ := UnalignedBlock{}
t := voidptr(&t_)
rem := isize(len - i)
xi := usize(x) + i
yi := usize(y) + i
unsafe {
vmemcpy(t, voidptr(xi), rem)
vmemcpy(voidptr(xi), voidptr(yi), rem)
vmemcpy(voidptr(yi), t, rem)
}
}
}
@[unsafe]
fn swap_nonoverlapping[T](x_ &T, y_ &T, count int) {
x := voidptr(x_)
y := voidptr(y_)
len := usize(sizeof(T)) * usize(count)
unsafe {
memswap(x, y, len)
}
}
// copy copies the `src` array elements to the `dst` array.
// The number of the elements copied is the minimum of the length of both arrays.
// Returns the number of elements copied.
pub fn copy[T](mut dst []T, src []T) int {
min_len := if dst.len < src.len { dst.len } else { src.len }
if min_len <= 0 {
return 0
}
if can_copy_bits[T]() {
blen := min_len * isize(sizeof(T))
unsafe { vmemmove(&T(dst.data), src.data, blen) }
} else {
for i in 0 .. min_len {
dst[i] = src[i]
}
}
return min_len
}
// can_copy_bits determines if T can be copied using `memcpy`.
// false if autofree needs to intervene
// false if type is not copyable e.g. map
fn can_copy_bits[T]() bool {
// references, C pointers, integers, floats, runes
if T.name[0] in [`&`, `b`, `c`, `f`, `i`, `r`, `u`, `v`] {
return true
}
return false
}
// carray_to_varray copies a C byte array into a V array of type `T`.
// See also: `cstring_to_vstring`
@[unsafe]
pub fn carray_to_varray[T](c_array_data voidptr, items int) []T {
mut v_array := []T{len: items}
total_size := items * isize(sizeof(T))
unsafe { vmemcpy(v_array.data, c_array_data, total_size) }
return v_array
}
// find_first returns the first element that matches the given predicate.
// Returns `none` if no match is found.
// Example: arrays.find_first([1, 2, 3, 4, 5], fn (i int) bool { return i == 3 })? // => 3
pub fn find_first[T](array []T, predicate fn (elem T) bool) ?T {
if array.len == 0 {
return none
}
for item in array {
if predicate(item) {
return item
}
}
return none
}
// find_last returns the last element that matches the given predicate.
// Returns `none` if no match is found.
// Example: arrays.find_last([1, 2, 3, 4, 5], fn (i int) bool { return i == 3})? // => 3
pub fn find_last[T](array []T, predicate fn (elem T) bool) ?T {
if array.len == 0 {
return none
}
for idx := array.len - 1; idx >= 0; idx-- {
item := array[idx]
if predicate(item) {
return item
}
}
return none
}
// join_to_string takes in a custom transform function and joins all elements into a string with
// the specified separator
@[manualfree]
pub fn join_to_string[T](array []T, separator string, transform fn (elem T) string) string {
mut sb := strings.new_builder(array.len * 2)
defer {
unsafe { sb.free() }
}
for i, item in array {
x := transform(item)
sb.write_string(x)
unsafe { x.free() }
if i < array.len - 1 {
sb.write_string(separator)
}
}
return sb.str()
}
// partition splits the original array into pair of lists.
// The first list contains elements for which the predicate fn returned true,
// while the second list contains elements for which the predicate fn returned false.
pub fn partition[T](array []T, predicate fn (elem T) bool) ([]T, []T) {
mut matching, mut non_matching := []T{}, []T{}
for item in array {
if predicate(item) {
matching << item
} else {
non_matching << item
}
}
return matching, non_matching
}
// each calls the callback fn `cb`, for each element of the given array `a`.
pub fn each[T](a []T, cb fn (elem T)) {
for item in a {
cb(item)
}
}
// each_indexed calls the callback fn `cb`, for each element of the given array `a`.
// It passes the callback both the index of the current element, and the element itself.
pub fn each_indexed[T](a []T, cb fn (i int, e T)) {
for idx, item in a {
cb(idx, item)
}
}
+41
View File
@@ -0,0 +1,41 @@
import arrays
type MyInt = int
interface FoldCyclable {
id() int
}
struct FoldWheel {
v int
}
fn (w FoldWheel) id() int {
return w.v
}
fn test_main() {
assert arrays.fold[int, []int]([1, 2, 3, 4], []int{}, fn (r []int, t int) []int {
return arrays.merge(r, [t])
}) == [1, 2, 3, 4]
assert arrays.fold[string, []string](['a', 'b', 'c', 'd'], []string{}, fn (r []string, t string) []string {
return arrays.merge(r, [t])
}) == ['a', 'b', 'c', 'd']
assert arrays.fold[bool, []bool]([true, false], []bool{}, fn (r []bool, t bool) []bool {
return arrays.merge(r, [t])
}) == [false, true]
assert arrays.fold[MyInt, []MyInt]([MyInt(0), 1], []MyInt{}, fn (r []MyInt, t MyInt) []MyInt {
return arrays.merge(r, [t])
}) == [MyInt(0), 1]
assert arrays.fold([1, 2, 3, 4], [5, 6, 7], fn (r []int, t int) []int {
return r.map(it * t)
}) == [120, 144, 168]
}
fn test_fold_with_interface_accumulator() {
items := [FoldWheel{1}, FoldWheel{2}]
result := arrays.fold[FoldWheel, FoldCyclable](items, FoldCyclable(FoldWheel{}), fn (_ FoldCyclable, elem FoldWheel) FoldCyclable {
return elem
})
assert result.id() == 2
}
+606
View File
@@ -0,0 +1,606 @@
module arrays
fn test_min() {
a := [8, 2, 6, 4]
mut ri := min(a)!
assert ri == 2
ri = min(a[2..])!
assert ri == 4
b := [f32(5.1), 3.1, 1.1, 9.1]
mut rf := min(b)!
assert rf == f32(1.1)
rf = min(b[..2])!
assert rf == f32(3.1)
c := [u8(4), 9, 3, 1]
mut rb := min(c)!
assert rb == u8(1)
rb = min(c[..3])!
assert rb == u8(3)
}
fn test_max() {
a := [8, 2, 6, 4]
mut ri := max(a)!
assert ri == 8
ri = max(a[1..])!
assert ri == 6
b := [f32(5.1), 3.1, 1.1, 9.1]
mut rf := max(b)!
assert rf == f32(9.1)
rf = max(b[..3])!
assert rf == f32(5.1)
c := [u8(4), 9, 3, 1]
mut rb := max(c)!
assert rb == u8(9)
rb = max(c[2..])!
assert rb == u8(3)
}
fn test_idx_min() {
a := [8, 2, 6, 4]
ri := idx_min(a)!
assert ri == 1
b := [f32(5.1), 3.1, 1.1, 9.1]
rf := idx_min(b)!
assert rf == 2
c := [u8(4), 9, 3, 1]
rb := idx_min(c)!
assert rb == 3
}
fn test_idx_max() {
a := [8, 2, 6, 4]
ri := idx_max(a)!
assert ri == 0
b := [f32(5.1), 3.1, 1.1, 9.1]
rf := idx_max(b)!
assert rf == 3
c := [u8(4), 9, 3, 1]
rb := idx_max(c)!
assert rb == 1
}
fn test_merge() {
a := [1, 3, 5, 5, 7]
b := [2, 4, 4, 5, 6, 8]
c := []int{}
d := []int{}
assert merge[int](a, b) == [1, 2, 3, 4, 4, 5, 5, 5, 6, 7, 8]
assert merge[int](c, d) == []
assert merge[int](a, c) == a
assert merge[int](d, b) == b
}
fn test_append() {
a := [1, 3, 5, 5, 7]
b := [2, 4, 4, 5, 6, 8]
assert append(a, b) == [1, 3, 5, 5, 7, 2, 4, 4, 5, 6, 8]
assert append(b, a) == [2, 4, 4, 5, 6, 8, 1, 3, 5, 5, 7]
assert append(a, []int{}) == a
assert append([]int{}, b) == b
}
fn test_fixed_array_assignment() {
mut a := [2]int{}
a[0] = 111
a[1] = 222
b := a
assert b[0] == a[0]
assert b[1] == a[1]
mut c := [2]int{}
c = a
assert c[0] == a[0]
assert c[1] == a[1]
d := [3]int{init: 333}
for val in d {
assert val == 333
}
e := [3]string{init: 'vlang'}
for val in e {
assert val == 'vlang'
}
}
fn test_array_flat_map() {
a := ['Hello V', 'Hello World', 'V Lang']
assert flat_map[string, string](a, fn (e string) []string {
return e.split(' ')
}) == ['Hello', 'V', 'Hello', 'World', 'V', 'Lang']
}
fn test_array_flat_map_indexed() {
a := ['AB', 'CD', 'EF']
assert flat_map_indexed[string, string](a, fn (i int, e string) []string {
mut arr := [i.str()]
arr << e.split('')
return arr
}) == ['0', 'A', 'B', '1', 'C', 'D', '2', 'E', 'F']
}
fn test_map_indexed() {
a := [1, 2, 3]
assert map_indexed[int, int](a, fn (i int, e int) int {
return i + e * e
}) == [1, 5, 11]
}
fn test_group() {
x := [4, 5, 6]
y := [2, 1, 3]
z := group[int](x, y)
assert z == [[4, 2], [5, 1], [6, 3]]
x2 := [8, 9]
z2 := group[int](x2, y)
assert z2 == [[8, 2], [9, 1]]
assert group[int](x, []int{}) == [][]int{}
}
fn test_chunk() {
x := [1, 2, 3, 4, 5]
y := ['a', 'b', 'c', 'd', 'e', 'f']
z1 := chunk[int](x, 2)
assert z1 == [[1, 2], [3, 4], [5]]
z2 := chunk[string](y, 3)
assert z2 == [['a', 'b', 'c'], ['d', 'e', 'f']]
assert chunk[int]([]int{}, 2) == [][]int{}
}
fn test_chunk_while() {
assert chunk_while([0, 9, 2, 2, 3, 2, 7, 5, 9, 5], fn (x int, y int) bool {
return x == y
}) == [[0], [9], [2, 2], [3], [2], [7], [5], [9], [5]]
assert chunk_while([0, 9, 2, 2, 3, 2, 7, 5, 9, 5], fn (x int, y int) bool {
return x <= y
}) == [[0, 9], [2, 2, 3], [2, 7], [5, 9], [5]]
assert chunk_while('aaaabbbcca'.runes(), fn (x rune, y rune) bool {
return x == y
}).map({
it[0]: it.len
}).str() == '[{`a`: 4}, {`b`: 3}, {`c`: 2}, {`a`: 1}]'
}
fn test_window() {
x := [1, 2, 3, 4, 5, 6]
assert window[int](x, size: 3) == [[1, 2, 3], [2, 3, 4], [3, 4, 5],
[4, 5, 6]]
assert window[int](x, size: 3, step: 2) == [[1, 2, 3], [3, 4, 5]]
assert window[int]([]int{}, size: 2) == [][]int{}
}
/////////////////////////////
type MyAlias = i64
fn (a MyAlias) + (b MyAlias) MyAlias {
return MyAlias(i64(a) * 10 + i64(b) * 10)
}
struct MyStruct {
x int = 5
}
fn (a MyStruct) + (b MyStruct) MyStruct {
return MyStruct{
x: a.x + b.x
}
}
fn test_sum() {
assert sum([1, 2, 3, 4, 5]) or { 0 } == 15
assert sum([1.0, 2.5, 3.5, 4.0]) or { 0.0 } == 11.0
assert sum([]int{}) or { 0 } == 0
// test summing of a struct, with an overloaded + operator:
s := [MyStruct{
x: 30
}, MyStruct{
x: 20
}, MyStruct{
x: 10
}]
assert sum(s)! == MyStruct{
x: 60
}
// test summing of a number type alias with an overloaded + operator:
assert sum([MyAlias(5), MyAlias(7), MyAlias(3)])! == MyAlias((5 * 10 + 7 * 10) * 10 + 3 * 10)
}
/////////////////////////////
fn test_reduce() {
x := [1, 2, 3, 4, 5]
assert reduce[int](x, fn (t1 int, t2 int) int {
return t1 + t2
}) or { 0 } == 15
assert reduce[string](['H', 'e', 'l', 'l', 'o'], fn (t1 string, t2 string) string {
return t1 + t2
}) or { '' } == 'Hello' // For the sake please use array's join instead.
assert reduce[int]([]int{}, fn (t1 int, t2 int) int {
return 0
}) or { -1 } == -1
}
fn test_reduce_indexed() {
x := [1, 2, 3, 4, 5]
assert reduce_indexed[int](x, fn (idx int, t1 int, t2 int) int {
return idx + t1 + t2
}) or { 0 } == 25
}
fn test_filter_indexed() {
x := [0, 1, 2, 3, 4, 5]
assert filter_indexed[int](x, fn (idx int, e int) bool {
return idx % 2 == 0
}) == [0, 2, 4]
}
fn test_fold() {
x := [1, 2, 3, 4, 5]
assert fold[int, int](x, 5, fn (r int, t int) int {
return r + t
}) == 20
assert fold[string, int](['H', 'e', 'l', 'l', 'l'], 0, fn (r int, t string) int {
return r + t[0]
}) == 497
assert fold[int, int]([]int{}, -1, fn (t1 int, t2 int) int {
return 0
}) == -1
}
fn test_fold_indexed() {
x := [1, 2, 3, 4, 5]
assert fold_indexed[int, int](x, 5, fn (idx int, r int, t int) int {
return idx + r + t
}) == 30
}
fn test_flatten() {
x := [[1, 2, 3], [4, 5, 6]]
assert flatten[int](x) == [1, 2, 3, 4, 5, 6]
assert flatten[int]([[]int{}]) == []
}
fn test_group_by() {
x := ['H', 'el', 'l', 'o ']
assert group_by[int, string](x, fn (v string) int {
return v.len
}) == {
1: ['H', 'l']
2: ['el', 'o ']
}
assert group_by[int, int]([]int{}, fn (v int) int {
return 0
}) == map[int][]int{}
}
fn test_concat_int() {
mut a := [1, 2, 3]
mut b := [3, 2, 1]
assert concat(a, ...b) == [1, 2, 3, 3, 2, 1]
}
fn test_concat_string() {
mut a := ['1', '2', '3']
mut b := ['3', '2', '1']
assert concat(a, ...b) == ['1', '2', '3', '3', '2', '1']
}
fn test_binary_search() {
a := [1, 3, 3, 4, 5, 6, 7, 8, 10]
assert binary_search(a, 3)! == 1
assert (binary_search(a, 0) or { -1 }) == -1
}
fn test_lower_bound() {
a := [1, 3, 3, 4, 5, 6, 7, 8, 10]
b := []int{}
c := [1, 2, 3]
assert lower_bound(a, 2)! == 3
assert (lower_bound(b, 4) or { -1 }) == -1
assert lower_bound(c, 3)! == 3
}
fn test_upper_bound() {
a := [1, 3, 3, 4, 5, 6, 7, 8, 10]
b := []int{}
c := [1, 2, 3]
assert upper_bound(a, 9)! == 8
assert (upper_bound(b, 4) or { -1 }) == -1
assert upper_bound(c, 2)! == 2
}
fn test_rotate_right() {
mut x := [1, 2, 3, 4, 5, 6]
rotate_right(mut x, 2)
assert x == [5, 6, 1, 2, 3, 4]
}
fn test_rotate_right_long() {
mut x := []u64{len: 128, init: 255}
x[0] = 0
x[1] = 0
x[2] = 0
rotate_right(mut x, 64)
assert x.len == 128
idx := index_of_first(x, fn (idx int, x u64) bool {
return x == 0
})
assert idx == 64
assert x[idx - 1] == 255
assert x[idx] == 0
assert x[idx + 1] == 0
assert x[idx + 2] == 0
assert x[idx + 3] == 255
}
fn test_rotate_left() {
mut x := [1, 2, 3, 4, 5, 6]
rotate_left(mut x, 2)
assert x == [3, 4, 5, 6, 1, 2]
}
struct Abc {
x u64 = 1
y u64 = 2
z u64 = 3
}
fn test_rotate_right_struct() {
mut x := [Abc{1, 0, 1}, Abc{2, 0, 1}, Abc{3, 0, 1}, Abc{4, 0, 1},
Abc{5, 0, 1}, Abc{6, 0, 1}]
rotate_right(mut x, 2)
assert x == [Abc{5, 0, 1}, Abc{6, 0, 1}, Abc{1, 0, 1}, Abc{2, 0, 1},
Abc{3, 0, 1}, Abc{4, 0, 1}]
}
fn test_rotate_left_struct() {
mut x := [Abc{1, 0, 1}, Abc{2, 0, 1}, Abc{3, 0, 1}, Abc{4, 0, 1},
Abc{5, 0, 1}, Abc{6, 0, 1}]
rotate_left(mut x, 2)
assert x == [Abc{3, 0, 1}, Abc{4, 0, 1}, Abc{5, 0, 1}, Abc{6, 0, 1},
Abc{1, 0, 1}, Abc{2, 0, 1}]
}
fn test_rotate_right_string() {
mut x := ['x1', 'x2', 'x3', 'x4', 'x5', 'x6']
rotate_right(mut x, 2)
assert x == ['x5', 'x6', 'x1', 'x2', 'x3', 'x4']
}
fn test_rotate_left_string() {
mut x := ['x1', 'x2', 'x3', 'x4', 'x5', 'x6']
rotate_left(mut x, 2)
assert x == ['x3', 'x4', 'x5', 'x6', 'x1', 'x2']
}
fn test_copy() {
mut a := [1, 2, 3]
mut b := [4, 5, 6]
assert copy(mut b, a) == 3
assert b == [1, 2, 3]
// check independent copies
b[0] = 99
assert a[0] == 1
// check longer src
b << 7
assert copy(mut a, b) == 3
assert a == [99, 2, 3]
// check longer dst
assert copy(mut b, [8, 9]) == 2
assert b == [8, 9, 3, 7]
}
fn test_can_copy_bits() {
assert can_copy_bits[u8]()
assert can_copy_bits[int]()
assert can_copy_bits[voidptr]()
assert can_copy_bits[&u8]()
// autofree needs to intercept assign
assert !can_copy_bits[string]()
assert !can_copy_bits[[]int]()
// map not copyable
assert !can_copy_bits[map[string]int]()
}
type Str = string
fn test_alias_string_contains() {
names := [Str('')]
assert (Str('') in names) == true
}
struct XYZ {}
fn test_array_append_empty_struct() {
mut names := []XYZ{cap: 2}
names << XYZ{}
assert (XYZ{} in names) == true
// test fixed array
array := [XYZ{}]!
assert (XYZ{} in names) == true
}
fn test_index_of_first() {
// vfmt off
assert index_of_first([1], fn (idx int, x int) bool { return x == 0 }) == -1
assert index_of_first([4, 5, 0, 7, 0, 9], fn (idx int, x int) bool { return x == 0 }) == 2
assert index_of_first([4, 5, 0, 7, 0, 9], fn (idx int, x int) bool { return x == 4 }) == 0
// vfmt on
}
fn test_index_of_last() {
// vfmt off
assert index_of_last([1], fn (idx int, x int) bool { return x == 0 }) == -1
assert index_of_last([4, 5, 0, 7, 0, 9], fn (idx int, x int) bool { return x == 0 }) == 4
assert index_of_last([4, 5, 0, 7, 0, 9], fn (idx int, x int) bool { return x == 4 }) == 0
// vfmt on
}
fn test_map_of_indexes() {
// vfmt off
assert arrays.map_of_indexes([]int{}) == {}
assert arrays.map_of_indexes([1]) == {1: [0]}
assert arrays.map_of_indexes([1, 2, 3, 999]) == {1: [0], 2: [1], 3: [2], 999: [3]}
assert arrays.map_of_indexes([999, 1, 2, 3]) == {1: [1], 2: [2], 3: [3], 999: [0]}
assert arrays.map_of_indexes([1, 2, 3, 4, 4, 2, 1, 4, 4, 999]) == {1: [0, 6], 2: [1, 5], 3: [2], 4: [3, 4, 7, 8], 999: [9]}
assert arrays.map_of_indexes([]string{}) == {}
assert arrays.map_of_indexes(['abc']) == {'abc': [0]}
assert arrays.map_of_indexes(['abc', 'abc']) == {'abc': [0, 1]}
assert arrays.map_of_indexes(['abc', 'def', 'abc']) == {'abc': [0, 2], 'def': [1]}
// vfmt on
}
fn test_map_of_counts() {
// vfmt off
assert map_of_counts([]int{}) == {}
assert map_of_counts([1]) == {1: 1}
assert map_of_counts([1, 2, 3, 999]) == {1: 1, 2: 1, 3: 1, 999: 1}
assert map_of_counts([999, 1, 2, 3]) == {1: 1, 2: 1, 3: 1, 999: 1}
assert map_of_counts([1, 2, 3, 4, 4, 2, 1, 4, 4, 999]) == {1: 2, 2: 2, 3: 1, 4: 4, 999: 1}
assert map_of_counts([]string{}) == {}
assert map_of_counts(['abc']) == {'abc': 1}
assert map_of_counts(['abc', 'abc']) == {'abc': 2}
assert map_of_counts(['abc', 'def', 'abc']) == {'abc': 2, 'def': 1}
// vfmt on
}
struct FindTest {
name string
age int
}
const test_structs = [FindTest{'one', 1}, FindTest{'two', 2},
FindTest{'three', 3}, FindTest{'one', 4}]
fn test_find_first() {
// element in array
a := [1, 2, 3, 4, 5]
assert find_first[int](a, fn (arr int) bool {
return arr == 3
})? == 3, 'find element couldnt find the right element'
// find struct
find_by_name := find_first(test_structs, fn (arr FindTest) bool {
return arr.name == 'one'
})?
assert find_by_name == FindTest{'one', 1}
// not found
if _ := find_first(test_structs, fn (arr FindTest) bool {
return arr.name == 'nothing'
})
{
assert false
} else {
assert true
}
}
fn test_find_last() {
// // element in array
a := [1, 2, 3, 4, 5]
assert find_last[int](a, fn (arr int) bool {
return arr == 3
})? == 3, 'find element couldnt find the right element'
// find struct
find_by_name := find_last(test_structs, fn (arr FindTest) bool {
return arr.name == 'one'
})?
assert find_by_name == FindTest{'one', 4}
// not found
if _ := find_last(test_structs, fn (arr FindTest) bool {
return arr.name == 'nothing'
})
{
assert false
} else {
assert true
}
}
fn test_join_to_string() {
assert join_to_string[FindTest](test_structs, ':', fn (it FindTest) string {
return it.name
}) == 'one:two:three:one'
assert join_to_string[FindTest](test_structs, '', fn (it FindTest) string {
return it.name
}) == 'onetwothreeone'
assert join_to_string[int]([]int{}, ':', fn (it int) string {
return '1'
}) == ''
}
fn test_partition() {
a := [1, 2, 3, 4, 5, 6, 7, 8]
lower, upper := partition(a, fn (it int) bool {
return it < 5
})
assert lower.len == 4
assert upper.len == 4
assert lower == [1, 2, 3, 4]
assert upper == [5, 6, 7, 8]
lower2, upper2 := partition(a, fn (it int) bool {
return it < 1
})
assert lower2.len == 0
assert upper2.len == 8
}
fn test_each() {
a := [99, 1, 2, 3, 4, 5, 6, 7, 8, 1001]
mut control_sum := 0
for x in a {
control_sum += x
}
each(a, fn (x int) {
println(x)
})
mut sum := 0
mut psum := &sum
each(a, fn [mut psum] (x int) {
unsafe {
*psum += x
}
})
assert control_sum == sum
}
fn test_each_indexed() {
a := [99, 1, 2, 3, 4, 5, 6, 7, 8, 1001]
mut control_sum := 0
f := fn (idx int, x int) int {
return (idx + 1) * 1000_000 + x
}
for idx, x in a {
control_sum += f(idx, x)
}
each_indexed(a, fn (idx int, x int) {
println('idx: ${idx}, x: ${x}')
})
mut sum := 0
mut psum := &sum
each_indexed(a, fn [mut psum, f] (idx int, x int) {
unsafe {
*psum += f(idx, x)
}
})
assert control_sum == sum
}
+295
View File
@@ -0,0 +1,295 @@
module diff
import strings
// DiffChange contains one or more deletions or inserts at one position in two arrays.
pub struct DiffChange {
pub mut:
a int // position in input a []T
b int // position in input b []T
del int // delete Del elements from input a
ins int // insert Ins elements from input b
}
@[flag]
enum DiffContextFlag {
delete
insert
}
pub struct DiffContext[T] {
mut:
a []T
b []T
flags []DiffContextFlag
max int
// forward and reverse d-path endpoint x components
forward []int
reverse []int
pub mut:
changes []DiffChange
}
// diff returns the difference of two arrays.
pub fn diff[T](a []T, b []T) &DiffContext[T] {
mut c := &DiffContext[T]{
a: a
b: b
}
c.flags = if a.len > b.len {
[]DiffContextFlag{len: a.len}
} else {
[]DiffContextFlag{len: b.len}
}
c.max = a.len + b.len + 1
c.forward = []int{len: 2 * c.max}
c.reverse = []int{len: 2 * c.max}
c.compare(0, 0, a.len, b.len)
c.changes = c.result(a.len, b.len)
return c
}
// A directly conversion from https://github.com/covrom/diff
// Fast diff library for Myers algorithm.
// The algorithm is described in "An O(ND) Difference Algorithm and its Variations", Eugene Myers, Algorithmica Vol. 1 No. 2, 1986, pp. 251-266
@[direct_array_access]
fn (mut c DiffContext[T]) compare(mut_aoffset int, mut_boffset int, mut_alimit int, mut_blimit int) {
mut aoffset := mut_aoffset
mut boffset := mut_boffset
mut alimit := mut_alimit
mut blimit := mut_blimit
// eat common prefix
for aoffset < alimit && boffset < blimit && c.a[aoffset] == c.b[boffset] {
aoffset++
boffset++
}
// eat common suffix
for alimit > aoffset && blimit > boffset && c.a[alimit - 1] == c.b[blimit - 1] {
alimit--
blimit--
}
// both equal or b inserts
if aoffset == alimit {
for boffset < blimit {
c.flags[boffset].set(.insert)
boffset++
}
return
}
// a deletes
if boffset == blimit {
for aoffset < alimit {
c.flags[aoffset].set(.delete)
aoffset++
}
return
}
x, y := c.find_middle_snake(aoffset, boffset, alimit, blimit)
c.compare(aoffset, boffset, x, y)
c.compare(x, y, alimit, blimit)
}
@[direct_array_access]
fn (mut c DiffContext[T]) find_middle_snake(aoffset int, boffset int, alimit int, blimit int) (int, int) {
// midpoints
fmid := aoffset - boffset
rmid := alimit - blimit
// correct offset in d-path slices
foff := c.max - fmid
roff := c.max - rmid
isodd := (rmid - fmid) & 1 != 0
maxd := (alimit - aoffset + blimit - boffset + 2) / 2
c.forward[c.max + 1] = aoffset
c.reverse[c.max - 1] = alimit
mut x, mut y := 0, 0
for d := 0; d <= maxd; d++ {
// forward search
for k := fmid - d; k <= fmid + d; k += 2 {
if k == fmid - d || (k != fmid + d && c.forward[foff + k + 1] > c.forward[foff + k - 1]) {
x = c.forward[foff + k + 1] // down
} else {
x = c.forward[foff + k - 1] + 1 // right
}
y = x - k
for x < alimit && y < blimit && c.a[x] == c.b[y] {
x++
y++
}
c.forward[foff + k] = x
if isodd && k > rmid - d && k < rmid + d {
if c.reverse[roff + k] <= c.forward[foff + k] {
return x, x - k
}
}
}
// reverse search x,y correspond to u,v
for k := rmid - d; k <= rmid + d; k += 2 {
if k == rmid + d || (k != rmid - d && c.reverse[roff + k - 1] < c.reverse[roff + k + 1]) {
x = c.reverse[roff + k - 1] // up
} else {
x = c.reverse[roff + k + 1] - 1 // left
}
y = x - k
for x > aoffset && y > boffset && c.a[x - 1] == c.b[y - 1] {
x--
y--
}
c.reverse[roff + k] = x
if !isodd && k >= fmid - d && k <= fmid + d {
if c.reverse[roff + k] <= c.forward[foff + k] {
// lookup opposite end
x = c.forward[foff + k]
return x, x - k
}
}
}
}
panic('diff.find_middle_snake: should never be reached')
}
@[direct_array_access]
fn (c DiffContext[T]) result(n int, m int) []DiffChange {
mut x, mut y := 0, 0
mut res := []DiffChange{}
for x < n || y < m {
if x < n && y < m && !c.flags[x].has(.delete) && !c.flags[y].has(.insert) {
x++
y++
} else {
mut a := x
mut b := y
for x < n && (y >= m || c.flags[x].has(.delete)) {
x++
}
for y < m && (x >= n || c.flags[y].has(.insert)) {
y++
}
if a < x || b < y {
res << DiffChange{a, b, x - a, y - b}
}
}
}
return res
}
// merge_changes merges neighboring changes smaller than the specified context_lines.
// The changes must be ordered by ascending positions.
@[direct_array_access]
fn (mut c DiffContext[T]) merge_changes(context_lines int) {
if c.changes.len == 0 {
return
}
mut merged := []DiffChange{}
mut current := c.changes[0]
for i in 1 .. c.changes.len {
next := c.changes[i]
if next.a <= current.a + current.del + context_lines {
current = DiffChange{
a: current.a
b: current.b
del: next.a + next.del - current.a
ins: next.b + next.ins - current.b
}
} else {
merged << current
current = next
}
}
merged << current
c.changes = merged
}
@[params]
pub struct DiffGenStrParam {
pub mut:
colorful bool
unified int = 3 // how many context lines before/after diff block
block_header bool // output `@@ -3,4 +3,5 @@` or not
}
// generate_patch generate a diff string of two arrays.
@[direct_array_access]
pub fn (mut c DiffContext[T]) generate_patch(param DiffGenStrParam) string {
mut sb := strings.new_builder(100)
defer { unsafe { sb.free() } }
mut unified := if param.unified < 0 { 0 } else { param.unified }
c.merge_changes(unified)
if c.changes.len == 0 {
return ''
}
mut prev_a_end := 0
mut prev_b_end := 0
for change in c.changes {
ctx_start_a := int_max(prev_a_end, change.a - unified)
ctx_end_a := change.a + change.del + unified
ctx_start_b := int_max(prev_b_end, change.b - unified)
ctx_end_b := change.b + change.ins + unified
if param.block_header {
if param.colorful {
sb.write_string('\033[36m')
}
sb.writeln('@@ -${ctx_start_a + 1},${ctx_end_a - ctx_start_a} +${ctx_start_b + 1},${ctx_end_b - ctx_start_b} @@')
if param.colorful {
sb.write_string('\033[0m')
}
}
c.write_context(mut sb, ctx_start_b, change.b, param)
c.write_change(mut sb, change, param)
c.write_context(mut sb, change.b + change.ins, ctx_end_b, param)
prev_a_end = ctx_end_a
prev_b_end = ctx_end_b
}
return sb.str()
}
@[direct_array_access]
fn (c DiffContext[T]) write_context(mut sb strings.Builder,
start int, end int,
param DiffGenStrParam) {
for i in start .. end {
if i >= c.b.len {
break
}
line := c.b[i].str()
if param.colorful {
sb.writeln('\033[37m${line}\033[0m')
} else {
sb.writeln(line)
}
}
}
@[direct_array_access]
fn (c DiffContext[T]) write_change(mut sb strings.Builder,
change DiffChange,
param DiffGenStrParam) {
for i in change.a .. change.a + change.del {
line := c.a[i].str()
if param.colorful {
sb.writeln('\033[31m-${line}\033[0m')
} else {
sb.writeln('-${line}')
}
}
for i in change.b .. change.b + change.ins {
line := c.b[i].str()
if param.colorful {
sb.writeln('\033[32m+${line}\033[0m')
} else {
sb.writeln('+${line}')
}
}
}
+130
View File
@@ -0,0 +1,130 @@
import arrays.diff
fn test_diff_array() {
mut aa := ['hi', '1', '5', '3']
mut bb := aa.clone()
mut ctx := diff.diff(aa, bb)
assert ctx.changes.len == 0
bb.insert(2, 'max')
// aa := ['hi', '1', '5', '3']
// bb := ['hi', '1', 'max', '5', '3']
ctx = diff.diff(aa, bb)
assert ctx.changes == [
diff.DiffChange{
a: 2
b: 2
del: 0
ins: 1
},
]
bb.delete(4)
// aa := ['hi', '1', '5', '3']
// bb := ['hi', '1', 'max', '5']
ctx = diff.diff(aa, bb)
assert ctx.changes == [
diff.DiffChange{
a: 2
b: 2
del: 0
ins: 1
},
diff.DiffChange{
a: 3
b: 4
del: 1
ins: 0
},
]
str1 := ctx.generate_patch()
assert str1 == 'hi
1
-5
-3
+max
+5
'
str2 := ctx.generate_patch(block_header: true)
assert str2 == '@@ -1,7 +1,7 @@
hi
1
-5
-3
+max
+5
'
str3 := ctx.generate_patch(block_header: true, unified: 1)
assert str3 == '@@ -2,4 +2,4 @@
1
-5
-3
+max
+5
'
str4 := ctx.generate_patch(block_header: true, unified: 10)
assert str4 == '@@ -1,14 +1,14 @@
hi
1
-5
-3
+max
+5
'
str5 := ctx.generate_patch(block_header: true, unified: -1)
assert str5 == '@@ -3,2 +3,2 @@
-5
-3
+max
+5
'
}
fn test_diff_runes() {
mut aa := 'brown fox jumps over the lazy dog'.runes()
mut bb := 'brwn faax junps ovver the lay daog'.runes()
mut ctx := diff.diff(aa, bb)
str1 := ctx.generate_patch()
assert str1 == 'b
r
-o
w
n
f
-o
+a
+a
x
j
u
-m
+n
p
s
o
+v
v
e
r
l
a
-z
-y
-
-d
+y
+
+d
+a
o
g
'
}
+26
View File
@@ -0,0 +1,26 @@
module arrays
// index_of_first returns the index of the first element of `array`, for which the predicate fn returns true.
// If predicate does not return true for any of the elements, then index_of_first will return -1.
// Example: assert arrays.index_of_first([4,5,0,7,0,9], fn(idx int, x int) bool { return x == 0 }) == 2
pub fn index_of_first[T](array []T, predicate fn (idx int, elem T) bool) int {
for i, e in array {
if predicate(i, e) {
return i
}
}
return -1
}
// index_of_last returns the index of the last element of `array`, for which the predicate fn returns true.
// If predicate does not return true for any of the elements, then index_of_last will return -1.
// Example: assert arrays.index_of_last([4,5,0,7,0,9], fn(idx int, x int) bool { return x == 0 }) == 4
pub fn index_of_last[T](array []T, predicate fn (idx int, elem T) bool) int {
for i := array.len - 1; i >= 0; i-- {
e := array[i]
if predicate(i, e) {
return i
}
}
return -1
}
+28
View File
@@ -0,0 +1,28 @@
module arrays
// map_of_indexes returns a map, where each key is an unique value in `array`.
// Each value in that map for that key, is an array, containing the indexes in `array`, where that value has been found.
// Example: assert arrays.map_of_indexes([1,2,3,4,4,2,1,4,4,999]) == {1: [0, 6], 2: [1, 5], 3: [2], 4: [3, 4, 7, 8], 999: [9]}
pub fn map_of_indexes[T](array []T) map[T][]int {
mut result := map[T][]int{}
for i, e in array {
if _ := result[e] {
result[e] << i
} else {
result[e] = [i]
}
}
return result
}
// map_of_counts returns a map, where each key is an unique value in `array`.
// Each value in that map for that key, is how many times that value occurs in `array`.
// It can be useful for building histograms of discrete measurements.
// Example: assert arrays.map_of_counts([1,2,3,4,4,2,1,4,4]) == {1: 2, 2: 2, 3: 1, 4: 4}
pub fn map_of_counts[T](array []T) map[T]int {
mut result := map[T]int{}
for e in array {
result[e]++
}
return result
}
+109
View File
@@ -0,0 +1,109 @@
module parallel
import sync
import runtime
// Params contains the optional parameters that can be passed to `run` and `amap`.
@[params]
pub struct Params {
pub mut:
workers int // 0 by default, so that VJOBS will be used, through runtime.nr_jobs()
}
fn limited_workers(max_workers int, ilen int) int {
// create a limited amount of workers to handle the load
workers := if max_workers != 0 { max_workers } else { runtime.nr_jobs() }
if ilen < workers {
return ilen
}
return workers
}
// run lets the user run an array of input with a user provided function in parallel.
// It limits the number of worker threads to min(num_workers, num_cpu).
// The function aborts if an error is encountered.
// Example: parallel.run([1, 2, 3, 4, 5], |i| println(i))
pub fn run[T](input []T, worker fn (T), opt Params) {
if input.len == 0 {
return
}
workers := limited_workers(opt.workers, input.len)
ch := chan T{cap: workers * 2}
mut wg := sync.new_waitgroup()
wg.add(input.len)
for _ in 0 .. workers {
spawn fn [ch, worker, mut wg] [T]() {
for {
task := <-ch or { break }
worker(task)
wg.done()
}
}()
}
// put the input into the channel
for i in input {
ch <- i
}
// wait for all tasks to complete
wg.wait()
ch.close() // this will signal all the workers to exit, and we can return, without having to wait for them to finish
}
struct Task[T, R] {
idx int
input T
result R
}
// amap lets the user run an array of input with a user provided function in parallel.
// It limits the number of worker threads to max number of cpus.
// The worker function can return a value. The returning array maintains the input order.
// Any error handling should have happened within the worker function.
// Example: squares := parallel.amap([1, 2, 3, 4, 5], |i| i * i); assert squares == [1, 4, 9, 16, 25]
pub fn amap[T, R](input []T, worker fn (T) R, opt Params) []R {
if input.len == 0 {
return []
}
mut tasks := []Task[T, R]{len: input.len}
// the tasks array will be passed to the closure of each worker by reference, so that it could
// then modify the same tasks:
mut tasks_ref := &tasks
workers := limited_workers(opt.workers, input.len)
// use a buffered channel for transfering the tasks, that has enough space to keep all the workers busy,
// without blocking the main thread needlessly
ch := chan Task[T, R]{cap: workers * 2}
mut wg := sync.new_waitgroup()
wg.add(input.len)
for _ in 0 .. workers {
spawn fn [ch, worker, mut wg, mut tasks_ref] [T, R]() {
for {
mut task := <-ch or { break }
unsafe {
tasks_ref[task.idx] = Task[T, R]{
idx: task.idx
input: task.input
result: worker(task.input)
}
}
wg.done()
}
}()
}
// put the input into the channel
for idx, inp in input {
ch <- Task[T, R]{
idx: idx
input: inp
}
}
// wait for all tasks to complete
wg.wait()
ch.close()
tasks.sort(a.idx < b.idx)
return tasks.map(it.result)
}
@@ -0,0 +1,67 @@
// vtest build: !musl?
import arrays.parallel
import rand
import time
fn test_parallel_run_with_empty_arrays() {
parallel.run([]int{}, fn (x int) {})
parallel.run([]u8{}, fn (x u8) {})
parallel.run([]u32{}, fn (x u32) {}, workers: 1000)
assert true
}
fn test_parallel_amap_with_empty_arrays() {
assert parallel.amap([]int{}, fn (x int) u8 {
return 0
}) == []
assert parallel.amap([]u8{}, fn (x u8) int {
return 0
}) == []
assert parallel.amap([]u8{}, fn (x u8) int {
return 0
}, workers: 1000) == []
assert true
}
fn test_parallel_run() {
counters := []int{len: 10, init: index}
dump(counters)
mut res := []string{len: 10}
mut pres := &res
parallel.run(counters, fn [mut pres] (i int) {
delay := rand.intn(250) or { 250 }
time.sleep(delay * time.millisecond)
unsafe {
pres[i] = 'task ${i}, delay=${delay}ms'
}
assert true
})
dump(res)
assert res.len == counters.len
}
fn test_parallel_amap() {
input := [1, 2, 3, 4, 5, 6, 7, 8, 9]
dump(input)
dump(input.len)
output := parallel.amap(input, fn (i int) int {
delay := rand.intn(250) or { 250 }
time.sleep(delay * time.millisecond)
return i * i
})
dump(output)
dump(output.len)
assert input.len == output.len
for i, _ in output {
assert output[i] == input[i] * input[i]
}
// unordered output validation
assert output.len == input.len
op_sorted := output.sorted()
dump(op_sorted)
for i, op in op_sorted {
assert op == input[i] * input[i]
}
}
+37
View File
@@ -0,0 +1,37 @@
module arrays
// ReverseIterator provides a convenient way to iterate in reverse over all elements of an array without allocations.
// I.e. it allows you to use this syntax: `for elem in arrays.reverse_iterator(a) {` .
pub struct ReverseIterator[T] {
mut:
a []T
i int
}
// reverse_iterator can be used to iterate over the elements in an array.
// i.e. you can use this syntax: `for elem in arrays.reverse_iterator(a) {` .
pub fn reverse_iterator[T](a []T) ReverseIterator[T] {
return ReverseIterator[T]{
a: a
i: a.len
}
}
// next is the required method, to implement an iterator in V.
// It returns none when the iteration should stop.
// Otherwise it returns the current element of the array.
@[direct_array_access]
pub fn (mut iter ReverseIterator[T]) next() ?&T {
iter.i--
if iter.i < 0 {
return none
}
return unsafe { &iter.a[iter.i] }
}
// free frees the iterator resources.
pub fn (iter &ReverseIterator[T]) free() {
// The array stored in the iterator is not owned by the iterator.
// It should not be freed, when the iterator goes out of scope.
// This is the reason, that this manual free method exists and is empty.
}
@@ -0,0 +1,89 @@
import arrays
struct Compound {
mut:
s string
i int
u u64
m map[string]i16
}
fn check[T](original []T) {
mut result := []T{cap: original.len}
for x in arrays.reverse_iterator(original) {
result << x
}
assert result.len == original.len
assert result.first() == original.last()
assert result.reverse() == original
eprintln('> original: ${original}')
eprintln('> result: ${result}')
}
fn test_reverse_iterator_basic() {
check(['abc', 'def', 'ghi', 'jkl'])
check([10, 20, 30, 40])
check([
Compound{'abc', 123, 444, {
'aa': i16(12)
'bb': 31
}},
Compound{'def', 456, 555, {
'bb': i16(22)
'cc': 32
}},
Compound{'xyz', 789, 666, {
'cc': i16(32)
'dd': 33
}},
])
}
fn test_reverse_iterator_with_mut() {
mut original := [10, 20]
mut before := []int{cap: original.len}
mut after := []int{cap: original.len}
for mut x in arrays.reverse_iterator(original) {
before << *x
(*x)++
after << *x
}
assert before == [20, 10]
assert after == [21, 11]
assert original == [11, 21]
}
fn test_reverse_iterator_with_mut_compound() {
mut original := [Compound{
s: 'abc'
i: 123
}, Compound{
s: 'xyz'
i: 987
}]
mut before := []Compound{cap: original.len}
mut after := []Compound{cap: original.len}
for mut x in arrays.reverse_iterator(original) {
before << *x
x.i++
x.s += ' tail'
x.u = 99
x.m['modified'] = 1
after << *x
}
assert after[0] == Compound{
s: 'xyz tail'
i: 988
u: 99
m: {
'modified': i16(1)
}
}
assert before[0] == Compound{
s: 'xyz'
i: 987
u: 0
m: {}
}
assert after.reverse() == original
}
+153
View File
@@ -0,0 +1,153 @@
module arrays
// uniq filters the adjacent matching elements from the given array.
// All adjacent matching elements, are merged to their first occurrence,
// so the output will have no repeating elements.
// Note: `uniq` does not detect repeats, unless they are adjacent.
// You may want to call a.sorted() on your array, before passing the result to arrays.uniq().
// See also arrays.distinct, which is essentially arrays.uniq(a.sorted()) .
// Example: assert arrays.uniq( []int{} ) == []
// Example: assert arrays.uniq( [1, 1] ) == [1]
// Example: assert arrays.uniq( [2, 1] ) == [2, 1]
// Example: assert arrays.uniq( [5, 5, 1, 5, 2, 1, 1, 9] ) == [5, 1, 5, 2, 1, 9]
pub fn uniq[T](a []T) []T {
mut res := []T{cap: a.len / 10}
mut j := -1
if a.len > 0 {
j = 0
res << a[0]
}
for idx, e in a {
if a[j] == e {
continue
}
j = idx
res << e
}
return res
}
// uniq_only filters the adjacent matching elements from the given array.
// All adjacent matching elements, are removed.
// The output will contain only the elements that *did not have* any adjacent matches.
// Note: `uniq_only` does not detect repeats, unless they are adjacent.
// You may want to call a.sorted() on your array, before passing the result to arrays.uniq_only().
// Example: assert arrays.uniq_only( []int{} ) == []
// Example: assert arrays.uniq_only( [1, 1] ) == []
// Example: assert arrays.uniq_only( [2, 1] ) == [2, 1]
// Example: assert arrays.uniq_only( [1, 5, 5, 1, 5, 2, 1, 1, 9] ) == [1, 1, 5, 2, 9]
pub fn uniq_only[T](a []T) []T {
// simple cases:
if a.len == 0 {
return []
}
if a.len == 1 {
return a.clone()
}
if a.len == 2 {
if a[0] != a[1] {
return a.clone()
}
return []
}
mut res := []T{cap: a.len / 20}
// head element:
if a[0] != a[1] {
res << a[0]
}
// middle elements:
for idx := 1; idx + 1 < a.len; idx++ {
if a[idx - 1] != a[idx] && a[idx + 1] != a[idx] {
res << a[idx]
}
}
// tail element:
if a[a.len - 2] != a[a.len - 1] {
res << a[a.len - 1]
}
return res
}
// uniq_only_repeated produces the adjacent matching elements from the given array.
// Unique elements, with no duplicates are removed.
// Adjacent matching elements, are reduced to just 1 element per repeat group.
// Note: `uniq_only_repeated` does not detect repeats, unless they are adjacent.
// You may want to call a.sorted() on your array, before passing the result to arrays.uniq_only_repeated().
// Example: assert arrays.uniq_only_repeated( []int{} ) == []
// Example: assert arrays.uniq_only_repeated( [1, 5] ) == []
// Example: assert arrays.uniq_only_repeated( [5, 5] ) == [5]
// Example: assert arrays.uniq_only_repeated( [5, 5, 1, 5, 2, 1, 1, 9] ) == [5, 1]
pub fn uniq_only_repeated[T](a []T) []T {
// simple cases:
if a.len == 0 || a.len == 1 {
return []
}
mut res := []T{cap: a.len / 20}
loop: for i := 0; i + 1 < a.len; i++ {
if a[i] == a[i + 1] {
// at least 2 match; find the span length:
for j := i + 2; j < a.len; j++ {
if a[i] != a[j] {
// found the right border of the repeated elements
if j - i > 1 {
res << a[i]
i = j - 1
continue loop
}
}
}
break
}
}
// tail element:
if a[a.len - 2] == a[a.len - 1] {
res << a[a.len - 1]
}
return res
}
// uniq_all_repeated produces all adjacent matching elements from the given array.
// Unique elements, with no duplicates are removed.
// The output will contain all the duplicated elements, repeated just like they were in the original.
// Note: `uniq_all_repeated` does not detect repeats, unless they are adjacent.
// You may want to call a.sorted() on your array, before passing the result to arrays.uniq_all_repeated().
// Example: assert arrays.uniq_all_repeated( []int{} ) == []
// Example: assert arrays.uniq_all_repeated( [1, 5] ) == []
// Example: assert arrays.uniq_all_repeated( [5, 5] ) == [5,5]
// Example: assert arrays.uniq_all_repeated( [5, 5, 1, 5, 2, 1, 1, 9] ) == [5, 5, 1, 1]
pub fn uniq_all_repeated[T](a []T) []T {
// simple cases:
if a.len == 0 || a.len == 1 {
return []
}
if a.len == 2 {
if a[0] == a[1] {
return a.clone()
}
}
mut res := []T{cap: a.len / 20}
loop: for i := 0; i + 1 < a.len; i++ {
if a[i] == a[i + 1] {
res << a[i]
for j := i + 1; j < a.len; j++ {
if a[i] != a[j] && j - i > 0 {
// found the right border of the repeated elements
i = j - 1
continue loop
}
res << a[i]
}
break
}
}
return res
}
// distinct returns all distinct elements from the given array a.
// The results are guaranteed to be unique, i.e. not have duplicates.
// See also arrays.uniq, which can be used to achieve the same goal,
// but needs you to first sort the array.
// Example: assert arrays.distinct( [5, 5, 1, 5, 2, 1, 1, 9] ) == [1, 2, 5, 9]
pub fn distinct[T](a []T) []T {
return uniq(a.sorted(a < b))
}
+96
View File
@@ -0,0 +1,96 @@
import arrays
const a = [1, 5, 5, 1, 5, 2, 1, 1, 9, 2]
const s = [1, 1, 1, 1, 2, 2, 5, 5, 5, 9] // a.sorted()
const many_repeats = [1, 1, 1, 1, 2, 2, 5, 5, 5, 9, 1, 1, 9, 5, 5]
const astrings = ['b', 'b', 'a', 'b', 'z', 'a', 'a', 'd']
const sstrings = ['a', 'a', 'a', 'b', 'b', 'b', 'd', 'z'] // astrings.sorted()
fn test_uniq() {
assert arrays.uniq([]int{}) == []
assert arrays.uniq([1, 1]) == [1]
assert arrays.uniq([2, 1]) == [2, 1]
assert arrays.uniq(a) == [1, 5, 1, 5, 2, 1, 9, 2]
assert arrays.uniq(s) == [1, 2, 5, 9]
assert arrays.uniq(astrings) == ['b', 'a', 'b', 'z', 'a', 'd']
assert arrays.uniq(sstrings) == ['a', 'b', 'd', 'z']
assert arrays.uniq(many_repeats) == [1, 2, 5, 9, 1, 9, 5]
}
fn test_uniq_only() {
assert arrays.uniq_only([]int{}) == []
assert arrays.uniq_only([1, 5]) == [1, 5]
assert arrays.uniq_only([5, 5]) == []
assert arrays.uniq_only([5, 1, 1]) == [5]
assert arrays.uniq_only(a) == [1, 1, 5, 2, 9, 2]
assert arrays.uniq_only(s) == [9]
assert arrays.uniq_only(astrings) == ['a', 'b', 'z', 'd']
assert arrays.uniq_only(sstrings) == ['d', 'z']
assert arrays.uniq_only(many_repeats) == [9, 9]
}
fn test_uniq_only_repeated() {
assert arrays.uniq_only_repeated([]int{}) == []
assert arrays.uniq_only_repeated([1, 5]) == []
assert arrays.uniq_only_repeated([5, 5]) == [5]
assert arrays.uniq_only_repeated([5, 5, 5]) == [5]
assert arrays.uniq_only_repeated([5, 1, 1]) == [1]
assert arrays.uniq_only_repeated([1, 1, 5]) == [1]
assert arrays.uniq_only_repeated([1, 5, 1]) == []
assert arrays.uniq_only_repeated([0, 5, 1, 1]) == [1]
assert arrays.uniq_only_repeated([0, 1, 1, 5]) == [1]
assert arrays.uniq_only_repeated([0, 1, 5, 1]) == []
assert arrays.uniq_only_repeated([5, 1, 1, 0]) == [1]
assert arrays.uniq_only_repeated([1, 1, 5, 0]) == [1]
assert arrays.uniq_only_repeated([1, 5, 1, 0]) == []
assert arrays.uniq_only_repeated([5, 5, 5, 5, 1]) == [5]
assert arrays.uniq_only_repeated([1, 5, 5, 5, 5, 1]) == [5]
assert arrays.uniq_only_repeated([1, 5, 5, 2, 5, 5, 3]) == [5, 5]
assert arrays.uniq_only_repeated([1, 5, 5, 2, 5, 5, 3, 5]) == [5, 5]
assert arrays.uniq_only_repeated([1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5]
assert arrays.uniq_only_repeated([5, 1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5]
assert arrays.uniq_only_repeated([5, 5, 1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5, 5]
assert arrays.uniq_only_repeated([5, 5, 5, 1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5, 5]
assert arrays.uniq_only_repeated(a) == [5, 1]
assert arrays.uniq_only_repeated(s) == [1, 2, 5]
assert arrays.uniq_only_repeated(many_repeats) == [1, 2, 5, 1, 5]
}
fn test_uniq_all_repeated() {
assert arrays.uniq_all_repeated([]int{}) == []
assert arrays.uniq_all_repeated([1, 5]) == []
assert arrays.uniq_all_repeated([5, 5]) == [5, 5]
assert arrays.uniq_all_repeated([5, 5, 5]) == [5, 5, 5]
assert arrays.uniq_all_repeated([5, 1, 1]) == [1, 1]
assert arrays.uniq_all_repeated([1, 1, 5]) == [1, 1]
assert arrays.uniq_all_repeated([1, 5, 1]) == []
assert arrays.uniq_all_repeated([0, 5, 1, 1]) == [1, 1]
assert arrays.uniq_all_repeated([0, 1, 1, 5]) == [1, 1]
assert arrays.uniq_all_repeated([0, 1, 5, 1]) == []
assert arrays.uniq_all_repeated([5, 1, 1, 0]) == [1, 1]
assert arrays.uniq_all_repeated([1, 1, 5, 0]) == [1, 1]
assert arrays.uniq_all_repeated([1, 5, 1, 0]) == []
assert arrays.uniq_all_repeated([5, 5, 5, 5, 1]) == [5, 5, 5, 5]
assert arrays.uniq_all_repeated([1, 5, 5, 5, 5, 1]) == [5, 5, 5, 5]
assert arrays.uniq_all_repeated([1, 5, 5, 2, 5, 5, 3]) == [5, 5, 5, 5]
assert arrays.uniq_all_repeated([1, 5, 5, 2, 5, 5, 3, 5]) == [5, 5, 5, 5]
assert arrays.uniq_all_repeated([1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5, 5, 5, 5]
assert arrays.uniq_all_repeated([5, 1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5, 5, 5, 5]
assert arrays.uniq_all_repeated([5, 5, 1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5, 5, 5, 5, 5,
5]
assert arrays.uniq_all_repeated([5, 5, 5, 1, 5, 5, 2, 5, 5, 3, 5, 5]) == [5, 5, 5, 5, 5, 5,
5, 5, 5]
assert arrays.uniq_all_repeated(a) == [5, 5, 1, 1]
assert arrays.uniq_all_repeated(s) == [1, 1, 1, 1, 2, 2, 5, 5, 5]
assert arrays.uniq_all_repeated(many_repeats) == [1, 1, 1, 1, 2, 2, 5, 5, 5, 1, 1, 5, 5]
}
fn test_distinct() {
assert arrays.distinct(a) == arrays.distinct(s)
assert arrays.distinct(a) == [1, 2, 5, 9]
assert arrays.distinct(many_repeats) == [1, 2, 5, 9]
}
+52
View File
@@ -0,0 +1,52 @@
## Description
`benchmark` provides tools for measuring and reporting on the performance of code.
## Example 1
```v
import benchmark
mut bmark := benchmark.new_benchmark()
// by default the benchmark will be verbose, i.e. it will include timing information
// if you want it to be silent, set bmark.verbose = false
for {
bmark.step() // call this when you want to advance the benchmark.
// The timing info in bmark.step_message will be measured starting from the last call to bmark.step
// ....
// bmark.fail() // call this if the step failed
// bmark.step_message(('failed')
bmark.ok() // call this when the step succeeded
println(bmark.step_message('ok'))
}
bmark.stop()
// call when you want to finalize the benchmark
println(bmark.total_message('remarks about the benchmark'))
```
`.start()` and `.measure()` are convenience methods,
intended to be used in combination. Their goal is to make
benchmarking of small snippets of code as _short_, easy to
write, and easy to read and analyze as possible.
## Example 2
```v
import time
import benchmark
mut b := benchmark.start()
// your code section 1 ...
time.sleep(1500 * time.millisecond)
b.measure('code_1')
// your code section 2 ...
time.sleep(500 * time.millisecond)
b.measure('code_2')
```
... which will produce on stdout something like this:
```text
SPENT 1500.063 ms in code_1
SPENT 500.061 ms in code_2
```
+287
View File
@@ -0,0 +1,287 @@
module benchmark
import time
import term
import arrays
const step_label_width = 5
pub const b_ok = term.ok_message('OK ')
pub const b_fail = term.fail_message('FAIL')
pub const b_skip = term.warn_message('SKIP')
pub const b_spent = term.ok_message('SPENT')
pub struct Benchmark {
pub mut:
bench_timer time.StopWatch
verbose bool
no_cstep bool
step_timer time.StopWatch
ntotal int
nok int
nfail int
nskip int
nexpected_steps int
njobs int
cstep int
bok string
bfail string
measured_steps []string
step_data map[string][]f64
}
// new_benchmark returns a `Benchmark` instance on the stack.
pub fn new_benchmark() Benchmark {
return Benchmark{
bench_timer: time.new_stopwatch()
verbose: true
}
}
// new_benchmark_no_cstep returns a new `Benchmark` instance with step counting disabled.
pub fn new_benchmark_no_cstep() Benchmark {
return Benchmark{
bench_timer: time.new_stopwatch()
verbose: true
no_cstep: true
}
}
// new_benchmark_pointer returns a new `Benchmark` instance allocated on the heap.
// This is useful for long-lived use of `Benchmark` instances.
pub fn new_benchmark_pointer() &Benchmark {
return &Benchmark{
bench_timer: time.new_stopwatch()
verbose: true
}
}
// set_total_expected_steps sets the total amount of steps the benchmark is expected to take.
pub fn (mut b Benchmark) set_total_expected_steps(n int) {
b.nexpected_steps = n
}
// stop stops the internal benchmark timer.
pub fn (mut b Benchmark) stop() {
b.bench_timer.stop()
}
// step increases the step count by 1 and restarts the internal timer.
pub fn (mut b Benchmark) step() {
b.step_timer.restart()
if !b.no_cstep {
b.cstep++
}
}
// step_restart will restart the internal step timer.
// Note that the step count will *stay the same*.
// This method is useful, when you want to do some optional preparation
// after you have called .step(), so that the time for that optional
// preparation will *not* be added to the duration of the step.
pub fn (mut b Benchmark) step_restart() {
b.step_timer.restart()
}
// fail increases the fail count by 1 and stops the internal timer.
pub fn (mut b Benchmark) fail() {
b.step_timer.stop()
b.ntotal++
b.nfail++
}
// ok increases the ok count by 1 and stops the internal timer.
pub fn (mut b Benchmark) ok() {
b.step_timer.stop()
b.ntotal++
b.nok++
}
// skip increases the skip count by 1 and stops the internal timer.
pub fn (mut b Benchmark) skip() {
b.step_timer.stop()
b.ntotal++
b.nskip++
}
// fail_many increases the fail count by `n` and stops the internal timer.
pub fn (mut b Benchmark) fail_many(n int) {
b.step_timer.stop()
b.ntotal += n
b.nfail += n
}
// ok_many increases the ok count by `n` and stops the internal timer.
pub fn (mut b Benchmark) ok_many(n int) {
b.step_timer.stop()
b.ntotal += n
b.nok += n
}
// neither_fail_nor_ok stops the internal timer.
pub fn (mut b Benchmark) neither_fail_nor_ok() {
b.step_timer.stop()
}
// start returns a new, running, instance of `Benchmark`.
// This is a shorthand for calling `new_benchmark().step()`.
pub fn start() Benchmark {
mut b := new_benchmark()
b.step()
return b
}
// measure prints the current time spent doing `label`, since the benchmark was started, or since its last call.
pub fn (mut b Benchmark) measure(label string) i64 {
b.ok()
res := b.step_timer.elapsed().microseconds()
println(b.step_message_with_label(b_spent, 'in ${label}'))
b.step()
return res
}
// record_measure stores the current time doing `label`, since the benchmark was started, or since the last call to `b.record_measure`.
// It is similar to `b.measure`, but unlike it, will not print the measurement
// immediately, just record it for later. You can call `b.all_recorded_measures`
// to retrieve all measures stored by `b.record_measure` calls.
pub fn (mut b Benchmark) record_measure(label string) i64 {
b.ok()
res := b.step_timer.elapsed().microseconds()
b.measured_steps << b.step_message_with_label(b_spent, 'in ${label}')
if label !in b.step_data {
b.step_data[label] = []f64{}
}
b.step_data[label] << f64(res)
b.step()
return res
}
// MessageOptions allows passing an optional preparation time too to each label method.
// If it is set, the preparation time (compile time) will be shown before the measured runtime.
@[params]
pub struct MessageOptions {
pub:
preparation time.Duration // the duration of the preparation time for the step
}
// step_message_with_label_and_duration returns a string describing the current step.
pub fn (b &Benchmark) step_message_with_label_and_duration(label string, msg string, sduration time.Duration,
opts MessageOptions) string {
timed_line := b.tdiff_in_ms(msg, sduration.microseconds())
flabel := format_step_label(label)
if b.nexpected_steps > 1 {
mut sprogress := ''
if b.nexpected_steps < 10 {
sprogress = if b.no_cstep {
'TMP1/${b.nexpected_steps:1d}'
} else {
'${b.cstep:1d}/${b.nexpected_steps:1d}'
}
} else if b.nexpected_steps >= 10 && b.nexpected_steps < 100 {
sprogress = if b.no_cstep {
'TMP2/${b.nexpected_steps:2d}'
} else {
'${b.cstep:2d}/${b.nexpected_steps:2d}'
}
} else if b.nexpected_steps >= 100 && b.nexpected_steps < 1000 {
sprogress = if b.no_cstep {
'TMP3/${b.nexpected_steps:3d}'
} else {
'${b.cstep:3d}/${b.nexpected_steps:3d}'
}
} else {
sprogress = if b.no_cstep {
'TMP4/${b.nexpected_steps:4d}'
} else {
'${b.cstep:4d}/${b.nexpected_steps:4d}'
}
}
if opts.preparation > 0 {
return '${flabel} [${sprogress}] C: ${f64(opts.preparation.microseconds()) / 1_000.0:7.1F} ms, R: ${timed_line}'
}
return '${flabel} [${sprogress}] ${timed_line}'
}
return '${flabel}${timed_line}'
}
fn format_step_label(label string) string {
visible_len := term.strip_ansi(label).len
if visible_len >= step_label_width {
return label
}
return label + ' '.repeat(step_label_width - visible_len)
}
// step_message_with_label returns a string describing the current step using current time as duration.
pub fn (b &Benchmark) step_message_with_label(label string, msg string, opts MessageOptions) string {
return b.step_message_with_label_and_duration(label, msg, b.step_timer.elapsed(), opts)
}
// step_message returns a string describing the current step.
pub fn (b &Benchmark) step_message(msg string, opts MessageOptions) string {
return b.step_message_with_label('', msg, opts)
}
// step_message_ok returns a string describing the current step with an standard "OK" label.
pub fn (b &Benchmark) step_message_ok(msg string, opts MessageOptions) string {
return b.step_message_with_label(b_ok, msg, opts)
}
// step_message_fail returns a string describing the current step with an standard "FAIL" label.
pub fn (b &Benchmark) step_message_fail(msg string, opts MessageOptions) string {
return b.step_message_with_label(b_fail, msg, opts)
}
// step_message_skip returns a string describing the current step with an standard "SKIP" label.
pub fn (b &Benchmark) step_message_skip(msg string, opts MessageOptions) string {
return b.step_message_with_label(b_skip, msg, opts)
}
// total_message returns a string with total summary of the benchmark run.
pub fn (b &Benchmark) total_message(msg string) string {
the_label := term.colorize(term.gray, msg)
mut tmsg := term.colorize(term.bold, 'Summary for ${the_label}:') + ' '
if b.nfail > 0 {
tmsg += term.colorize(term.bold, term.colorize(term.red, '${b.nfail} failed')) + ', '
}
if b.nok > 0 {
tmsg += term.colorize(term.bold, term.colorize(term.green, '${b.nok} passed')) + ', '
}
if b.nskip > 0 {
tmsg += term.colorize(term.bold, term.colorize(term.yellow, '${b.nskip} skipped')) + ', '
}
mut njobs_label := ''
if b.njobs > 0 {
if b.njobs == 1 {
njobs_label = ', on ${term.colorize(term.bold, 1.str())} job'
} else {
njobs_label = ', on ${term.colorize(term.bold, b.njobs.str())} parallel jobs'
}
}
tmsg += '${b.ntotal} total. ${term.colorize(term.bold, 'Elapsed time:')} ${b.bench_timer.elapsed().microseconds() / 1000} ms${njobs_label}.'
if msg in b.step_data && b.step_data[msg].len > 1 {
min := arrays.min(b.step_data[msg]) or { 0 } / 1000.0
max := arrays.max(b.step_data[msg]) or { 0 } / 1000.0
avg := (arrays.sum(b.step_data[msg]) or { 0 } / b.step_data[msg].len) / 1000.0
tmsg += ' Min: ${min:.3f} ms. Max: ${max:.3f} ms. Avg: ${avg:.3f} ms'
}
return tmsg
}
// all_recorded_measures returns a string, that contains all the recorded measure messages, done by individual calls to `b.record_measure`.
pub fn (b &Benchmark) all_recorded_measures() string {
return b.measured_steps.join_lines()
}
// total_duration returns the duration in ms.
pub fn (b &Benchmark) total_duration() i64 {
return b.bench_timer.elapsed().milliseconds()
}
// tdiff_in_ms prefixes `s` with a time difference calculation.
fn (b &Benchmark) tdiff_in_ms(s string, tdiff i64) string {
if b.verbose {
return '${f64(tdiff) / 1000.0:9.3f} ms ${s}'
}
return s
}
+75
View File
@@ -0,0 +1,75 @@
import time
import benchmark
import term
fn test_measure() {
mut b := benchmark.start()
time.sleep(200 * time.millisecond)
x := b.measure('sleeping') // returns microseconds
flush_stdout()
assert x > 150_000
// assert x < 800_000 // this can be much longer on a slow CI runner
}
fn test_record_measure() {
mut b := benchmark.start()
println('step 1')
flush_stdout()
time.sleep(100 * time.millisecond)
x := b.record_measure('sleeping 1')
assert x > 50_000
// assert x < 200_000
flush_stdout()
println('step 2')
flush_stdout()
time.sleep(150 * time.millisecond)
y := b.record_measure('sleeping 2')
assert y > 100_000
// assert y < 200_000
flush_stdout()
res := b.all_recorded_measures()
println('All recorded measurements:')
println(res)
flush_stdout()
assert res.contains('ms in sleeping 1')
assert res.contains('ms in sleeping 1')
assert res.contains('SPENT')
}
fn test_total_message() {
mut b := benchmark.start()
for _ in 0 .. 100 {
time.sleep(time.millisecond)
x := b.record_measure('sleeping 1')
assert x > 1_000
}
res := b.total_message('sleeping 1')
println(res)
assert res.contains(' Min: ')
assert res.contains(' Max: ')
assert res.contains(' Avg: ')
time.sleep(time.millisecond)
y := b.record_measure('sleeping 2')
assert y > 1_000
// Should not contain min max avg, insufficient information
res2 := b.total_message('sleeping 2')
assert !res2.contains(' Min: ')
assert !res2.contains(' Max: ')
assert !res2.contains(' Avg: ')
}
fn test_step_message_handles_ansi_label_longer_than_label_width() {
mut b := benchmark.start()
ansi_fail_label := '\x1b[41m\x1b[1m\x1b[37m FAIL \x1b[39m\x1b[22m\x1b[49m'
long_cmd := 'command: /nix/store/' + 'x'.repeat(90) + '-vlang/build/source/v -o v_g -g cmd/v'
message := b.step_message_with_label(ansi_fail_label, long_cmd)
assert term.strip_ansi(message).contains('FAIL')
assert message.contains(long_cmd)
}
+10
View File
@@ -0,0 +1,10 @@
## Description
`bitfield` is a module for manipulating arrays of bits, i.e. a series of zeroes and ones spread
across an array of storage units.
## BitField Structure
Bit arrays are stored in data structures called `BitField`. The structure is 'opaque', i.e. its
internals are not available to the end user. This module provides API (functions and methods) for
accessing and modifying bit arrays.
+608
View File
@@ -0,0 +1,608 @@
module bitfield
import math.bits
pub struct BitField {
mut:
size int
field []u32
}
// helper functions
const slot_size = 32
// from_bytes converts a byte array into a bitfield.
// [0x0F, 0x01] => 0000 1111 0000 0001
// Each byte is bit-reversed via a 256-entry LUT (bits.reverse_8).
pub fn from_bytes(bytes []u8) BitField {
mut output := new(bytes.len * 8)
for i, b in bytes {
output.field[i / 4] |= u32(bits.reverse_8(b)) << ((i % 4) * 8)
}
return output
}
// from_bytes_lowest_bits_first converts a byte array into a bitfield.
// For example: [0x0F, 0x01] => 1111 0000 1000 0000
pub fn from_bytes_lowest_bits_first(bytes []u8) BitField {
mut output := new(bytes.len * 8)
for i, b in bytes {
output.field[i / 4] |= u32(b) << ((i % 4) * 8)
}
return output
}
// from_str converts a string of characters (`0` and `1`) to a bitfield.
// Any character different from `0` is treated as `1`.
pub fn from_str(str string) BitField {
mut output := new(str.len)
for i in 0 .. str.len {
if str[i] != `0` {
output.set_bit(i)
}
}
return output
}
// str converts the bit array to a string of characters (`0` and `1`).
pub fn (bf BitField) str() string {
mut output := []u8{len: bf.size}
for i in 0 .. bf.size {
output[i] = if bf.get_bit(i) == 1 { `1` } else { `0` }
}
return output.bytestr()
}
// new creates an empty bit array capable of storing `size` bits.
pub fn new(size int) BitField {
output := BitField{
size: size
// field: *u32(calloc(zbitnslots(size) * slot_size / 8))
field: []u32{len: zbitnslots(size)}
}
return output
}
// free frees the memory allocated for a bitfield.
@[unsafe]
pub fn (bf &BitField) free() {
bf.field.free()
}
// get_bit returns the value (0 or 1) of bit number `bitnr` (count from 0).
@[inline]
pub fn (bf BitField) get_bit(bitnr int) int {
if bitnr < 0 || bitnr >= bf.size {
return 0
}
return int((bf.field[bitslot(bitnr)] >> (bitnr % slot_size)) & u32(1))
}
// set_bit sets bit number `bitnr` to 1 (count from 0).
@[inline]
pub fn (mut bf BitField) set_bit(bitnr int) {
if bitnr < 0 || bitnr >= bf.size {
return
}
bf.field[bitslot(bitnr)] |= bitmask(bitnr)
}
// clear_bit sets bit number `bitnr` to 0 (count from 0).
@[inline]
pub fn (mut bf BitField) clear_bit(bitnr int) {
if bitnr < 0 || bitnr >= bf.size {
return
}
bf.field[bitslot(bitnr)] &= ~bitmask(bitnr)
}
// extract returns the value converted from a slice of bit numbers from `start` to length of `len`.
// For example 0101 . extract(1, 2) => 0b10
pub fn (bf BitField) extract(start int, len int) u64 {
// panic?
if start < 0 {
return 0
}
mut output := u64(0)
for i in 0 .. len {
output |= u64(bf.get_bit(start + len - i - 1)) << i
}
return output
}
// insert sets bit numbers from `start` to `len` length with the value converted from the number `_value`.
// For example 0000.insert(1, 2, 0b10) => 0100
pub fn (mut bf BitField) insert[T](start int, len int, _value T) {
// panic?
if start < 0 {
return
}
mut value := _value
for i in 0 .. len {
pos := start + len - i - 1
if value & 1 == 1 {
bf.set_bit(pos)
} else {
bf.clear_bit(pos)
}
value >>= 1
}
}
// extract_lowest_bits_first returns the value converted from a slice of bit numbers from `start` to length of `len`.
// For example 0101.extract_lowest_bits_first(1, 2) => 0b01
pub fn (bf BitField) extract_lowest_bits_first(start int, len int) u64 {
// panic?
if start < 0 {
return 0
}
mut output := u64(0)
for i in 0 .. len {
output |= u64(bf.get_bit(start + i)) << i
}
return output
}
// insert_lowest_bits_first sets bit numbers from `start` to `len` length with the value converted from the number `_value`.
// For example 0000.insert_lowest_bits_first(1, 2, 0b10) => 0010
pub fn (mut bf BitField) insert_lowest_bits_first[T](start int, len int, _value T) {
// panic?
if start < 0 {
return
}
mut value := _value
for pos in start .. start + len {
if value & 1 == 1 {
bf.set_bit(pos)
} else {
bf.clear_bit(pos)
}
value >>= 1
}
}
// set_all sets all bits in the bitfield to 1.
pub fn (mut bf BitField) set_all() {
for i in 0 .. zbitnslots(bf.size) {
bf.field[i] = u32(0xFFFF_FFFF)
}
bf.clear_tail()
}
// clear_all sets all bits in the bitfield to 0.
pub fn (mut bf BitField) clear_all() {
for i in 0 .. zbitnslots(bf.size) {
bf.field[i] = u32(0)
}
}
// toggle_bit changes the value (from 0 to 1 or from 1 to 0) of bit number `bitnr`.
@[inline]
pub fn (mut bf BitField) toggle_bit(bitnr int) {
if bitnr < 0 || bitnr >= bf.size {
return
}
bf.field[bitslot(bitnr)] ^= bitmask(bitnr)
}
// set_if sets bit number `bitnr` to 1 (count from 0) if `cond` is true else clears the bit.
@[inline]
pub fn (mut bf BitField) set_if(cond bool, bitnr int) {
if bitnr < 0 || bitnr >= bf.size {
return
}
if cond {
bf.field[bitslot(bitnr)] |= bitmask(bitnr)
} else {
bf.field[bitslot(bitnr)] &= ~bitmask(bitnr)
}
}
// toggle_bits changes the value (from 0 to 1 or from 1 to 0) of bits.
// Example: mut bf := bitfield.new(10); bf.toggle_bits(1,3,5,7); assert bf.str() == '0101010100'
@[inline]
pub fn (mut bf BitField) toggle_bits(a ...int) {
for bitnr in a {
if bitnr < 0 || bitnr >= bf.size {
continue
}
bf.field[bitslot(bitnr)] ^= bitmask(bitnr)
}
}
// set_bits sets multiple bits in the bitfield to 1.
// Example: mut bf := bitfield.new(10); bf.set_bits(1,3,5,7); assert bf.str() == '0101010100'
@[inline]
pub fn (mut bf BitField) set_bits(a ...int) {
for bitnr in a {
if bitnr < 0 || bitnr >= bf.size {
continue
}
bf.field[bitslot(bitnr)] |= bitmask(bitnr)
}
}
// clear_bits sets multiple bits in the bitfield to 0.
// Example: mut bf := bitfield.from_str('1111111111111'); bf.clear_bits(1,2,5,6,7); assert bf.str() == '1001100011111'
@[inline]
pub fn (mut bf BitField) clear_bits(a ...int) {
for bitnr in a {
if bitnr < 0 || bitnr >= bf.size {
continue
}
bf.field[bitslot(bitnr)] &= ~bitmask(bitnr)
}
}
// has test if *at least one* of the bits is set.
// Example: mut bf := bitfield.from_str('111111100000000'); assert bf.has(1,3,5,7)
@[inline]
pub fn (bf BitField) has(a ...int) bool {
for bitnr in a {
if bitnr < 0 || bitnr >= bf.size {
return false
}
if int((bf.field[bitslot(bitnr)] >> (bitnr % slot_size)) & u32(1)) == 1 {
return true
}
}
return false
}
// all test if *all* of the bits are set.
// Example: mut bf := bitfield.from_str('111111100000000'); assert !bf.all(1,3,5,7)
@[inline]
pub fn (bf BitField) all(a ...int) bool {
for bitnr in a {
if bitnr < 0 || bitnr >= bf.size {
return false
}
if int((bf.field[bitslot(bitnr)] >> (bitnr % slot_size)) & u32(1)) == 0 {
return false
}
}
return true
}
// bf_and performs logical AND operation on every pair of bits from `input1` and `input2`.
// It returns the result as a new bitfield. If inputs differ in size, the tail of the longer one is ignored.
pub fn bf_and(input1 BitField, input2 BitField) BitField {
size := min(input1.size, input2.size)
bitnslots := zbitnslots(size)
mut output := new(size)
for i in 0 .. bitnslots {
output.field[i] = input1.field[i] & input2.field[i]
}
output.clear_tail()
return output
}
// bf_not toggles all bits in a bitfield and returns the result as a new bitfield.
pub fn bf_not(bf BitField) BitField {
size := bf.size
bitnslots := zbitnslots(size)
mut output := new(size)
for i in 0 .. bitnslots {
output.field[i] = ~bf.field[i]
}
output.clear_tail()
return output
}
// bf_or performs logical OR operation on every pair of bits from `input1` and `input2`.
// It returns the result as a new bitfield. If inputs differ in size, the tail of the longer one is ignored.
pub fn bf_or(input1 BitField, input2 BitField) BitField {
size := min(input1.size, input2.size)
bitnslots := zbitnslots(size)
mut output := new(size)
for i in 0 .. bitnslots {
output.field[i] = input1.field[i] | input2.field[i]
}
output.clear_tail()
return output
}
// bf_xor perform logical XOR operation on every pair of bits from `input1` and `input2`.
// It returns the result as a new bitfield. If inputs differ in size, the tail of the longer one is ignored.
pub fn bf_xor(input1 BitField, input2 BitField) BitField {
size := min(input1.size, input2.size)
bitnslots := zbitnslots(size)
mut output := new(size)
for i in 0 .. bitnslots {
output.field[i] = input1.field[i] ^ input2.field[i]
}
output.clear_tail()
return output
}
// join concatenates two bitfields and returns the result as a new bitfield.
pub fn join(input1 BitField, input2 BitField) BitField {
output_size := input1.size + input2.size
mut output := new(output_size)
// copy the first input to output as is
for i in 0 .. zbitnslots(input1.size) {
output.field[i] = input1.field[i]
}
// find offset bit and offset slot
offset_bit := input1.size % slot_size
offset_slot := input1.size / slot_size
for i in 0 .. zbitnslots(input2.size) {
output.field[i + offset_slot] |= u32(input2.field[i] << u32(offset_bit))
}
// If offset_bit is not zero, additional operations are needed.
// Number of iterations depends on the nr of slots in output. Two
// options:
// (a) nr of slots in output is the sum of inputs' slots. In this
// case, the nr of bits in the last slot of output is less than the
// nr of bits in the second input (i.e. ), OR
// (b) nr of slots of output is the sum of inputs' slots less one
// (i.e. less iterations needed). In this case, the nr of bits in
// the last slot of output is greater than the nr of bits in the second
// input.
// If offset_bit is zero, no additional copies needed.
if (output_size - 1) % slot_size < (input2.size - 1) % slot_size {
for i in 0 .. zbitnslots(input2.size) {
output.field[i + offset_slot + 1] |= u32(input2.field[i] >> u32(slot_size - offset_bit))
}
} else if (output_size - 1) % slot_size > (input2.size - 1) % slot_size {
for i in 0 .. zbitnslots(input2.size) - 1 {
output.field[i + offset_slot + 1] |= u32(input2.field[i] >> u32(slot_size - offset_bit))
}
}
return output
}
// get_size returns the number of bits the array can hold.
@[inline]
pub fn (bf BitField) get_size() int {
return bf.size
}
// clone creates a copy of a bit array.
pub fn (bf BitField) clone() BitField {
bitnslots := zbitnslots(bf.size)
mut output := new(bf.size)
for i in 0 .. bitnslots {
output.field[i] = bf.field[i]
}
return output
}
// == compares 2 bitfields, and returns true if they are equal.
pub fn (a BitField) == (b BitField) bool {
if a.size != b.size {
return false
}
for i in 0 .. zbitnslots(a.size) {
if a.field[i] != b.field[i] {
return false
}
}
return true
}
// pop_count returns the number of set bits (ones) in the array.
pub fn (bf BitField) pop_count() int {
mut count := 0
for i in 0 .. zbitnslots(bf.size) {
count += bits.ones_count_32(bf.field[i])
}
return count
}
// hamming computes the Hamming distance between two bit arrays.
@[inline]
pub fn hamming(input1 BitField, input2 BitField) int {
input_xored := bf_xor(input1, input2)
return input_xored.pop_count()
}
// pos checks if the bitfield contains a sub-array `needle`.
// It returns its position if it does, -1 if it does not, and -2 on error.
pub fn (bf BitField) pos(needle BitField) int {
heystack_size := bf.size
needle_size := needle.size
diff := heystack_size - needle_size
// needle longer than bitfield; return error code -2
if diff < 0 {
return -2
}
for i := 0; i <= diff; i++ {
needle_candidate := bf.slice(i, needle_size + i)
if needle_candidate == needle {
// needle matches a section of the bitfield; return starting position of the section
return i
}
}
// nothing matched; return -1
return -1
}
// slice returns a sub-array of bits between `_start` (included) and `_end` (excluded).
pub fn (bf BitField) slice(_start int, _end int) BitField {
// boundary checks
mut start := _start
mut end := _end
if end > bf.size {
end = bf.size // or panic?
}
if start > end {
start = end // or panic?
}
mut output := new(end - start)
if end == start {
// zero-length slice: nothing to copy; field is empty
return output
}
start_offset := start % slot_size
end_offset := (end - 1) % slot_size
start_slot := start / slot_size
end_slot := (end - 1) / slot_size
output_slots := zbitnslots(end - start)
if output_slots > 1 {
if start_offset != 0 {
for i in 0 .. output_slots - 1 {
output.field[i] = u32(bf.field[start_slot + i] >> u32(start_offset))
output.field[i] = output.field[i] | u32(bf.field[start_slot + i + 1] << u32(slot_size - start_offset))
}
} else {
for i in 0 .. output_slots - 1 {
output.field[i] = u32(bf.field[start_slot + i])
}
}
}
if start_offset > end_offset {
output.field[(end - start - 1) / slot_size] = u32(bf.field[end_slot - 1] >> u32(start_offset))
mut mask := u32((1 << (end_offset + 1)) - 1)
mask = bf.field[end_slot] & mask
mask = u32(mask << u32(slot_size - start_offset))
output.field[(end - start - 1) / slot_size] |= mask
} else if start_offset == 0 {
mut mask := u32(0)
if end_offset == slot_size - 1 {
mask = u32(-1)
} else {
mask = u32(u32(1) << u32(end_offset + 1))
mask = mask - u32(1)
}
output.field[(end - start - 1) / slot_size] = (bf.field[end_slot] & mask)
} else {
mut mask := u32(((1 << (end_offset - start_offset + 1)) - 1) << start_offset)
mask = bf.field[end_slot] & mask
mask = u32(mask >> u32(start_offset))
output.field[(end - start - 1) / slot_size] |= mask
}
return output
}
// reverse reverses the order of bits in the bitfield (swap the first with the last, the second with the last but one and so on).
pub fn (bf BitField) reverse() BitField {
size := bf.size
bitnslots := zbitnslots(size)
mut output := new(size)
for i := 0; i < (bitnslots - 1); i++ {
for j in 0 .. slot_size {
if u32(bf.field[i] >> u32(j)) & u32(1) == u32(1) {
output.set_bit(size - i * slot_size - j - 1)
}
}
}
bits_in_last_input_slot := (size - 1) % slot_size + 1
for j in 0 .. bits_in_last_input_slot {
if u32(bf.field[bitnslots - 1] >> u32(j)) & u32(1) == u32(1) {
output.set_bit(bits_in_last_input_slot - j - 1)
}
}
return output
}
// resize changes the size of the bit array to `new_size`.
pub fn (mut bf BitField) resize(new_size int) {
new_bitnslots := zbitnslots(new_size)
old_size := bf.size
old_bitnslots := zbitnslots(old_size)
mut field := []u32{len: new_bitnslots}
for i := 0; i < old_bitnslots && i < new_bitnslots; i++ {
field[i] = bf.field[i]
}
bf.field = field
bf.size = new_size
if new_size < old_size && new_size % slot_size != 0 {
bf.clear_tail()
}
}
// rotate performs a circular-shift on the bits by `offset` positions (move `offset` bit to 0, `offset+1` bit to 1, and so on).
pub fn (bf BitField) rotate(offset int) BitField {
// This function "cuts" the bitfield into two and swaps the pieces.
// If the offset is positive, the cutting point is counted from the
// beginning of the bit array, otherwise from the end.
size := bf.size
if size == 0 {
return bf
}
// removing extra rotations
mut offset_internal := offset % size
if offset_internal == 0 {
// nothing to shift
return bf
}
if offset_internal < 0 {
offset_internal = offset_internal + size
}
first_chunk := bf.slice(0, offset_internal)
second_chunk := bf.slice(offset_internal, size)
output := join(second_chunk, first_chunk)
return output
}
// shift_left shift-left the bits by `count` positions.
pub fn (bf BitField) shift_left(count int) BitField {
size := bf.size
if count <= 0 {
return bf
} else if count >= size {
// return zeroes
return new(size)
}
zeroes := new(count)
return join(bf.slice(count, size), zeroes)
}
// shift_right shift-right the bits by `count` positions.
pub fn (bf BitField) shift_right(count int) BitField {
size := bf.size
if count <= 0 {
return bf
} else if count >= size {
// return zeroes
return new(size)
}
zeroes := new(count)
return join(zeroes, bf.slice(0, size - count))
}
// Internal functions
// clear_tail clears the extra bits that are not part of the bitfield, but yet are allocated
@[inline]
fn (mut bf BitField) clear_tail() {
tail := bf.size % slot_size
if tail != 0 {
// create a mask for the tail
mask := u32((1 << tail) - 1)
// clear the extra bits
bf.field[zbitnslots(bf.size) - 1] = bf.field[zbitnslots(bf.size) - 1] & mask
}
}
// bitmask is the bitmask needed to access a particular bit at offset bitnr
@[inline]
fn bitmask(bitnr int) u32 {
return u32(u32(1) << u32(bitnr % slot_size))
}
// bitslot is the slot index (i.e. the integer) where a particular bit is located
@[inline]
fn bitslot(size int) int {
return size / slot_size
}
// min returns the minimum of 2 integers; it is here to avoid importing math just for that
@[inline]
fn min(input1 int, input2 int) int {
if input1 < input2 {
return input1
} else {
return input2
}
}
// zbitnslots returns the minimum number of whole integers, needed to represent a bitfield of size length
@[inline]
fn zbitnslots(length int) int {
if length <= 0 {
return 0
}
return (length - 1) / slot_size + 1
}
+401
View File
@@ -0,0 +1,401 @@
import bitfield
import rand
fn test_bf_new_size() {
instance := bitfield.new(75)
assert instance.get_size() == 75
}
fn test_bf_set_clear_toggle_get() {
mut instance := bitfield.new(75)
instance.set_bit(47)
assert instance.get_bit(47) == 1
instance.clear_bit(47)
assert instance.get_bit(47) == 0
instance.toggle_bit(47)
assert instance.get_bit(47) == 1
instance.set_if(true, 48)
assert instance.get_bit(48) == 1
instance.set_if(false, 48)
assert instance.get_bit(48) == 0
}
fn test_bf_multiple_flags() {
mut instance := bitfield.new(75)
// 1,3,5,7,9 set
instance.set_bits(1, 3, 5, 7, 9)
assert instance.all(1, 3, 5, 7, 9)
assert instance.all(1, 3, 20) == false
assert instance.has(1, 20, 30, 40)
assert instance.has(20, 30, 40) == false
// 1,3 set
instance.clear_bits(5, 7, 9)
assert instance.all(1, 3, 5, 7, 9) == false
assert instance.all(1, 3)
assert instance.has(3, 20, 30, 40)
// 3,5,7 set
instance.toggle_bits(1, 5, 7)
assert instance.all(3, 5, 7)
assert instance.all(1, 3, 5, 7) == false
assert instance.has(5, 20, 30, 40)
}
fn test_bf_insert_extract() {
mut instance := bitfield.new(11)
instance.set_all()
instance.insert(2, 9, 3)
assert instance.extract(2, 1) == 0
assert instance.extract(2, 8) == 1
assert instance.extract(10, 1) == 1
instance.set_all()
instance.insert_lowest_bits_first(2, 9, 3)
assert instance.extract_lowest_bits_first(2, 1) == 1
assert instance.extract_lowest_bits_first(2, 8) == 3
assert instance.extract_lowest_bits_first(10, 1) == 0
}
fn test_bf_and_not_or_xor() {
len := 80
mut input1 := bitfield.new(len)
mut input2 := bitfield.new(len)
mut i := 0
for i < len {
if rand.intn(2) or { 0 } == 1 {
input1.set_bit(i)
}
if rand.intn(2) or { 0 } == 1 {
input2.set_bit(i)
}
i++
}
output1 := bitfield.bf_xor(input1, input2)
bf_and := bitfield.bf_and(input1, input2)
bf_or := bitfield.bf_or(input1, input2)
bf_not := bitfield.bf_not(bf_and)
output2 := bitfield.bf_and(bf_or, bf_not)
mut result := 1
i = 0
for i < len {
if output1.get_bit(i) != output2.get_bit(i) {
result = 0
}
i++
}
assert result == 1
}
fn test_clone_cmp() {
len := 80
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input.set_bit(i)
}
}
output := input.clone()
assert output.get_size() == len
assert input == output
}
fn test_slice_join() {
len := 80
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input.set_bit(i)
}
}
mut result := 1
for point := 1; point < (len - 1); point++ {
// divide a bitfield into two subfields
chunk1 := input.slice(0, point)
chunk2 := input.slice(point, input.get_size())
// concatenate them back into one and compare to the original
output := bitfield.join(chunk1, chunk2)
if input != output {
result = 0
}
}
assert result == 1
}
fn test_pop_count() {
len := 80
mut count0 := 0
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input.set_bit(i)
count0++
}
}
count1 := input.pop_count()
assert count0 == count1
}
fn test_pop_count2() {
b :=
bitfield.from_str('011000110110110000010001000011010011011111011110101001010011011010001100001001101111111011010011')
assert b.pop_count() == 50
}
fn test_hamming() {
len := 80
mut count := 0
mut input1 := bitfield.new(len)
mut input2 := bitfield.new(len)
for i in 0 .. len {
match rand.intn(4) {
0, 1 {
input1.set_bit(i)
count++
}
2 {
input2.set_bit(i)
count++
}
3 {
input1.set_bit(i)
input2.set_bit(i)
}
else {}
} or { 0 }
}
assert count == bitfield.hamming(input1, input2)
}
fn test_bf_from_bytes() {
input := [u8(0x01), 0xF0, 0x0F, 0xF0, 0xFF]
output := bitfield.from_bytes(input).str()
assert output == '00000001' + '11110000' + '00001111' + '11110000' + '11111111'
newoutput := bitfield.from_str(output).str()
assert newoutput == output
}
fn test_bf_from_bytes_lowest_bits_first() {
input := [u8(0x01), 0xF0]
output := bitfield.from_bytes_lowest_bits_first(input).str()
assert output == '10000000' + '00001111'
newoutput := bitfield.from_str(output).str()
assert newoutput == output
}
fn test_bf_from_str() {
len := 80
mut input := ''
for _ in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input = input + '1'
} else {
input = input + '0'
}
}
output := bitfield.from_str(input)
mut result := 1
for i in 0 .. len {
if input[i] != output.get_bit(i) + 48 {
result = 0
}
}
assert result == 1
}
fn test_bf_bf2str() {
len := 80
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input.set_bit(i)
}
}
mut check := ''
for i in 0 .. len {
if input.get_bit(i) == 1 {
check = check + '1'
} else {
check = check + '0'
}
}
output := input.str()
mut result := 1
for i in 0 .. len {
if check[i] != output[i] {
result = 0
}
}
assert result == 1
}
fn test_bf_set_all() {
len := 80
mut input := bitfield.new(len)
input.set_all()
mut result := 1
for i in 0 .. len {
if input.get_bit(i) != 1 {
result = 0
}
}
assert result == 1
}
fn test_bf_clear_all() {
len := 80
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input.set_bit(i)
}
}
input.clear_all()
mut result := 1
for i in 0 .. len {
if input.get_bit(i) != 0 {
result = 0
}
}
assert result == 1
}
fn test_bf_reverse() {
len := 80
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 1 {
input.set_bit(i)
}
}
check := input.clone()
output := input.reverse()
mut result := 1
for i in 0 .. len {
if output.get_bit(i) != check.get_bit(len - i - 1) {
result = 0
}
}
assert result == 1
}
fn test_bf_resize() {
len := 80
mut input := bitfield.new(rand.intn(len) or { 0 } + 1)
for _ in 0 .. 100 {
input.resize(rand.intn(len) or { 0 } + 1)
input.set_bit(input.get_size() - 1)
}
assert input.get_bit(input.get_size() - 1) == 1
}
fn test_bf_pos() {
/*
*
* set haystack size to 80
* test different sizes of needle, from 1 to 80
* test different positions of needle, from 0 to where it fits
* all haystacks here contain exactly one instance of needle,
* so search should return non-negative-values
*
*/
len := 80
mut result := 1
for i := 1; i < len; i++ { // needle size
for j in 0 .. len - i { // needle position in the haystack
// create the needle
mut needle := bitfield.new(i)
// fill the needle with random values
for k in 0 .. i {
if rand.intn(2) or { 0 } == 1 {
needle.set_bit(k)
}
}
// make sure the needle contains at least one set bit, selected randomly
r := rand.intn(i) or { 0 }
needle.set_bit(r)
// create the haystack, make sure it contains the needle
mut haystack := needle.clone()
// if there is space between the start of the haystack and the sought needle, fill it with zeroes
if j > 0 {
start := bitfield.new(j)
tmp := bitfield.join(start, haystack)
haystack = tmp
}
// if there is space between the sought needle and the end of haystack, fill it with zeroes
if j + i < len {
end := bitfield.new(len - j - i)
tmp2 := bitfield.join(haystack, end)
haystack = tmp2
}
// now let's test
// the result should be equal to j
if haystack.pos(needle) != j {
result = 0
}
}
}
assert result == 1
}
fn test_bf_rotate() {
mut result := 1
len := 80
for i := 1; i < 80 && result == 1; i++ {
mut chunk1 := bitfield.new(i)
chunk2 := bitfield.new(len - i)
chunk1.set_all()
input := bitfield.join(chunk1, chunk2)
output := input.rotate(i)
if output.get_bit(len - i - 1) != 0 || output.get_bit(len - i) != 1 {
result = 0
}
}
assert result == 1
}
fn test_bf_printing() {
len := 80
mut input := bitfield.new(len)
for i in 0 .. len {
if rand.intn(2) or { 0 } == 0 {
input.set_bit(i)
}
}
// the following should convert the bitfield input into a string automatically
println(input)
assert true
}
fn test_bf_shift() {
str := '0001001101111111'
bf := bitfield.from_str(str)
bf_left := bf.shift_left(4)
assert bf_left.str() == '0011011111110000'
bf_right := bf.shift_right(4)
assert bf_right.str() == '0000000100110111'
bf_large_left := bf.shift_left(100)
bf_large_right := bf.shift_right(100)
assert bf_large_left.str() == '0000000000000000'
assert bf_large_right.str() == '0000000000000000'
}
// Regression test: zero-length slice must not panic (reported via slice(i,i) and pos(new(0))).
fn test_zero_length_slice_and_pos() {
// slice(i, i) on a non-empty bitfield must return an empty bitfield
bf := bitfield.from_str('101')
empty := bf.slice(1, 1)
assert empty.get_size() == 0
assert empty.str() == ''
// pos with a zero-length needle on a non-empty bitfield must not panic
needle := bitfield.new(0)
pos := bf.pos(needle)
// empty needle matches at position 0 by convention
assert pos == 0
// pos on a zero-length bitfield with a zero-length needle must not panic
empty_bf := bitfield.new(0)
assert empty_bf.pos(needle) == 0
}
+55
View File
@@ -0,0 +1,55 @@
## Description
`build` provides a small build system leveraging V(SH) for the buildscript.
## Example
> See also: [build_system example](https://github.com/vlang/v/tree/master/examples/build_system)
```v
#!/usr/bin/env v
import build
// .vsh automatically imports `os`, so you don't need this typically
import os { system }
const app_name = 'vlang'
const program_args = 'World'
mut context := build.context(
// Set the default task to `release` when no arguments are provided
default: 'release'
)
context.task(name: 'doc', run: |self| system('v doc .'))
context.task(name: 'run', run: |self| system('v run . ${program_args}'))
context.task(name: 'build', run: |self| system('v .'))
context.task(name: 'build.prod', run: |self| system('v -prod .'))
context.task(
name: 'release'
depends: ['doc']
run: fn (self build.Task) ! {
system('v -prod -o build/${app_name} .')
// You could use Git to publish a release here too
}
)
context.run()
```
For portable `.vsh` scripts, prefer `#!/usr/bin/env v`.
That works on BusyBox and OpenBSD too, where `/usr/bin/env -S` is not available.
## Pre-Compiling
Running VSH scripts requires V to compile the script before executing it, which can cause a delay
between when you run `./build.vsh` and when the script actually starts executing.
If you want to fix this, you can "pre-compile" the buildscript by building the script with
`v build build.vsh` (or `v -skip-running build.vsh`).
> You will need to rebuild every time you change the buildscript, and you should also add `/build`
> to your `.gitignore`
> If you want maximum speed, you can also `v -prod build build.vsh`
+180
View File
@@ -0,0 +1,180 @@
module build
import os
@[heap; noinit]
pub struct BuildContext {
mut:
// should_run caches the result of should_run from tasks.
should_run map[string]bool
tasks []Task
pub mut:
// default is the default task to run when no others are provided.
default ?string
}
@[heap; noinit]
pub struct Task {
run fn (Task) ! @[required]
should_run fn (Task) !bool @[required]
// repeatable controls whether or not this task can run multiple times per build cycle
repeatable bool
pub:
name string
help string
depends []string
mut:
did_run bool
}
@[params]
pub struct BuildContextParams {
pub:
default ?string
}
@[params]
pub struct TaskParams {
pub:
name string @[required]
help string
depends []string
should_run fn (Task) !bool = |self| true
run fn (Task) ! @[required]
// repeatable controls whether or not this task can run multiple times per build cycle
repeatable bool
}
@[params]
pub struct ArtifactParams {
pub:
name string @[required]
help string
depends []string
should_run fn (Task) !bool = |self| !os.exists(self.name)
run fn (Task) ! @[required]
// repeatable controls whether or not this task can run multiple times per build cycle
repeatable bool
}
// context creates an empty BuildContext.
pub fn context(params BuildContextParams) BuildContext {
return BuildContext{
default: params.default
}
}
// task creates a task for the given context.
pub fn (mut context BuildContext) task(config TaskParams) {
if context.get_task(config.name) != none {
eprintln('error: task already exists with name `${config.name}`')
exit(1)
}
context.tasks << Task{
should_run: config.should_run
run: config.run
name: config.name
help: config.help
depends: config.depends
}
}
// artifact creates an artifact task for the given context.
pub fn (mut context BuildContext) artifact(config ArtifactParams) {
if context.get_task(config.name) != none {
eprintln('error: task already exists with name `${config.name}`')
exit(1)
}
context.tasks << Task{
should_run: config.should_run
run: config.run
name: config.name
help: config.help
depends: config.depends
repeatable: config.repeatable
}
}
// get_task gets the task with the given name.
pub fn (mut context BuildContext) get_task(name string) ?&Task {
for mut task in context.tasks {
if task.name == name {
return mut task
}
}
return none
}
// exec executes the task with the given name in the context.
pub fn (mut context BuildContext) exec(name string) {
if mut task := context.get_task(name) {
task.exec(mut context)
} else {
eprintln('error: no such task: ${name}')
exit(1)
}
}
// exec runs the given task and its dependencies.
pub fn (mut task Task) exec(mut context BuildContext) {
if task.did_run && !task.repeatable {
println(': ${task.name} (skipped)')
return
}
if task.name !in context.should_run {
context.should_run[task.name] = task.should_run(task) or {
eprintln('error: failed to call should_run for task `${task.name}`: ${err}')
exit(1)
}
}
if !context.should_run[task.name] {
println(': ${task.name} (skipped)')
return
}
for dep in task.depends {
if dep == task.name {
eprintln('error: cyclic task dependency detected, `${task.name}` depends on itself')
exit(1)
}
context.exec(dep)
}
println(': ${task.name}')
task.did_run = true
task.run(task) or {
eprintln('error: failed to run task `${task.name}`: ${err}')
exit(1)
}
}
// run executes all tasks provided through os.args.
pub fn (mut context BuildContext) run() {
// filter out options
mut tasks := os.args[1..].filter(|it| !it.starts_with('-'))
// check options
if '--tasks' in os.args || '-tasks' in os.args {
println('Tasks:')
for _, task in context.tasks {
println('- ${task.name}: ${task.help}')
}
return
}
if tasks.len == 0 {
if context.default != none {
tasks << context.default
} else {
eprintln('error: no task provided, run with `--tasks` for a list')
exit(1)
}
}
// execute tasks
for arg in tasks {
context.exec(arg)
}
}
+14
View File
@@ -0,0 +1,14 @@
## Description
`builtin` is a module that is implicitly imported by every V program.
It implements the builtin V types `array`, `string`, `map`.
It also includes Unicode-aware string helpers such as `string.graphemes()`
for splitting text into grapheme clusters.
It also implements builtin functions like `println`, `eprintln`, `malloc`,
`panic`, `print_backtrace`.
The autogenerated documentation for `builtin` functions is lacking, so for these
functions, please refer to the
[official V documentation](https://github.com/vlang/v/blob/master/doc/docs.md).
+635
View File
@@ -0,0 +1,635 @@
@[has_globals]
module builtin
// v_memory_panic will be true, *only* when a call to malloc/realloc/vcalloc etc could not succeed.
// In that situation, functions that are registered with at_exit(), should be able to limit their
// activity accordingly, by checking this flag.
// The V compiler itself for example registers a function with at_exit(), for showing timers.
// Without a way to distinguish, that we are in a memory panic, that would just display a second panic,
// which would be less clear to the user.
__global v_memory_panic = false
@[noreturn]
fn _memory_panic(fname string, size isize) {
v_memory_panic = true
// Note: do not use string interpolation here at all, since string interpolation itself allocates
eprint(fname)
eprint('(')
$if freestanding || vinix || v2_native_windows_pe_minimal ? {
eprint('size') // TODO: use something more informative here
} $else {
C.fprintf(C.stderr, c'%p', voidptr(size))
}
if size < 0 {
eprint(' < 0')
}
eprintln(')')
panic('memory allocation failure')
}
__global total_m = i64(0)
// Opt-in heap allocation hooks, enabled with `-d track_heap`. When the flag is
// set, every heap allocation calls `vheap_alloc(ptr, size)` and every free calls
// `vheap_free(ptr)`; link an external profiler / leak tracker that implements
// them to observe live allocations at runtime. The `@[if track_heap ?]` attribute
// compiles the hooks to no-ops (zero cost) when the flag is off.
//
// track_heap models *manual* memory management (`-gc none`): the free hook only
// fires on the manual-free path, so under a GC the collector reclaims blocks with
// no matching hook and they would be reported as permanently live. Reject that
// combination at C compile time rather than emit misleading stats. The guard is
// emitted as C preprocessor code so `-cross` can still preserve conditional C.
#insert "@VEXEROOT/vlib/builtin/track_heap_checks.h"
fn C.vheap_alloc(p voidptr, n u64)
fn C.vheap_free(p voidptr)
@[if track_heap ?]
fn _ht_alloc(p &u8, n isize) {
C.vheap_alloc(p, u64(n))
}
@[if track_heap ?]
fn _ht_free(p voidptr) {
C.vheap_free(p)
}
// malloc dynamically allocates a `n` bytes block of memory on the heap.
// malloc returns a `byteptr` pointing to the memory address of the allocated space.
// unlike the `calloc` family of functions - malloc will not zero the memory block.
@[unsafe]
pub fn malloc(n isize) &u8 {
$if trace_malloc ? {
total_m += n
C.fprintf(C.stderr, c'_v_malloc %6d total %10d\n', n, total_m)
// print_backtrace()
}
if n < 0 {
_memory_panic(@FN, n)
} else if n == 0 {
return &u8(unsafe { nil })
}
mut res := &u8(unsafe { nil })
$if prealloc {
return unsafe { prealloc_malloc(n) }
} $else $if vgc ? {
unsafe {
res = &u8(vgc_malloc(usize(n)))
}
} $else $if gcboehm ? {
unsafe {
res = C.GC_MALLOC(n)
}
} $else $if freestanding {
// todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes
// so theoretically it is safe
res = unsafe { __malloc(usize(n)) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_malloc to allocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc.
res = unsafe { C._aligned_malloc(n, 1) }
} $else {
res = unsafe { C.malloc(n) }
}
}
if res == 0 {
_memory_panic(@FN, n)
}
$if debug_malloc ? {
// Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
// when the calling code wrongly relies on it being zeroed.
unsafe { C.memset(res, 0x4D, n) }
}
_ht_alloc(res, n)
return res
}
@[unsafe]
pub fn malloc_noscan(n isize) &u8 {
$if trace_malloc ? {
total_m += n
C.fprintf(C.stderr, c'malloc_noscan %6d total %10d\n', n, total_m)
// print_backtrace()
}
if n < 0 {
_memory_panic(@FN, n)
}
mut res := &u8(unsafe { nil })
$if prealloc {
return unsafe { prealloc_malloc(n) }
} $else $if vgc ? {
unsafe {
res = &u8(vgc_malloc_noscan(usize(n)))
}
} $else $if gcboehm ? {
$if gcboehm_opt ? {
unsafe {
res = C.GC_MALLOC_ATOMIC(n)
}
} $else {
unsafe {
res = C.GC_MALLOC(n)
}
}
} $else $if freestanding {
res = unsafe { __malloc(usize(n)) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_malloc to allocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc.
res = unsafe { C._aligned_malloc(n, 1) }
} $else {
res = unsafe { C.malloc(n) }
}
}
if res == 0 {
_memory_panic(@FN, n)
}
$if debug_malloc ? {
// Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
// when the calling code wrongly relies on it being zeroed.
unsafe { C.memset(res, 0x4D, n) }
}
_ht_alloc(res, n)
return res
}
@[unsafe]
fn malloc_uninit(n isize) &u8 {
if n < 0 {
_memory_panic(@FN, n)
} else if n == 0 {
return &u8(unsafe { nil })
}
$if vgc ? {
return unsafe { &u8(vgc_malloc_typed_opts(usize(n), 0, 0, false)) }
}
return malloc(n)
}
@[unsafe]
fn malloc_noscan_uninit(n isize) &u8 {
if n < 0 {
_memory_panic(@FN, n)
} else if n == 0 {
return &u8(unsafe { nil })
}
$if vgc ? {
return unsafe { &u8(vgc_malloc_noscan_opts(usize(n), false)) }
}
return malloc_noscan(n)
}
@[inline]
fn __at_least_one(how_many u64) u64 {
// handle the case for allocating memory for empty structs, which have sizeof(EmptyStruct) == 0
// in this case, just allocate a single byte, avoiding the panic for malloc(0)
if how_many == 0 {
return 1
}
return how_many
}
// malloc_uncollectable dynamically allocates a `n` bytes block of memory
// on the heap, which will NOT be garbage-collected (but its contents will).
@[unsafe]
pub fn malloc_uncollectable(n isize) &u8 {
$if trace_malloc ? {
total_m += n
C.fprintf(C.stderr, c'malloc_uncollectable %6d total %10d\n', n, total_m)
// print_backtrace()
}
if n < 0 {
_memory_panic(@FN, n)
}
mut res := &u8(unsafe { nil })
$if prealloc {
return unsafe { prealloc_malloc(n) }
} $else $if vgc ? {
unsafe {
res = &u8(vgc_malloc(usize(n)))
}
} $else $if gcboehm ? {
unsafe {
res = C.GC_MALLOC_UNCOLLECTABLE(n)
}
} $else $if freestanding {
res = unsafe { __malloc(usize(n)) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_malloc to allocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc.
res = unsafe { C._aligned_malloc(n, 1) }
} $else {
res = unsafe { C.malloc(n) }
}
}
if res == 0 {
_memory_panic(@FN, n)
}
$if debug_malloc ? {
// Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
// when the calling code wrongly relies on it being zeroed.
unsafe { C.memset(res, 0x4D, n) }
}
_ht_alloc(res, n)
return res
}
// v_realloc resizes the memory block `b` with `n` bytes.
// The `b byteptr` must be a pointer to an existing memory block
// previously allocated with `malloc` or `vcalloc`.
// Please, see also realloc_data, and use it instead if possible.
@[unsafe]
pub fn v_realloc(b &u8, n isize) &u8 {
$if trace_realloc ? {
C.fprintf(C.stderr, c'v_realloc %6d\n', n)
}
mut new_ptr := &u8(unsafe { nil })
$if prealloc {
unsafe {
new_ptr = malloc(n)
C.memcpy(new_ptr, b, n)
}
return new_ptr
} $else $if vgc ? {
new_ptr = unsafe { &u8(vgc_realloc(b, usize(n))) }
} $else $if gcboehm ? {
new_ptr = unsafe { C.GC_REALLOC(b, n) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_realloc to reallocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc/_aligned_realloc.
new_ptr = unsafe { C._aligned_realloc(b, n, 1) }
} $else {
new_ptr = unsafe { C.realloc(b, n) }
}
}
if new_ptr == 0 {
_memory_panic(@FN, n)
}
// realloc(nil, …) means "allocate", not "free then allocate" (the stbi
// STBI_REALLOC path forwards null `b` here), so only report a free when there
// was a real prior block — mirrors the nil guard in free().
if b != unsafe { nil } {
_ht_free(b)
}
_ht_alloc(new_ptr, n)
return new_ptr
}
// realloc_data resizes the memory block pointed by `old_data` to `new_size`
// bytes. `old_data` must be a pointer to an existing memory block, previously
// allocated with `malloc` or `vcalloc`, of size `old_data`.
// realloc_data returns a pointer to the new location of the block.
// Note: if you know the old data size, it is preferable to call `realloc_data`,
// instead of `v_realloc`, at least during development, because `realloc_data`
// can make debugging easier, when you compile your program with
// `-d debug_realloc`.
@[unsafe]
pub fn realloc_data(old_data &u8, old_size int, new_size int) &u8 {
$if trace_realloc ? {
C.fprintf(C.stderr, c'realloc_data old_size: %6d new_size: %6d\n', old_size, new_size)
}
$if prealloc {
return unsafe { prealloc_realloc(old_data, old_size, new_size) }
}
$if debug_realloc ? {
// Note: this is slower, but helps debugging memory problems.
// The main idea is to always force reallocating:
// 1) allocate a new memory block
// 2) copy the old to the new
// 3) fill the old with 0x57 (`W`)
// 4) free the old block
// => if there is still a pointer to the old block somewhere
// it will point to memory that is now filled with 0x57.
unsafe {
new_ptr := malloc(new_size)
min_size := if old_size < new_size { old_size } else { new_size }
C.memcpy(new_ptr, old_data, min_size)
C.memset(old_data, 0x57, old_size)
free(old_data)
return new_ptr
}
}
mut nptr := &u8(unsafe { nil })
$if vgc ? {
nptr = unsafe { &u8(vgc_realloc(old_data, usize(new_size))) }
} $else $if gcboehm ? {
nptr = unsafe { C.GC_REALLOC(old_data, new_size) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_realloc to reallocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc/_aligned_realloc.
nptr = unsafe { C._aligned_realloc(old_data, new_size, 1) }
} $else {
nptr = unsafe { C.realloc(old_data, new_size) }
}
}
if nptr == 0 {
_memory_panic(@FN, isize(new_size))
}
// realloc(nil, …) means "allocate"; don't report a free with no prior block.
if old_data != unsafe { nil } {
_ht_free(old_data)
}
_ht_alloc(nptr, isize(new_size))
return nptr
}
// vcalloc dynamically allocates a zeroed `n` bytes block of memory on the heap.
// vcalloc returns a `byteptr` pointing to the memory address of the allocated space.
// vcalloc checks for negative values given in `n`.
pub fn vcalloc(n isize) &u8 {
$if trace_vcalloc ? {
total_m += n
C.fprintf(C.stderr, c'vcalloc %6d total %10d\n', n, total_m)
}
if n < 0 {
_memory_panic(@FN, n)
} else if n == 0 {
return &u8(unsafe { nil })
}
$if prealloc {
return unsafe { prealloc_calloc(n) }
} $else $if vgc ? {
return unsafe { &u8(vgc_calloc(usize(n))) }
} $else $if gcboehm ? {
return unsafe { &u8(C.GC_MALLOC(n)) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_malloc to allocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc/_aligned_realloc/_aligned_recalloc.
ptr := unsafe { C._aligned_malloc(n, 1) }
if ptr != &u8(unsafe { nil }) {
unsafe { C.memset(ptr, 0, n) }
}
_ht_alloc(ptr, n)
return ptr
} $else {
r := unsafe { C.calloc(1, n) }
_ht_alloc(r, n)
return r
}
}
return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved
}
// special versions of the above that allocate memory which is not scanned
// for pointers (but is collected) when the Boehm garbage collection is used
pub fn vcalloc_noscan(n isize) &u8 {
$if trace_vcalloc ? {
total_m += n
C.fprintf(C.stderr, c'vcalloc_noscan %6d total %10d\n', n, total_m)
}
$if prealloc {
return unsafe { prealloc_calloc(n) }
} $else $if vgc ? {
if n < 0 {
_memory_panic(@FN, n)
}
return unsafe { &u8(vgc_calloc(usize(n))) }
} $else $if gcboehm ? {
if n < 0 {
_memory_panic(@FN, n)
}
$if gcboehm_opt ? {
res := unsafe { C.GC_MALLOC_ATOMIC(n) }
unsafe { C.memset(res, 0, n) }
return &u8(res)
} $else {
res := unsafe { C.GC_MALLOC(n) }
return &u8(res)
}
} $else {
return unsafe { vcalloc(n) }
}
return &u8(unsafe { nil }) // not reached, TODO: remove when V's checker is improved
}
// free allows for manually freeing memory allocated at the address `ptr`.
@[unsafe]
pub fn free(ptr voidptr) {
$if trace_free ? {
C.fprintf(C.stderr, c'free ptr: %p\n', ptr)
}
$if builtin_free_nop ? {
return
}
if ptr == unsafe { 0 } {
$if trace_free_nulls ? {
C.fprintf(C.stderr, c'free null ptr\n', ptr)
}
$if trace_free_nulls_break ? {
break_if_debugger_attached()
}
return
}
// `none__` is a process-wide singleton IError used by option/results to
// represent "no error object". Self-hosted builds can still route that
// sentinel through explicit cleanup paths under `-gc none`, so ignore it.
none_err := &C.IError(&none__)
if ptr == none_err._object {
return
}
$if prealloc {
return
} $else $if vgc ? {
// VGC: explicit free is optional (GC will collect unreachable objects).
// But hint the allocator for faster reuse.
vgc_free(ptr)
} $else $if gcboehm ? {
// It is generally better to leave it to Boehm's gc to free things.
// Calling C.GC_FREE(ptr) was tried initially, but does not work
// well with programs that do manual management themselves.
//
// The exception is doing leak detection for manual memory management:
$if gcboehm_leak ? {
unsafe { C.GC_FREE(ptr) }
}
} $else {
// Manual memory management: this is the only path that actually returns
// the block to the C allocator, and it mirrors where _ht_alloc fires, so
// report the free here (after the nil / none__ / nop / GC guards above).
_ht_free(ptr)
$if windows {
// Warning! On windows, we always use _aligned_free to free memory.
unsafe { C._aligned_free(ptr) }
} $else {
C.free(ptr)
}
}
}
// memdup dynamically allocates a `sz` bytes block of memory on the heap
// memdup then copies the contents of `src` into the allocated space and
// returns a pointer to the newly allocated space.
@[unsafe]
pub fn memdup(src voidptr, sz isize) voidptr {
$if trace_memdup ? {
C.fprintf(C.stderr, c'memdup size: %10d\n', sz)
}
if sz == 0 {
return vcalloc(1)
}
$if vgc ? {
return vgc_memdup(src, sz)
}
unsafe {
mem := malloc(sz)
return C.memcpy(mem, src, sz)
}
}
@[unsafe]
pub fn memdup_noscan(src voidptr, sz isize) voidptr {
$if trace_memdup ? {
C.fprintf(C.stderr, c'memdup_noscan size: %10d\n', sz)
}
if sz == 0 {
return vcalloc_noscan(1)
}
$if vgc ? {
return vgc_memdup_noscan(src, sz)
}
unsafe {
mem := malloc_noscan(sz)
return C.memcpy(mem, src, sz)
}
}
// memdup_uncollectable dynamically allocates a `sz` bytes block of memory
// on the heap, which will NOT be garbage-collected (but its contents will).
// memdup_uncollectable then copies the contents of `src` into the allocated
// space and returns a pointer to the newly allocated space.
@[unsafe]
pub fn memdup_uncollectable(src voidptr, sz isize) voidptr {
$if trace_memdup ? {
C.fprintf(C.stderr, c'memdup_uncollectable size: %10d\n', sz)
}
if sz == 0 {
return vcalloc(1)
}
unsafe {
mem := malloc_uncollectable(sz)
return C.memcpy(mem, src, sz)
}
}
// memdup_align dynamically allocates a memory block of `sz` bytes on the heap,
// copies the contents from `src` into the allocated space, and returns a pointer
// to the newly allocated memory. The returned pointer is aligned to the specified `align` boundary.
// - `align` must be a power of two and at least 1
// - `sz` must be non-negative
// - The memory regions should not overlap
@[unsafe]
pub fn memdup_align(src voidptr, sz isize, align isize) voidptr {
$if trace_memdup ? {
C.fprintf(C.stderr, c'memdup_align size: %10d align: %10d\n', sz, align)
}
if sz == 0 {
return vcalloc(1)
}
n := sz
$if trace_malloc ? {
total_m += n
C.fprintf(C.stderr, c'_v_memdup_align %6d total %10d\n', n, total_m)
// print_backtrace()
}
if n < 0 {
_memory_panic(@FN, n)
}
mut res := &u8(unsafe { nil })
$if prealloc {
res = prealloc_malloc_align(n, align)
} $else $if gcboehm ? {
unsafe {
res = C.GC_memalign(align, n)
}
} $else $if freestanding {
// todo: is this safe to call malloc there? We export __malloc as malloc and it uses dlmalloc behind the scenes
// so theoretically it is safe
panic('memdup_align is not implemented with -freestanding')
res = unsafe { __malloc(usize(n)) }
} $else {
$if windows {
// Warning! On windows, we always use _aligned_malloc to allocate memory.
// This ensures that we can later free the memory with _aligned_free
// without needing to track whether the memory was originally allocated
// by malloc or _aligned_malloc.
res = unsafe { C._aligned_malloc(n, align) }
} $else {
res = unsafe { C.aligned_alloc(align, n) }
}
}
if res == 0 {
_memory_panic(@FN, n)
}
$if debug_malloc ? {
// Fill in the memory with something != 0 i.e. `M`, so it is easier to spot
// when the calling code wrongly relies on it being zeroed.
unsafe { C.memset(res, 0x4D, n) }
}
// memdup_align allocates directly via aligned_alloc / _aligned_malloc, so
// report it like malloc does; otherwise the later free() of an aligned heap
// literal (HEAP_align) would emit vheap_free for an untracked pointer.
_ht_alloc(res, n)
return C.memcpy(res, src, sz)
}
// GCHeapUsage contains stats about the current heap usage of your program.
pub struct GCHeapUsage {
pub:
heap_size usize
free_bytes usize
total_bytes usize
unmapped_bytes usize
bytes_since_gc usize
}
// gc_heap_usage returns the info about heap usage.
pub fn gc_heap_usage() GCHeapUsage {
$if vgc ? {
heap_size, free_bytes, total_bytes, unmapped, bytes_since := vgc_heap_usage()
return GCHeapUsage{
heap_size: heap_size
free_bytes: free_bytes
total_bytes: total_bytes
unmapped_bytes: unmapped
bytes_since_gc: bytes_since
}
} $else $if gcboehm ? {
mut res := GCHeapUsage{}
C.GC_get_heap_usage_safe(&res.heap_size, &res.free_bytes, &res.unmapped_bytes,
&res.bytes_since_gc, &res.total_bytes)
return res
} $else {
return GCHeapUsage{}
}
}
// gc_memory_use returns the total memory use in bytes by all allocated blocks.
pub fn gc_memory_use() usize {
$if vgc ? {
return vgc_memory_use()
} $else $if gcboehm ? {
return C.GC_get_memory_use()
} $else {
return 0
}
}
File diff suppressed because it is too large. Load diff
+461
View File
@@ -0,0 +1,461 @@
// non-pub versions of array functions
// that allocale new memory using `GC_MALLOC_ATOMIC()`
// when `-gc boehm_*_opt` is used. These memory areas are not
// scanned for pointers.
module builtin
@[inline]
fn alloc_array_data_noscan(total_size u64) voidptr {
raw := vcalloc_noscan(array_data_allocation_size(total_size))
return unsafe { &u8(raw) + array_data_header_size() }
}
@[inline]
fn alloc_array_data_noscan_uninit(total_size u64) voidptr {
raw := unsafe { malloc_noscan_uninit(array_data_allocation_size(total_size)) }
unsafe {
(&ArrayDataHeader(raw)).has_slices = false
return &u8(raw) + array_data_header_size()
}
}
@[inline]
fn (mut a array) clone_shallow_to_cap_noscan(new_cap int) {
if new_cap <= 0 {
unsafe { a.flags.clear(.managed | .noscan_data | .is_slice) }
a.data = unsafe { nil }
a.offset = 0
a.cap = 0
return
}
total_size := u64(new_cap) * u64(a.element_size)
new_data := alloc_array_data_noscan_uninit(total_size)
copy_size := u64(a.len) * u64(a.element_size)
if a.data != unsafe { nil } && copy_size > 0 {
unsafe { vmemcpy(new_data, a.data, copy_size) }
}
a.data = new_data
a.offset = 0
a.cap = new_cap
unsafe { a.flags.set(.noscan_data) }
a.set_managed_flags(false)
}
fn __new_array_noscan(mylen int, cap int, elm_size int) array {
panic_on_negative_len(mylen)
panic_on_negative_cap(cap)
cap_ := if cap < mylen { mylen } else { cap }
total_size := u64(cap_) * u64(elm_size)
mut data := unsafe { nil }
if cap_ > 0 && mylen == 0 {
data = alloc_array_data_noscan_uninit(total_size)
} else if cap_ > 0 {
data = alloc_array_data_noscan(total_size)
}
arr := array{
element_size: elm_size
data: data
len: mylen
cap: cap_
flags: .managed | .noscan_data
}
return arr
}
fn __new_array_with_default_noscan(mylen int, cap int, elm_size int, val voidptr) array {
panic_on_negative_len(mylen)
panic_on_negative_cap(cap)
cap_ := if cap < mylen { mylen } else { cap }
mut arr := array{
element_size: elm_size
len: mylen
cap: cap_
flags: .managed | .noscan_data
}
if cap_ > 0 {
arr.data = alloc_array_data_noscan(u64(cap_) * u64(elm_size))
}
if val != 0 && arr.data != unsafe { nil } {
if elm_size == 1 {
byte_value := *(&u8(val))
dptr := &u8(arr.data)
for i in 0 .. arr.len {
unsafe {
dptr[i] = byte_value
}
}
} else {
for i in 0 .. arr.len {
unsafe { arr.set_unsafe(i, val) }
}
}
}
return arr
}
fn __new_array_with_multi_default_noscan(mylen int, cap int, elm_size int, val voidptr) array {
panic_on_negative_len(mylen)
panic_on_negative_cap(cap)
cap_ := if cap < mylen { mylen } else { cap }
mut arr := array{
element_size: elm_size
len: mylen
cap: cap_
flags: .managed | .noscan_data
}
if cap_ > 0 {
arr.data = alloc_array_data_noscan(u64(cap_) * u64(elm_size))
}
if val != 0 && arr.data != unsafe { nil } {
for i in 0 .. arr.len {
unsafe { arr.set_unsafe(i, charptr(val) + i * elm_size) }
}
}
return arr
}
fn __new_array_with_array_default_noscan(mylen int, cap int, elm_size int, val array) array {
panic_on_negative_len(mylen)
panic_on_negative_cap(cap)
cap_ := if cap < mylen { mylen } else { cap }
mut arr := array{
element_size: elm_size
len: mylen
cap: cap_
flags: .managed | .noscan_data
}
if cap_ > 0 {
arr.data = alloc_array_data_noscan(u64(cap_) * u64(elm_size))
}
for i in 0 .. arr.len {
val_clone := val.clone()
unsafe { arr.set_unsafe(i, &val_clone) }
}
return arr
}
// Private function, used by V (`nums := [1, 2, 3]`)
fn new_array_from_c_array_noscan(len int, cap int, elm_size int, c_array voidptr) array {
panic_on_negative_len(len)
panic_on_negative_cap(cap)
cap_ := if cap < len { len } else { cap }
arr := array{
element_size: elm_size
data: alloc_array_data_noscan(u64(cap_) * u64(elm_size))
len: len
cap: cap_
flags: .managed | .noscan_data
}
// TODO: Write all memory functions (like memcpy) in V
unsafe { vmemcpy(arr.data, c_array, u64(len) * u64(elm_size)) }
return arr
}
// Private function. Doubles array capacity if needed.
fn (mut a array) ensure_cap_noscan(required int) {
if required <= a.cap {
return
}
if a.flags.has(.nogrow) {
panic_n('array.ensure_cap_noscan: array with the flag `.nogrow` cannot grow in size, array required new size:',
required)
}
mut cap := if a.cap > 0 { i64(a.cap) } else { i64(2) }
for required > cap {
cap *= 2
}
if cap > max_int {
if a.cap < max_int {
// limit the capacity, since bigger values, will overflow the 32bit integer used to store it
cap = max_int
} else {
panic_n('array.ensure_cap_noscan: array needs to grow to cap (which is > 2^31):', cap)
}
}
new_size := u64(cap) * u64(a.element_size)
new_data := alloc_array_data_noscan_uninit(new_size)
if a.data != unsafe { nil } {
unsafe { vmemcpy(new_data, a.data, u64(a.len) * u64(a.element_size)) }
// TODO: the old data may be leaked when no GC is used (ref-counting?)
}
a.data = new_data
a.offset = 0
a.cap = int(cap)
a.set_managed_flags(false)
}
// repeat returns a new array with the given array elements repeated given times.
// `cgen` will replace this with an appropriate call to `repeat_to_depth()`
// version of `repeat()` that handles multi dimensional arrays
// `unsafe` to call directly because `depth` is not checked
@[unsafe]
fn (a array) repeat_to_depth_noscan(count int, depth int) array {
if count < 0 {
panic_n('array.repeat: count is negative:', count)
}
mut size := u64(count) * u64(a.len) * u64(a.element_size)
if size == 0 {
size = u64(a.element_size)
}
mut data := unsafe { nil }
if depth > 0 {
data = alloc_array_data(size)
} else {
data = alloc_array_data_noscan(size)
}
arr := array{
element_size: a.element_size
data: data
len: count * a.len
cap: count * a.len
flags: if depth == 0 { .managed | .noscan_data } else { .managed }
}
if a.len > 0 {
a_total_size := u64(a.len) * u64(a.element_size)
arr_step_size := u64(a.len) * u64(arr.element_size)
mut eptr := &u8(arr.data)
unsafe {
for _ in 0 .. count {
if depth > 0 {
ary_clone := a.clone_to_depth_noscan(depth)
vmemcpy(eptr, &u8(ary_clone.data), a_total_size)
} else {
vmemcpy(eptr, &u8(a.data), a_total_size)
}
eptr += arr_step_size
}
}
}
return arr
}
// insert inserts a value in the array at index `i`
fn (mut a array) insert_noscan(i int, val voidptr) {
if i < 0 || i > a.len {
panic_n2('array.insert_noscan: index out of range (i,a.len):', i, a.len)
}
if a.len == max_int {
panic('array.insert_noscan: a.len reached max_int')
}
required := a.len + 1
if a.needs_unique_shift(required) {
a.clone_shallow_to_cap_noscan(a.cap)
} else if required > a.cap {
a.ensure_cap_noscan(required)
}
unsafe {
vmemmove(a.get_unsafe(i + 1), a.get_unsafe(i), u64(a.len - i) * u64(a.element_size))
a.set_unsafe(i, val)
}
a.len++
}
// insert_many inserts many values into the array from index `i`.
@[unsafe]
fn (mut a array) insert_many_noscan(i int, val voidptr, size int) {
if i < 0 || i > a.len {
panic_n2('array.insert_many: index out of range (i, a.len):', i, a.len)
}
new_len := i64(a.len) + i64(size)
if new_len > max_int {
panic_n('array.insert_many_noscan: max_int will be exceeded by a.len:', new_len)
}
if a.needs_unique_shift(int(new_len)) {
a.clone_shallow_to_cap_noscan(a.cap)
} else if int(new_len) > a.cap {
a.ensure_cap_noscan(int(new_len))
}
elem_size := a.element_size
unsafe {
iptr := a.get_unsafe(i)
vmemmove(a.get_unsafe(i + size), iptr, u64(a.len - i) * u64(elem_size))
vmemcpy(iptr, val, u64(size) * u64(elem_size))
}
a.len = int(new_len)
}
// prepend prepends one value to the array.
fn (mut a array) prepend_noscan(val voidptr) {
a.insert_noscan(0, val)
}
// prepend_many prepends another array to this array.
@[unsafe]
fn (mut a array) prepend_many_noscan(val voidptr, size int) {
unsafe { a.insert_many_noscan(0, val, size) }
}
// pop_left returns the first element of the array and removes it by advancing the data pointer.
fn (mut a array) pop_left_noscan() voidptr {
if a.len == 0 {
panic('array.pop_left: array is empty')
}
first_elem := a.data
unsafe {
a.data = &u8(a.data) + u64(a.element_size)
}
a.offset += a.element_size
a.len--
a.cap--
return unsafe { memdup_noscan(first_elem, a.element_size) }
}
// pop returns the last element of the array, and removes it.
fn (mut a array) pop_noscan() voidptr {
// in a sense, this is the opposite of `a << x`
if a.len == 0 {
panic('array.pop: array is empty')
}
new_len := a.len - 1
last_elem := unsafe { &u8(a.data) + u64(new_len) * u64(a.element_size) }
if a.needs_unique_shrink() {
cloned := unsafe { memdup_noscan(last_elem, a.element_size) }
a.delete_many(new_len, 1)
return cloned
}
a.len = new_len
// Note: a.cap is not changed here *on purpose*, so that
// further << ops on that array will be more efficient.
return unsafe { memdup_noscan(last_elem, a.element_size) }
}
// `clone_static_to_depth_noscan()` returns an independent copy of a given array.
// Unlike `clone_to_depth_noscan()` it has a value receiver and is used internally
// for slice-clone expressions like `a[2..4].clone()` and in -autofree generated code.
fn (a array) clone_static_to_depth_noscan(depth int) array {
return unsafe { a.clone_to_depth_noscan(depth) }
}
// recursively clone given array - `unsafe` when called directly because depth is not checked
@[unsafe]
fn (a &array) clone_to_depth_noscan(depth int) array {
mut size := u64(a.cap) * u64(a.element_size)
if size == 0 {
size++
}
mut data := unsafe { nil }
if depth == 0 {
data = alloc_array_data_noscan(size)
} else {
data = alloc_array_data(size)
}
mut arr := array{
element_size: a.element_size
data: data
len: a.len
cap: a.cap
flags: if depth == 0 { .managed | .noscan_data } else { .managed }
}
// Recursively clone-generated elements if array element is array type
if depth > 0 {
for i in 0 .. a.len {
ar := array{}
unsafe { vmemcpy(&ar, a.get_unsafe(i), int(sizeof(array))) }
ar_clone := unsafe { ar.clone_to_depth_noscan(depth - 1) }
unsafe { arr.set_unsafe(i, &ar_clone) }
}
return arr
} else {
if a.data != 0 {
unsafe { vmemcpy(&u8(arr.data), a.data, u64(a.cap) * u64(a.element_size)) }
}
return arr
}
}
fn (mut a array) push_noscan(val voidptr) {
$if !no_bounds_checking {
if a.len < 0 {
panic('array.push_noscan: negative len')
}
}
if a.len >= max_int {
panic('array.push_noscan: len bigger than max_int')
}
required := a.len + 1
if required > a.cap {
a.ensure_cap_noscan(required)
} else if a.flags.has(.is_slice) {
// `required <= a.cap` here, so this is the `needs_unique_append` case
a.clone_shallow_to_cap_noscan(a.cap)
}
unsafe {
copy_element_to(&u8(a.data) + u64(a.element_size) * u64(a.len), val, a.element_size)
}
a.len++
}
// push_many implements the functionality for pushing another array.
// `val` is array.data and user facing usage is `a << [1,2,3]`
@[unsafe]
fn (mut a array) push_many_noscan(val voidptr, size int) {
if size == 0 || val == unsafe { nil } {
return
}
new_len := i64(a.len) + i64(size)
if new_len > max_int {
// string interpolation also uses <<; avoid it, use a fixed string for the panic
panic('array.push_many_noscan: new len exceeds max_int')
}
if a.needs_unique_append(int(new_len)) {
a.clone_shallow_to_cap_noscan(a.cap)
}
if a.data == val && a.data != 0 {
// handle `arr << arr`
cloned := a.clone()
if int(new_len) > a.cap {
a.ensure_cap_noscan(int(new_len))
}
unsafe {
vmemcpy(a.get_unsafe(a.len), cloned.data, u64(a.element_size) * u64(size))
}
} else {
if int(new_len) > a.cap {
a.ensure_cap_noscan(int(new_len))
}
if a.data != 0 && val != 0 {
unsafe { vmemcpy(a.get_unsafe(a.len), val, u64(a.element_size) * u64(size)) }
}
}
a.len = int(new_len)
}
// reverse returns a new array with the elements of the original array in reverse order.
fn (a array) reverse_noscan() array {
if a.len < 2 {
return a
}
mut arr := array{
element_size: a.element_size
data: alloc_array_data_noscan(u64(a.cap) * u64(a.element_size))
len: a.len
cap: a.cap
flags: .managed | .noscan_data
}
for i in 0 .. a.len {
unsafe { arr.set_unsafe(i, a.get_unsafe(a.len - 1 - i)) }
}
return arr
}
// grow_cap grows the array's capacity by `amount` elements.
fn (mut a array) grow_cap_noscan(amount int) {
new_cap := i64(amount) + i64(a.cap)
if new_cap > max_int {
panic_n('array.grow_cap: max_int will be exceeded by new cap:', new_cap)
}
a.ensure_cap_noscan(int(new_cap))
}
// grow_len ensures that an array has a.len + amount of length
@[unsafe]
fn (mut a array) grow_len_noscan(amount int) {
new_len := i64(amount) + i64(a.len)
if new_len > max_int {
panic_n('array.grow_len: max_int will be exceeded by new len:', new_len)
}
a.ensure_cap_noscan(int(new_len))
a.len = int(new_len)
}
+94
View File
@@ -0,0 +1,94 @@
fn show_array(name string, a []int) {
eprintln('${name:10} .flags: ${a.flags:34} | .cap: ${a.cap:2} | .len: ${a.len:2} | .data: ${a.data} | ${a}')
}
fn trace_delete_elements(name string, mut a []int) int {
a.delete_many(5, 3)
show_array(name, a)
a << 55
show_array(name, a)
a << 66
show_array(name, a)
a << 77
res := a.cap
eprintln(' << ${name:10} .cap: ${a.cap} >>')
show_array(name, a)
a << 88
show_array(name, a)
a << 99
show_array(name, a)
eprintln('-------------------------------')
return res
}
fn test_array_cap_after_deletion() {
mut a := [0]
mut middle_cap := 0
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
middle_cap = trace_delete_elements('normal', mut a)
assert middle_cap == 10
assert a.len == 12
assert a.cap == 20
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
unsafe { a.flags.set(.noslices) }
middle_cap = dump(trace_delete_elements('noslices', mut a))
assert middle_cap == 10
assert a.len == 12
assert a.cap == 20
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
unsafe { a.flags.set(.noshrink) }
middle_cap = dump(trace_delete_elements('noshrink', mut a))
assert middle_cap == 10
assert a.len == 12
assert a.cap == 20
// Note: deleting array elements should not shrink the backing
// memory block, thus << after the deletion will still have space
// till .cap is reached.
a = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
unsafe { a.flags.set(.noslices | .noshrink) }
middle_cap = dump(trace_delete_elements('both', mut a))
assert middle_cap == 10
assert a.len == 12
assert a.cap == 20
}
fn fixed_array_on_the_heap(len int, size int) []u8 {
data := vcalloc(size)
println(ptr_str(data))
return unsafe {
array{
element_size: 1
len: len
cap: size
data: data
flags: .noshrink | .nogrow | .nofree
}
}
}
fn test_array_fixed_growth() {
mut x := fixed_array_on_the_heap(0, 10)
println(ptr_str(x.data))
x << 5
x << 10
x << 15
x << 20
dump(x)
dump(x.flags)
assert x[2] == 15
assert x.flags == .noshrink | .nogrow | .nofree
}
fn test_array_fixed() {
mut x := fixed_array_on_the_heap(10, 10)
println(ptr_str(x.data))
x[2] = 5
dump(x)
dump(x.flags)
assert x[2] == 5
assert x.flags == .noshrink | .nogrow | .nofree
}
@@ -0,0 +1,95 @@
// dummy placeholder for functions from `array_d_gcboehm_opt.v`
// that might be needed for compile time
// `$if gcboehm_opt ? { ... } $else { ... }`
module builtin
// these are needed for calls inside `$if gcboehm_opt ? { ... }` in array.v
fn alloc_array_data_noscan(total_size u64) voidptr {
return alloc_array_data(total_size)
}
fn alloc_array_data_noscan_uninit(total_size u64) voidptr {
return alloc_array_data_uninit(total_size)
}
// this is needed in `string.v`
fn __new_array_noscan(mylen int, cap int, elm_size int) array {
return __new_array(mylen, cap, elm_size)
}
fn __new_array_with_default_noscan(mylen int, cap int, elm_size int, val voidptr) array {
return __new_array_with_default(mylen, cap, elm_size, val)
}
fn __new_array_with_multi_default_noscan(mylen int, cap int, elm_size int, val voidptr) array {
return __new_array_with_multi_default(mylen, cap, elm_size, val)
}
fn __new_array_with_array_default_noscan(mylen int, cap int, elm_size int, val array, depth int) array {
return __new_array_with_array_default(mylen, cap, elm_size, val, depth)
}
fn new_array_from_c_array_noscan(len int, cap int, elm_size int, c_array voidptr) array {
return new_array_from_c_array(len, cap, elm_size, c_array)
}
fn (mut a array) ensure_cap_noscan(required int) {
a.ensure_cap(required)
}
fn (a array) repeat_to_depth_noscan(count int, depth int) array {
return unsafe { a.repeat_to_depth(count, depth) }
}
fn (mut a array) insert_noscan(i int, val voidptr) {
a.insert(i, val)
}
fn (mut a array) insert_many_noscan(i int, val voidptr, size int) {
unsafe { a.insert_many(i, val, size) }
}
fn (mut a array) prepend_noscan(val voidptr) {
a.prepend(val)
}
fn (mut a array) prepend_many_noscan(val voidptr, size int) {
unsafe { a.prepend_many(val, size) }
}
fn (mut a array) pop_left_noscan() voidptr {
return a.pop_left()
}
fn (mut a array) pop_noscan() voidptr {
return a.pop()
}
fn (a array) clone_static_to_depth_noscan(depth int) array {
return a.clone_static_to_depth(depth)
}
fn (a &array) clone_to_depth_noscan(depth int) array {
return unsafe { a.clone_to_depth(depth) }
}
fn (mut a array) push_noscan(val voidptr) {
a.push(val)
}
fn (mut a array) push_many_noscan(val voidptr, size int) {
unsafe { a.push_many(val, size) }
}
fn (a array) reverse_noscan() array {
return a.reverse()
}
fn (mut a array) grow_cap_noscan(amount int) {
a.grow_cap(amount)
}
fn (mut a array) grow_len_noscan(amount int) {
unsafe { a.grow_len(amount) }
}
@@ -0,0 +1,243 @@
// Guards the single element push fast path in `array.push`/`push_noscan`.
// Single element pushes dispatch the common small element sizes (1/2/4/8/16 bytes) to
// `vmemcpy` with a constant size, and fall back to a runtime sized `vmemcpy` otherwise.
// These tests make sure every size copies the right bytes for awkward element types too:
// low alignment size 8/16 structs, structs with padding, and floats - i.e. types where a
// typed integer copy would impose alignment it does not have or read padding through the
// wrong type. They also check that pushing to a slice view clones the backing buffer
// instead of corrupting the parent array.
struct S16 {
a u64
b u64
}
struct S3 {
a u8
b u8
c u8
}
struct S24 {
a u64
b u64
c u64
}
// Pair8 is 8 bytes with only 4 byte alignment (no field needs 8 byte alignment).
struct Pair8 {
a int
b int
}
// Padded8 is 8 bytes and contains padding: a u8 followed by 3 padding bytes, then a u32.
struct Padded8 {
a u8
b u32
}
// LowAlign16 is a 16 byte element with only 1 byte alignment (a fixed byte array field).
// This is the case a 16 byte typed (two u64) copy would mishandle on strict alignment targets.
struct LowAlign16 {
mut:
data [16]u8
}
// Padded16 is 16 bytes and contains padding: a u8 followed by 3 padding bytes, then three
// u32s. It avoids u64 fields so its size is exactly 16 on every ABI (u64 alignment can be 4
// on some 32-bit targets, which would make a u8+u64 struct 12 bytes instead of 16).
struct Padded16 {
a u8
b u32
c u32
d u32
}
fn test_push_u8() {
mut a := []u8{cap: 4}
for i in 0 .. 300 {
a << u8(i & 0xff)
}
assert a.len == 300
for i in 0 .. 300 {
assert a[i] == u8(i & 0xff)
}
}
fn test_push_u16() {
mut a := []u16{}
for i in 0 .. 300 {
a << u16(i * 7)
}
for i in 0 .. 300 {
assert a[i] == u16(i * 7)
}
}
fn test_push_int() {
mut a := []int{}
for i in 0 .. 300 {
a << i * 12345
}
for i in 0 .. 300 {
assert a[i] == i * 12345
}
}
fn test_push_i64() {
mut a := []i64{}
for i in 0 .. 300 {
a << i64(i) * 9876543210
}
for i in 0 .. 300 {
assert a[i] == i64(i) * 9876543210
}
}
fn test_push_f64() {
mut a := []f64{}
for i in 0 .. 100 {
a << f64(i) + 0.5
}
for i in 0 .. 100 {
assert a[i] == f64(i) + 0.5
}
}
fn test_push_string() {
// string is a 16 byte element
mut a := []string{}
for i in 0 .. 200 {
a << 'item_${i}'
}
for i in 0 .. 200 {
assert a[i] == 'item_${i}'
}
}
fn test_push_struct_16() {
mut a := []S16{}
for i in 0 .. 200 {
a << S16{u64(i), u64(i) * 2}
}
for i in 0 .. 200 {
assert a[i].a == u64(i)
assert a[i].b == u64(i) * 2
}
}
fn test_push_struct_odd_size() {
// 3 byte element exercises the vmemcpy fallback path
mut a := []S3{}
for i in 0 .. 200 {
a << S3{u8(i), u8(i + 1), u8(i + 2)}
}
for i in 0 .. 200 {
assert a[i].a == u8(i)
assert a[i].b == u8(i + 1)
assert a[i].c == u8(i + 2)
}
}
fn test_push_struct_large() {
// 24 byte element exercises the vmemcpy fallback path
mut a := []S24{}
for i in 0 .. 200 {
a << S24{u64(i), u64(i) * 2, u64(i) * 3}
}
for i in 0 .. 200 {
assert a[i].a == u64(i)
assert a[i].b == u64(i) * 2
assert a[i].c == u64(i) * 3
}
}
fn test_push_to_slice_view_clones() {
mut a := []int{cap: 10}
for i in 0 .. 5 {
a << i
}
// a real slice view into a's buffer, with spare capacity after it
mut s := unsafe { a[1..3] }
// pushing to a slice view must clone first, not overwrite a[3]
s << 999
assert a == [0, 1, 2, 3, 4]
assert s == [1, 2, 999]
}
fn test_push_to_byte_slice_view_clones() {
mut a := []u8{cap: 10}
for i in 0 .. 5 {
a << u8(i)
}
mut s := unsafe { a[1..3] }
s << u8(200)
assert a == [u8(0), 1, 2, 3, 4]
assert s == [u8(1), 2, 200]
}
fn test_push_f32() {
// f32 is a 4 byte element; the copy must not route it through an integer type
mut a := []f32{}
for i in 0 .. 100 {
a << f32(i) + 0.25
}
for i in 0 .. 100 {
assert a[i] == f32(i) + 0.25
}
}
fn test_push_pair8_low_alignment() {
assert sizeof(Pair8) == 8
mut a := []Pair8{}
for i in 0 .. 200 {
a << Pair8{i, i * 3}
}
for i in 0 .. 200 {
assert a[i].a == i
assert a[i].b == i * 3
}
}
fn test_push_padded8() {
assert sizeof(Padded8) == 8
mut a := []Padded8{}
for i in 0 .. 200 {
a << Padded8{u8(i), u32(i) * 7}
}
for i in 0 .. 200 {
assert a[i].a == u8(i)
assert a[i].b == u32(i) * 7
}
}
fn test_push_low_alignment_16() {
assert sizeof(LowAlign16) == 16
mut a := []LowAlign16{}
for i in 0 .. 200 {
mut e := LowAlign16{}
for j in 0 .. 16 {
e.data[j] = u8(i + j)
}
a << e
}
for i in 0 .. 200 {
for j in 0 .. 16 {
assert a[i].data[j] == u8(i + j)
}
}
}
fn test_push_padded16() {
assert sizeof(Padded16) == 16
mut a := []Padded16{}
for i in 0 .. 200 {
a << Padded16{u8(i), u32(i) * 99, u32(i) * 7, u32(i) * 13}
}
for i in 0 .. 200 {
assert a[i].a == u8(i)
assert a[i].b == u32(i) * 99
assert a[i].c == u32(i) * 7
assert a[i].d == u32(i) * 13
}
}
File diff suppressed because it is too large. Load diff
+87
View File
@@ -0,0 +1,87 @@
module builtin
import strings
const autostr_type_stack_max_depth = 64
__global g_autostr_type_stack = [autostr_type_stack_max_depth]int{}
__global g_autostr_type_stack_len = 0
@[markused]
fn autostr_type_in_stack(typ int) bool {
for i := 0; i < g_autostr_type_stack_len; i++ {
if g_autostr_type_stack[i] == typ {
return true
}
}
return false
}
@[markused]
fn autostr_type_push(typ int) {
if g_autostr_type_stack_len >= autostr_type_stack_max_depth {
return
}
g_autostr_type_stack[g_autostr_type_stack_len] = typ
g_autostr_type_stack_len++
}
@[markused]
fn autostr_type_pop() {
if g_autostr_type_stack_len > 0 {
g_autostr_type_stack_len--
}
}
// Address-based circular reference detection.
// Tracks visited struct addresses to detect actual circular references
// (same instance reached again) without false positives from same-type
// different instances.
__global g_autostr_addr_stack = [autostr_type_stack_max_depth]voidptr{}
__global g_autostr_addr_stack_len = 0
@[markused]
fn autostr_addr_in_stack(addr voidptr) bool {
for i := 0; i < g_autostr_addr_stack_len; i++ {
if g_autostr_addr_stack[i] == addr {
return true
}
}
return false
}
@[markused]
fn autostr_addr_push(addr voidptr) {
if g_autostr_addr_stack_len >= autostr_type_stack_max_depth {
return
}
g_autostr_addr_stack[g_autostr_addr_stack_len] = addr
g_autostr_addr_stack_len++
}
@[markused]
fn autostr_addr_pop() {
if g_autostr_addr_stack_len > 0 {
g_autostr_addr_stack_len--
}
}
@[markused]
fn autostr_array_circular(len int) string {
if len <= 0 {
return '[]'
}
mut sb := strings.new_builder(2 + len * 12)
sb.write_string('[')
for i in 0 .. len {
if i > 0 {
sb.write_string(', ')
}
sb.write_string('<circular>')
}
sb.write_string(']')
res := sb.str()
unsafe { sb.free() }
return res
}
+116
View File
@@ -0,0 +1,116 @@
module builtin
// print_backtrace shows a backtrace of the current call stack on stdout.
pub fn print_backtrace() {
// At the time of backtrace_symbols_fd call, the C stack would look something like this:
// * print_backtrace_skipping_top_frames
// * print_backtrace itself
// * the rest of the backtrace frames
// => top 2 frames should be skipped, since they will not be informative to the developer
$if !no_backtrace ? {
$if freestanding {
println(bare_backtrace())
} $else $if tinyc {
C.tcc_backtrace(c'Backtrace')
} $else $if use_libbacktrace ? {
$if openbsd {
print_backtrace_skipping_top_frames(2)
} $else {
// NOTE: TCC doesn't have the unwind library
print_libbacktrace(1)
}
} $else {
print_backtrace_skipping_top_frames(2)
}
}
}
// demangle_v_symbol converts a C-mangled V symbol name to a human-readable V name.
// For example: `veb__run_T_main__App_main__Context` => `veb.run[main.App, main.Context]`
@[direct_array_access]
fn demangle_v_symbol(cname string) string {
mut name := cname
// Strip builtin__ prefix:
if name.starts_with('builtin__') {
name = name[9..]
}
// Replace pointer type encoding:
name = name.replace('__ptr__', '&')
// Split base name from generic parameters on `_T_`:
// C mangling: `base_T_type1_type2` where each type is prefixed with `_`
// and types may contain `__` for module separators.
t_pos := name.index('_T_') or { -1 }
if t_pos >= 0 {
base := name[..t_pos].replace('__', '.')
generic_suffix := name[t_pos + 3..]
// Split on single `_` that is not part of `__` (double underscore).
params := split_generic_params(generic_suffix)
mut demangled_params := []string{cap: params.len}
for param in params {
demangled_params << param.replace('__', '.')
}
return base + '[' + demangled_params.join(', ') + ']'
}
// No generics - just replace module separator:
name = name.replace('__', '.')
// `main.main` is V's internal C mangling, in V it's just `main`:
if name == 'main.main' {
return 'main'
}
return name
}
// split_generic_params splits a generic suffix like `main__App_main__Context`
// into individual type parameters [`main__App`, `main__Context`].
// Splits on single `_` but not `__` (double underscore used for module separators).
@[direct_array_access]
fn split_generic_params(s string) []string {
mut params := []string{}
mut start := 0
mut i := 0
for i < s.len {
if s[i] == `_` {
if i + 1 < s.len && s[i + 1] == `_` {
// Part of `__` (module separator), skip both.
i += 2
} else {
// Single `_` is a generic param separator.
if i > start {
params << s[start..i]
}
i++
start = i
}
} else {
i++
}
}
if start < s.len {
params << s[start..]
}
return params
}
// demangle_backtrace_sym demangles a V symbol within a Linux-style backtrace fragment.
// Linux format: `./executable(symbol_name+0x1a4) `
fn demangle_backtrace_sym(s string) string {
paren_start := s.index('(') or { return s }
plus_pos := s.index_after_('+', paren_start)
if plus_pos < 0 {
return s
}
symbol := s[paren_start + 1..plus_pos]
if symbol.len == 0 {
return s
}
return s[..paren_start + 1] + demangle_v_symbol(symbol) + s[plus_pos..]
}
fn eprint_space_padding(output string, max_len int) {
padding_len := max_len - output.len
if padding_len > 0 {
for _ in 0 .. padding_len {
eprint(' ')
}
}
}
+244
View File
@@ -0,0 +1,244 @@
module builtin
// print_backtrace_skipping_top_frames prints the backtrace skipping N top frames.
pub fn print_backtrace_skipping_top_frames(xskipframes int) bool {
$if no_backtrace ? {
return false
} $else {
skipframes := xskipframes + 2
$if macos || freebsd || openbsd || netbsd {
return print_backtrace_skipping_top_frames_bsd(skipframes)
} $else $if linux {
return print_backtrace_skipping_top_frames_linux(skipframes)
} $else {
println('print_backtrace_skipping_top_frames is not implemented. skipframes: ${skipframes}')
}
}
return false
}
// the functions below are not called outside this file,
// so there is no need to have their twins in builtin_windows.v
@[direct_array_access]
fn print_backtrace_skipping_top_frames_bsd(skipframes int) bool {
$if no_backtrace ? {
return false
} $else {
$if macos || freebsd || netbsd {
buffer := [100]voidptr{}
nr_ptrs := C.backtrace(&buffer[0], 100)
if nr_ptrs < 2 {
eprintln('C.backtrace returned less than 2 frames')
return false
}
$if detailed_backtraces ? {
nr_actual_frames := nr_ptrs - skipframes
csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
atos_lines := bsd_backtrace_resolve_atos(&buffer[skipframes], nr_actual_frames)
for i in 0 .. nr_actual_frames {
sframe := unsafe { tos2(&u8(csymbols[i])) }
mut file_line := ''
if i < atos_lines.len {
file_line = atos_lines[i]
}
// macOS format: `0 main 0x00000001047232f8 veb__run_T_main__App_main__Context + 356`
symbol_start := sframe.index('0x') or { -1 }
if symbol_start < 0 {
continue
}
rest := sframe[symbol_start..]
space_after_addr := rest.index(' ') or { -1 }
if space_after_addr < 0 {
continue
}
symbol_and_offset := rest[space_after_addr + 1..]
plus_pos := symbol_and_offset.index(' + ') or { -1 }
mut raw_symbol := symbol_and_offset
if plus_pos >= 0 {
raw_symbol = symbol_and_offset[..plus_pos]
}
// Skip C runtime frames that are not V functions:
if raw_symbol in ['main', 'start', '_main'] {
continue
}
mut demangled := ''
if plus_pos >= 0 {
demangled = demangle_v_symbol(raw_symbol) + symbol_and_offset[plus_pos..]
} else {
demangled = demangle_v_symbol(raw_symbol)
}
if file_line.len > 0 {
eprint(file_line)
eprint_space_padding(file_line, 45)
eprint(' | ')
eprintln(demangled)
} else {
eprintln(demangled)
}
}
if nr_actual_frames > 0 {
unsafe { C.free(csymbols) }
}
} $else {
C.backtrace_symbols_fd(&buffer[skipframes], nr_ptrs - skipframes, 2)
}
}
return true
}
}
// bsd_backtrace_resolve_atos uses macOS `atos` to resolve addresses to file:line info.
// Returns an array of file:line strings (one per frame). Empty string if unresolved.
@[direct_array_access]
fn bsd_backtrace_resolve_atos(buffer &voidptr, nr_frames int) []string {
$if macos {
exe_name := backtrace_current_executable_name()
if exe_name.len == 0 {
return []string{}
}
base_addr := voidptr(C._dyld_get_image_header(0))
if base_addr == unsafe { nil } {
return []string{}
}
// Build single atos command with all addresses for efficiency:
mut cmd := 'atos --fullPath -o "' + exe_name + '" -l ' + ptr_str(base_addr)
for i in 0 .. nr_frames {
cmd += ' ' + ptr_str(unsafe { buffer[i] })
}
f := C.popen(&char(cmd.str), c'r')
if f == unsafe { nil } {
return []string{}
}
buf := [4096]u8{}
mut lines := []string{cap: nr_frames}
unsafe {
bp := &u8(&buf[0])
for C.fgets(&char(bp), 4096, f) != 0 {
line := tos(bp, vstrlen(bp)).trim_chars(' \t\n\r', .trim_both)
// atos output format: `func_name (in binary) (file.v:42)`
// Extract the last parenthesized (file:line) part:
paren_pos := line.index_last_('(')
if paren_pos >= 0 {
file_part := line[paren_pos + 1..]
end_paren := file_part.index_last_(')')
if end_paren >= 0 {
file_line := file_part[..end_paren]
if file_line.contains(':') && !file_line.starts_with('in ')
&& !file_line.contains('.tmp.c:') {
lines << file_line
continue
}
}
}
lines << ''
}
}
C.pclose(f)
return lines
}
return []string{}
}
fn C.tcc_backtrace(fmt &char) i32
fn backtrace_current_executable_name() string {
args := arguments()
if args.len == 0 {
return ''
}
return args[0]
}
fn backtrace_addr2line_executable(executable string, current_executable_name string) string {
if executable.len == 0 {
return '/proc/self/exe'
}
if executable.contains('/') {
return executable
}
if current_executable_name.len > 0
&& executable.all_after_last('/') == current_executable_name.all_after_last('/') {
return '/proc/self/exe'
}
return executable
}
@[direct_array_access]
fn print_backtrace_skipping_top_frames_linux(skipframes int) bool {
$if android {
eprintln('On Android no backtrace is available.')
return false
}
$if no_backtrace ? {
return false
} $else {
$if linux && !freestanding {
$if tinyc {
C.tcc_backtrace(c'Backtrace')
return false
} $else {
$if !glibc {
eprintln('backtrace_symbols is missing => printing backtraces is not available.')
eprintln('Some libc implementations like musl simply do not provide it.')
return false
} $else {
current_executable_name := backtrace_current_executable_name()
buffer := [100]voidptr{}
nr_ptrs := C.backtrace(&buffer[0], 100)
if nr_ptrs < 2 {
eprintln('C.backtrace returned less than 2 frames')
return false
}
nr_actual_frames := nr_ptrs - skipframes
//////csymbols := backtrace_symbols(*voidptr(&buffer[skipframes]), nr_actual_frames)
csymbols := C.backtrace_symbols(voidptr(&buffer[skipframes]), nr_actual_frames)
for i in 0 .. nr_actual_frames {
sframe := unsafe { tos2(&u8(csymbols[i])) }
executable := sframe.all_before('(')
addr2line_executable := backtrace_addr2line_executable(executable,
current_executable_name)
addr := sframe.all_after('[').all_before(']')
beforeaddr := sframe.all_before('[')
cmd := 'addr2line -e ' + addr2line_executable + ' ' + addr
// taken from os, to avoid depending on the os module inside builtin.v
f := C.popen(&char(cmd.str), c'r')
if f == unsafe { nil } {
eprintln(sframe)
continue
}
buf := [1000]u8{}
mut output := ''
unsafe {
bp := &u8(&buf[0])
for C.fgets(&char(bp), 1000, f) != 0 {
output += tos(bp, vstrlen(bp))
}
}
output = output.trim_chars(' \t\n', .trim_both) + ':'
if C.pclose(f) != 0 {
eprintln(sframe)
continue
}
if output in ['??:0:', '??:?:'] {
output = ''
}
// See http://wiki.dwarfstd.org/index.php?title=Path_Discriminators
// Note: it is shortened here to just d. , just so that it fits, and so
// that the common error file:lineno: line format is enforced.
output = output.replace(' (discriminator', ': (d.')
eprint(output)
eprint_space_padding(output, 55)
eprint(' | ')
eprint(addr)
eprint(' | ')
eprintln(demangle_backtrace_sym(beforeaddr))
}
if nr_actual_frames > 0 {
unsafe { C.free(csymbols) }
}
}
}
}
}
return true
}
+180
View File
@@ -0,0 +1,180 @@
module builtin
// dbghelp.h is already included in cheaders.v
#flag windows -l dbghelp
// SymbolInfo is used by print_backtrace_skipping_top_frames_msvc.
pub struct SymbolInfo {
pub mut:
f_size_of_struct u32 // must be 88 to be recognised by SymFromAddr
f_type_index u32 // Type Index of symbol
f_reserved [2]u64
f_index u32
f_size u32
f_mod_base u64 // Base Address of module containing this symbol
f_flags u32
f_value u64 // Value of symbol, ValuePresent should be 1
f_address u64 // Address of symbol including base address of module
f_register u32 // register holding value or pointer to value
f_scope u32 // scope of the symbol
f_tag u32 // pdb classification
f_name_len u32 // Actual length of name
f_max_name_len u32 // must be manually set
f_name u8 // must be calloc(f_max_name_len)
}
pub struct SymbolInfoContainer {
pub mut:
syminfo SymbolInfo
f_name_rest [254]char
}
pub struct Line64 {
pub mut:
f_size_of_struct u32
f_key voidptr
f_line_number u32
f_file_name &u8 = unsafe { nil }
f_address u64
}
// returns the current options mask
fn C.SymSetOptions(symoptions u32) u32
// returns handle
fn C.GetCurrentProcess() voidptr
fn C.SymInitialize(h_process voidptr, p_user_search_path &u8, b_invade_process i32) i32
fn C.CaptureStackBackTrace(frames_to_skip u32, frames_to_capture u32, p_backtrace voidptr, p_backtrace_hash voidptr) u16
$if windows {
$if msvc {
fn C.SymFromAddr(h_process voidptr, address u64, p_displacement voidptr, p_symbol voidptr) i32
fn C.SymGetLineFromAddr64(h_process voidptr, address u64, p_displacement voidptr, p_line &Line64) i32
}
}
// Ref - https://docs.microsoft.com/en-us/windows/win32/api/dbghelp/nf-dbghelp-symsetoptions
const symopt_undname = 0x00000002
const symopt_deferred_loads = 0x00000004
const symopt_no_cpp = 0x00000008
const symopt_load_lines = 0x00000010
const symopt_include_32bit_modules = 0x00002000
const symopt_allow_zero_address = 0x01000000
const symopt_debug = u32(0x80000000)
// print_backtrace_skipping_top_frames prints the backtrace skipping N top frames.
pub fn print_backtrace_skipping_top_frames(skipframes int) bool {
$if msvc {
return print_backtrace_skipping_top_frames_msvc(skipframes)
}
$if tinyc {
return print_backtrace_skipping_top_frames_tcc(skipframes)
}
$if mingw {
return print_backtrace_skipping_top_frames_mingw(skipframes)
}
eprintln('print_backtrace_skipping_top_frames is not implemented')
return false
}
@[direct_array_access]
fn print_backtrace_skipping_top_frames_msvc(skipframes int) bool {
$if msvc {
mut offset := u64(0)
backtraces := [100]voidptr{}
sic := SymbolInfoContainer{}
mut si := &sic.syminfo
si.f_size_of_struct = sizeof(SymbolInfo) // Note: C.SYMBOL_INFO is 88
si.f_max_name_len = sizeof(SymbolInfoContainer) - sizeof(SymbolInfo) - 1
fname := &char(&si.f_name)
mut sline64 := Line64{
f_file_name: &u8(unsafe { nil })
}
sline64.f_size_of_struct = sizeof(Line64)
handle := C.GetCurrentProcess()
defer {
C.SymCleanup(handle)
}
C.SymSetOptions(symopt_debug | symopt_load_lines | symopt_undname)
syminitok := C.SymInitialize(handle, 0, 1)
if syminitok != 1 {
eprintln('Failed getting process: Aborting backtrace.\n')
return false
}
frames := int(C.CaptureStackBackTrace(skipframes + 1, 100, &backtraces[0], 0))
if frames < 2 {
eprintln('C.CaptureStackBackTrace returned less than 2 frames')
return false
}
for i in 0 .. frames {
frame_addr := backtraces[i]
if C.SymFromAddr(handle, frame_addr, &offset, si) == 1 {
nframe := frames - i - 1
mut lineinfo := ''
if C.SymGetLineFromAddr64(handle, frame_addr, &offset, &sline64) == 1 {
file_name := unsafe { tos3(sline64.f_file_name) }
lnumber := sline64.f_line_number
lineinfo = file_name + ':' + i64(lnumber).str()
} else {
// addr:
lineinfo = '?? : address = 0x' + ptr_str(frame_addr)
}
sfunc := demangle_v_symbol(unsafe { tos3(fname) })
snframe := i64(nframe).str()
eprint_space_padding(snframe, 2)
eprint(': ')
eprint(sfunc)
eprint_space_padding(sfunc, 25)
eprint(' ')
eprintln(lineinfo)
} else {
// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes
cerr := int(C.GetLastError())
eprint('SymFromAddr failure: ')
eprint(i64(cerr).str())
if cerr == 87 {
eprintln(' = The parameter is incorrect)')
} else if cerr == 487 {
// probably caused because the .pdb isn't in the executable folder
eprintln(' = Attempt to access invalid address (Verify that you have the .pdb file in the right folder.)')
} else {
eprintln(' (see https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes)')
}
}
}
return true
} $else {
eprintln('print_backtrace_skipping_top_frames_msvc must be called only when the compiler is msvc')
return false
}
}
fn print_backtrace_skipping_top_frames_mingw(skipframes int) bool {
eprintln('print_backtrace_skipping_top_frames_mingw is not implemented')
return false
}
fn C.tcc_backtrace(fmt &char) i32
fn print_backtrace_skipping_top_frames_tcc(skipframes int) bool {
$if tinyc {
$if no_backtrace ? {
eprintln('backtraces are disabled')
return false
} $else {
C.tcc_backtrace(c'Backtrace')
return true
}
} $else {
eprintln('print_backtrace_skipping_top_frames_tcc must be called only when the compiler is tcc')
return false
}
// Not reachable, but it looks like it's not detectable by V
return false
}
+189
View File
@@ -0,0 +1,189 @@
@[has_globals]
module builtin
// NOTE: g_main_argc and g_main_argv are filled in right after C's main start.
// They are used internally by V's builtin; for user code, it is much
// more convenient to just use `os.args` or call `arguments()` instead.
@[markused]
__global g_main_argc = int(0)
@[markused]
__global g_main_argv = unsafe { nil }
@[markused]
__global g_live_reload_info voidptr
pub type FnExitCb = fn ()
fn C.atexit(f FnExitCb) i32
fn C.strerror(i32) &char
// These functions (_vinit, and _vcleanup), are generated by V.
// `module no_main` programs that use custom manual entrypoints should ensure to call them when appropriate.
// Exported V entrypoints are initialized automatically by the generated wrappers.
fn C._vinit(argc i32, argv &&char)
fn C._vcleanup()
// exit terminates execution immediately and returns exit `code` to the shell.
@[noreturn]
pub fn exit(code int) {
C.exit(code)
for {}
}
// at_exit registers a fn callback, that will be called at normal process termination.
// It returns an error, if the registration was not successful.
// The registered callback functions, will be called either via exit/1,
// or via return from the main program, in the reverse order of their registration.
// The same fn may be registered multiple times.
// Each callback fn will called once for each registration.
pub fn at_exit(cb FnExitCb) ! {
$if freestanding {
return error('at_exit not implemented with -freestanding')
} $else {
res := C.atexit(cb)
if res != 0 {
return error_with_code('at_exit failed', res)
}
}
}
fn v_segmentation_fault_handler(signal_number i32) {
$if v2_native_windows_pe_minimal ? {
eprintln('signal 11: segmentation fault')
exit(128 + signal_number)
} $else {
$if freestanding {
eprintln('signal 11: segmentation fault')
} $else {
C.fprintf(C.stderr, c'signal %d: segmentation fault\n', signal_number)
}
$if use_libbacktrace ? {
$if openbsd {
print_backtrace()
} $else {
eprint_libbacktrace(1)
}
} $else {
print_backtrace()
}
exit(128 + signal_number)
}
}
@[inline]
fn v_fixed_index(i int, len int) int {
$if !no_bounds_checking {
if i < 0 || i >= len {
panic('fixed array index out of range (index: ' + i64(i).str() + ', len: ' +
i64(len).str() + ')')
}
}
return i
}
@[inline; markused]
fn v_fixed_index_i64(i i64, len int) int {
$if !no_bounds_checking {
if i < 0 || i >= i64(len) {
panic('fixed array index out of range (index: ' + i.str() + ', len: ' + i64(len).str() +
')')
}
}
return int(i)
}
@[inline; markused]
fn v_fixed_index_u64(i u64, len int) int {
$if !no_bounds_checking {
if i >= u64(len) {
panic('fixed array index out of range (index: ' + i.str() + ', len: ' + i64(len).str() +
')')
}
}
return int(i)
}
@[inline; markused]
fn v_fixed_index_ni(i int, len int) int {
return v_fixed_index(v_ni_index(i, len), len)
}
@[inline; markused]
fn v_slice_index_i64(i i64) int {
if i < i64(min_int) || i > i64(max_int) {
panic('slice index out of range for int: ' + i.str())
}
return int(i)
}
@[inline; markused]
fn v_slice_index_u64(i u64) int {
if i > u64(max_int) {
panic('slice index out of range for int: ' + i.str())
}
return int(i)
}
// arguments returns the command line arguments, used for starting the current program as a V array of strings.
// The first string in the array (index 0), is the name of the program, used for invoking the program.
// The second string in the array (index 1), if it exists, is the first argument to the program, etc.
// For example, if you started your program as `myprogram -option`, then arguments() will return ['myprogram', '-option'].
// Note: if you `v run file.v abc def`, then arguments() will return ['file', 'abc', 'def'], or ['file.exe', 'abc', 'def'] (on Windows).
pub fn arguments() []string {
argv := &&u8(g_main_argv)
mut res := []string{cap: g_main_argc}
for i in 0 .. g_main_argc {
$if windows {
res << unsafe { string_from_wide(&u16(argv[i])) }
} $else {
res << unsafe { tos_clone(argv[i]) }
}
}
return res
}
// vcurrent_hash returns @VCURRENTHASH, which depends on the git version of
// the V repository, from which the V executable had been compiled.
pub fn vcurrent_hash() string {
return @VCURRENTHASH
}
// v_getpid returns a process identifier. It is a number that is guaranteed to
// remain the same while the current process is running. It may or may not be
// equal to the value of v_gettid(). Note: it is *NOT equal on Windows*.
pub fn v_getpid() u64 {
$if no_getpid ? {
// support non posix/windows systems that lack process management
return 0
} $else $if windows {
return u64(C.GetCurrentProcessId())
} $else {
return u64(C.getpid())
}
}
// v_gettid retuns a thread identifier. It is a number that is guaranteed to not
// change, while the current thread is running. Different threads, running at
// the same time in the same process, have different thread ids. There is no
// such guarantee for threads running in different processes.
// Important: this will be the same number returned by v_getpid(), but only on
// non windows systems, when the current thread is the main one. It is best to
// *avoid relying on this equivalence*, and use v_gettid and v_getpid only for
// tracing and debugging multithreaded issues, but *NOT for logic decisions*.
pub fn v_gettid() u64 {
$if no_gettid ? {
// support non posix/windows systems that lack process management
return 0
} $else $if windows {
return u64(C.GetCurrentThreadId())
} $else $if linux && !musl ? {
return u64(C.gettid())
} $else $if threads {
return u64(C.pthread_self())
} $else {
return v_getpid()
}
}
+58
View File
@@ -0,0 +1,58 @@
// Copyright (c) 2019-2023 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@[has_globals]
module builtin
// isnil returns true if an object is nil (only for C objects).
@[inline]
pub fn isnil(v voidptr) bool {
return v == 0
}
struct VCastTypeIndexName {
tindex int
tname string
}
// will be filled in cgen
__global as_cast_type_indexes []VCastTypeIndexName
// SliceIndex describes one overloaded `[]` index or slice part.
pub struct SliceIndex {
pub:
is_range bool
value int
low int
high int
has_low bool
has_high bool
}
@[direct_array_access]
fn __as_cast(obj voidptr, obj_type int, expected_type int) voidptr {
if obj_type != expected_type {
mut obj_name := as_cast_type_indexes[0].tname.clone()
mut expected_name := as_cast_type_indexes[0].tname.clone()
for x in as_cast_type_indexes {
if x.tindex == obj_type {
obj_name = x.tname.clone()
}
if x.tindex == expected_type {
expected_name = x.tname.clone()
}
}
panic('as cast: cannot cast `' + obj_name + '` to `' + expected_name + '`')
}
return obj
}
@[inline]
fn __v2_flag_has_int(receiver int, flag int) bool {
return (receiver & flag) != 0
}
@[inline]
fn __v2_flag_all_int(receiver int, flags int) bool {
return (receiver & flags) == flags
}
@@ -0,0 +1,3 @@
module builtin
#include "@VEXEROOT/thirdparty/android/android.h"
@@ -0,0 +1,20 @@
module builtin
// These are just dummy implementations to appease linking on musl/alpine
@[export: 'backtrace_symbols']
@[weak]
fn vbacktrace_symbols(const_buffer &voidptr, size int) &&char {
return 0
}
@[export: 'backtrace']
@[weak]
fn vbacktrace(buffer &voidptr, size int) int {
return 0
}
@[export: 'backtrace_symbols_fd']
@[weak]
fn vbacktrace_symbols_fd(const_buffer &voidptr, size int, fd int) {
}
@@ -0,0 +1,12 @@
module builtin
// <execinfo.h>
fn C.backtrace(a &voidptr, size i32) i32
fn C.backtrace_symbols(a &voidptr, size i32) &&char
fn C.backtrace_symbols_fd(a &voidptr, size i32, fd i32)
$if macos {
#include <mach-o/dyld.h>
fn C._dyld_get_image_header(image_index u32) voidptr
}
+318
View File
@@ -0,0 +1,318 @@
module builtin
$if !no_gc_threads ? {
#flag -DGC_THREADS=1
}
$if use_bundled_libgc ? {
#flag -DGC_BUILTIN_ATOMIC=1
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -DALL_INTERIOR_POINTERS=1
#flag @VEXEROOT/thirdparty/libgc/gc.o
}
$if dynamic_boehm ? {
$if windows {
$if tinyc {
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -L @VEXEROOT/thirdparty/tcc/lib
#flag -lgc
} $else $if msvc {
#flag -DGC_BUILTIN_ATOMIC=1
#flag -I @VEXEROOT/thirdparty/libgc/include
} $else {
#flag -DGC_WIN32_THREADS=1
#flag -DGC_BUILTIN_ATOMIC=1
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -DALL_INTERIOR_POINTERS=1
#flag @VEXEROOT/thirdparty/libgc/gc.o
}
} $else {
$if $pkgconfig('bdw-gc-threaded') {
#pkgconfig bdw-gc-threaded
} $else $if $pkgconfig('bdw-gc') {
#pkgconfig bdw-gc
} $else {
$if openbsd || freebsd {
#flag -I/usr/local/include
#flag -L/usr/local/lib
}
$if freebsd {
#flag -lgc-threaded
} $else {
#flag -lgc
}
}
}
} $else {
$if macos || linux {
#flag -DGC_BUILTIN_ATOMIC=1
#flag -I @VEXEROOT/thirdparty/libgc/include
$if (prod && !tinyc && !debug) || !(amd64 || arm64 || i386 || arm32 || rv64) {
// TODO: replace the architecture check with a `!$exists("@VEXEROOT/thirdparty/tcc/lib/libgc.a")` comptime call
#flag -DALL_INTERIOR_POINTERS=1
#flag @VEXEROOT/thirdparty/libgc/gc.o
} $else {
$if !use_bundled_libgc ? {
$if macos {
$if tinyc {
$if arm64 {
// tcc on macOS arm64 can leave the bundled GC archive symbols unresolved.
#flag @VEXEROOT/thirdparty/tcc/lib/libgc.dylib
#flag -Wl,-rpath,"@VEXEROOT/thirdparty/tcc/lib"
} $else {
// macOS amd64 tccbin only ships libgc.a (no .dylib).
#flag @VEXEROOT/thirdparty/tcc/lib/libgc.a
}
} $else {
#flag -L@VEXEROOT/thirdparty/tcc/lib
#flag -lgc
#flag -Xlinker -rpath -Xlinker "@VEXEROOT/thirdparty/tcc/lib"
}
} $else {
$if musl ? {
// The bundled tcc libgc archive is built for glibc and
// references __data_start/data_start, which musl does
// not provide. Alpine installs musl-compatible libgc.
$if tinyc {
// Prefer the shared library when present: Alpine's
// static libgc archive can leave weak data segment
// probes unresolved under tcc.
#flag $when_first_existing("/usr/lib/libgc.so", "/usr/local/lib/libgc.so", "/lib/libgc.so")
}
#flag -lgc
} $else {
#flag @VEXEROOT/thirdparty/tcc/lib/libgc.a
}
}
}
}
$if macos {
#flag -DMPROTECT_VDB=1
}
#flag -ldl
#flag -lpthread
} $else $if freebsd {
// Tested on FreeBSD 13.0-RELEASE-p3, with clang, gcc and tcc
#flag -DGC_BUILTIN_ATOMIC=1
#flag -DBUS_PAGE_FAULT=T_PAGEFLT
#flag -DALL_INTERIOR_POINTERS=1
$if !tinyc {
#flag -DUSE_MMAP
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag @VEXEROOT/thirdparty/libgc/gc.o
}
$if tinyc {
// Prefer the bundled header: older FreeBSD libgc headers still use the
// unsupported `"X"` asm constraint in `GC_reachable_here` under tcc.
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -I/usr/local/include
#flag $first_existing("@VEXEROOT/thirdparty/tcc/lib/libgc.a", "/usr/local/lib/libgc-threaded.a", "/usr/lib/libgc-threaded.a")
#flag -lgc-threaded
}
#flag -lpthread
} $else $if openbsd {
// Tested on OpenBSD 7.5, with clang, gcc and tcc
#flag -DGC_BUILTIN_ATOMIC=1
$if !tinyc {
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -DALL_INTERIOR_POINTERS=1
#flag @VEXEROOT/thirdparty/libgc/gc.o
}
$if tinyc {
// Prefer the bundled header: older OpenBSD libgc headers still use the
// unsupported `"X"` asm constraint in `GC_reachable_here` under tcc.
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -L/usr/local/lib
#flag -I/usr/local/include
#flag $first_existing("/usr/local/lib/libgc.a", "/usr/lib/libgc.a")
#flag -lgc
}
#flag -lpthread
} $else $if windows {
#flag -DGC_NOT_DLL=1
#flag -DGC_WIN32_THREADS=1
#flag -luser32
$if tinyc {
#flag -DGC_BUILTIN_ATOMIC=1
#flag -I @VEXEROOT/thirdparty/libgc/include
$if !use_bundled_libgc ? {
#flag @VEXEROOT/thirdparty/tcc/lib/libgc.a
}
} $else $if msvc {
// Build libatomic_ops
#flag @VEXEROOT/thirdparty/libatomic_ops/atomic_ops.o
#flag -I @VEXEROOT/thirdparty/libatomic_ops
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -DALL_INTERIOR_POINTERS=1
#flag @VEXEROOT/thirdparty/libgc/gc.o
} $else {
#flag -DGC_BUILTIN_ATOMIC=1
#flag -I @VEXEROOT/thirdparty/libgc/include
#flag -DALL_INTERIOR_POINTERS=1
#flag @VEXEROOT/thirdparty/libgc/gc.o
}
} $else $if $pkgconfig('bdw-gc') {
#flag -DGC_BUILTIN_ATOMIC=1
#pkgconfig bdw-gc
} $else {
#flag -DGC_BUILTIN_ATOMIC=1
#flag -lgc
}
}
$if gcboehm_leak ? {
#flag -DGC_DEBUG=1
}
#include <gc.h>
#include "@VEXEROOT/vlib/builtin/gc_debugger_linux.h"
// #include <gc/gc_mark.h>
// replacements for `malloc()/calloc()`, `realloc()` and `free()`
// for use with Boehm-GC
// Do not use them manually. They are automatically chosen when
// compiled with `-gc boehm` or `-gc boehm_leak`.
fn C.GC_MALLOC(n usize) voidptr
fn C.GC_MALLOC_ATOMIC(n usize) voidptr
fn C.GC_MALLOC_UNCOLLECTABLE(n usize) voidptr
fn C.GC_REALLOC(ptr voidptr, n usize) voidptr
fn C.GC_FREE(ptr voidptr)
fn C.GC_memalign(align isize, size isize) voidptr
// explicitly perform garbage collection now! Garbage collections
// are done automatically when needed, so this function is hardly needed
fn C.GC_gcollect()
// functions to temporarily suspend/resume garbage collection
fn C.GC_disable()
fn C.GC_enable()
// returns non-zero if GC is disabled
fn C.GC_is_disabled() i32
fn C.GC_set_no_dls(i32)
// protect memory block from being freed before this call
fn C.GC_reachable_here(voidptr)
// gc_is_enabled returns true, if the GC is enabled at runtime.
// See also gc_disable() and gc_enable().
pub fn gc_is_enabled() bool {
return 0 == C.GC_is_disabled()
}
// gc_collect explicitly performs a single garbage collection run.
// Note, that garbage collections, are done automatically, when needed in most cases,
// so usually you should NOT need to call gc_collect() often.
// Note that gc_collect() is a NOP with `-gc none`.
pub fn gc_collect() {
C.GC_gcollect()
}
// gc_enable explicitly enables the GC.
// Note, that garbage collections are done automatically, when needed in most cases,
// and also that by default the GC is on, so you do not need to enable it.
// See also gc_disable() and gc_collect().
// Note that gc_enable() is a NOP with `-gc none`.
pub fn gc_enable() {
C.GC_enable()
}
// gc_disable explicitly disables the GC. Do not forget to enable it again by calling gc_enable(), when your program is otherwise idle, and can afford it.
// See also gc_enable() and gc_collect().
// Note that gc_disable() is a NOP with `-gc none`.
pub fn gc_disable() {
C.GC_disable()
}
// gc_check_leaks is useful for leak detection (it does an explicit garbage collections, but only when a program is compiled with `-gc boehm_leak`).
pub fn gc_check_leaks() {
$if gcboehm_leak ? {
C.GC_gcollect()
}
}
fn C.GC_get_heap_usage_safe(pheap_size &usize, pfree_bytes &usize, punmapped_bytes &usize, pbytes_since_gc &usize,
ptotal_bytes &usize)
fn C.GC_get_memory_use() usize
pub struct C.GC_stack_base {
mem_base voidptr
// reg_base voidptr
}
fn C.GC_get_stack_base(voidptr) i32
fn C.GC_register_my_thread(voidptr) i32
fn C.GC_unregister_my_thread() i32
// fn C.GC_get_my_stackbottom(voidptr) voidptr
fn C.GC_set_stackbottom(voidptr, voidptr)
// fn C.GC_push_all_stacks()
fn C.GC_add_roots(voidptr, voidptr)
fn C.GC_remove_roots(voidptr, voidptr)
fn C.v__gc_can_register_main_data_roots_linux() i32
fn C.v__gc_debugger_present_linux() i32
fn C.v__gc_register_main_data_roots_linux()
// fn C.GC_get_push_other_roots() fn()
// fn C.GC_set_push_other_roots(fn())
fn C.GC_get_sp_corrector() fn (voidptr, voidptr)
fn C.GC_set_sp_corrector(fn (voidptr, voidptr))
// FnGC_WarnCB is the type of the callback, that you have to define, if you want to redirect GC warnings and handle them.
// Note: GC warnings are silenced by default. Use gc_set_warn_proc/1 to set your own handler for them.
pub type FnGC_WarnCB = fn (const_msg &char, arg usize)
fn C.GC_get_warn_proc() FnGC_WarnCB
fn C.GC_set_warn_proc(cb FnGC_WarnCB)
fn C.GC_register_displacement(offset usize)
// gc_get_warn_proc returns the current callback fn, that will be used for printing GC warnings.
pub fn gc_get_warn_proc() FnGC_WarnCB {
return C.GC_get_warn_proc()
}
// gc_set_warn_proc sets the callback fn, that will be used for printing GC warnings.
pub fn gc_set_warn_proc(cb FnGC_WarnCB) {
C.GC_set_warn_proc(cb)
}
// used by builtin_init:
fn internal_gc_warn_proc_none(const_msg &char, arg usize) {}
@[markused]
fn gc_prepare_for_debugger_init() bool {
$if linux {
if C.v__gc_debugger_present_linux() != 0
&& C.v__gc_can_register_main_data_roots_linux() != 0 {
C.GC_set_no_dls(1)
return true
}
}
return false
}
@[markused]
fn gc_restore_roots_after_debugger_init(use_manual_roots bool) {
if !use_manual_roots {
return
}
$if linux {
C.v__gc_register_main_data_roots_linux()
C.GC_set_no_dls(0)
}
}
@@ -0,0 +1,15 @@
module builtin
$if tinyc {
// Alpine's libgc references these as weak probes. TCC errors on the
// unresolved weak symbols when linking against musl's libgc, so provide
// weak definitions with the same array type libgc declares.
@[export: '__data_start']
@[markused; weak]
__global C.__data_start = [1]int{}
@[export: 'data_start']
@[markused; weak]
__global C.data_start = [1]int{}
}
@@ -0,0 +1,119 @@
@[has_globals]
module builtin
$if openbsd {
// OpenBSD lacks the sysctl path lookup that bundled libbacktrace needs,
// so `-d use_libbacktrace` falls back to the native BSD backtrace path.
fn print_libbacktrace(frames_to_skip int) {
}
@[noinline]
fn eprint_libbacktrace(frames_to_skip int) {
}
} $else {
#flag -I@VEXEROOT/thirdparty/libbacktrace
#flag @VEXEROOT/thirdparty/libbacktrace/backtrace.o
#include <backtrace.h>
// NOTE: Don't mark this as a @[typedef] or it may cause compiler errors!
pub struct C.backtrace_state {
// filename &char
}
type BacktraceErrorCallback = fn (data voidptr, msg &char, errnum int)
type BacktraceFullCallback = fn (data voidptr, pc voidptr, filename &char, lineno int, func &char) int
fn C.backtrace_create_state(filename &char, threaded i32, error_callback BacktraceErrorCallback, data voidptr) &C.backtrace_state
fn C.backtrace_full(state &C.backtrace_state, skip i32, cb BacktraceFullCallback, err_cb BacktraceErrorCallback,
data voidptr) i32
__global bt_state = init_bt_state()
fn init_bt_state() &C.backtrace_state {
$if !tinyc {
mut filename := &char(unsafe { nil })
$if windows {
filename = unsafe { string_from_wide(&&u16(g_main_argv)[0]).str }
} $else {
filename = unsafe { &&char(g_main_argv)[0] }
}
return C.backtrace_create_state(filename, 1, bt_error_handler, 0)
}
return &C.backtrace_state(unsafe { nil })
}
// for bt_error_callback
struct BacktraceOptions {
stdin bool = true
}
fn bt_print_callback(data_ptr voidptr, pc voidptr, filename_ptr &char, line int, fn_name_ptr &char) int {
data := unsafe { &BacktraceOptions(data_ptr) }
filename := if filename_ptr == unsafe { nil } {
'???'
} else {
unsafe { filename_ptr.vstring() }
}
fn_name := if fn_name_ptr == unsafe { nil } {
'???'
} else {
demangle_v_symbol(unsafe { fn_name_ptr.vstring() })
}
// keep it for later
// pc_64 := u64(pc)
bt_str := '${filename}:${line}: by ${fn_name}'
if data.stdin {
println(bt_str)
} else {
eprintln(bt_str)
}
return 0
}
fn bt_error_callback(data voidptr, msg_ptr &char, errnum int) {
// if data != unsafe { nil } && data.state != unsafe { nil } && data.state.filename != unsafe { nil } {
// filename := unsafe{ data.state.filename.vstring() }
// eprint('${filename}: ')
// }
msg := unsafe { msg_ptr.vstring() }
eprint('libbacktrace: ${msg}')
if errnum > 0 {
eprint(': ${C.strerror(errnum)}')
}
eprintln('')
}
// for backtrace_create_state only
fn bt_error_handler(data voidptr, msg &char, errnum int) {
eprint('libbacktrace: ')
eprint(unsafe { msg.vstring() })
if errnum > 0 {
eprint(': ${C.strerror(errnum)}')
}
eprintln('')
exit(1)
}
@[noinline]
fn print_libbacktrace(frames_to_skip int) {
$if no_backtrace ? {
return
}
data := &BacktraceOptions{}
C.backtrace_full(bt_state, frames_to_skip, bt_print_callback, bt_error_callback, data)
}
@[noinline]
fn eprint_libbacktrace(frames_to_skip int) {
$if no_backtrace ? {
return
}
data := &BacktraceOptions{
stdin: false
}
C.backtrace_full(bt_state, frames_to_skip, bt_print_callback, bt_error_callback, data)
}
}
+10
View File
@@ -0,0 +1,10 @@
// builtin_d_vgc.c.v - V Garbage Collector additional support
// Conditionally compiled when -d vgc is used.
// The gc_heap_usage/gc_memory_use functions are in allocation.c.v with $if vgc ? branches.
module builtin
// This file exists to ensure the vgc compile define activates VGC-specific code paths
// throughout the builtin module. The actual VGC implementation is in:
// - vgc_d_vgc.c.v: Core types, heap, allocation
// - vgc_gc_d_vgc.c.v: GC mark, sweep, orchestration
+6
View File
@@ -0,0 +1,6 @@
module builtin
// TODO: Remove this later, added to make sure v self works
$if ios {
#include "@VEXEROOT/thirdparty/ios/ios.m"
}
+47
View File
@@ -0,0 +1,47 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module builtin
@[markused]
fn builtin_init() {
$if gcboehm ? {
$if !gc_warn_on_stderr ? {
gc_set_warn_proc(internal_gc_warn_proc_none)
}
}
$if !vinix {
unbuffer_stdout()
}
}
fn break_if_debugger_attached() {
unsafe {
mut ptr := &voidptr(0)
*ptr = nil
}
}
@[noreturn]
pub fn panic_lasterr(base string) {
// TODO: use strerror_r and errno
panic(base + ' unknown')
}
// write_buf_to_console is a Windows-only helper; on non-Windows platforms
// it is a no-op stub so that callers guarded by `$if windows { ... }` still
// type-check on other targets.
fn write_buf_to_console(fd int, buf &u8, buf_len int) bool {
return false
}
// write_buf_to_fd_kernel32 is a Windows-only helper for the minimal V2 PE path.
// Keep a non-Windows stub so optional compile-time branches type-check while
// bootstrapping V on other hosts.
fn write_buf_to_fd_kernel32(fd int, buf &u8, buf_len int) bool {
return false
}
fn write_buf_to_fd_kernel32_or_exit(fd int, buf &u8, buf_len int) {}
fn write_buf_to_fd_windows_non_minimal(fd int, buf &u8, buf_len int) {}
@@ -0,0 +1,68 @@
module builtin
// Just define the C functions, so that V does not error because of the missing definitions.
// Note: they will NOT be used, since calls to them are wrapped with `$if gcboehm ? { }`
fn C.GC_MALLOC(n usize) voidptr
fn C.GC_MALLOC_ATOMIC(n usize) voidptr
fn C.GC_MALLOC_UNCOLLECTABLE(n usize) voidptr
fn C.GC_REALLOC(ptr voidptr, n usize) voidptr
fn C.GC_FREE(ptr voidptr)
fn C.GC_memalign(align isize, size isize) voidptr
fn C.GC_get_heap_usage_safe(pheap_size &usize, pfree_bytes &usize, punmapped_bytes &usize, pbytes_since_gc &usize,
ptotal_bytes &usize)
fn C.GC_get_memory_use() usize
fn C.GC_gcollect()
// gc_check_leaks is useful for detecting leaks, but it needs the GC to run.
// When GC is not used, it is a NOP.
pub fn gc_check_leaks() {}
// gc_is_enabled() returns true, if the GC is enabled at runtime.
// It will always return false, with `-gc none`.
// See also gc_disable() and gc_enable().
pub fn gc_is_enabled() bool {
return false
}
// gc_enable explicitly enables the GC.
// Note, that garbage collections are done automatically, when needed in most cases,
// and also that by default the GC is on, so you do not need to enable it.
// See also gc_disable() and gc_collect().
// Note that gc_enable() is a NOP with `-gc none`.
pub fn gc_enable() {}
// gc_disable explicitly disables the GC.
// Do not forget to enable it again by calling gc_enable(), when your program is otherwise idle, and can afford it.
// See also gc_enable() and gc_collect().
// Note that gc_disable() is a NOP with `-gc none`.
pub fn gc_disable() {}
// gc_collect explicitly performs a garbage collection.
// When the GC is not on, (with `-gc none`), it is a NOP.
pub fn gc_collect() {}
pub type FnGC_WarnCB = fn (msg &char, arg usize)
fn C.GC_get_warn_proc() FnGC_WarnCB
fn C.GC_set_warn_proc(cb FnGC_WarnCB)
// gc_get_warn_proc returns the current callback fn, that will be used for printing GC warnings.
// When the GC is not on, it is a NOP.
pub fn gc_get_warn_proc() {}
// gc_set_warn_proc sets the callback fn, that will be used for printing GC warnings.
// When the GC is not on, it is a NOP.
pub fn gc_set_warn_proc(cb FnGC_WarnCB) {}
// used by builtin_init
fn internal_gc_warn_proc_none(msg &char, arg usize) {}
@@ -0,0 +1,8 @@
module builtin
fn print_libbacktrace(frames_to_skip int) {
}
@[noinline]
fn eprint_libbacktrace(frames_to_skip int) {
}
@@ -0,0 +1,63 @@
import os
import time
const broken_stdout_child_source = r'
import os
fn main() {
mut broken := os.pipe() or { panic(err) }
saved_stdout := os.fd_dup(1)
defer {
os.fd_dup2(saved_stdout, 1)
os.fd_close(saved_stdout)
broken.close()
}
assert os.fd_dup2(broken.read_fd, 1) != -1
os.fd_close(broken.read_fd)
broken.read_fd = -1
println("broken stdout should not hang")
}
'
fn test_println_does_not_hang_on_failed_stdout_write() {
$if windows {
return
}
child_source_path := os.join_path(os.vtmp_dir(),
'broken_stdout_child_${time.now().unix_milli()}.v')
os.write_file(child_source_path, broken_stdout_child_source)!
defer {
os.rm(child_source_path) or {}
}
mut p := os.new_process(@VEXE)
p.set_args(['run', child_source_path])
p.set_redirect_stdio()
p.run()
defer {
if p.is_alive() {
p.signal_kill()
p.wait()
}
p.close()
}
max_wait_iterations := $if s390x || rv64 { 1200 } $else { 300 }
for _ in 0 .. max_wait_iterations {
if !p.is_alive() {
break
}
time.sleep(50 * time.millisecond)
}
if p.is_alive() {
p.signal_kill()
p.wait()
stdout := p.stdout_slurp()
stderr := p.stderr_slurp()
assert false, 'println hung on a failed stdout write\nstdout:\n${stdout}\nstderr:\n${stderr}'
}
p.wait()
stdout := p.stdout_slurp()
stderr := p.stderr_slurp()
assert p.code == 0, 'child exited with ${p.code}\nstdout:\n${stdout}\nstderr:\n${stderr}'
assert stdout == '', stdout
assert stderr == '', stderr
}
@@ -0,0 +1,52 @@
import os
import time
const ready_marker = 'ready\n'
fn test_stdout_is_flushed_by_default() {
$if windows {
return
}
mut cap := os.stdio_capture()!
println('ready')
mut pending := false
for _ in 0 .. 100 {
if os.fd_is_pending(cap.stdout.read_fd) {
pending = true
break
}
time.sleep(10 * time.millisecond)
}
cap.stop()
stdout := cap.stdout.slurp().join('')
stderr := cap.stderr.slurp().join('')
cap.close()
assert pending
assert stdout == ready_marker, stdout
assert stderr == ''
}
fn redirected_stdout_bytes(snippet string) ![]u8 {
stamp := time.now().unix_milli()
source_path := os.join_path(os.vtmp_dir(), 'redirected_stdout_${stamp}.v')
output_path := os.join_path(os.vtmp_dir(), 'redirected_stdout_${stamp}.bin')
os.write_file(source_path, 'fn main() {\n\t${snippet}\n}\n')!
defer {
os.rm(source_path) or {}
os.rm(output_path) or {}
}
cmd := '${os.quoted_path(@VEXE)} run ${os.quoted_path(source_path)} > ${os.quoted_path(output_path)}'
res := os.execute(cmd)
assert res.exit_code == 0, 'command failed: ${cmd}\noutput:\n${res.output}'
return os.read_bytes(output_path)!
}
fn assert_redirected_stdout_bytes(snippet string, expected []u8) ! {
bytes := redirected_stdout_bytes(snippet)!
assert bytes == expected, 'expected ${expected}, got ${bytes}'
}
fn test_redirected_stdout_preserves_newline_bytes() ! {
assert_redirected_stdout_bytes(r"print('\n')", [u8(`\n`)])!
assert_redirected_stdout_bytes(r"print('\r\n')", [u8(`\r`), `\n`])!
}
+32
View File
@@ -0,0 +1,32 @@
fn test_g_main_argc() {
assert g_main_argc > 0
}
fn test_g_main_argv() {
assert g_main_argv != 0
mut first_arg := ''
$if windows {
first_arg = unsafe { string_from_wide(&&u16(g_main_argv)[0]) }
} $else {
first_arg = unsafe { cstring_to_vstring(&&char(g_main_argv)[0]) }
}
assert first_arg.contains('builtin_test')
}
fn test_c_strstr_is_declared() {
found := unsafe { C.strstr(c'hello world', c'world') } != unsafe { nil }
assert found
}
@[if windows]
fn test_bool_size() {
println(@LOCATION)
assert sizeof(C.BOOL) == 4
}
fn test_v_getpid__and__v_gettid() {
p := v_getpid()
dump(p)
t := v_gettid()
dump(t)
}
@@ -0,0 +1,17 @@
import time
fn test_unbuffer_stdout() {
// TODO: add a more extensive test, perhaps using expect,
// that does not require an active user to verify its validity.
// Currently this just tests that unbuffer_stdout/0 can be called,
// and its effects can be observed by commenting the call to it,
// on the next line, and then re-running the test:
unbuffer_stdout()
print('#---')
for i in 0 .. 21 {
print('\b\b\b> ${i:02}')
time.sleep(20 * time.millisecond)
}
println('\b\b\b done')
assert true
}
+411
View File
@@ -0,0 +1,411 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
@[has_globals]
module builtin
#include <io.h>
#include <fcntl.h>
// Cast the V callback to the Windows SDK callback type. Clang 20 treats the
// otherwise-compatible struct pointer mismatch as a hard error for v_win.c.
#define v_set_unhandled_exception_filter(handler) SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)(handler))
// See https://learn.microsoft.com/en-us/windows/win32/winprog/windows-data-types
// See https://www.codeproject.com/KB/string/cppstringguide1.aspx
pub type C.BOOL = int
pub type C.LONG = int
pub type C.HINSTANCE = voidptr
pub type C.HICON = voidptr
pub type C.HCURSOR = voidptr
pub type C.HBRUSH = voidptr
pub type C.HWND = voidptr
pub type C.HGLOBAL = voidptr
pub type C.HANDLE = voidptr
pub type C.LRESULT = voidptr
pub type C.CHAR = char
pub type C.TCHAR = u16 // It is u8 if UNICODE is not defined, but for V programs it always is
pub type C.WCHAR = u16
pub type C.LPSTR = &char
pub type C.LPWSTR = &C.WCHAR
pub type C.LPTSTR = &C.TCHAR
pub type C.LPCTSTR = &C.TCHAR
fn C.WriteConsoleW(voidptr, &u16, u32, &u32, voidptr) bool
fn C.WriteFile(voidptr, &u8, u32, &u32, voidptr) bool
fn C.ExitProcess(u32)
fn C.GetProcessHeap() voidptr
fn C.HeapAlloc(voidptr, u32, usize) voidptr
fn C.HeapFree(voidptr, u32, voidptr) bool
fn C._setmode(int, int) int
// set_stream_binary_mode disables CRT newline translation for redirected stdio streams.
fn set_stream_binary_mode(stream &C.FILE) {
fd := C._fileno(stream)
if fd >= 0 {
C._setmode(fd, C._O_BINARY)
}
}
fn is_terminal(fd int) int {
mut mode := u32(0)
$if v2_native_windows_pe_minimal ? {
if fd != 0 && fd != 1 && fd != 2 {
return 0
}
handle_id := if fd == 0 {
std_input_handle
} else if fd == 2 {
std_error_handle
} else {
std_output_handle
}
handle := C.GetStdHandle(handle_id)
if isnil(handle) || handle == voidptr(-1) {
return 0
}
if !C.GetConsoleMode(handle, &mode) {
return 0
}
return int(mode)
} $else {
osfh := voidptr(C._get_osfhandle(fd))
C.GetConsoleMode(osfh, voidptr(&mode))
return int(mode)
}
}
// GetStdHandle takes DWORD values. Microsoft defines these as ((DWORD)-N),
// so spell the rollover values explicitly for native backends.
const std_input_handle = u32(0xfffffff6)
const std_output_handle = u32(0xfffffff5)
const std_error_handle = u32(0xfffffff4)
const enable_processed_output = 1
const enable_wrap_at_eol_output = 2
const evable_virtual_terminal_processing = 4
// Write UTF-8 directly as UTF-16 for console hosts instead of changing the console code page.
@[manualfree]
fn write_buf_to_console(fd int, buf &u8, buf_len int) bool {
if buf_len <= 0 || is_terminal(fd) <= 0 {
return false
}
mut console_handle := C.GetStdHandle(std_output_handle)
if fd == 2 {
console_handle = C.GetStdHandle(std_error_handle)
}
if isnil(console_handle) || console_handle == voidptr(-1) {
return false
}
unsafe {
wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
if wide_len <= 0 {
return false
}
mut wide_buf := &u16(malloc_noscan((wide_len + 1) * int(sizeof(u16))))
if isnil(wide_buf) {
return false
}
defer {
free(wide_buf)
}
converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
if converted <= 0 {
return false
}
wide_buf[converted] = 0
mut remaining_chars := converted
mut wide_ptr := wide_buf
for remaining_chars > 0 {
mut chars_written := u32(0)
if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), &chars_written, nil)
|| chars_written == 0 {
return false
}
wide_ptr += int(chars_written)
remaining_chars -= int(chars_written)
}
return true
}
}
@[manualfree]
fn write_buf_to_console_kernel32(fd int, buf &u8, buf_len int) bool {
if buf_len <= 0 || (fd != 1 && fd != 2) {
return false
}
console_handle := C.GetStdHandle(if fd == 2 { std_error_handle } else { std_output_handle })
if isnil(console_handle) || console_handle == voidptr(-1) {
return false
}
mut mode := u32(0)
if !C.GetConsoleMode(console_handle, &mode) {
return false
}
unsafe {
wide_len := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, 0, 0)
if wide_len <= 0 {
return false
}
heap := C.GetProcessHeap()
if isnil(heap) {
return false
}
wide_size := usize((wide_len + 1) * int(sizeof(u16)))
mut wide_buf := &u16(C.HeapAlloc(heap, 0, wide_size))
if isnil(wide_buf) {
return false
}
defer {
C.HeapFree(heap, 0, wide_buf)
}
converted := C.MultiByteToWideChar(cp_utf8, 0, &char(buf), buf_len, wide_buf, wide_len)
if converted <= 0 {
return false
}
wide_buf[converted] = 0
mut remaining_chars := converted
mut wide_ptr := wide_buf
for remaining_chars > 0 {
mut chars_written := u32(0)
if !C.WriteConsoleW(console_handle, wide_ptr, u32(remaining_chars), &chars_written, nil)
|| chars_written == 0 {
return false
}
wide_ptr += int(chars_written)
remaining_chars -= int(chars_written)
}
return true
}
}
fn write_buf_to_fd_kernel32(fd int, buf &u8, buf_len int) bool {
return write_buf_to_fd_kernel32_status(fd, buf, buf_len) == 0
}
fn write_buf_to_fd_kernel32_status(fd int, buf &u8, buf_len int) int {
if buf_len <= 0 {
return 0
}
if fd != 1 && fd != 2 {
return 1
}
if write_buf_to_console_kernel32(fd, buf, buf_len) {
return 0
}
handle_id := if fd == 2 { std_error_handle } else { std_output_handle }
handle := C.GetStdHandle(handle_id)
if isnil(handle) || handle == voidptr(-1) {
return 2
}
mut ptr := unsafe { buf }
mut remaining_bytes := buf_len
unsafe {
for remaining_bytes > 0 {
chunk := if remaining_bytes > int(0x7fffffff) {
int(0x7fffffff)
} else {
remaining_bytes
}
mut written := u32(0)
if !C.WriteFile(handle, ptr, u32(chunk), &written, nil) {
return 3
}
if written == 0 {
return 4
}
ptr += int(written)
remaining_bytes -= int(written)
}
}
return 0
}
@[manualfree]
fn write_buf_to_fd_windows_non_minimal(fd int, buf &u8, buf_len int) {
if buf_len <= 0 {
return
}
mut ptr := unsafe { buf }
mut remaining_bytes := isize(buf_len)
if write_buf_to_console(fd, ptr, int(remaining_bytes)) {
return
}
mut stream := voidptr(C.stdout)
if fd == 2 {
stream = voidptr(C.stderr)
}
mut x := isize(0)
unsafe {
for remaining_bytes > 0 {
x = isize(C.fwrite(ptr, 1, remaining_bytes, stream))
if x <= 0 {
// GUI programs on Windows may not have a writable stdout/stderr stream.
break
}
ptr += x
remaining_bytes -= x
}
}
}
fn write_buf_to_fd_kernel32_or_exit(fd int, buf &u8, buf_len int) {
write_status := write_buf_to_fd_kernel32_status(fd, buf, buf_len)
if write_status != 0 {
C.ExitProcess(u32(220 + write_status))
}
}
@[markused]
fn builtin_init() {
$if v2_native_windows_pe_minimal ? {
return
} $else {
$if gcboehm ? {
$if !gc_warn_on_stderr ? {
gc_set_warn_proc(internal_gc_warn_proc_none)
}
}
set_stream_binary_mode(C.stdout)
set_stream_binary_mode(C.stderr)
if is_terminal(1) > 0 {
C.SetConsoleMode(C.GetStdHandle(std_output_handle),
enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
C.SetConsoleMode(C.GetStdHandle(std_error_handle),
enable_processed_output | enable_wrap_at_eol_output | evable_virtual_terminal_processing)
unsafe {
set_stream_unbuffered(C.stdout)
set_stream_unbuffered(C.stderr)
}
}
$if !no_backtrace ? {
add_unhandled_exception_handler()
}
// On windows, the default buffering is block based (~4096bytes), which interferes badly with non cmd shells
// It is much better to have it off by default instead.
unbuffer_stdout()
}
}
// TODO: copypaste from os
// we want to be able to use this here without having to `import os`
struct ExceptionRecord {
pub:
// status_ constants
code u32
flags u32
record &ExceptionRecord = unsafe { nil }
address voidptr
param_count u32
// params []voidptr
}
struct ContextRecord {
// TODO
}
struct ExceptionPointers {
pub:
exception_record &ExceptionRecord = unsafe { nil }
context_record &ContextRecord = unsafe { nil }
}
@[callconv: stdcall]
type TopLevelExceptionFilter = fn (&ExceptionPointers) C.LONG
fn C.SetUnhandledExceptionFilter(TopLevelExceptionFilter) voidptr
fn C.v_set_unhandled_exception_filter(TopLevelExceptionFilter) voidptr
@[callconv: stdcall]
fn unhandled_exception_handler(e &ExceptionPointers) C.LONG {
match e.exception_record.code {
// These are 'used' by the backtrace printer
// so we dont want to catch them...
0x4001000A, 0x40010006, 0x406D1388, 0xE06D7363 {
return 0
}
else {
eprintln('Unhandled Exception 0x' + ptr_str(e.exception_record.code) + ' at ' +
ptr_str(e.exception_record.address))
flush_stdout()
flush_stderr()
print_backtrace_skipping_top_frames(5)
flush_stdout()
flush_stderr()
}
}
return 0
}
fn add_unhandled_exception_handler() {
// A vectored handler also sees first-chance exceptions that Windows APIs may
// handle internally, which can lead to false-positive "Unhandled Exception"
// reports. Register a top-level filter instead.
C.v_set_unhandled_exception_filter(unhandled_exception_handler)
}
fn C.IsDebuggerPresent() bool
fn C.__debugbreak()
fn break_if_debugger_attached() {
$if tinyc {
unsafe {
mut ptr := &voidptr(0)
*ptr = nil
_ = ptr
}
} $else {
if C.IsDebuggerPresent() {
C.__debugbreak()
}
}
}
const format_message_allocate_buffer = 0x00000100
const format_message_argument_array = 0x00002000
const format_message_from_hmodule = 0x00000800
const format_message_from_string = 0x00000400
const format_message_from_system = 0x00001000
const format_message_ignore_inserts = 0x00000200
// return an error message generated from WinAPI's `LastError`
pub fn winapi_lasterr_str() string {
err_msg_id := C.GetLastError()
if err_msg_id == 8 {
// handle this case special since `FormatMessageW()` might not work anymore
return 'insufficient memory'
}
mut msgbuf := &u16(unsafe { nil })
res := C.FormatMessageW(format_message_allocate_buffer | format_message_from_system | format_message_ignore_inserts,
0, err_msg_id, 0, voidptr(&msgbuf), 0, 0)
err_msg := if res == 0 {
'Win-API error ${err_msg_id}'
} else {
unsafe { string_from_wide(msgbuf) }
}
return err_msg
}
// panic with an error message generated from WinAPI's `LastError`
@[noreturn]
pub fn panic_lasterr(base string) {
panic(base + winapi_lasterr_str())
}
+22
View File
@@ -0,0 +1,22 @@
fn test_clone() {
a := [u8(0), 1, 2]
b := a.clone()
assert b.len == 3
assert b[0] == 0
assert b[1] == 1
assert b[2] == 2
assert b[1].str() == '1'
xx := u8(35)
assert xx.str() == '35'
assert xx.ascii_str() == '#'
println(typeof(`A`).name)
assert typeof(`A`).name == 'rune'
x := rune(`A`)
assert x.str() == 'A'
assert typeof(x).name == 'rune'
//
y := `Z`
assert typeof(y).name == 'rune'
assert y.str() == 'Z'
// assert b[1].str() == '1' TODO
}
+591
View File
@@ -0,0 +1,591 @@
module builtin
@[typedef]
pub struct C.FILE {}
// Virtual C globals that are available through libc or generated C headers.
__global C.errno int
__global C.stdin &C.FILE
__global C.stdout &C.FILE
__global C.stderr &C.FILE
__global C.environ &&char
__global C._wyp &u64
// <string.h>
fn C.memcpy(dest voidptr, const_src voidptr, n usize) voidptr
fn C.memcmp(const_s1 voidptr, const_s2 voidptr, n usize) i32
fn C.memmove(dest voidptr, const_src voidptr, n usize) voidptr
fn C.memset(str voidptr, c i32, n usize) voidptr
fn C.memchr(str voidptr, c i32, n usize) voidptr
fn C.memmem(haystack voidptr, haystacklen usize, needle voidptr, needlelen usize) voidptr
fn C.mempcpy(dest voidptr, src voidptr, n usize) voidptr
@[trusted]
fn C.calloc(usize, usize) voidptr
fn C.atoi(&char) i32
fn C.malloc(usize) voidptr
fn C.realloc(a voidptr, b usize) voidptr
fn C.free(ptr voidptr)
fn C.mmap(addr_length voidptr, length usize, prot i32, flags i32, fd i32, offset isize) voidptr
fn C.mprotect(addr_length voidptr, len usize, prot i32) i32
fn C.aligned_alloc(align usize, size usize) voidptr
// windows aligned memory functions
fn C._aligned_malloc(size isize, align isize) voidptr
fn C._aligned_free(voidptr)
fn C._aligned_realloc(voidptr, size isize, align isize) voidptr
fn C._aligned_offset_malloc(size isize, align isize, offset isize) voidptr
fn C._aligned_offset_realloc(voidptr, size isize, align isize, offset isize) voidptr
fn C._aligned_msize(voidptr, align isize, offset isize) isize
fn C._aligned_recalloc(voidptr, num isize, size isize, align isize) voidptr
$if windows {
fn C.VirtualAlloc(voidptr, isize, u32, u32) voidptr
fn C.VirtualProtect(voidptr, isize, u32, &u32) bool
}
@[noreturn; trusted]
fn C.exit(code i32)
fn C.qsort(base voidptr, items usize, item_size usize, cb C.qsort_callback_func)
fn C.strlen(s &char) i32
@[trusted]
fn C.isdigit(c i32) bool
// stdio.h
fn C.popen(c &char, t &char) voidptr
// <libproc.h>
fn C.proc_pidpath(i32, voidptr, i32) i32
fn C.realpath(const_path &char, resolved_path &char) &char
// fn C.chmod(byteptr, mode_t) int
fn C.chmod(path &char, mode u32) i32
fn C.printf(const_format &char, opt ...voidptr) i32
fn C.dprintf(fd i32, const_format &char, opt ...voidptr) i32
fn C.fprintf(fstream &C.FILE, const_format &char, opt ...voidptr) i32
fn C.sprintf(str &char, const_format &char, opt ...voidptr) i32
fn C.snprintf(str &char, size usize, const_format &char, opt ...voidptr) i32
fn C.wprintf(const_format &u16, opt ...voidptr) i32
// used by Android for (e)println to output to the Android log system / logcat
pub fn C.android_print(fstream voidptr, format &char, opt ...voidptr)
fn C.sscanf(str &char, const_format &char, opt ...voidptr) i32
fn C.scanf(const_format &char, opt ...voidptr) i32
fn C.puts(msg &char) i32
@[trusted]
fn C.abs(f64) f64
fn C.fputs(msg &char, fstream &C.FILE) i32
fn C.fflush(fstream &C.FILE) i32
// TODO: define args in these functions
fn C.fseek(stream &C.FILE, offset i32, whence i32) i32
fn C.fopen(filename &char, mode &char) &C.FILE
fn C.fileno(&C.FILE) i32
fn C.fread(ptr voidptr, item_size usize, items usize, stream &C.FILE) usize
fn C.fwrite(ptr voidptr, item_size usize, items usize, stream &C.FILE) usize
fn C.fclose(stream &C.FILE) i32
fn C.pclose(stream &C.FILE) i32
fn C.open(path &char, flags i32, mode ...int) i32
fn C.close(fd i32) i32
fn C.strrchr(s &char, c i32) &char
fn C.strchr(s &char, c i32) &char
fn C.strstr(const_haystack &char, const_needle &char) &char
// process execution, os.process:
@[trusted]
fn C.GetCurrentProcessId() u32
@[trusted]
fn C._getpid() i32
@[trusted]
fn C.getpid() i32
@[trusted]
fn C.GetCurrentThreadId() u32
@[trusted]
fn C.gettid() u32
@[trusted]
fn C.getuid() i32
@[trusted]
fn C.geteuid() i32
fn C.system(cmd &char) i32
fn C.posix_spawn(child_pid &int, path &char, file_actions voidptr, attrp voidptr, argv &&char, envp &&char) i32
fn C.posix_spawnp(child_pid &int, exefile &char, file_actions voidptr, attrp voidptr, argv &&char, envp &&char) i32
fn C.execve(cmd_path &char, args voidptr, envs voidptr) i32
fn C.execvp(cmd_path &char, args &&char) i32
fn C._execve(cmd_path &char, args voidptr, envs voidptr) i32
fn C._execvp(cmd_path &char, args &&char) i32
fn C.strcmp(s1 &char, s2 &char) i32
@[trusted]
fn C.fork() i32
fn C.wait(status &int) i32
fn C.waitpid(pid i32, status &int, options i32) i32
@[trusted]
fn C.kill(pid i32, sig i32) i32
fn C.setenv(&char, &char, i32) i32
fn C.unsetenv(&char) i32
fn C.access(path &char, amode i32) i32
fn C.remove(filename &char) i32
fn C.rmdir(path &char) i32
fn C.chdir(path &char) i32
fn C.rewind(stream &C.FILE) i32
fn C.ftell(&C.FILE) isize
fn C.stat(&char, voidptr) i32
fn C.lstat(path &char, buf &C.stat) i32
fn C.statvfs(const_path &char, buf &C.statvfs) i32
fn C.rename(old_filename &char, new_filename &char) i32
fn C.fgets(str &char, n i32, stream &C.FILE) i32
fn C.fgetpos(&C.FILE, voidptr) i32
@[trusted]
fn C.sigemptyset() i32
fn C.getcwd(buf &char, size usize) &char
@[trusted]
fn C.mktime() i32
fn C.gettimeofday(tv &C.timeval, tz &C.timezone) i32
@[trusted]
fn C.sleep(seconds u32) u32
// fn C.usleep(usec useconds_t) int
@[trusted]
fn C.usleep(usec u32) i32
@[typedef]
pub struct C.DIR {
}
fn C.opendir(&char) &C.DIR
fn C.closedir(dirp &C.DIR) i32
// fn C.mkdir(path &char, mode mode_t) int
fn C.mkdir(path &char, mode u32) i32
// C.rand returns a pseudorandom integer from 0 (inclusive) to C.RAND_MAX (exclusive)
@[trusted]
fn C.rand() i32
// C.srand seeds the internal PRNG with the given value.
@[trusted]
fn C.srand(seed u32)
fn C.atof(str &char) f64
@[trusted]
fn C.tolower(c i32) i32
@[trusted]
fn C.toupper(c i32) i32
@[trusted]
fn C.isspace(c i32) i32
fn C.strchr(s &char, c i32) &char
@[trusted]
fn C.getchar() i32
@[trusted]
fn C.putchar(i32) i32
fn C.strdup(s &char) &char
fn C.strncasecmp(s &char, s2 &char, n i32) i32
fn C.strcasecmp(s &char, s2 &char) i32
fn C.strncmp(s &char, s2 &char, n i32) i32
@[trusted]
fn C.strerror(i32) &char
@[trusted]
fn C.WIFEXITED(status i32) bool
@[trusted]
fn C.WEXITSTATUS(status i32) i32
@[trusted]
fn C.WIFSIGNALED(status i32) bool
@[trusted]
fn C.WTERMSIG(status i32) i32
@[trusted]
fn C.isatty(fd i32) i32
fn C.syscall(number i32, va ...voidptr) i32
fn C.sysctl(name &int, namelen u32, oldp voidptr, oldlenp voidptr, newp voidptr, newlen usize) i32
@[trusted]
fn C._fileno(&C.FILE) i32
pub type C.intptr_t = voidptr
fn C._get_osfhandle(fd i32) C.intptr_t
fn C.GetModuleFileName(hModule voidptr, lpFilename &u16, nSize u32) u32
fn C.GetModuleFileNameW(hModule voidptr, lpFilename &u16, nSize u32) u32
fn C.CreateFile(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32,
dwFlagsAndAttributes u32, hTemplateFile voidptr) voidptr
fn C.CreateFileW(lpFilename &u16, dwDesiredAccess u32, dwShareMode u32, lpSecurityAttributes &u16, dwCreationDisposition u32,
dwFlagsAndAttributes u32, hTemplateFile voidptr) voidptr
fn C.GetFinalPathNameByHandleW(hFile voidptr, lpFilePath &u16, nSize u32, dwFlags u32) u32
fn C.CreatePipe(hReadPipe &voidptr, hWritePipe &voidptr, lpPipeAttributes voidptr, nSize u32) bool
fn C.SetHandleInformation(hObject voidptr, dwMask u32, dw_flags u32) bool
fn C.ExpandEnvironmentStringsW(lpSrc &u16, lpDst &u16, nSize u32) u32
fn C.GetComputerNameW(&u16, &u32) bool
fn C.GetUserNameW(&u16, &u32) bool
@[trusted]
fn C.SendMessageTimeout() isize
fn C.SendMessageTimeoutW(hWnd voidptr, msg u32, wParam &u16, lParam &u32, fuFlags u32, uTimeout u32, lpdwResult &u64) isize
fn C.CreateProcessW(lpApplicationName &u16, lpCommandLine &u16, lpProcessAttributes voidptr, lpThreadAttributes voidptr,
bInheritHandles bool, dwCreationFlags u32, lpEnvironment voidptr, lpCurrentDirectory &u16, lpStartupInfo voidptr,
lpProcessInformation voidptr) bool
fn C.ReadFile(hFile voidptr, lpBuffer voidptr, nNumberOfBytesToRead u32, lpNumberOfBytesRead &u32, lpOverlapped voidptr) bool
fn C.GetFileAttributesW(lpFileName &u8) u32
fn C.RegQueryValueEx(hKey voidptr, lpValueName &u16, lp_reserved &u32, lpType &u32, lpData &u8, lpcbData &u32) i32
fn C.RegQueryValueExW(hKey voidptr, lpValueName &u16, lp_reserved &u32, lpType &u32, lpData &u8, lpcbData &u32) i32
fn C.RegOpenKeyEx(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, phkResult voidptr) i32
fn C.RegOpenKeyExW(hKey voidptr, lpSubKey &u16, ulOptions u32, samDesired u32, phkResult voidptr) i32
fn C.RegSetValueEx(hKey voidptr, lpValueName &u16, dwType u32, lpData &u16, cbData u32) i32
fn C.RegSetValueExW(hKey voidptr, lpValueName &u16, reserved u32, dwType u32, const_lpData &u8, cbData u32) i32
fn C.RegCloseKey(hKey voidptr) i32
fn C.RemoveDirectory(lpPathName &u16) bool
fn C.RemoveDirectoryW(lpPathName &u16) bool
fn C.GetStdHandle(u32) voidptr
fn C.SetConsoleMode(voidptr, u32) bool
fn C.GetConsoleMode(voidptr, &u32) bool
// fn C.setbuf()
fn C.setbuf(voidptr, &char)
fn C.SymCleanup(hProcess voidptr)
fn C.MultiByteToWideChar(codePage u32, dwFlags u32, lpMultiMyteStr &char, cbMultiByte i32, lpWideCharStr &u16,
cchWideChar i32) i32
fn C.wcslen(str voidptr) usize
fn C.WideCharToMultiByte(codePage u32, dwFlags u32, lpWideCharStr &u16, cchWideChar i32, lpMultiByteStr &char,
cbMultiByte i32, lpDefaultChar &char, lpUsedDefaultChar &int) i32
fn C._wstat(path &u16, buffer &C._stat) i32
fn C._wrename(oldname &u16, newname &u16) i32
fn C._wfopen(filename &u16, mode &u16) voidptr
fn C._wpopen(command &u16, mode &u16) voidptr
fn C._pclose(stream &C.FILE) i32
fn C._wsystem(command &u16) i32
fn C._wgetenv(varname &u16) voidptr
fn C._putenv(envstring &char) i32
fn C._wputenv(envstring &u16) i32
fn C._waccess(path &u16, mode i32) i32
fn C._wremove(path &u16) i32
fn C.ReadConsole(in_input_handle voidptr, out_buffer voidptr, in_chars_to_read u32, out_read_chars &u32,
in_input_control voidptr) bool
fn C.WriteConsole() voidptr
fn C.WriteFile(hFile voidptr, lpBuffer &u8, nNumberOfBytesToWrite u32, lpNumberOfBytesWritten &u32, lpOverlapped voidptr) bool
fn C._wchdir(dirname &u16) i32
fn C._wgetcwd(buffer &u16, maxlen i32) i32
fn C._fullpath() i32
fn C.GetFullPathName(voidptr, u32, voidptr, voidptr) u32
@[trusted]
fn C.GetCommandLine() voidptr
fn C.LocalFree(voidptr)
fn C.FindFirstFileW(lpFileName &u16, lpFindFileData voidptr) voidptr
fn C.FindFirstFile(lpFileName &u8, lpFindFileData voidptr) voidptr
fn C.FindNextFile(hFindFile voidptr, lpFindFileData voidptr) i32
fn C.FindClose(hFindFile voidptr)
// macro
fn C.MAKELANGID(lgid voidptr, srtid voidptr) i32
fn C.FormatMessageW(dwFlags u32, lpSource voidptr, dwMessageId u32, dwLanguageId u32, lpBuffer voidptr,
nSize u32, arguments ...voidptr) u32
fn C.CloseHandle(voidptr) i32
fn C.GetExitCodeProcess(hProcess voidptr, lpExitCode &u32)
@[trusted]
fn C.GetTickCount() i64
@[trusted]
fn C.Sleep(dwMilliseconds u32)
fn C.WSAStartup(u16, &voidptr) i32
@[trusted]
fn C.WSAGetLastError() i32
fn C.closesocket(i32) i32
fn C.vschannel_init(&C.TlsContext, C.BOOL)
fn C.request(&C.TlsContext, i32, &u16, &u8, u32, &&u8, fn (voidptr, isize) voidptr) i32
fn C.vschannel_cleanup(&C.TlsContext)
fn C.URLDownloadToFile(i32, &u16, &u16, i32, i32)
@[trusted]
fn C.GetLastError() u32
fn C.CreateDirectory(&u8, i32) bool
// win crypto
fn C.BCryptGenRandom(i32, voidptr, i32, i32) i32
// win synchronization
fn C.CreateMutex(i32, bool, &u8) voidptr
fn C.WaitForSingleObject(voidptr, i32) i32
fn C.ReleaseMutex(voidptr) bool
fn C.CreateEvent(i32, bool, bool, &u8) voidptr
fn C.SetEvent(voidptr) i32
fn C.CreateSemaphore(voidptr, i32, i32, voidptr) voidptr
fn C.ReleaseSemaphore(voidptr, i32, voidptr) voidptr
$if windows {
fn C.InitializeSRWLock(voidptr)
fn C.AcquireSRWLockShared(voidptr)
fn C.AcquireSRWLockExclusive(voidptr)
fn C.ReleaseSRWLockShared(voidptr)
fn C.ReleaseSRWLockExclusive(voidptr)
}
// pthread.h
fn C.pthread_self() usize
fn C.pthread_mutex_init(voidptr, voidptr) i32
fn C.pthread_mutex_lock(voidptr) i32
fn C.pthread_mutex_unlock(voidptr) i32
fn C.pthread_mutex_destroy(voidptr) i32
fn C.pthread_rwlockattr_init(voidptr) i32
fn C.pthread_rwlockattr_setkind_np(voidptr, i32) i32
fn C.pthread_rwlockattr_setpshared(voidptr, i32) i32
fn C.pthread_rwlock_init(voidptr, voidptr) i32
fn C.pthread_rwlock_rdlock(voidptr) i32
fn C.pthread_rwlock_wrlock(voidptr) i32
fn C.pthread_rwlock_unlock(voidptr) i32
fn C.pthread_condattr_init(voidptr) i32
fn C.pthread_condattr_setpshared(voidptr, i32) i32
fn C.pthread_condattr_destroy(voidptr) i32
fn C.pthread_cond_init(voidptr, voidptr) i32
fn C.pthread_cond_signal(voidptr) i32
fn C.pthread_cond_wait(voidptr, voidptr) i32
fn C.pthread_cond_timedwait(voidptr, voidptr, voidptr) i32
fn C.pthread_cond_destroy(voidptr) i32
fn C.sem_init(voidptr, i32, u32) i32
fn C.sem_post(voidptr) i32
fn C.sem_wait(voidptr) i32
fn C.sem_trywait(voidptr) i32
fn C.sem_timedwait(voidptr, voidptr) i32
fn C.sem_destroy(voidptr) i32
// MacOS semaphore functions
@[trusted]
fn C.dispatch_semaphore_create(i64) voidptr
fn C.dispatch_semaphore_signal(voidptr) i64
fn C.dispatch_semaphore_wait(voidptr, u64) i64
@[trusted]
fn C.dispatch_time(u64, i64) u64
fn C.dispatch_release(voidptr)
// file descriptor based reading/writing
fn C.read(fd i32, buf voidptr, count usize) i32
fn C.write(fd i32, buf voidptr, count usize) i32
fn C.close(fd i32) i32
// pipes
fn C.pipe(pipefds &int) i32
fn C.dup2(oldfd i32, newfd i32) i32
// used by gl, stbi, freetype
fn C.glTexImage2D()
// used by ios for println
fn C.WrappedNSLog(str &u8)
// absolute value
@[trusted]
fn C.abs(number i32) i32
$if windows {
fn C.GetDiskFreeSpaceExA(const_path &char, free_bytes_available_to_caller &u64, total_number_of_bytes &u64, total_number_of_free_bytes &u64) bool
fn C.GetNativeSystemInfo(voidptr)
// C.SYSTEM_INFO contains information about the current computer system. This includes the architecture and type of the processor, the number of processors in the system, the page size, and other such information.
@[typedef]
pub struct C.SYSTEM_INFO {
// workaround: v doesn't support a truely C anon union/struct here
// union {
dwOemId u32
// struct {
wProcessorArchitecture u16
wReserved u16
// }
//}
dwPageSize u32
lpMinimumApplicationAddress voidptr
lpMaximumApplicationAddress voidptr
dwActiveProcessorMask u32
dwNumberOfProcessors u32
dwProcessorType u32
dwAllocationGranularity u32
wProcessorLevel u16
wProcessorRevision u16
}
fn C.GetSystemInfo(&C.SYSTEM_INFO)
@[typedef]
pub struct C.SRWLOCK {}
}
fn C.sysconf(name i32) i32
+179
View File
@@ -0,0 +1,179 @@
module builtin
// vstrlen returns the V length of the C string `s` (0 terminator is not counted).
// The C string is expected to be a &u8 pointer.
@[inline; unsafe]
pub fn vstrlen(s &u8) int {
return int(unsafe { C.strlen(&char(s)) })
}
// vstrlen_char returns the V length of the C string `s` (0 terminator is not counted).
// The C string is expected to be a &char pointer.
@[inline; unsafe]
pub fn vstrlen_char(s &char) int {
return int(unsafe { C.strlen(s) })
}
// vmemcpy copies n bytes from memory area src to memory area dest.
// The memory areas *MUST NOT OVERLAP*. Use vmemmove, if the memory
// areas do overlap. vmemcpy returns a pointer to `dest`.
@[inline; unsafe]
pub fn vmemcpy(dest voidptr, const_src voidptr, n isize) voidptr {
$if trace_vmemcpy ? {
C.fprintf(C.stderr, c'vmemcpy dest: %p src: %p n: %6ld\n', dest, const_src, n)
}
$if trace_vmemcpy_nulls ? {
if dest == unsafe { 0 } || const_src == unsafe { 0 } {
C.fprintf(C.stderr, c'vmemcpy null pointers passed, dest: %p src: %p n: %6ld\n', dest,
const_src, n)
print_backtrace()
}
}
if n == 0 || u64(dest) <= 0xFFFF || u64(const_src) <= 0xFFFF {
return dest
}
unsafe {
return C.memcpy(dest, const_src, n)
}
}
// vmemmove copies n bytes from memory area `src` to memory area `dest`.
// The memory areas *MAY* overlap: copying takes place as though the bytes
// in `src` are first copied into a temporary array that does not overlap
// `src` or `dest`, and the bytes are then copied from the temporary array
// to `dest`. vmemmove returns a pointer to `dest`.
@[inline; unsafe]
pub fn vmemmove(dest voidptr, const_src voidptr, n isize) voidptr {
$if trace_vmemmove ? {
C.fprintf(C.stderr, c'vmemmove dest: %p src: %p n: %6ld\n', dest, const_src, n)
}
if n == 0 || u64(dest) <= 0xFFFF || u64(const_src) <= 0xFFFF {
return dest
}
unsafe {
return C.memmove(dest, const_src, n)
}
}
// vmemcmp compares the first n bytes (each interpreted as unsigned char)
// of the memory areas s1 and s2. It returns an integer less than, equal to,
// or greater than zero, if the first n bytes of s1 is found, respectively,
// to be less than, to match, or be greater than the first n bytes of s2.
// For a nonzero return value, the sign is determined by the sign of the
// difference between the first pair of bytes (interpreted as unsigned char)
// that differ in s1 and s2.
// If n is zero, the return value is zero.
// Do NOT use vmemcmp to compare security critical data, such as cryptographic
// secrets, because the required CPU time depends on the number of equal bytes.
// You should use a function that performs comparisons in constant time for
// this.
@[inline; unsafe]
pub fn vmemcmp(const_s1 voidptr, const_s2 voidptr, n isize) int {
$if trace_vmemcmp ? {
C.fprintf(C.stderr, c'vmemcmp s1: %p s2: %p n: %6ld\n', const_s1, const_s2, n)
}
if n == 0 || u64(const_s1) <= 0xFFFF || u64(const_s2) <= 0xFFFF {
return 0
}
unsafe {
return C.memcmp(const_s1, const_s2, n)
}
}
// vmemset fills the first `n` bytes of the memory area pointed to by `s`,
// with the constant byte `c`. It returns a pointer to the memory area `s`.
@[inline; unsafe]
pub fn vmemset(s voidptr, c int, n isize) voidptr {
$if trace_vmemset ? {
C.fprintf(C.stderr, c'vmemset s: %p c: %d n: %6ld\n', s, c, n)
}
$if trace_vmemset_nulls ? {
if s == unsafe { 0 } {
C.fprintf(C.stderr, c'vmemset null pointers passed s: %p, c: %6d, n: %6ld\n', s, c, n)
print_backtrace()
}
}
if n == 0 || u64(s) <= 0xFFFF {
return s
}
unsafe {
return C.memset(s, c, n)
}
}
type FnSortCB = fn (const_a voidptr, const_b voidptr) int
@[inline; unsafe]
fn vsort_ptr_at(base voidptr, index usize, size usize) voidptr {
return unsafe { voidptr(&u8(base) + index * size) }
}
@[unsafe]
fn vstable_sort_merge(source voidptr, dest voidptr, left usize, mid usize, right usize, size usize, sort_cb FnSortCB) {
mut left_index := left
mut right_index := mid
mut dest_index := left
for left_index < mid && right_index < right {
left_ptr := vsort_ptr_at(source, left_index, size)
right_ptr := vsort_ptr_at(source, right_index, size)
if sort_cb(left_ptr, right_ptr) <= 0 {
vmemcpy(vsort_ptr_at(dest, dest_index, size), left_ptr, isize(size))
left_index++
} else {
vmemcpy(vsort_ptr_at(dest, dest_index, size), right_ptr, isize(size))
right_index++
}
dest_index++
}
if left_index < mid {
vmemcpy(vsort_ptr_at(dest, dest_index, size), vsort_ptr_at(source, left_index, size),
isize((mid - left_index) * size))
}
if right_index < right {
vmemcpy(vsort_ptr_at(dest, dest_index, size), vsort_ptr_at(source, right_index, size),
isize((right - right_index) * size))
}
}
@[inline; unsafe]
fn vqsort(base voidptr, nmemb usize, size usize, sort_cb FnSortCB) {
$if trace_vqsort ? {
C.fprintf(C.stderr, c'vqsort base: %p, nmemb: %6uld, size: %6uld, sort_cb: %p\n', base,
nmemb, size, sort_cb)
}
if nmemb < 2 || size == 0 {
return
}
$if trace_vqsort_nulls ? {
if base == unsafe { 0 } || voidptr(sort_cb) == unsafe { nil } {
C.fprintf(C.stderr,
c'vqsort null pointers passed base: %p, nmemb: %6uld, size: %6uld, sort_cb: %p\n',
base, nmemb, size, sort_cb)
print_backtrace()
}
}
total_size := isize(nmemb * size)
buffer := malloc(total_size)
defer {
free(buffer)
}
mut source := base
mut dest := voidptr(buffer)
mut width := usize(1)
for width < nmemb {
mut left := usize(0)
for left < nmemb {
mid := if left + width < nmemb { left + width } else { nmemb }
right := if left + width + width < nmemb { left + width + width } else { nmemb }
vstable_sort_merge(source, dest, left, mid, right, size, sort_cb)
left += width + width
}
mut tmp := source
source = dest
dest = tmp
width += width
}
if source != base {
vmemcpy(base, source, total_size)
}
}
+216
View File
@@ -0,0 +1,216 @@
module builtin
// ChanState describes the result of an attempted channel transaction.
pub enum ChanState {
success
not_ready // push()/pop() would have to wait, but no_block was requested
closed
}
/*
The following methods are only stubs.
The real implementation is in `vlib/sync/channels.v`
*/
// close closes the channel for further push transactions.
// closed channels cannot be pushed to, and after the buffer is drained
// `<-ch or {}` and `<-ch?` use `err` or default to `channel closed`.
pub fn (ch chan) close(err ...IError) {}
// try_pop returns `ChanState.success` if an object is popped from the channel.
// try_pop effectively pops from the channel without waiting for objects to become available.
// Both the test and pop transaction is done atomically.
// Pass the destination as `mut`: `ch.try_pop(mut value)`, not `ch.try_pop(&value)`.
pub fn (ch chan) try_pop(obj voidptr) ChanState {
return .success
}
// try_push returns `ChanState.success` if the object is pushed to the channel.
// try_push effectively both push and test if the transaction `ch <- a` succeeded.
// Both the test and push transaction is done atomically.
pub fn (ch chan) try_push(obj voidptr) ChanState {
return .success
}
// IError holds information about an error instance.
pub interface IError {
msg() string
code() int
}
struct _result {
is_error bool
err IError = none__
// Data is trailing after err
// and is not included in here but in the
// derived Result_xxx types
}
fn _result_ok(data voidptr, mut res _result, size int) {
unsafe {
*res = _result{}
// use err to get the end of ResultBase and then memcpy into it
vmemcpy(&u8(&res.err) + sizeof(IError), data, size)
}
}
fn _result_clone(current &_result, mut res _result, size int) {
unsafe {
*res = _result{
is_error: current.is_error
err: current.err
}
// use err to get the end of ResultBase and then memcpy into it
vmemcpy(&u8(&res.err) + sizeof(IError), &u8(&current.err) + sizeof(IError), size)
}
}
// str returns the message of IError.
pub fn (err IError) str() string {
if err is None__ {
return 'none'
}
c := err.code()
if c > 0 {
return err.msg() + '; code: ' + c.str()
}
return err.msg()
}
// Error is the empty default implementation of `IError`.
pub struct Error {}
pub fn (err Error) msg() string {
return ''
}
pub fn (err Error) code() int {
return 0
}
// MessageError is the default implementation of the `IError` interface that is returned by the `error()` function.
struct MessageError {
pub:
msg string
code int
}
// str returns both the .msg and .code of MessageError, when .code is != 0 .
pub fn (err MessageError) str() string {
if err.code > 0 {
return '${err.msg}; code: ${err.code}'
}
return err.msg
}
// msg returns only the message of MessageError.
pub fn (err MessageError) msg() string {
return err.msg
}
// code returns only the code of MessageError.
pub fn (err MessageError) code() int {
return err.code
}
@[unsafe]
pub fn (err &MessageError) free() {
unsafe { err.msg.free() }
}
@[if trace_error ?]
fn trace_error(x string) {
eprintln('> ${@FN} | ${x}')
}
// error returns a default error instance containing the error given in `message`.
// Example: f := fn (ouch bool) ! { if ouch { return error('an error occurred') } }; f(false)!
@[inline]
pub fn error(message string) IError {
trace_error(message)
return &MessageError{
msg: message
}
}
// error_with_code returns a default error instance containing the given `message` and error `code`.
// Example: f := fn (ouch bool) ! { if ouch { return error_with_code('an error occurred', 1) } }; f(false)!
@[inline]
pub fn error_with_code(message string, code int) IError {
trace_error('${message} | code: ${code}')
return &MessageError{
msg: message
code: code
}
}
// Option is the base of V's internal option return system.
struct Option {
state u8 // 0 - ok; 2 - none; 1 - ?
err IError = none__
// Data is trailing after err
// and is not included in here but in the
// derived Option_xxx types
}
// option is the base of V's internal option return system.
struct _option {
state u8
err IError = none__
// Data is trailing after err
// and is not included in here but in the
// derived _option_xxx types
}
fn _option_none(data voidptr, mut option _option, size int) {
unsafe {
*option = _option{
state: 2
}
// use err to get the end of OptionBase and then memcpy into it
vmemcpy(&u8(&option.err) + sizeof(IError), data, size)
}
}
fn _option_ok(data voidptr, mut option _option, size int) {
unsafe {
*option = _option{}
// use err to get the end of OptionBase and then memcpy into it
vmemcpy(&u8(&option.err) + sizeof(IError), data, size)
}
}
fn _option_clone(current &_option, mut option _option, size int) {
unsafe {
*option = _option{
state: current.state
err: current.err
}
// use err to get the end of OptionBase and then memcpy into it
vmemcpy(&u8(&option.err) + sizeof(IError), &u8(&current.err) + sizeof(IError), size)
}
}
@[markused]
fn _result_ok_markused() {
mut res := _result{}
// Keep _result_ok emitted for code that constructs Result directly.
_result_ok(unsafe { nil }, mut res, 0)
}
//
const none__ = IError(&None__{})
struct None__ {
Error
}
fn (_ None__) str() string {
return 'none'
}
// str for none, returns 'none'
pub fn (_ none) str() string {
return 'none'
}
+43
View File
@@ -0,0 +1,43 @@
module builtin
// input_character gives back a single character, read from the standard input.
// It returns -1 on error (when the input is finished (EOF), on a broken pipe etc).
pub fn input_character() int {
mut ch := 0
$if freestanding {
// TODO
return -1
} $else $if vinix {
// TODO
return -1
} $else {
ch = C.getchar()
if ch == C.EOF {
return -1
}
}
return ch
}
// print_character writes the single character `ch` to the standard output.
// It returns -1 on error (when the output is closed, on a broken pipe, etc).
// Note: this function does not allocate memory, unlike `print(ch.ascii_str())`
// which does, and is thus cheaper to call, which is important, if you have
// to output many characters one by one. If you instead want to print entire
// strings at once, use `print(your_string)`.
pub fn print_character(ch u8) int {
$if android && !termux {
C.android_print(C.stdout, c'%.*s', 1, voidptr(&ch))
} $else $if freestanding {
bare_print(voidptr(&ch), u64(1))
} $else $if vinix {
// TODO
return 0
} $else {
x := C.putchar(ch)
if x == C.EOF {
return -1
}
}
return ch
}
@@ -0,0 +1,18 @@
fn test_print_character() {
assert print_character(u8(`a`)) > 0
assert print_character(u8(`A`)) > 0
println('')
assert print_character(rune(`a`)) > 0
assert print_character(rune(`A`)) > 0
println('')
assert print_character(u8(char(`a`))) > 0
assert print_character(u8(char(`A`))) > 0
println('')
assert print_character(u8(int(`a`))) > 0
assert print_character(u8(int(`A`))) > 0
println('')
}
fn test_input_character() {
// TODO: add an `expect` based test on Linux
}
+2
View File
@@ -0,0 +1,2 @@
The files in this directory implement the `closure` feature of the V language, which is called
internally by the V compiler.
+872
View File
@@ -0,0 +1,872 @@
@[has_globals]
module closure
// Inspired from Chris Wellons's work
// https://nullprogram.com/blog/2017/01/08/
const assumed_page_size = int(0x4000)
const ppc64_architecture = int(11)
type ClosureGetDataFn = fn () voidptr
type ClosureInitFn = fn ()
struct ClosurePage {
mut:
next &ClosurePage = unsafe { nil }
exec_page_start voidptr
}
struct ClosureLiveInfo {
mut:
ctx voidptr
owns_data bool
generation u64
}
struct ClosureLifetimeRecord {
mut:
exec_ptr voidptr
generation u64
}
struct ClosureLifetimeFrame {
mut:
start int
end int
}
@[heap]
struct ClosureLifetimeState {
mut:
owner_thread u64
active bool
disposed bool
suspended int
frame_start int
frame_gen u64
generation u64
frame_generation u64
records []ClosureLifetimeRecord
frames []ClosureLifetimeFrame
next_free &ClosureLifetimeState = unsafe { nil }
}
// Lifetime owns a set of tracked closure callbacks that can be reclaimed together.
pub struct Lifetime {
mut:
state &ClosureLifetimeState = unsafe { nil }
generation u64
disposed bool
}
struct FrameToken {
mut:
state &ClosureLifetimeState = unsafe { nil }
thread_id u64
state_generation u64
generation u64
}
@[heap]
struct Closure {
ClosureMutex
mut:
closure_ptr voidptr
closure_get_data ClosureGetDataFn = unsafe { nil }
closure_cap int
free_closure_ptr voidptr
pages &ClosurePage = unsafe { nil }
v_page_size int = int(0x4000)
live map[voidptr]ClosureLiveInfo
active_lifetimes map[u64]&ClosureLifetimeState
next_generation u64
free_lifetime_states &ClosureLifetimeState = unsafe { nil }
next_lifetime_generation u64
lifetime_state_allocs u64
}
__global g_closure = Closure{}
enum MemoryProtectAtrr {
read_exec
read_write
}
// Keep this runtime check bootstrap-compatible. Older compilers can not parse `$if ppc64` yet.
@[inline]
fn is_ppc64() bool {
$if big_endian {
return C.__V_architecture == ppc64_architecture
} $else {
return false
}
}
// refer to https://godbolt.org/z/r7P3EYv6c for a complete assembly
//
// NOTE: Keep the first branch as the longest byte sequence. In translated/bootstrap C mode
// (`vc/v.c`), V emits a fixed C array whose size is inferred from the first branch.
// The final `big_endian` branch maps to ppc64 here, since the supported big-endian
// closure targets handled above are s390x and sparc64.
// vfmt off
pub const closure_thunk = $if ppc64le {
[
u8(0xa6), 0x02, 0x08, 0x7c, // mflr %r0
0x05, 0x00, 0x00, 0x48, // bl here
0xa6, 0x02, 0xc8, 0x7d, // here: mflr %r14
0xf8, 0xbf, 0xce, 0x39, // addi %r14, %r14, -16392
0x00, 0x00, 0xce, 0xc9, // lfd %f14, 0(%r14)
0x08, 0x00, 0xce, 0xe9, // ld %r14, 8(%r14)
0x78, 0x73, 0xcc, 0x7d, // mr %r12, %r14
0xa6, 0x03, 0x08, 0x7c, // mtlr %r0
0xa6, 0x03, 0xc9, 0x7d, // mtctr %r14
0x20, 0x04, 0x80, 0x4e, // bctr
]!
} $else $if !ppc64le && !amd64 && !i386 && !arm64 && !arm32 && !rv64 && !rv32 && !s390x && !loongarch64 {
// ppc (32-bit PowerPC) - expressed as negation of all other arches for bootstrap compat
[
u8(0x7c), 0x08, 0x02, 0xa6, // mflr %r0
0x48, 0x00, 0x00, 0x05, // bl here
0x7d, 0x88, 0x02, 0xa6, // here: mflr %r12
0x39, 0x8c, 0xbf, 0xf8, // addi %r12, %r12, -16392
0xc9, 0xcc, 0x00, 0x00, // lfd %f14, 0(%r12)
0x81, 0x8c, 0x00, 0x04, // lwz %r12, 4(%r12)
0x7c, 0x08, 0x03, 0xa6, // mtlr %r0
0x7d, 0x89, 0x03, 0xa6, // mtctr %r12
0x4e, 0x80, 0x04, 0x20, // bctr
]!
} $else $if amd64 {
[
u8(0xF3), 0x44, 0x0F, 0x7E, 0x3D, 0xF7, 0xBF, 0xFF, 0xFF, // movq xmm15, QWORD PTR [rip - userdata]
0xFF, 0x25, 0xF9, 0xBF, 0xFF, 0xFF // jmp QWORD PTR [rip - fn]
]!
} $else $if i386 {
[
u8(0xe8), 0x00, 0x00, 0x00, 0x00, // call here
// here:
0x59, // pop ecx
0x66, 0x0F, 0x6E, 0xF9, // movd xmm7, ecx
0xff, 0xA1, 0xff, 0xbf, 0xff, 0xff, // jmp DWORD PTR [ecx - 0x4001] # <fn>
]!
} $else $if arm64 {
[
u8(0x11), 0x00, 0xFE, 0x5C, // ldr d17, userdata
0x30, 0x00, 0xFE, 0x58, // ldr x16, fn
0x00, 0x02, 0x1F, 0xD6 // br x16
]!
} $else $if arm32 {
[
u8(0x04), 0xC0, 0x4F, 0xE2, // adr ip, here
// here:
0x01, 0xC9, 0x4C, 0xE2, // sub ip, ip, #0x4000
0x90, 0xCA, 0x07, 0xEE, // vmov s15, ip
0x00, 0xC0, 0x9C, 0xE5, // ldr ip, [ip, 0]
0x1C, 0xFF, 0x2F, 0xE1 // bx ip
]!
} $else $if rv64 {
[
u8(0x97), 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc
0x03, 0xBF, 0x8F, 0x00, // ld t5, 8(t6)
0x07, 0xB3, 0x0F, 0x00, // fld ft6, 0(t6)
0x67, 0x00, 0x0F, 0x00, // jr t5
]!
} $else $if rv32 {
[
u8(0x97), 0xCF, 0xFF, 0xFF, // auipc t6, 0xffffc
0x03, 0xAF, 0x4F, 0x00, // lw t5, 4(t6)
0x07, 0xAB, 0x0F, 0x00, // flw fs6, 0(t6)
0x67, 0x00, 0x0F, 0x00 // jr t5
]!
} $else $if s390x {
[
u8(0xC0), 0x10, 0xFF, 0xFF, 0xE0, 0x00, // larl %r1, -16384
0x68, 0xF0, 0x10, 0x00, // ld %f15, 0(%r1)
0xE3, 0x10, 0x10, 0x08, 0x00, 0x04, // lg %r1, 8(%r1)
0x07, 0xF1, // br %r1
]!
} $else $if loongarch64 {
[
u8(0x92), 0xFF, 0xFF, 0x1D, // pcaddu12i t6, -4
0x48, 0x02, 0x80, 0x2B, // fld.d f8, t6, 0
0x51, 0x22, 0xC0, 0x28, // ld.d t5, t6, 8
0x20, 0x02, 0x00, 0x4C, // jr t5
]!
} $else $if sparc64 {
[
u8(0x83), 0x41, 0x40, 0x00, // rd %pc, %g1
0x05, 0x00, 0x00, 0x10, // sethi %hi(0x4000), %g2
0x84, 0x10, 0xa0, 0x00, // mov %g2, %g2 ! 4000 <main>
0x82, 0x20, 0x40, 0x02, // sub %g1, %g2, %g1
0xff, 0x18, 0x60, 0x00, // ldd [ %l1 ], %d62
0xc2, 0x58, 0x60, 0x08, // ldx [ %g1 + 8 ], %g1
0x81, 0xc0, 0x40, 0x00, // jmp %g1
0x01, 0x00, 0x00, 0x00 // nop
]!
} $else $if big_endian {
[
u8(0x7C), 0x08, 0x02, 0xA6, // mflr %r0
0x48, 0x00, 0x00, 0x05, // bl here
0x7D, 0xC8, 0x02, 0xA6, // here: mflr %r14
0x39, 0xCE, 0xC0, 0x08, // addi %r14, %r14, -16376
0xC9, 0xCE, 0x00, 0x00, // lfd %f14, 0(%r14) // userdata
0xE9, 0xCE, 0x00, 0x08, // ld %r14, 8(%r14) // func descriptor ptr
0xE9, 0x8E, 0x00, 0x00, // ld %r12, 0(%r14) // code addr from descriptor
0xE8, 0x4E, 0x00, 0x08, // ld %r2, 8(%r14) // TOC from descriptor
0x7C, 0x08, 0x03, 0xA6, // mtlr %r0
0x7D, 0x89, 0x03, 0xA6, // mtctr %r12
0x4E, 0x80, 0x04, 0x20, // bctr
]!
} $else {
[u8(0)]!
}
// NOTE: Keep the first branch as the longest byte sequence. In translated/bootstrap C mode
// (`vc/v.c`), V emits a fixed C array whose size is inferred from the first branch.
const closure_get_data_bytes = $if !ppc64le && !amd64 && !i386 && !arm64 && !arm32 && !rv64 && !rv32 && !s390x && !loongarch64 {
// ppc (32-bit PowerPC) - expressed as negation of all other arches for bootstrap compat
[
u8(0x94), 0x21, 0xff, 0xf0, // stwu %r1, -16(%r1)
0xd9, 0xc1, 0x00, 0x08, // stfd %f14, 8(%r1)
0x80, 0x61, 0x00, 0x08, // lwz %r3, 8(%r1)
0x38, 0x21, 0x00, 0x10, // addi %r1, %r1, 16
0x4e, 0x80, 0x00, 0x20, // blr
]!
} $else $if arm32 {
[
u8(0x90), 0x0A, 0x17, 0xEE, // vmov r0, s15
0x04, 0x00, 0x10, 0xE5, // ldr r0, [r0, #-4]
0x1E, 0xFF, 0x2F, 0xE1 // bx lr
]!
} $else $if amd64 {
[
u8(0x66), 0x4C, 0x0F, 0x7E, 0xF8, // movq rax, xmm15
0xC3 // ret
]!
} $else $if i386 {
[
u8(0x66), 0x0F, 0x7E, 0xF8, // movd eax, xmm7
0x8B, 0x80, 0xFB, 0xBF, 0xFF, 0xFF, // mov eax, DWORD PTR [eax - 0x4005]
0xc3 // ret
]!
} $else $if arm64 {
[
u8(0x20), 0x02, 0x66, 0x9E, // fmov x0, d17
0xC0, 0x03, 0x5F, 0xD6 // ret
]!
} $else $if rv64 {
[
u8(0x53), 0x05, 0x03, 0xE2, // fmv.x.d a0, ft6
0x67, 0x80, 0x00, 0x00, // ret
]!
} $else $if rv32 {
[
u8(0x53), 0x05, 0x0B, 0xE0, // fmv.x.w a0, fs6
0x67, 0x80, 0x00, 0x00 // ret
]!
} $else $if s390x {
[
u8(0xB3), 0xCD, 0x00, 0x2F, // lgdr %r2, %f15
0x07, 0xFE, // br %r14
]!
} $else $if ppc64le {
[
u8(0x66), 0x00, 0xc3, 0x7d, // mfvsrd %r3, %f14
0x20, 0x00, 0x80, 0x4e, // blr
]!
} $else $if loongarch64 {
[
u8(0x04), 0xB9, 0x14, 0x01, // movfr2gr.d a0, f8
0x20, 0x00, 0x00, 0x4C, // ret
]!
} $else $if sparc64 {
[
u8(0x91), 0xb0, 0x22, 0x1f, // movdtox %f62, %o0
0x81, 0xc3, 0xe0, 0x08, // retl
0x01, 0x00, 0x00, 0x00 // nop
]!
} $else $if big_endian {
[
u8(0x7d), 0xc3, 0x00, 0x66, // mfvsrd %r3, %f14
0x4e, 0x80, 0x00, 0x20 // blr
]!
} $else {
[u8(0)]!
}
// vfmt on
// equal to `max(2*sizeof(void*), sizeof(__closure_thunk))`, rounded up to the next multiple of `sizeof(void*)`
// NOTE: This is a workaround for `-usecache` bug, as it can't include `fn get_closure_size()` needed by `const closure_size` in `build-module` mode.
const closure_size_1 = if 2 * u32(sizeof(voidptr)) > u32(closure_thunk.len) {
2 * u32(sizeof(voidptr))
} else {
u32(closure_thunk.len) + u32(sizeof(voidptr)) - 1
}
const closure_size = int(closure_size_1 & ~(u32(sizeof(voidptr)) - 1))
@[inline]
fn closure_exec_ptr(closure voidptr) voidptr {
if is_ppc64() {
return unsafe { &u8(closure) + assumed_page_size }
}
return closure
}
@[inline]
fn closure_return_ptr(exec_ptr voidptr) voidptr {
if is_ppc64() {
return unsafe { &u8(exec_ptr) - assumed_page_size }
}
return exec_ptr
}
@[inline]
fn closure_slot_meta(exec_ptr voidptr) &voidptr {
return unsafe { &voidptr(&u8(exec_ptr) - assumed_page_size) }
}
fn closure_register_page(exec_page_start voidptr) {
unsafe {
node := &ClosurePage(malloc(sizeof(ClosurePage)))
*node = ClosurePage{
next: g_closure.pages
exec_page_start: exec_page_start
}
g_closure.pages = node
}
}
fn closure_is_managed(exec_ptr voidptr) bool {
if isnil(exec_ptr) {
return false
}
exec_addr := unsafe { usize(exec_ptr) }
mut page := g_closure.pages
for page != unsafe { nil } {
page_addr := unsafe { usize(page.exec_page_start) }
if exec_addr >= page_addr && exec_addr < page_addr + usize(g_closure.v_page_size) {
slot_offset := exec_addr - page_addr
return slot_offset >= usize(closure_size) && slot_offset % usize(closure_size) == 0
}
page = page.next
}
return false
}
fn closure_live_set(exec_ptr voidptr, data voidptr, owns_data bool) {
g_closure.next_generation++
g_closure.live[exec_ptr] = ClosureLiveInfo{
ctx: data
owns_data: owns_data
generation: g_closure.next_generation
}
}
fn closure_live_delete(exec_ptr voidptr) ClosureLiveInfo {
if info := g_closure.live[exec_ptr] {
g_closure.live[exec_ptr] = ClosureLiveInfo{}
g_closure.live.delete(exec_ptr)
return info
}
return ClosureLiveInfo{}
}
fn new_closure_lifetime_state_no_lock() &ClosureLifetimeState {
mut state := g_closure.free_lifetime_states
if !isnil(state) {
g_closure.free_lifetime_states = state.next_free
} else {
unsafe {
state = &ClosureLifetimeState(malloc(sizeof(ClosureLifetimeState)))
}
g_closure.lifetime_state_allocs++
}
g_closure.next_lifetime_generation++
unsafe {
*state = ClosureLifetimeState{
owner_thread: closure_current_thread_id_platform()
generation: g_closure.next_lifetime_generation
}
}
return state
}
fn new_closure_lifetime_state() &ClosureLifetimeState {
closure_mtx_lock_platform()
state := new_closure_lifetime_state_no_lock()
closure_mtx_unlock_platform()
return state
}
fn closure_lifetime_recycle_state_no_lock(mut state &ClosureLifetimeState) {
state.disposed = true
state.active = false
state.suspended = 0
state.frame_start = 0
state.frame_gen = 0
state.frame_generation = 0
unsafe {
state.records.free()
state.frames.free()
}
state.records = []ClosureLifetimeRecord{}
state.frames = []ClosureLifetimeFrame{}
state.next_free = g_closure.free_lifetime_states
g_closure.free_lifetime_states = state
}
fn closure_lifetime_error(state &ClosureLifetimeState, generation u64, thread_id u64) string {
if state.disposed || state.generation != generation {
return 'closure lifetime used after dispose'
}
if state.owner_thread != thread_id {
return 'closure lifetime used from a different thread'
}
return ''
}
fn (mut lifetime Lifetime) ensure_state() !&ClosureLifetimeState {
closure_ensure_initialized()
if isnil(lifetime.state) {
if lifetime.disposed {
return error('closure lifetime used after dispose')
}
lifetime.state = new_closure_lifetime_state()
lifetime.generation = lifetime.state.generation
return lifetime.state
}
closure_mtx_lock_platform()
state := lifetime.state
if lifetime.disposed || state.disposed || state.generation != lifetime.generation {
closure_mtx_unlock_platform()
return error('closure lifetime used after dispose')
}
closure_mtx_unlock_platform()
return state
}
fn closure_lifetime_track_no_lock(exec_ptr voidptr) {
thread_id := closure_current_thread_id_platform()
mut state := g_closure.active_lifetimes[thread_id] or { return }
if state.suspended > 0 {
return
}
info := g_closure.live[exec_ptr] or { return }
state.records << ClosureLifetimeRecord{
exec_ptr: exec_ptr
generation: info.generation
}
}
@[direct_array_access]
fn closure_slot_data(exec_ptr voidptr) voidptr {
unsafe {
mut p := closure_slot_meta(exec_ptr)
if is_ppc64() {
return p[2]
}
return p[0]
}
}
@[direct_array_access]
fn closure_release_no_lock(exec_ptr voidptr, generation u64) bool {
if !closure_is_managed(exec_ptr) {
return false
}
info := g_closure.live[exec_ptr] or { return false }
if generation != 0 && info.generation != generation {
return false
}
data := closure_slot_data(exec_ptr)
_ := closure_live_delete(exec_ptr)
if info.owns_data && !isnil(data) {
unsafe { free(data) }
}
unsafe {
mut p := closure_slot_meta(exec_ptr)
p[0] = g_closure.free_closure_ptr
if is_ppc64() {
p[1] = nil
p[2] = nil
p[3] = nil
} else {
p[1] = nil
}
g_closure.free_closure_ptr = exec_ptr
}
return true
}
fn closure_lifetime_release_records_no_lock(records []ClosureLifetimeRecord, start int, end int) {
for i in start .. end {
record := records[i]
closure_release_no_lock(record.exec_ptr, record.generation)
}
}
fn closure_lifetime_reclaim_no_lock(mut state ClosureLifetimeState, retain int) {
keep := if retain < 0 { 0 } else { retain }
if state.frames.len <= keep {
return
}
reclaim_count := state.frames.len - keep
mut cutoff := 0
for i in 0 .. reclaim_count {
frame := state.frames[i]
closure_lifetime_release_records_no_lock(state.records, frame.start, frame.end)
cutoff = frame.end
}
state.frames.delete_many(0, reclaim_count)
if cutoff > 0 {
state.records.delete_many(0, cutoff)
for mut frame in state.frames {
frame.start -= cutoff
frame.end -= cutoff
}
}
}
fn closure_ensure_initialized() {
closure_init_once_platform()
}
// new_lifetime creates a lifetime object for tracking closure callbacks created inside frames.
pub fn new_lifetime() Lifetime {
closure_ensure_initialized()
state := new_closure_lifetime_state()
return Lifetime{
state: state
generation: state.generation
}
}
// lifetime_state_allocs writes the number of allocated closure lifetime bookkeeping states to out.
@[if track_heap ?]
pub fn lifetime_state_allocs(out &u64) {
closure_ensure_initialized()
closure_mtx_lock_platform()
unsafe {
*out = g_closure.lifetime_state_allocs
}
closure_mtx_unlock_platform()
}
fn (mut lifetime Lifetime) begin_frame() !FrameToken {
mut state := lifetime.ensure_state()!
thread_id := closure_current_thread_id_platform()
closure_mtx_lock_platform()
err := closure_lifetime_error(state, lifetime.generation, thread_id)
if err != '' {
closure_mtx_unlock_platform()
return error(err)
}
if state.active {
closure_mtx_unlock_platform()
return error('closure lifetime frames can not be nested')
}
if state.suspended > 0 {
closure_mtx_unlock_platform()
return error('closure lifetime frame while suspended')
}
if _ := g_closure.active_lifetimes[thread_id] {
closure_mtx_unlock_platform()
return error('another closure lifetime is already active on this thread')
}
state.frame_generation++
state.active = true
state.frame_start = state.records.len
state.frame_gen = state.frame_generation
g_closure.active_lifetimes[thread_id] = state
closure_mtx_unlock_platform()
return FrameToken{
state: state
thread_id: thread_id
state_generation: lifetime.generation
generation: state.frame_generation
}
}
fn (mut lifetime Lifetime) end_frame(token FrameToken) ! {
if isnil(token.state) {
return error('invalid closure lifetime frame token')
}
mut state := token.state
thread_id := closure_current_thread_id_platform()
closure_mtx_lock_platform()
err := closure_lifetime_error(state, token.state_generation, thread_id)
if err != '' {
closure_mtx_unlock_platform()
return error(err)
}
if token.thread_id != thread_id || token.generation != state.frame_gen || !state.active {
closure_mtx_unlock_platform()
return error('invalid closure lifetime frame token')
}
state.frames << ClosureLifetimeFrame{
start: state.frame_start
end: state.records.len
}
state.active = false
state.frame_start = 0
state.frame_gen = 0
g_closure.active_lifetimes[thread_id] = unsafe { nil }
g_closure.active_lifetimes.delete(thread_id)
closure_mtx_unlock_platform()
}
// frame runs work while tracking closure callbacks created by that work in this lifetime.
// The work callback itself is borrowed; only closures allocated while the frame is active
// are owned by the lifetime and later released by reclaim or dispose.
pub fn (mut lifetime Lifetime) frame(work fn ()) ! {
token := lifetime.begin_frame()!
mut ended := false
defer {
if !ended {
lifetime.end_frame(token) or {}
}
}
// Terminal panic can abort before scoped cleanup runs.
work()
lifetime.end_frame(token)!
ended = true
}
// reclaim releases tracked closure callbacks from old frames while retaining the newest retain frames.
pub fn (mut lifetime Lifetime) reclaim(retain int) ! {
mut state := lifetime.ensure_state()!
thread_id := closure_current_thread_id_platform()
closure_mtx_lock_platform()
err := closure_lifetime_error(state, lifetime.generation, thread_id)
if err != '' {
closure_mtx_unlock_platform()
return error(err)
}
if state.active {
closure_mtx_unlock_platform()
return error('closure lifetime reclaim while a frame is active')
}
closure_lifetime_reclaim_no_lock(mut state, retain)
closure_mtx_unlock_platform()
}
// reclaim_all releases all tracked closure callbacks owned by this lifetime.
pub fn (mut lifetime Lifetime) reclaim_all() ! {
lifetime.reclaim(0)!
}
// dispose releases all tracked closure callbacks and invalidates this lifetime.
pub fn (mut lifetime Lifetime) dispose() ! {
mut state := lifetime.ensure_state()!
thread_id := closure_current_thread_id_platform()
closure_mtx_lock_platform()
err := closure_lifetime_error(state, lifetime.generation, thread_id)
if err != '' {
closure_mtx_unlock_platform()
return error(err)
}
if state.active {
closure_mtx_unlock_platform()
return error('closure lifetime dispose while a frame is active')
}
if state.suspended > 0 {
closure_mtx_unlock_platform()
return error('closure lifetime dispose while suspended')
}
closure_lifetime_reclaim_no_lock(mut state, 0)
lifetime.state = unsafe { nil }
lifetime.disposed = true
closure_lifetime_recycle_state_no_lock(mut state)
closure_mtx_unlock_platform()
}
// suspend runs work without tracking closure callbacks in the active frame of this lifetime.
// The work callback is borrowed and is not owned or released by the lifetime.
pub fn (mut lifetime Lifetime) suspend(work fn ()) ! {
mut state := lifetime.ensure_state()!
thread_id := closure_current_thread_id_platform()
closure_mtx_lock_platform()
err := closure_lifetime_error(state, lifetime.generation, thread_id)
if err != '' {
closure_mtx_unlock_platform()
return error(err)
}
if active := g_closure.active_lifetimes[thread_id] {
if active != state {
closure_mtx_unlock_platform()
return error('another closure lifetime is already active on this thread')
}
}
state.suspended++
closure_mtx_unlock_platform()
defer {
closure_mtx_lock_platform()
state.suspended--
closure_mtx_unlock_platform()
}
work()
}
// untracked runs work without tracking closure callbacks in this lifetime.
// The work callback is borrowed and is not owned or released by the lifetime.
pub fn (mut lifetime Lifetime) untracked(work fn ()) ! {
lifetime.suspend(work)!
}
// closure_alloc allocates executable memory pages for closures(INTERNAL COMPILER USE ONLY).
fn closure_alloc() {
p := closure_alloc_platform()
if isnil(p) {
return
}
// Setup executable and guard pages
x := unsafe { p + g_closure.v_page_size } // End of guard page
mut remaining := g_closure.v_page_size / closure_size // Calculate slot count
closure_register_page(x)
g_closure.closure_ptr = x // Current allocation pointer
g_closure.closure_cap = remaining // Remaining slot count
// Fill page with closure templates
for remaining > 0 {
unsafe { vmemcpy(x, &closure_thunk[0], closure_thunk.len) } // Copy template
remaining--
unsafe {
x += closure_size // Move to next slot
}
}
closure_memory_protect_platform(g_closure.closure_ptr, g_closure.v_page_size, .read_exec)
}
// closure_init initializes global closure subsystem(INTERNAL COMPILER USE ONLY).
fn closure_init() {
closure_ensure_initialized()
}
fn closure_init_body() {
// Determine system page size
mut page_size := get_page_size_platform()
g_closure.v_page_size = page_size // Store calculated size
g_closure.live = map[voidptr]ClosureLiveInfo{}
g_closure.active_lifetimes = map[u64]&ClosureLifetimeState{}
g_closure.next_generation = 0
g_closure.free_lifetime_states = unsafe { nil }
g_closure.next_lifetime_generation = 0
g_closure.lifetime_state_allocs = 0
// Initialize thread-safety lock
closure_mtx_lock_init_platform()
// Initial memory allocation
closure_alloc()
// Install closure handler template
unsafe {
// Temporarily enable write access to executable memory
closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_write)
// Copy closure entry stub code
vmemcpy(g_closure.closure_ptr, &closure_get_data_bytes[0], closure_get_data_bytes.len)
// Re-normalize execution protection
closure_memory_protect_platform(g_closure.closure_ptr, page_size, .read_exec)
}
// Setup global closure handler pointer
if is_ppc64() {
mut desc := unsafe { &voidptr(&u8(g_closure.closure_ptr) - assumed_page_size) }
unsafe {
desc[0] = g_closure.closure_ptr
desc[1] = nil
}
g_closure.closure_get_data = unsafe { ClosureGetDataFn(desc) }
} else {
g_closure.closure_get_data = g_closure.closure_ptr
}
// Advance allocation pointer past header
unsafe {
g_closure.closure_ptr = &u8(g_closure.closure_ptr) + closure_size
}
g_closure.closure_cap-- // Account for header slot
}
// closure_create creates closure objects at compile-time(INTERNAL COMPILER USE ONLY).
fn closure_create(func voidptr, data voidptr) voidptr {
return closure_create_with_data(func, data, true)
}
// closure_create_with_data creates closure objects with explicit context ownership(INTERNAL COMPILER USE ONLY).
@[direct_array_access]
fn closure_create_with_data(func voidptr, data voidptr, owns_data bool) voidptr {
closure_ensure_initialized()
closure_mtx_lock_platform()
mut curr_closure := g_closure.free_closure_ptr
if !isnil(curr_closure) {
unsafe {
mut p := closure_slot_meta(curr_closure)
g_closure.free_closure_ptr = p[0]
}
} else {
// Handle memory exhaustion
if g_closure.closure_cap == 0 {
closure_alloc() // Allocate new memory page
}
g_closure.closure_cap-- // Decrement slot counter
// Claim current closure slot
curr_closure = g_closure.closure_ptr
unsafe {
// Move to next available slot
g_closure.closure_ptr = &u8(g_closure.closure_ptr) + closure_size
}
}
unsafe {
// Write closure metadata (data + function pointer)
mut p := closure_slot_meta(curr_closure)
if is_ppc64() {
// ELFv1: guard page layout per slot:
// [0] desc[0] = thunk code address <- returned as ELFv1 function pointer
// [1] desc[1] = nil (TOC unused; thunk loads real TOC from func descriptor)
// [2] userdata
// [3] func (V function descriptor pointer into .opd)
p[0] = curr_closure
p[1] = nil
p[2] = data
p[3] = func
} else {
p[0] = data // Stored closure context
p[1] = func // Target function to execute
}
}
closure_live_set(curr_closure, data, owns_data)
closure_lifetime_track_no_lock(curr_closure)
closure_mtx_unlock_platform()
// Return executable closure object
return closure_return_ptr(curr_closure)
}
// closure_data returns the userdata pointer associated with a closure object.
@[direct_array_access]
fn closure_data(closure voidptr) voidptr {
unsafe {
mut p := closure_slot_meta(closure_exec_ptr(closure))
$if ppc64 {
return p[2]
} $else {
return p[0]
}
}
}
// Legacy compiler hook for one-shot local cleanup. Scoped lifetime reclaim uses generation checks.
@[direct_array_access]
fn closure_try_destroy(closure voidptr) {
if isnil(closure) {
return
}
closure_ensure_initialized()
exec_ptr := closure_exec_ptr(closure)
closure_mtx_lock_platform()
closure_release_no_lock(exec_ptr, 0)
closure_mtx_unlock_platform()
}
+4
View File
@@ -0,0 +1,4 @@
module closure
// placeholder
// js gen need at least one `.v` file under the module dir.
@@ -0,0 +1,108 @@
module closure
$if !freestanding && !vinix {
#include <sys/mman.h>
#insert "@VEXEROOT/vlib/builtin/closure/closure_once_nix.h"
fn C.v_closure_init_once(ClosureInitFn)
}
struct ClosureMutex {
closure_mtx [128]u8
}
@[inline]
fn closure_mtx_ptr_platform() voidptr {
return unsafe { voidptr(&g_closure.closure_mtx[0]) }
}
@[inline]
fn closure_alloc_platform() &u8 {
mut p := &u8(unsafe { nil })
$if freestanding {
// Freestanding environments (no OS) use simple malloc
p = unsafe { malloc(g_closure.v_page_size * 2) }
if isnil(p) {
return unsafe { nil }
}
} $else {
// Main OS environments use mmap to get aligned pages
p = unsafe {
C.mmap(0, g_closure.v_page_size * 2, C.PROT_READ | C.PROT_WRITE,
C.MAP_ANONYMOUS | C.MAP_PRIVATE, -1, 0)
}
if p == &u8(C.MAP_FAILED) {
return unsafe { nil }
}
}
return p
}
@[inline]
fn closure_memory_protect_platform(ptr voidptr, size isize, attr MemoryProtectAtrr) {
$if freestanding {
// No memory protection in freestanding mode
} $else {
match attr {
.read_exec {
unsafe { C.mprotect(ptr, size, C.PROT_READ | C.PROT_EXEC) }
}
.read_write {
unsafe { C.mprotect(ptr, size, C.PROT_READ | C.PROT_WRITE) }
}
}
}
}
@[inline]
fn get_page_size_platform() int {
// Determine system page size
mut page_size := 0x4000
$if !freestanding {
// Query actual page size in OS environments
page_size = unsafe { int(C.sysconf(C._SC_PAGESIZE)) }
}
// Calculate required allocation size
page_size = page_size * (((assumed_page_size - 1) / page_size) + 1)
return page_size
}
@[inline]
fn closure_mtx_lock_init_platform() {
$if !freestanding || vinix {
C.pthread_mutex_init(closure_mtx_ptr_platform(), 0)
}
}
@[inline]
fn closure_mtx_lock_platform() {
$if !freestanding || vinix {
C.pthread_mutex_lock(closure_mtx_ptr_platform())
}
}
@[inline]
fn closure_mtx_unlock_platform() {
$if !freestanding || vinix {
C.pthread_mutex_unlock(closure_mtx_ptr_platform())
}
}
@[inline]
fn closure_current_thread_id_platform() u64 {
$if !freestanding {
return u64(C.pthread_self())
}
return u64(0)
}
@[inline]
fn closure_init_once_platform() {
$if freestanding || vinix {
if isnil(g_closure.closure_ptr) {
closure_init_body()
}
} $else {
C.v_closure_init_once(closure_init_body)
}
}
@@ -0,0 +1,28 @@
#ifndef V_CLOSURE_ONCE_NIX_H
#define V_CLOSURE_ONCE_NIX_H
#include <pthread.h>
typedef void (*v_closure_init_fn)(void);
#ifndef V_CLOSURE_STATIC_INLINE
# ifdef _MSC_VER
# define V_CLOSURE_STATIC_INLINE static __inline
# else
# define V_CLOSURE_STATIC_INLINE static inline
# endif
#endif
static pthread_mutex_t v_closure_once_mutex = PTHREAD_MUTEX_INITIALIZER;
static int v_closure_once_done = 0;
V_CLOSURE_STATIC_INLINE void v_closure_init_once(v_closure_init_fn init_fn) {
pthread_mutex_lock(&v_closure_once_mutex);
if (!v_closure_once_done) {
init_fn();
v_closure_once_done = 1;
}
pthread_mutex_unlock(&v_closure_once_mutex);
}
#endif
@@ -0,0 +1,28 @@
#ifndef V_CLOSURE_ONCE_WINDOWS_H
#define V_CLOSURE_ONCE_WINDOWS_H
#include <windows.h>
typedef void (*v_closure_init_fn)(void);
#ifndef V_CLOSURE_STATIC_INLINE
# ifdef _MSC_VER
# define V_CLOSURE_STATIC_INLINE static __inline
# else
# define V_CLOSURE_STATIC_INLINE static inline
# endif
#endif
static SRWLOCK v_closure_once_lock;
static int v_closure_once_done = 0;
V_CLOSURE_STATIC_INLINE void v_closure_init_once(v_closure_init_fn init_fn) {
AcquireSRWLockExclusive(&v_closure_once_lock);
if (!v_closure_once_done) {
init_fn();
v_closure_once_done = 1;
}
ReleaseSRWLockExclusive(&v_closure_once_lock);
}
#endif
@@ -0,0 +1,66 @@
module closure
#include <synchapi.h>
#insert "@VEXEROOT/vlib/builtin/closure/closure_once_windows.h"
fn C.v_closure_init_once(ClosureInitFn)
struct ClosureMutex {
closure_mtx C.SRWLOCK
}
@[inline]
fn closure_alloc_platform() &u8 {
p := &u8(C.VirtualAlloc(0, g_closure.v_page_size * 2, C.MEM_COMMIT | C.MEM_RESERVE,
C.PAGE_READWRITE))
return p
}
@[inline]
fn closure_memory_protect_platform(ptr voidptr, size isize, attr MemoryProtectAtrr) {
mut tmp := u32(0)
match attr {
.read_exec {
_ := C.VirtualProtect(ptr, size, C.PAGE_EXECUTE_READ, &tmp)
}
.read_write {
_ := C.VirtualProtect(ptr, size, C.PAGE_READWRITE, &tmp)
}
}
}
@[inline]
fn get_page_size_platform() int {
// Determine system page size
mut si := C.SYSTEM_INFO{}
C.GetNativeSystemInfo(&si)
// Calculate required allocation size
page_size := int(si.dwPageSize) * (((assumed_page_size - 1) / int(si.dwPageSize)) + 1)
return page_size
}
@[inline]
fn closure_mtx_lock_init_platform() {
C.InitializeSRWLock(&g_closure.closure_mtx)
}
@[inline]
fn closure_mtx_lock_platform() {
C.AcquireSRWLockExclusive(&g_closure.closure_mtx)
}
@[inline]
fn closure_mtx_unlock_platform() {
C.ReleaseSRWLockExclusive(&g_closure.closure_mtx)
}
@[inline]
fn closure_current_thread_id_platform() u64 {
return u64(C.GetCurrentThreadId())
}
@[inline]
fn closure_init_once_platform() {
C.v_closure_init_once(closure_init_body)
}
+498
View File
@@ -0,0 +1,498 @@
fn a() {}
fn b() {}
fn c() {}
fn v() {}
fn test_any_all_of_ints() {
ia := [1, 2, 3]!
assert ia.any(it > 2)
assert ia.any(|x| x > 2)
assert [1, 2, 3]!.any(it > 2)
assert [1, 2, 3]!.any(|x| x > 2)
assert !ia.all(it > 1)
assert !ia.all(|x| x > 1)
assert ![1, 2, 3]!.all(it > 1)
assert ![1, 2, 3]!.all(|x| x > 1)
assert ia.any(it == 2)
assert ia.any(|x| x == 2)
assert [1, 2, 3]!.any(it == 2)
assert [1, 2, 3]!.any(|x| x == 2)
assert !ia.all(it == 3)
assert !ia.all(|x| x == 3)
assert ![1, 2, 3]!.all(it == 3)
assert ![1, 2, 3]!.all(|x| x == 3)
}
fn test_any_all_of_strings() {
sa := ['a', 'b', 'c']!
assert sa.any(it == 'b')
assert sa.any(|x| x == 'b')
assert ['a', 'b', 'c']!.any(it == 'b')
assert ['a', 'b', 'c']!.any(|x| x == 'b')
assert !sa.all(it == 'c')
assert !sa.all(|x| x == 'c')
assert !['a', 'b', 'c']!.all(it == 'c')
assert !['a', 'b', 'c']!.all(|x| x == 'c')
}
fn test_any_all_of_voidptrs() {
pa := [voidptr(123), voidptr(45), voidptr(99)]!
assert pa.any(it == voidptr(45))
assert pa.any(|x| x == voidptr(45))
assert [voidptr(123), voidptr(45), voidptr(99)]!.any(it == voidptr(45))
assert [voidptr(123), voidptr(45), voidptr(99)]!.any(|x| x == voidptr(45))
assert !pa.all(it == voidptr(123))
assert !pa.all(|x| x == voidptr(123))
assert ![voidptr(123), voidptr(45), voidptr(99)]!.all(it == voidptr(123))
assert ![voidptr(123), voidptr(45), voidptr(99)]!.all(|x| x == voidptr(123))
}
fn test_any_all_of_fns() {
fa := [a, b, c]!
assert fa.any(it == b)
assert fa.any(|x| x == b)
assert [a, b, c]!.any(it == b)
assert [a, b, c]!.any(|x| x == b)
assert !fa.all(it == v)
assert !fa.all(|x| x == v)
assert ![a, b, c]!.all(it == v)
assert ![a, b, c]!.all(|x| x == v)
}
fn test_contains_of_ints() {
ia := [1, 2, 3]!
mut ii := ia.contains(2)
dump(ii)
assert ii
assert [1, 2, 3]!.contains(2)
ii = ia.contains(5)
dump(ii)
assert !ii
assert ![1, 2, 3]!.contains(5)
}
fn test_contains_of_strings() {
sa := ['a', 'b', 'c']!
mut si := sa.contains('b')
dump(si)
assert si
assert ['a', 'b', 'c']!.contains('b')
si = sa.contains('v')
dump(si)
assert !si
assert !['a', 'b', 'c']!.contains('v')
}
fn test_contains_of_voidptrs() {
pa := [voidptr(123), voidptr(45), voidptr(99)]!
mut pi := pa.contains(voidptr(45))
dump(pi)
assert pi
assert [voidptr(123), voidptr(45), voidptr(99)]!.contains(voidptr(45))
pi = pa.contains(unsafe { nil })
dump(pi)
assert !pi
assert ![voidptr(123), voidptr(45), voidptr(99)]!.contains(unsafe { nil })
}
fn test_contains_of_fns() {
fa := [a, b, c]!
mut fi := fa.contains(b)
dump(fi)
assert fi
assert [a, b, c]!.contains(b)
fi = fa.contains(v)
dump(fi)
assert !fi
assert ![a, b, c]!.contains(v)
}
fn test_index_of_ints() {
ia := [1, 2, 3]!
mut ii := ia.index(2)
dump(ii)
assert ii == 1
assert [1, 2, 3]!.index(2) == 1
ii = ia.index(5)
dump(ii)
assert ii == -1
assert [1, 2, 3]!.index(5) == -1
}
fn test_index_of_strings() {
sa := ['a', 'b', 'c']!
mut si := sa.index('b')
dump(si)
assert si == 1
assert ['a', 'b', 'c']!.index('b') == 1
si = sa.index('v')
dump(si)
assert si == -1
assert ['a', 'b', 'c']!.index('v') == -1
}
fn test_index_of_voidptrs() {
pa := [voidptr(123), voidptr(45), voidptr(99)]!
mut pi := pa.index(voidptr(45))
dump(pi)
assert pi == 1
assert [voidptr(123), voidptr(45), voidptr(99)]!.index(voidptr(45)) == 1
pi = pa.index(unsafe { nil })
dump(pi)
assert pi == -1
assert [voidptr(123), voidptr(45), voidptr(99)]!.index(unsafe { nil }) == -1
}
fn test_index_of_fns() {
fa := [a, b, c]!
mut fi := fa.index(b)
dump(fi)
assert fi == 1
assert [a, b, c]!.index(b) == 1
fi = fa.index(v)
dump(fi)
assert fi == -1
assert [a, b, c]!.index(v) == -1
}
fn test_fixed_array_map() {
a := [1, 2, 3]!
b1 := a.map(it * 2)
println(b1)
assert b1 == [2, 4, 6]!
b10 := [1, 2, 3]!.map(it * 2)
assert b10 == [2, 4, 6]!
assert [1, 2, 3]!.map(it * 2) == [2, 4, 6]!
b11 := a.map(|x| x * 2)
println(b11)
assert b11 == [2, 4, 6]!
b110 := [1, 2, 3]!.map(|x| x * 2)
assert b110 == [2, 4, 6]!
assert [1, 2, 3]!.map(|x| x * 2) == [2, 4, 6]!
b2 := a.map('${it}')
println(b2)
assert b2 == ['1', '2', '3']!
b20 := [1, 2, 3]!.map('${it}')
assert b20 == ['1', '2', '3']!
assert [1, 2, 3]!.map('${it}') == ['1', '2', '3']!
b22 := a.map(|x| '${x}')
println(b22)
assert b22 == ['1', '2', '3']!
b220 := [1, 2, 3]!.map(|x| '${x}')
assert b220 == ['1', '2', '3']!
assert [1, 2, 3]!.map(|x| '${x}') == ['1', '2', '3']!
b3 := a.map(it + 2)
println(b3)
assert b3 == [3, 4, 5]!
b30 := [1, 2, 3]!.map(it + 2)
assert b30 == [3, 4, 5]!
assert [1, 2, 3]!.map(it + 2) == [3, 4, 5]!
b33 := a.map(|x| x + 2)
println(b33)
assert b33 == [3, 4, 5]!
b330 := [1, 2, 3]!.map(|x| x + 2)
assert b330 == [3, 4, 5]!
assert [1, 2, 3]!.map(|x| x + 2) == [3, 4, 5]!
}
struct User {
age int
name string
}
fn test_fixed_array_reverse_in_place() {
mut a := ['hi', '1', '5', '3']!
a.reverse_in_place()
assert a == ['3', '5', '1', 'hi']!
mut nums := [67, -3, 108, 42, 7]!
nums.reverse_in_place()
assert nums == [7, 42, 108, -3, 67]!
mut users := [User{22, 'Peter'}, User{20, 'Bob'}, User{25, 'Alice'}]!
users.reverse_in_place()
assert users[0].age == 25
assert users[1].age == 20
assert users[2].age == 22
assert users[0].name == 'Alice'
assert users[1].name == 'Bob'
assert users[2].name == 'Peter'
}
fn test_fixed_array_reverse() {
a := ['hi', '1', '5', '3']!
b := a.reverse()
assert a == ['hi', '1', '5', '3']!
assert b == ['3', '5', '1', 'hi']!
assert ['hi', '1', '5', '3']!.reverse() == ['3', '5', '1', 'hi']!
mut nums := [67, -3, 108, 42, 7]!
n := nums.reverse()
assert nums == [67, -3, 108, 42, 7]!
assert n == [7, 42, 108, -3, 67]!
assert [67, -3, 108, 42, 7]!.reverse() == [7, 42, 108, -3, 67]!
mut users := [User{22, 'Peter'}, User{20, 'Bob'}, User{25, 'Alice'}]!
u := users.reverse()
assert users[0].age == 22
assert users[1].age == 20
assert users[2].age == 25
assert users[0].name == 'Peter'
assert users[1].name == 'Bob'
assert users[2].name == 'Alice'
assert u[0].age == 25
assert u[1].age == 20
assert u[2].age == 22
assert u[0].name == 'Alice'
assert u[1].name == 'Bob'
assert u[2].name == 'Peter'
u2 := [User{22, 'Peter'}, User{20, 'Bob'}, User{25, 'Alice'}]!.reverse()
assert u2[0].age == 25
assert u2[1].age == 20
assert u2[2].age == 22
assert u2[0].name == 'Alice'
assert u2[1].name == 'Bob'
assert u2[2].name == 'Peter'
}
fn test_fixed_array_sort() {
mut a := ['hi', '1', '5', '3']!
a.sort()
assert a == ['1', '3', '5', 'hi']!
mut nums := [67, -3, 108, 42, 7]!
nums.sort()
assert nums == [-3, 7, 42, 67, 108]!
nums.sort(a < b)
assert nums == [-3, 7, 42, 67, 108]!
nums.sort(b < a)
assert nums == [108, 67, 42, 7, -3]!
mut users := [User{22, 'Peter'}, User{20, 'Bob'}, User{25, 'Alice'}]!
users.sort(a.age < b.age)
assert users[0].age == 20
assert users[1].age == 22
assert users[2].age == 25
assert users[0].name == 'Bob'
assert users[1].name == 'Peter'
assert users[2].name == 'Alice'
users.sort(a.age > b.age)
assert users[0].age == 25
assert users[1].age == 22
assert users[2].age == 20
users.sort(b.age > a.age)
assert users[0].age == 20
assert users[1].age == 22
assert users[2].age == 25
users.sort(a.name < b.name)
assert users[0].name == 'Alice'
assert users[1].name == 'Bob'
assert users[2].name == 'Peter'
}
fn test_fixed_array_sort_preserves_relative_order_for_equal_elements() {
mut source := [User{4, 'B'}, User{4, 'A'}, User{5, 'C'}]!
mut sorted := source
sorted.sort(a.age > b.age)
assert sorted[0].name == 'C'
assert sorted[1].name == 'B'
assert sorted[2].name == 'A'
copy := source.sorted(a.age > b.age)
assert copy[0].name == 'C'
assert copy[1].name == 'B'
assert copy[2].name == 'A'
}
fn test_sorted_immutable_original_should_not_change() {
a := ['hi', '1', '5', '3']!
b := a.sorted()
assert a == ['hi', '1', '5', '3']!
assert b == ['1', '3', '5', 'hi']!
assert ['hi', '1', '5', '3']!.sorted() == ['1', '3', '5', 'hi']!
}
fn test_sorted_mutable_original_should_not_change() {
mut a := ['hi', '1', '5', '3']!
b := a.sorted()
assert a == ['hi', '1', '5', '3']!
assert b == ['1', '3', '5', 'hi']!
assert ['hi', '1', '5', '3']!.sorted() == ['1', '3', '5', 'hi']!
}
fn test_sorted_reversed() {
aa := ['hi', '1', '5', '3']!
bb := aa.sorted(a > b)
assert aa == ['hi', '1', '5', '3']!
assert bb == ['hi', '5', '3', '1']!
assert ['hi', '1', '5', '3']!.sorted(a > b) == ['hi', '5', '3', '1']!
}
fn test_sorted_by_len() {
a := ['hi', 'abc', 'a', 'zzzzz']!
c := a.sorted(a.len < b.len)
assert c == ['a', 'hi', 'abc', 'zzzzz']!
assert ['hi', 'abc', 'a', 'zzzzz']!.sorted(a.len < b.len) == ['a', 'hi', 'abc', 'zzzzz']!
}
fn test_sort_with_compare_1() {
mut a := ['hi', '1', '5', '3']!
a.sort_with_compare(fn (a &string, b &string) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
})
assert a == ['1', '3', '5', 'hi']!
}
fn test_sorted_with_compare_1() {
a := ['hi', '1', '5', '3']!
b := a.sorted_with_compare(fn (a &string, b &string) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
})
assert a == ['hi', '1', '5', '3']!
assert b == ['1', '3', '5', 'hi']!
assert ['hi', '1', '5', '3']!.sorted_with_compare(fn (a &string, b &string) int {
if a < b {
return -1
}
if a > b {
return 1
}
return 0
}) == ['1', '3', '5', 'hi']!
}
struct Ka {
s string
i int
}
fn test_sort_with_compare_2() {
mut arr := [
Ka{
s: 'bbb'
i: 100
},
Ka{
s: 'aaa'
i: 101
},
Ka{
s: 'ccc'
i: 102
},
]!
cmp := fn (a &Ka, b &Ka) int {
return compare_strings(a.s, b.s)
}
arr.sort_with_compare(cmp)
assert arr[0].s == 'aaa'
assert arr[0].i == 101
assert arr[1].s == 'bbb'
assert arr[1].i == 100
assert arr[2].s == 'ccc'
assert arr[2].i == 102
}
fn test_sorted_with_compare_2() {
arr := [
Ka{
s: 'bbb'
i: 100
},
Ka{
s: 'aaa'
i: 101
},
Ka{
s: 'ccc'
i: 102
},
]!
cmp := fn (a &Ka, b &Ka) int {
return compare_strings(a.s, b.s)
}
b := arr.sorted_with_compare(cmp)
assert arr[0].s == 'bbb'
assert arr[0].i == 100
assert arr[1].s == 'aaa'
assert arr[1].i == 101
assert arr[2].s == 'ccc'
assert arr[2].i == 102
assert b[0].s == 'aaa'
assert b[0].i == 101
assert b[1].s == 'bbb'
assert b[1].i == 100
assert b[2].s == 'ccc'
assert b[2].i == 102
b2 := [
Ka{
s: 'bbb'
i: 100
},
Ka{
s: 'aaa'
i: 101
},
Ka{
s: 'ccc'
i: 102
},
]!.sorted_with_compare(cmp)
assert b2[0].s == 'aaa'
assert b2[0].i == 101
assert b2[1].s == 'bbb'
assert b2[1].i == 100
assert b2[2].s == 'ccc'
assert b2[2].i == 102
}
+228
View File
@@ -0,0 +1,228 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license that can be found in the LICENSE file.
module builtin
$if !nofloat ? {
import strconv
}
#include <float.h>
// str returns a string representation of the given `f64` in a suitable notation.
@[inline]
pub fn (x f64) str() string {
unsafe {
f := strconv.Float64u{
f: x
}
if f.u == strconv.double_minus_zero {
return '-0.0'
}
if f.u == strconv.double_plus_zero {
return '0.0'
}
}
abs_x := f64_abs(x)
if abs_x >= 0.0001 && abs_x < 1.0e6 {
return strconv.f64_to_str_l(x)
} else {
return strconv.ftoa_64(x)
}
}
// strg return a `f64` as `string` in "g" printf format.
@[inline]
pub fn (x f64) strg() string {
unsafe {
f := strconv.Float64u{
f: x
}
if f.u == strconv.double_minus_zero || f.u == strconv.double_plus_zero {
return '0.0'
}
}
abs_x := f64_abs(x)
if abs_x >= 0.0001 && abs_x < 1.0e6 {
return strconv.f64_to_str_l_with_dot(x)
} else {
return strconv.ftoa_64(x)
}
}
// str returns the value of the `float_literal` as a `string`.
@[inline]
pub fn (d float_literal) str() string {
return f64(d).str()
}
// strsci returns the `f64` as a `string` in scientific notation with `digit_num` decimals displayed, max 17 digits.
// Example: assert f64(1.234).strsci(3) == '1.234e+00'
@[inline]
pub fn (x f64) strsci(digit_num int) string {
mut n_digit := digit_num
if n_digit < 1 {
n_digit = 1
} else if n_digit > 17 {
n_digit = 17
}
return strconv.f64_to_str(x, n_digit)
}
// strlong returns a decimal notation of the `f64` as a `string`.
// Example: assert f64(1.23456).strlong() == '1.23456'
@[inline]
pub fn (x f64) strlong() string {
return strconv.f64_to_str_l(x)
}
/*
-----------------------------------
----- f32 to string functions -----
*/
// str returns a `f32` as `string` in suitable notation.
@[inline]
pub fn (x f32) str() string {
unsafe {
f := strconv.Float32u{
f: x
}
if f.u == strconv.single_minus_zero {
return '-0.0'
}
if f.u == strconv.single_plus_zero {
return '0.0'
}
}
abs_x := f32_abs(x)
if abs_x >= 0.0001 && abs_x < 1.0e6 {
return strconv.f32_to_str_l(x)
} else {
return strconv.ftoa_32(x)
}
}
// strg return a `f32` as `string` in "g" printf format
@[inline]
pub fn (x f32) strg() string {
unsafe {
f := strconv.Float32u{
f: x
}
if f.u == strconv.single_minus_zero || f.u == strconv.single_plus_zero {
return '0.0'
}
}
abs_x := f32_abs(x)
if abs_x >= 0.0001 && abs_x < 1.0e6 {
return strconv.f32_to_str_l_with_dot(x)
} else {
return strconv.ftoa_32(x)
}
}
// strsci returns the `f32` as a `string` in scientific notation with `digit_num` decimals displayed, max 8 digits.
// Example: assert f32(1.234).strsci(3) == '1.234e+00'
@[inline]
pub fn (x f32) strsci(digit_num int) string {
mut n_digit := digit_num
if n_digit < 1 {
n_digit = 1
} else if n_digit > 8 {
n_digit = 8
}
return strconv.f32_to_str(x, n_digit)
}
// strlong returns a decimal notation of the `f32` as a `string`.
@[inline]
pub fn (x f32) strlong() string {
return strconv.f32_to_str_l(x)
}
// f32_abs returns the absolute value of `a` as a `f32` value.
// Example: assert f32_abs(-2.0) == 2.0
@[inline]
pub fn f32_abs(a f32) f32 {
if a < 0 {
return -a
}
return a
}
// f64_abs returns the absolute value of `a` as a `f64` value.
// Example: assert f64_abs(-2.0) == f64(2.0)
@[inline]
pub fn f64_abs(a f64) f64 {
if a < 0 {
return -a
}
return a
}
// f32_min returns the smaller `f32` of input `a` and `b`.
// Example: assert f32_min(2.0,3.0) == 2.0
@[inline]
pub fn f32_min(a f32, b f32) f32 {
if a < b {
return a
}
return b
}
// f32_max returns the larger `f32` of input `a` and `b`.
// Example: assert f32_max(2.0,3.0) == 3.0
@[inline]
pub fn f32_max(a f32, b f32) f32 {
if a > b {
return a
}
return b
}
// f64_min returns the smaller `f64` of input `a` and `b`.
// Example: assert f64_min(2.0,3.0) == 2.0
@[inline]
pub fn f64_min(a f64, b f64) f64 {
if a < b {
return a
}
return b
}
// f64_max returns the larger `f64` of input `a` and `b`.
// Example: assert f64_max(2.0,3.0) == 3.0
@[inline]
pub fn f64_max(a f64, b f64) f64 {
if a > b {
return a
}
return b
}
// eq_epsilon returns true if the `f32` is equal to input `b`.
// using an epsilon of typically 1E-5 or higher (backend/compiler dependent).
// Example: assert f32(2.0).eq_epsilon(2.0)
@[inline]
pub fn (a f32) eq_epsilon(b f32) bool {
hi := f32_max(f32_abs(a), f32_abs(b))
delta := f32_abs(a - b)
if hi > f32(1.0) {
return delta <= hi * (4 * f32(C.FLT_EPSILON))
} else {
return (1 / (4 * f32(C.FLT_EPSILON))) * delta <= hi
}
}
// eq_epsilon returns true if the `f64` is equal to input `b`.
// using an epsilon of typically 1E-9 or higher (backend/compiler dependent).
// Example: assert f64(2.0).eq_epsilon(2.0)
@[inline]
pub fn (a f64) eq_epsilon(b f64) bool {
hi := f64_max(f64_abs(a), f64_abs(b))
delta := f64_abs(a - b)
if hi > 1.0 {
return delta <= hi * (4 * f64(C.DBL_EPSILON))
} else {
return (1 / (4 * f64(C.DBL_EPSILON))) * delta <= hi
}
}
+313
View File
@@ -0,0 +1,313 @@
import math
fn test_float_decl() {
// TODO: ARM64 backend doesn't support typeof()
// z := 1f
// assert z > 0
x1 := 1e10
x2 := -2e16
x3 := 1e-15
x4 := -9e-4
// assert typeof(x1).name == 'f64'
// assert typeof(x2).name == 'f64'
// assert typeof(x3).name == 'f64'
// assert typeof(x4).name == 'f64'
x5 := 4e108
x6 := -7e99
x7 := 3e-205
x8 := -6e-147
// assert typeof(x5).name == 'f64'
// assert typeof(x6).name == 'f64'
// assert typeof(x7).name == 'f64'
// assert typeof(x8).name == 'f64'
x9 := 312874834.77
x10 := -22399994.06
x11 := 0.0000000019
x12 := -0.00000000008
// assert typeof(x9).name == 'f64'
// assert typeof(x10).name == 'f64'
// assert typeof(x11).name == 'f64'
// assert typeof(x12).name == 'f64'
x13 := 34234234809890890898903213154353453453253253243432413232228908902183918392183902432432438980380123021983901392183921389083913890389089031.0
x14 := -39999999999999999999222212128182813294989082302832183928343325325233253242312331324392839238239829389038097438248932789371837218372837293.8
x15 := 0.000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002
x16 := -0.00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004
// assert typeof(x13).name == 'f64'
// assert typeof(x14).name == 'f64'
// assert typeof(x15).name == 'f64'
// assert typeof(x16).name == 'f64'
_ = x1
_ = x2
_ = x3
_ = x4
_ = x5
_ = x6
_ = x7
_ = x8
_ = x9
_ = x10
_ = x11
_ = x12
_ = x13
_ = x14
_ = x15
_ = x16
}
fn test_f32_equal_operator() {
b := f32(1.0)
mut a := f32(1.0)
a += 0.0000019073486328125
assert a != b
a -= 0.0000019073486328125
assert a == b
assert -1 == 1 * -1
assert -1.0 == 1.0 * -1.0
a = 1
a += 0.0000019073486328125
a -= 0.0000019073486328125
assert a == f32(1.0)
a += 0.000001
assert !(a < f32(1))
assert !(a <= f32(1))
assert a > f32(1)
assert a >= 1
assert a != 1
f := 1.2
ab := int(f)
assert ab == 1
e := f32(-1.602176634e-19)
m := f32(9.1093837015e-31)
assert e < m
assert e <= m
assert e != m
assert !(e == m)
assert m >= e
assert m > e
}
fn test_f64_equal_operator() {
b := 1.0
mut a := 1.0
a += 0.0000019073486328125
assert a != b
a -= 0.0000019073486328125
assert a == b
e := -1.602176634e-19
m := 9.1093837015e-31
assert e < m
assert e <= m
assert e != m
assert !(e == m)
assert m >= e
assert m > e
}
fn test_f64_eq_epsilon() {
a := 1.662248544459347e308
b := 1.662248544459348e308
x := 1.662248544459352e308
assert a != b
assert a.eq_epsilon(b)
assert b.eq_epsilon(a)
assert (-a).eq_epsilon(-b)
assert (-b).eq_epsilon(-a)
assert !a.eq_epsilon(x)
assert !x.eq_epsilon(a)
assert !a.eq_epsilon(-b)
assert !(-a).eq_epsilon(b)
c := 1.5367748374385438503
d := -1.5367748374385447257
z := 1.5367748378943546
assert c != -d
assert c.eq_epsilon(-d)
assert d.eq_epsilon(-c)
assert !c.eq_epsilon(z)
assert !z.eq_epsilon(c)
e := 2.531434251587394233e-308
f := 2.531434251587395675e-308
y := 2.531434251587398934e-308
assert e != f
assert e.eq_epsilon(f)
assert (-f).eq_epsilon(-e)
assert !e.eq_epsilon(y)
assert !(-y).eq_epsilon(-e)
}
fn test_f32_eq_epsilon() {
a := f32(3.244331e38)
b := f32(3.244332e38)
x := f32(3.244338e38)
assert a != b
assert a.eq_epsilon(b)
assert b.eq_epsilon(a)
assert (-a).eq_epsilon(-b)
assert (-b).eq_epsilon(-a)
assert !a.eq_epsilon(x)
assert !(-x).eq_epsilon(-a)
assert !a.eq_epsilon(-b)
assert !(-a).eq_epsilon(b)
c := f32(0.9546742)
d := f32(-0.9546745)
z := f32(0.9546754)
assert c != -d
assert c.eq_epsilon(-d)
assert d.eq_epsilon(-c)
assert !c.eq_epsilon(z)
assert !z.eq_epsilon(c)
e := f32(-1.5004390e-38)
f := f32(-1.5004395e-38)
y := f32(-1.5004409e-38)
assert e != f
assert e.eq_epsilon(f)
assert (-f).eq_epsilon(-e)
assert !e.eq_epsilon(y)
assert !(-y).eq_epsilon(-e)
}
fn test_float_point_formatting_rounding() {
float_1 := 462.18
float_2 := 45.02227
float_3 := 238.5
float_4 := 239.5
assert '${float_1:0.0f}' == '462'
assert '${float_2:0.0f}' == '45'
assert '${float_3:0.0f}' == '239'
assert '${float_4:0.0f}' == '240'
assert '${239.5555551:0.0f}' == '240'
assert '${239.5555551:0.1f}' == '239.6'
assert '${239.5555551:0.2f}' == '239.56'
assert '${239.5555551:0.3f}' == '239.556'
assert '${239.5555551:0.4f}' == '239.5556'
assert '${239.5555551:0.5f}' == '239.55556'
assert '${239.5555551:0.6f}' == '239.555555'
assert '${239.5555551:0.7f}' == '239.5555551'
assert '${239.5555551:0.8f}' == '239.55555510'
assert '${239.5555551:0.9f}' == '239.555555100'
assert '${239.5555551:0.10f}' == '239.5555551000'
assert '${239.5555551:0.11f}' == '239.55555510000'
assert '${239.5:0.0f}' == '240'
assert '${239.55:0.1f}' == '239.6'
assert '${239.555:0.2f}' == '239.56'
assert '${239.5555:0.3f}' == '239.555' // Note: 5 ?
assert '${239.55555:0.4f}' == '239.5556'
assert '${239.555555:0.5f}' == '239.55555' // Note: 5 ?
assert '${239.5555555:0.6f}' == '239.555556' // after this, it is all ending in 6
assert '${239.55555555:0.7f}' == '239.5555556'
assert '${239.555555555:0.8f}' == '239.55555556'
assert '${239.5555555555:0.9f}' == '239.555555556'
assert '${239.55555555555:0.10f}' == '239.5555555556'
assert '${239.5550:0.3f}' == '239.555'
assert '${239.5551:0.3f}' == '239.555'
assert '${239.5552:0.3f}' == '239.555'
assert '${239.5553:0.3f}' == '239.555'
assert '${239.5554:0.3f}' == '239.555'
assert '${239.5555:0.3f}' == '239.555'
assert '${239.5556:0.3f}' == '239.556' // rounding at last 6 ?
assert '${239.5557:0.3f}' == '239.556'
assert '${239.5558:0.3f}' == '239.556'
assert '${239.5559:0.3f}' == '239.556'
assert '${239.5555551:0.6f}' == '239.555555'
assert '${239.5555552:0.6f}' == '239.555555'
assert '${239.5555553:0.6f}' == '239.555555'
assert '${239.5555554:0.6f}' == '239.555555'
assert '${239.5555555:0.6f}' == '239.555556'
assert '${239.5555556:0.6f}' == '239.555556'
assert '${239.5555557:0.6f}' == '239.555556'
assert '${239.5555558:0.6f}' == '239.555556'
assert '${239.5555559:0.6f}' == '239.555556'
assert '${239.55555555555:0.10f}' == '239.5555555556'
assert '${239.55555555555:0.9f}' == '239.555555556'
assert '${239.55555555555:0.8f}' == '239.55555556'
assert '${239.55555555555:0.7f}' == '239.5555556'
assert '${239.55555555555:0.6f}' == '239.555556'
assert '${239.55555555555:0.5f}' == '239.55556'
assert '${239.55555555555:0.4f}' == '239.5556'
assert '${239.55555555555:0.3f}' == '239.556'
assert '${239.55555555555:0.2f}' == '239.56'
assert '${239.55555555555:0.1f}' == '239.6'
assert '${239.55555555555:0.0f}' == '240'
assert '${-239.55555555555:0.10f}' == '-239.5555555556'
assert '${-239.55555555555:0.9f}' == '-239.555555556'
assert '${-239.55555555555:0.8f}' == '-239.55555556'
assert '${-239.55555555555:0.7f}' == '-239.5555556'
assert '${-239.55555555555:0.6f}' == '-239.555556'
assert '${-239.55555555555:0.5f}' == '-239.55556'
assert '${-239.55555555555:0.4f}' == '-239.5556'
assert '${-239.55555555555:0.3f}' == '-239.556'
assert '${-239.55555555555:0.2f}' == '-239.56'
assert '${-239.55555555555:0.1f}' == '-239.6'
assert '${-239.55555555555:0.0f}' == '-240'
}
fn test_float_zero_str() {
f1 := f32(0.0)
f2 := 0.0
assert f1.str() == '0.0'
assert '${f1}' == '0.0'
assert f2.str() == '0.0'
assert '${f2}' == '0.0'
}
fn test_f32_zero_chars() {
bits := math.f32_bits('-0.0'.f32())
assert math.f32_bits('-0 ms'.f32()) == bits
assert math.f32_bits('-0.0 ms'.f32()) == bits
assert math.f32_bits('-0.000 ms'.f32()) == bits
assert math.f32_bits('0 ms'.f32()) == 0
assert math.f32_bits('0.0 ms'.f32()) == 0
assert math.f32_bits('0.000 ms'.f32()) == 0
assert math.f32_bits('-0'.f32()) == bits
assert math.f32_bits('-0.0'.f32()) == bits
assert math.f32_bits('-0.000'.f32()) == bits
assert math.f32_bits('0'.f32()) == 0
assert math.f32_bits('0.0'.f32()) == 0
assert math.f32_bits('0.000'.f32()) == 0
assert '-0 ms'.f32().str() == '-0.0'
assert '-0.0 ms'.f32().str() == '-0.0'
assert '-0.000 ms'.f32().str() == '-0.0'
assert '0 ms'.f32().str() == '0.0'
assert '0.0 ms'.f32().str() == '0.0'
assert '0.000 ms'.f32().str() == '0.0'
assert '-0'.f32().str() == '-0.0'
assert '-0.0'.f32().str() == '-0.0'
assert '-0.000'.f32().str() == '-0.0'
assert '0'.f32().str() == '0.0'
assert '0.0'.f32().str() == '0.0'
assert '0.00'.f32().str() == '0.0'
}
fn test_f64_zero_chars() {
bits := math.f64_bits('-0.0'.f64())
assert math.f64_bits('-0 ms'.f64()) == bits
assert math.f64_bits('-0.0 ms'.f64()) == bits
assert math.f64_bits('-0.000 ms'.f64()) == bits
assert math.f64_bits('0 ms'.f64()) == 0
assert math.f64_bits('0.0 ms'.f64()) == 0
assert math.f64_bits('0.000 ms'.f64()) == 0
assert math.f64_bits('-0'.f64()) == bits
assert math.f64_bits('-0.0'.f64()) == bits
assert math.f64_bits('-0.000'.f64()) == bits
assert math.f64_bits('0'.f64()) == 0
assert math.f64_bits('0.0'.f64()) == 0
assert math.f64_bits('0.000'.f64()) == 0
assert '-0 ms'.f64().str() == '-0.0'
assert '-0.0 ms'.f64().str() == '-0.0'
assert '-0.000 ms'.f64().str() == '-0.0'
assert '0 ms'.f64().str() == '0.0'
assert '0.0 ms'.f64().str() == '0.0'
assert '0.000 ms'.f64().str() == '0.0'
assert '-0'.f64().str() == '-0.0'
assert '-0.0'.f64().str() == '-0.0'
assert '-0.000'.f64().str() == '-0.0'
assert '0'.f64().str() == '0.0'
assert '0.0'.f64().str() == '0.0'
assert '0.00'.f64().str() == '0.0'
}
@@ -0,0 +1,93 @@
fn test_gated_arrays() {
a := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
assert a#[-1] == 9
assert a#[1] == 1
assert a#[-1..] == [9]
assert a#[..-9] == [0]
assert a#[-9..-7] == [1, 2]
assert a#[-2..] == [8, 9]
missing := a#[-11] or { 42 }
assert missing == 42
mut b := [1, 2, 3]
b#[-1] = 100
assert b == [1, 2, 100]
// fixed array
mut a1 := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]!
assert a1#[-1] == 9
assert a1#[-1..] == [9]
assert a1#[..-9] == [0]
assert a1#[-9..-7] == [1, 2]
assert a1#[-2..] == [8, 9]
a1#[-1] = 100
assert a1[9] == 100
// empty array
assert a#[-3..-4] == [] // start > end
assert a#[20..] == [] // start > array.len
assert a#[-20..-10] == [] // start+len < 0
assert a#[20..-9] == [] // start > end && start > end
}
fn test_gated_strings() {
a := '0123456789'
assert a#[-1] == `9`
assert a#[1] == `1`
assert a#[-1..] == '9'
assert a#[..-9] == '0'
assert a#[-9..-7] == '12'
assert a#[-2..] == '89'
missing := a#[-11] or { `x` }
assert missing == `x`
// empty string
assert a#[-3..-4] == '' // start > end
assert a#[20..] == '' // start > array.len
assert a#[-20..-10] == '' // start+len < 0
assert a#[20..-9] == '' // start > end && start > end
//
// test negative indexes in slices from github discussion
//
s := '0123456789'
// normal behaviour
assert s#[1..3] == '12'
assert s#[..3] == '012'
assert s#[8..] == '89'
// negative indexes behaviour
assert s#[-2..] == '89'
assert s#[..-8] == '01'
assert s#[2..-2] == '234567'
assert s#[-12..-16] == ''
assert s#[-8..-2] == '234567'
// out of bound both indexes
assert s#[12..14] == ''
assert s#[-12..16] == '0123456789'
}
fn test_gated_mixed_strings() {
//
// test negative indexes in slices
//
a := [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
// normal behaviour
assert a#[1..3].str() == '[1, 2]'
assert a#[..3].str() == '[0, 1, 2]'
assert a#[8..].str() == '[8, 9]'
// negative indexes behaviour
assert a#[-2..].str() == '[8, 9]'
assert a#[..-8].str() == '[0, 1]'
assert a#[2..-2].str() == '[2, 3, 4, 5, 6, 7]'
assert a#[-12..-16].str() == '[]'
assert a#[-8..-2].str() == '[2, 3, 4, 5, 6, 7]'
// out of bound both indexes
assert a#[12..14].str() == '[]'
assert a#[-12..16].str() == a.str()
}
+55
View File
@@ -0,0 +1,55 @@
#ifndef V_BUILTIN_GC_DEBUGGER_LINUX_H
#define V_BUILTIN_GC_DEBUGGER_LINUX_H
#if defined(__linux__)
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#pragma weak __data_start
#pragma weak data_start
#pragma weak _end
extern int __data_start[];
extern int data_start[];
extern int _end[];
static inline int v__gc_can_register_main_data_roots_linux(void) {
void* data_start_ptr = __data_start != NULL ? (void*)__data_start : (void*)data_start;
void* data_end_ptr = (void*)_end;
return data_start_ptr != NULL && data_end_ptr != NULL && data_start_ptr < data_end_ptr;
}
static inline int v__gc_debugger_present_linux(void) {
int fd = open("/proc/self/status", O_RDONLY);
if (fd < 0) {
return 0;
}
char buf[4096];
ssize_t n = read(fd, buf, sizeof(buf) - 1);
(void)close(fd);
if (n <= 0) {
return 0;
}
buf[n] = '\0';
char* tracer_pid = strstr(buf, "TracerPid:");
if (tracer_pid == NULL) {
return 0;
}
tracer_pid += sizeof("TracerPid:") - 1;
while (*tracer_pid == ' ' || *tracer_pid == '\t') {
++tracer_pid;
}
return *tracer_pid != '0' && *tracer_pid != '\n' && *tracer_pid != '\0';
}
static inline void v__gc_register_main_data_roots_linux(void) {
if (!v__gc_can_register_main_data_roots_linux()) {
return;
}
void* data_start_ptr = __data_start != NULL ? (void*)__data_start : (void*)data_start;
GC_add_roots(data_start_ptr, (void*)_end);
}
#endif
#endif
+277
View File
@@ -0,0 +1,277 @@
module builtin
// This file contains Unicode grapheme cluster property tables used by string.graphemes()
// and utf8_str_visible_length(). The data is derived from Unicode 13.0.0
// GraphemeBreakProperty.txt and emoji-data.txt.
enum GraphemeBreakProperty {
other
cr
lf
control
extend
regional_indicator
prepend
spacing_mark
l
v
t
lv
lvt
zwj
}
const grapheme_control_ranges = '00000000090000000b0000000c0000000e0000001f0000007f0000009f000000ad000000ad0000001c0600001c0600000e1800000e1800000b2000000b2000000e2000000f200000282000002820000029200000292000002a2000002e20000060200000642000006520000065200000662000006f200000fffe0000fffe0000f0ff0000f8ff0000f9ff0000fbff00003034010038340100a0bc0100a3bc010073d101007ad1010000000e0000000e0001000e0001000e0002000e001f000e0080000e00ff000e00f0010e00ff0f0e00'
const grapheme_extend_ranges =
'000300006f0300008304000087040000880400008904000091050000bd050000bf050000bf050000c1050000c2050000c4050000c5050000c7050000c7050000100600001a0600004b0600005f0600007006000070060000d6060000dc060000df060000e4060000e7060000e8060000ea060000ed0600001107000011070000300700004a070000a6070000b0070000eb070000f3070000fd070000fd07000016080000190800001b080000230800002508000027080000290800002d080000590800005b080000d3080000e1080000e3080000020900003a0900003a0900003c0900003c09000041090000480900004d0900004d090000510900005709000062090000630900008109000081090000bc090000bc090000be090000be090000c1090000c4090000cd090000cd090000d7090000d7090000e2090000e3090000fe090000fe090000010a0000020a00003c0a00003c0a0000410a0000420a0000470a0000480a00004b0a00004d0a0000510a0000510a0000700a0000710a0000750a0000750a0000810a0000820a0000bc0a0000bc0a0000c10a0000c50a0000c70a0000c80a0000cd0a0000cd0a0000e20a0000e30a0000fa0a0000ff0a0000010b0000010b00003c0b00003c0b00003e0b00003e0b00003f0b00003f0b0000410b0000440b00004d0b00004d0b0000550b0000560b0000570b0000570b0000620b0000630b0000820b0000820b0000be0b0000be0b0000c00b0000c00b0000cd0b0000cd0b0000d70b0000d70b0000000c0000000c0000040c0000040c00003e0c0000400c0000460c0000480c00004a0c00004d0c0000550c0000560c0000620c0000630c0000810c0000810c0000bc0c0000bc0c0000bf0c0000bf0c0000c20c0000c20c0000c60c0000c60c0000cc0c0000cd0c0000d50c0000d60c0000e20c0000e30c0000000d0000010d00003b0d00003c0d00003e0d00003e0d0000410d0000440d00004d0d00004d0d0000570d0000570d0000620d0000630d0000810d0000810d0000ca0d0000ca0d0000cf0d0000cf0d0000d20d0000d40d0000d60d0000d60d0000df0d0000df0d0000310e0000310e0000340e00003a0e0000470e00004e0e0000b10e0000b10e0000b40e0000bc0e0000c80e0000cd0e0000180f0000190f0000350f0000350f0000370f0000370f0000390f0000390f0000710f00007e0f0000800f0000840f0000860f0000870f00008d0f0000970f0000990f0000bc0f0000c60f0000c60f00002d100000301000003210000037100000391000003a1000003d1000003e10000058100000591000005e100000601000007110000074100000821000008210000085100000861000008d1000008d1000009d1000009d1000005d1300005f1300001217000014170000321700003417000052170000531700007217000073170000b4170000b5170000b7170000bd170000c6170000c6170000c9170000d3170000dd170000dd1700000b1800000d1800008518000086180000a9180000a9180000201900002219000027190000281900003219000032190000391900003b190000171a0000181a00001b1a00001b1a0000561a0000561a0000581a00005e1a0000601a0000601a0000621a0000621a0000651a00006c1a0000731a00007c1a00007f1a00007f1a0000b01a0000bd1a0000be1a0000be1a0000bf1a0000c01a0000001b0000031b0000341b0000341b0000351b0000351b0000361b00003a1b00003c1b00003c1b0000421b0000421b00006b1b0000731b0000801b0000811b0000a21b0000a51b0000a81b0000a91b0000ab1b0000ad1b0000e61b0000e61b0000e81b0000e91b0000ed1b0000ed1b0000ef1b0000f11b00002c1c0000331c0000361c0000371c0000d01c0000d21c0000d41c0000e01c0000e21c0000e81c0000ed1c0000ed1c0000f41c0000f41c0000f81c0000f91c0000c01d0000f91d0000fb1d0000ff1d00000c2000000c200000d0200000dc200000dd200000e0200000e1200000e1200000e2200000e4200000e5200000f0200000ef2c0000f12c00007f2d00007f2d0000e02d0000ff2d00002a3000002d3000002e3000002f300000993000009a3000006fa600006fa60000' +
'70a6000072a6000074a600007da600009ea600009fa60000f0a60000f1a6000002a8000002a8000006a8000006a800000ba800000ba8000025a8000026a800002ca800002ca80000c4a80000c5a80000e0a80000f1a80000ffa80000ffa8000026a900002da9000047a9000051a9000080a9000082a90000b3a90000b3a90000b6a90000b9a90000bca90000bda90000e5a90000e5a9000029aa00002eaa000031aa000032aa000035aa000036aa000043aa000043aa00004caa00004caa00007caa00007caa0000b0aa0000b0aa0000b2aa0000b4aa0000b7aa0000b8aa0000beaa0000bfaa0000c1aa0000c1aa0000ecaa0000edaa0000f6aa0000f6aa0000e5ab0000e5ab0000e8ab0000e8ab0000edab0000edab00001efb00001efb000000fe00000ffe000020fe00002ffe00009eff00009fff0000fd010100fd010100e0020100e0020100760301007a030100010a0100030a0100050a0100060a01000c0a01000f0a0100380a01003a0a01003f0a01003f0a0100e50a0100e60a0100240d0100270d0100ab0e0100ac0e0100460f0100500f0100011001000110010038100100461001007f10010081100100b3100100b6100100b9100100ba1001000011010002110100271101002b1101002d1101003411010073110100731101008011010081110100b6110100be110100c9110100cc110100cf110100cf1101002f12010031120100341201003412010036120100371201003e1201003e120100df120100df120100e3120100ea12010000130100011301003b1301003c1301003e1301003e13010040130100401301005713010057130100661301006c1301007013010074130100381401003f140100421401004414010046140100461401005e1401005e140100b0140100b0140100b3140100b8140100ba140100ba140100bd140100bd140100bf140100c0140100c2140100c3140100af150100af150100b2150100b5150100bc150100bd150100bf150100c0150100dc150100dd150100331601003a1601003d1601003d1601003f16010040160100ab160100ab160100ad160100ad160100b0160100b5160100b7160100b71601001d1701001f1701002217010025170100271701002b1701002f18010037180100391801003a18010030190100301901003b1901003c1901003e1901003e1901004319010043190100d4190100d7190100da190100db190100e0190100e0190100011a01000a1a0100331a0100381a01003b1a01003e1a0100471a0100471a0100511a0100561a0100591a01005b1a01008a1a0100961a0100981a0100991a0100301c0100361c0100381c01003d1c01003f1c01003f1c0100921c0100a71c0100aa1c0100b01c0100b21c0100b31c0100b51c0100b61c0100311d0100361d01003a1d01003a1d01003c1d01003d1d01003f1d0100451d0100471d0100471d0100901d0100911d0100951d0100951d0100971d0100971d0100f31e0100f41e0100f06a0100f46a0100306b0100366b01004f6f01004f6f01008f6f0100926f0100e46f0100e46f01009dbc01009ebc010065d1010065d1010067d1010069d101006ed1010072d101007bd1010082d1010085d101008bd10100aad10100add1010042d2010044d2010000da010036da01003bda01006cda010075da010075da010084da010084da01009bda01009fda0100a1da0100afda010000e0010006e0010008e0010018e001001be0010021e0010023e0010024e0010026e001002ae0010030e1010036e10100ece20100efe20100d0e80100d6e8010044e901004ae90100fbf30100fff3010020000e007f000e0000010e00ef010e00'
const grapheme_spacing_mark_ranges = '03090000030900003b0900003b0900003e09000040090000490900004c0900004e0900004f0900008209000083090000bf090000c0090000c7090000c8090000cb090000cc090000030a0000030a00003e0a0000400a0000830a0000830a0000be0a0000c00a0000c90a0000c90a0000cb0a0000cc0a0000020b0000030b0000400b0000400b0000470b0000480b00004b0b00004c0b0000bf0b0000bf0b0000c10b0000c20b0000c60b0000c80b0000ca0b0000cc0b0000010c0000030c0000410c0000440c0000820c0000830c0000be0c0000be0c0000c00c0000c10c0000c30c0000c40c0000c70c0000c80c0000ca0c0000cb0c0000020d0000030d00003f0d0000400d0000460d0000480d00004a0d00004c0d0000820d0000830d0000d00d0000d10d0000d80d0000de0d0000f20d0000f30d0000330e0000330e0000b30e0000b30e00003e0f00003f0f00007f0f00007f0f000031100000311000003b1000003c10000056100000571000008410000084100000b6170000b6170000be170000c5170000c7170000c81700002319000026190000291900002b19000030190000311900003319000038190000191a00001a1a0000551a0000551a0000571a0000571a00006d1a0000721a0000041b0000041b00003b1b00003b1b00003d1b0000411b0000431b0000441b0000821b0000821b0000a11b0000a11b0000a61b0000a71b0000aa1b0000aa1b0000e71b0000e71b0000ea1b0000ec1b0000ee1b0000ee1b0000f21b0000f31b0000241c00002b1c0000341c0000351c0000e11c0000e11c0000f71c0000f71c000023a8000024a8000027a8000027a8000080a8000081a80000b4a80000c3a8000052a9000053a9000083a9000083a90000b4a90000b5a90000baa90000bba90000bea90000c0a900002faa000030aa000033aa000034aa00004daa00004daa0000ebaa0000ebaa0000eeaa0000efaa0000f5aa0000f5aa0000e3ab0000e4ab0000e6ab0000e7ab0000e9ab0000eaab0000ecab0000ecab0000001001000010010002100100021001008210010082100100b0100100b2100100b7100100b81001002c1101002c11010045110100461101008211010082110100b3110100b5110100bf110100c0110100ce110100ce1101002c1201002e12010032120100331201003512010035120100e0120100e212010002130100031301003f1301003f130100411301004413010047130100481301004b1301004d1301006213010063130100351401003714010040140100411401004514010045140100b1140100b2140100b9140100b9140100bb140100bc140100be140100be140100c1140100c1140100b0150100b1150100b8150100bb150100be150100be15010030160100321601003b1601003c1601003e1601003e160100ac160100ac160100ae160100af160100b6160100b6160100201701002117010026170100261701002c1801002e1801003818010038180100311901003519010037190100381901003d1901003d19010040190100401901004219010042190100d1190100d3190100dc190100df190100e4190100e4190100391a0100391a0100571a0100581a0100971a0100971a01002f1c01002f1c01003e1c01003e1c0100a91c0100a91c0100b11c0100b11c0100b41c0100b41c01008a1d01008e1d0100931d0100941d0100961d0100961d0100f51e0100f61e0100516f0100876f0100f06f0100f16f010066d1010066d101006dd101006dd10100'
const grapheme_prepend_ranges = '0006000005060000dd060000dd0600000f0700000f070000e2080000e20800004e0d00004e0d0000bd100100bd100100cd100100cd100100c2110100c31101003f1901003f19010041190100411901003a1a01003a1a0100841a0100891a0100461d0100461d0100'
const grapheme_extended_pictographic_ranges =
'a9000000a9000000ae000000ae0000003c2000003c2000004920000049200000222100002221000039210000392100009421000099210000a9210000aa2100001a2300001b23000028230000282300008823000088230000cf230000cf230000e9230000ec230000ed230000ee230000ef230000ef230000f0230000f0230000f1230000f2230000f3230000f3230000f8230000fa230000c2240000c2240000aa250000ab250000b6250000b6250000c0250000c0250000fb250000fe2500000026000001260000022600000326000004260000042600000526000005260000072600000d2600000e2600000e2600000f2600001026000011260000112600001226000012260000142600001526000016260000172600001826000018260000192600001c2600001d2600001d2600001e2600001f2600002026000020260000212600002126000022260000232600002426000025260000262600002626000027260000292600002a2600002a2600002b2600002d2600002e2600002e2600002f2600002f260000302600003726000038260000392600003a2600003a2600003b2600003f26000040260000402600004126000041260000422600004226000043260000472600004826000053260000542600005e2600005f2600005f2600006026000060260000612600006226000063260000632600006426000064260000652600006626000067260000672600006826000068260000692600007a2600007b2600007b2600007c2600007d2600007e2600007e2600007f2600007f2600008026000085260000902600009126000092260000922600009326000093260000942600009426000095260000952600009626000097260000982600009826000099260000992600009a2600009a2600009b2600009c2600009d2600009f260000a0260000a1260000a2260000a6260000a7260000a7260000a8260000a9260000aa260000ab260000ac260000af260000b0260000b1260000b2260000bc260000bd260000be260000bf260000c3260000c4260000c5260000c6260000c7260000c8260000c8260000c9260000cd260000ce260000ce260000cf260000cf260000d0260000d0260000d1260000d1260000d2260000d2260000d3260000d3260000d4260000d4260000d5260000e8260000e9260000e9260000ea260000ea260000eb260000ef260000f0260000f1260000f2260000f3260000f4260000f4260000f5260000f5260000f6260000f6260000f7260000f9260000fa260000fa260000fb260000fc260000fd260000fd260000fe26000001270000022700000227000003270000042700000527000005270000082700000c2700000d2700000d2700000e2700000e2700000f2700000f27000010270000112700001227000012270000142700001427000016270000162700001d2700001d270000212700002127000028270000282700003327000034270000442700004427000047270000472700004c2700004c2700004e2700004e270000532700005527000057270000572700006327000063270000642700006427000065270000672700009527000097270000a1270000a1270000b0270000b0270000bf270000bf2700003429000035290000052b0000072b00001b2b00001c2b0000502b0000502b0000552b0000552b000030300000303000003d3000003d3000009732000097320000993200009932000000f0010003f0010004f0010004f0010005f00100cef00100cff00100cff00100d0f00100fff001000df101000ff101002ff101002ff101006cf101006ff1010070f1010071f101007ef101007ff101008ef101008ef1010091f101009af10100adf10100e5f1010001f2010002f2010003f201000ff201001af201001af201002ff201002ff2010032f201003af201003cf201003ff2010049f201004ff2010050f2010051f2010052f20100fff2010000f301000cf301000df301000ef301000ff301000ff3010010f3010010f3010011f3010011f3010012f3010012f3010013f3010015f3010016f3010018f3010019f3010019f301001af301001af301001bf301001bf301001cf301001cf301001df301001ef301001ff3010020f30100' +
'21f3010021f3010022f3010023f3010024f301002cf301002df301002ff3010030f3010031f3010032f3010033f3010034f3010035f3010036f3010036f3010037f301004af301004bf301004bf301004cf301004ff3010050f3010050f3010051f301007bf301007cf301007cf301007df301007df301007ef301007ff3010080f3010093f3010094f3010095f3010096f3010097f3010098f3010098f3010099f301009bf301009cf301009df301009ef301009ff30100a0f30100c4f30100c5f30100c5f30100c6f30100c6f30100c7f30100c7f30100c8f30100c8f30100c9f30100c9f30100caf30100caf30100cbf30100cef30100cff30100d3f30100d4f30100dff30100e0f30100e3f30100e4f30100e4f30100e5f30100f0f30100f1f30100f2f30100f3f30100f3f30100f4f30100f4f30100f5f30100f5f30100f6f30100f6f30100f7f30100f7f30100f8f30100faf3010000f4010007f4010008f4010008f4010009f401000bf401000cf401000ef401000ff4010010f4010011f4010012f4010013f4010013f4010014f4010014f4010015f4010015f4010016f4010016f4010017f4010029f401002af401002af401002bf401003ef401003ff401003ff4010040f4010040f4010041f4010041f4010042f4010064f4010065f4010065f4010066f401006bf401006cf401006df401006ef40100acf40100adf40100adf40100aef40100b5f40100b6f40100b7f40100b8f40100ebf40100ecf40100edf40100eef40100eef40100eff40100eff40100f0f40100f4f40100f5f40100f5f40100f6f40100f7f40100f8f40100f8f40100f9f40100fcf40100fdf40100fdf40100fef40100fef40100fff4010002f5010003f5010003f5010004f5010007f5010008f5010008f5010009f5010009f501000af5010014f5010015f5010015f5010016f501002bf501002cf501002df501002ef501003df5010046f5010048f5010049f501004af501004bf501004ef501004ff501004ff5010050f501005bf501005cf5010067f5010068f501006ef501006ff5010070f5010071f5010072f5010073f5010079f501007af501007af501007bf5010086f5010087f5010087f5010088f5010089f501008af501008df501008ef501008ff5010090f5010090f5010091f5010094f5010095f5010096f5010097f50100a3f50100a4f50100a4f50100a5f50100a5f50100a6f50100a7f50100a8f50100a8f50100a9f50100b0f50100b1f50100b2f50100b3f50100bbf50100bcf50100bcf50100bdf50100c1f50100c2f50100c4f50100c5f50100d0f50100d1f50100d3f50100d4f50100dbf50100dcf50100def50100dff50100e0f50100e1f50100e1f50100e2f50100e2f50100e3f50100e3f50100e4f50100e7f50100e8f50100e8f50100e9f50100eef50100eff50100eff50100f0f50100f2f50100f3f50100f3f50100f4f50100f9f50100faf50100faf50100fbf50100fff5010000f6010000f6010001f6010006f6010007f6010008f6010009f601000df601000ef601000ef601000ff601000ff6010010f6010010f6010011f6010011f6010012f6010014f6010015f6010015f6010016f6010016f6010017f6010017f6010018f6010018f6010019f6010019f601001af601001af601001bf601001bf601001cf601001ef601001ff601001ff6010020f6010025f6010026f6010027f6010028f601002bf601002cf601002cf601002df601002df601002ef601002ff6010030f6010033f6010034f6010034f6010035f6010035f6010036f6010036f6010037f6010040f6010041f6010044f6010045f601004ff6010080f6010080f6010081f6010082f6010083f6010085f6010086f6010086f6010087f6010087f6010088f6010088f6010089f6010089f601008af601008bf601008cf601008cf601008df601008df601008ef601008ef601008ff601008ff6010090f6010090f6010091f6010093f6010094f6010094f6010095f6010095f6010096f6010096f6010097f6010097f6010098f6010098f6010099f601009af601009bf60100a1f60100a2f60100a2f60100a3f60100a3f60100a4f60100a5f60100a6f60100a6f60100a7f60100adf60100' +
'aef60100b1f60100b2f60100b2f60100b3f60100b5f60100b6f60100b6f60100b7f60100b8f60100b9f60100bef60100bff60100bff60100c0f60100c0f60100c1f60100c5f60100c6f60100caf60100cbf60100cbf60100ccf60100ccf60100cdf60100cff60100d0f60100d0f60100d1f60100d2f60100d3f60100d4f60100d5f60100d5f60100d6f60100d7f60100d8f60100dff60100e0f60100e5f60100e6f60100e8f60100e9f60100e9f60100eaf60100eaf60100ebf60100ecf60100edf60100eff60100f0f60100f0f60100f1f60100f2f60100f3f60100f3f60100f4f60100f6f60100f7f60100f8f60100f9f60100f9f60100faf60100faf60100fbf60100fcf60100fdf60100fff6010074f701007ff70100d5f70100dff70100e0f70100ebf70100ecf70100fff701000cf801000ff8010048f801004ff801005af801005ff8010088f801008ff80100aef80100fff801000cf901000cf901000df901000ff9010010f9010018f9010019f901001ef901001ff901001ff9010020f9010027f9010028f901002ff9010030f9010030f9010031f9010032f9010033f901003af901003cf901003ef901003ff901003ff9010040f9010045f9010047f901004bf901004cf901004cf901004df901004ff9010050f901005ef901005ff901006bf901006cf9010070f9010071f9010071f9010072f9010072f9010073f9010076f9010077f9010078f9010079f9010079f901007af901007af901007bf901007bf901007cf901007ff9010080f9010084f9010085f9010091f9010092f9010097f9010098f90100a2f90100a3f90100a4f90100a5f90100aaf90100abf90100adf90100aef90100aff90100b0f90100b9f90100baf90100bff90100c0f90100c0f90100c1f90100c2f90100c3f90100caf90100cbf90100cbf90100ccf90100ccf90100cdf90100cff90100d0f90100e6f90100e7f90100fff9010000fa01006ffa010070fa010073fa010074fa010074fa010075fa010077fa010078fa01007afa01007bfa01007ffa010080fa010082fa010083fa010086fa010087fa01008ffa010090fa010095fa010096fa0100a8fa0100a9fa0100affa0100b0fa0100b6fa0100b7fa0100bffa0100c0fa0100c2fa0100c3fa0100cffa0100d0fa0100d6fa0100d7fa0100fffa010000fc0100fdff0100'
@[inline]
fn grapheme_hex_nibble(c u8) u32 {
return if c <= `9` { u32(c - `0`) } else { u32((c | 0x20) - `a` + 10) }
}
@[inline]
fn grapheme_hex_byte(ranges string, i int) u32 {
return (grapheme_hex_nibble(ranges[i]) << 4) | grapheme_hex_nibble(ranges[i + 1])
}
@[inline]
fn grapheme_range_value(ranges string, value_idx int) u32 {
i := value_idx * 8
b0 := grapheme_hex_byte(ranges, i)
b1 := grapheme_hex_byte(ranges, i + 2)
b2 := grapheme_hex_byte(ranges, i + 4)
b3 := grapheme_hex_byte(ranges, i + 6)
return b0 | (b1 << 8) | (b2 << 16) | (b3 << 24)
}
@[inline]
fn in_grapheme_ranges(r rune, ranges string) bool {
target := u32(r)
mut low := 0
mut high := ranges.len / 16
for low < high {
mid := low + (high - low) / 2
lo := grapheme_range_value(ranges, mid * 2)
hi := grapheme_range_value(ranges, mid * 2 + 1)
if target < lo {
high = mid
} else if target > hi {
low = mid + 1
} else {
return true
}
}
return false
}
@[inline]
fn grapheme_break_property(r rune) GraphemeBreakProperty {
if r == `\r` {
return .cr
}
if r == `\n` {
return .lf
}
if r == 0x200d {
return .zwj
}
if r >= 0x1f1e6 && r <= 0x1f1ff {
return .regional_indicator
}
if r >= 0xac00 && r <= 0xd7a3 {
return if (u32(r) - 0xac00) % 28 == 0 { .lv } else { .lvt }
}
if (r >= 0x1100 && r <= 0x115f) || (r >= 0xa960 && r <= 0xa97c) {
return .l
}
if (r >= 0x1160 && r <= 0x11a7) || (r >= 0xd7b0 && r <= 0xd7c6) {
return .v
}
if (r >= 0x11a8 && r <= 0x11ff) || (r >= 0xd7cb && r <= 0xd7fb) {
return .t
}
if in_grapheme_ranges(r, grapheme_control_ranges) {
return .control
}
if in_grapheme_ranges(r, grapheme_extend_ranges) {
return .extend
}
if in_grapheme_ranges(r, grapheme_spacing_mark_ranges) {
return .spacing_mark
}
if in_grapheme_ranges(r, grapheme_prepend_ranges) {
return .prepend
}
return .other
}
@[inline]
fn is_extended_pictographic(r rune) bool {
return in_grapheme_ranges(r, grapheme_extended_pictographic_ranges)
}
struct GraphemeState {
mut:
prev_prop GraphemeBreakProperty
ri_count int
extended_pictographic_state u8
}
@[inline]
fn grapheme_state_from_rune(r rune, prop GraphemeBreakProperty) GraphemeState {
return GraphemeState{
prev_prop: prop
ri_count: if prop == .regional_indicator { 1 } else { 0 }
extended_pictographic_state: if is_extended_pictographic(r) { u8(1) } else { u8(0) }
}
}
@[inline]
fn (mut gs GraphemeState) push(r rune, prop GraphemeBreakProperty) {
gs.prev_prop = prop
gs.ri_count = if prop == .regional_indicator { gs.ri_count + 1 } else { 0 }
if is_extended_pictographic(r) {
gs.extended_pictographic_state = 1
} else if prop == .extend && gs.extended_pictographic_state == 1 {
// Keep the `Extended_Pictographic Extend*` tail alive for GB11.
} else if prop == .zwj && gs.extended_pictographic_state == 1 {
gs.extended_pictographic_state = 2
} else {
gs.extended_pictographic_state = 0
}
}
@[inline]
fn should_break_grapheme(gs GraphemeState, r rune, prop GraphemeBreakProperty) bool {
match gs.prev_prop {
.cr {
if prop == .lf {
return false
}
return true
}
.lf, .control {
return true
}
.l {
if prop in [.l, .v, .lv, .lvt] {
return false
}
}
.lv, .v {
if prop in [.v, .t] {
return false
}
}
.lvt, .t {
if prop == .t {
return false
}
}
.prepend {
return false
}
.regional_indicator {
if prop == .regional_indicator && gs.ri_count % 2 == 1 {
return false
}
}
else {}
}
if prop in [.cr, .lf, .control] {
return true
}
if prop in [.extend, .zwj, .spacing_mark] {
return false
}
if gs.extended_pictographic_state == 2 && is_extended_pictographic(r) {
return false
}
return true
}
@[inline]
fn utf8_rune_visible_width(r rune, prop GraphemeBreakProperty) int {
if prop in [.extend, .zwj, .spacing_mark, .prepend] {
return 0
}
// Keep the historical formatting behavior for common East Asian wide runes and emoji,
// but apply it per grapheme cluster instead of per code point.
if r >= 0x1100
&& (r <= 0x115f || r == 0x2329 || r == 0x232a || (r >= 0x2e80 && r <= 0xa4cf && r != 0x303f)
|| (r >= 0xac00 && r <= 0xd7a3) || (r >= 0xf900 && r <= 0xfaff)
|| (r >= 0xfe10 && r <= 0xfe19) || (r >= 0xfe30 && r <= 0xfe6f)
|| (r >= 0xff00 && r <= 0xff60) || (r >= 0xffe0 && r <= 0xffe6)
|| (r >= 0x1f300 && r <= 0x1f64f) || (r >= 0x1f680 && r <= 0x1f6ff)
|| (r >= 0x1f900 && r <= 0x1f9ff) || (r >= 0x1fa70 && r <= 0x1faff)
|| (r >= 0x20000 && r <= 0x3fffd)) {
return 2
}
return 1
}
fn string_graphemes_impl(s string) []string {
runes := s.runes()
if runes.len == 0 {
return []string{}
}
mut res := []string{cap: runes.len}
mut cluster := []rune{cap: 4}
first_prop := grapheme_break_property(runes[0])
mut state := grapheme_state_from_rune(runes[0], first_prop)
cluster << runes[0]
for r in runes[1..] {
prop := grapheme_break_property(r)
if should_break_grapheme(state, r, prop) {
res << cluster.string()
cluster = []rune{cap: 4}
cluster << r
state = grapheme_state_from_rune(r, prop)
continue
}
cluster << r
state.push(r, prop)
}
res << cluster.string()
return res
}
@[inline]
fn utf8_grapheme_visible_length(s string) int {
runes := s.runes()
if runes.len == 0 {
return 0
}
first_prop := grapheme_break_property(runes[0])
mut state := grapheme_state_from_rune(runes[0], first_prop)
mut total := 0
mut cluster_width := utf8_rune_visible_width(runes[0], first_prop)
for r in runes[1..] {
prop := grapheme_break_property(r)
if should_break_grapheme(state, r, prop) {
total += cluster_width
cluster_width = utf8_rune_visible_width(r, prop)
state = grapheme_state_from_rune(r, prop)
continue
}
rune_width := utf8_rune_visible_width(r, prop)
if rune_width > cluster_width {
cluster_width = rune_width
}
state.push(r, prop)
}
return total + cluster_width
}
+30
View File
@@ -0,0 +1,30 @@
fn test_hex_methods_on_u8() {
assert u8(1).hex() == '01'
assert u8(` `).hex() == '20'
assert u8(255).hex() == 'ff'
}
fn test_hex_methods_on_byte() {
assert byte(1).hex() == '01'
assert byte(` `).hex() == '20'
assert byte(255).hex() == 'ff'
}
fn test_hex_methods_on_char() {
assert char(1).hex() == '01'
assert char(` `).hex() == '20'
assert char(255).hex() == 'ff'
}
fn test_hex_method_on_runes() {
assert ` `.hex() == '20'
assert `喂`.hex() == '5582'
// latin:
assert `A`.hex() == '41'
assert `Z`.hex() == '5a'
// cyrillic:
assert `Д`.hex() == '414'
assert `Я`.hex() == '42f'
// emojies:
assert `💣`.hex() == '1f4a3'
}
@@ -0,0 +1,40 @@
module builtin
// input_rune returns a single rune from the standart input (an unicode codepoint).
// It expects, that the input is utf8 encoded.
// It will return `none` on EOF.
pub fn input_rune() ?rune {
x := input_character()
if x <= 0 {
return none
}
char_len := utf8_char_len(u8(x))
if char_len == 1 {
return x
}
mut b := u8(x)
b = b << char_len
mut res := rune(b)
mut shift := 6 - char_len
for i := 1; i < char_len; i++ {
c := rune(input_character())
res = rune(res) << shift
res |= c & 63 // 0x3f
shift = 6
}
return res
}
// InputRuneIterator is an iterator over the input runes.
pub struct InputRuneIterator {}
// next returns the next rune from the input stream.
pub fn (mut self InputRuneIterator) next() ?rune {
return input_rune()
}
// input_rune_iterator returns an iterator to allow for `for i, r in input_rune_iterator() {`.
// When the input stream is closed, the loop will break.
pub fn input_rune_iterator() InputRuneIterator {
return InputRuneIterator{}
}
@@ -0,0 +1,52 @@
// vtest build: linux
// vtest retry: 2
import os
import time
fn test_input_rune_iterator_with_unicode_input() {
mut p := os.new_process(@VEXE)
p.set_args(['-e', 'for i, r in input_rune_iterator() { println("> i: \${i:04} | r: `\${r}`") }'])
p.set_redirect_stdio()
p.run()
spawn fn [mut p] () {
time.sleep(10 * time.millisecond)
dump(p.pid)
p.stdin_write('Проба Abc 你好 🌍 123')
time.sleep(10 * time.millisecond)
p.stdin_write('\0x00') // 0 should break the input stream
time.sleep(10 * time.millisecond)
eprintln('>>> done')
}()
mut olines := []string{}
for p.is_alive() {
if oline := p.pipe_read(.stdout) {
olines << oline
}
time.sleep(1 * time.millisecond)
}
p.close()
p.wait()
assert p.code == 0
eprintln('done')
solines := olines.join('').trim_space().replace('\r', '')
eprintln('solines.len: ${solines.len} | solines: ${solines}')
assert solines.len > 100
assert solines == '> i: 0000 | r: `П`
> i: 0001 | r: `р`
> i: 0002 | r: `о`
> i: 0003 | r: `б`
> i: 0004 | r: `а`
> i: 0005 | r: ` `
> i: 0006 | r: `A`
> i: 0007 | r: `b`
> i: 0008 | r: `c`
> i: 0009 | r: ` `
> i: 0010 | r: `你`
> i: 0011 | r: `好`
> i: 0012 | r: ` `
> i: 0013 | r: `🌍`
> i: 0014 | r: ` `
> i: 0015 | r: `1`
> i: 0016 | r: `2`
> i: 0017 | r: `3`'
}
+633
View File
@@ -0,0 +1,633 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module builtin
pub struct VContext {
allocator int
}
pub type byte = u8
// ptr_str returns a string with the address of `ptr`.
pub fn ptr_str(ptr voidptr) string {
buf1 := u64_to_hex_no_leading_zeros(u64(ptr), 16)
return buf1
}
// str returns the string equivalent of x.
pub fn (x isize) str() string {
return i64(x).str()
}
// str returns the string equivalent of x.
pub fn (x usize) str() string {
return u64(x).str()
}
// str returns a string with the address stored in the pointer cptr.
pub fn (cptr &char) str() string {
return u64(cptr).hex()
}
// digit pairs in reverse order
const digit_pairs = '00102030405060708090011121314151617181910212223242526272829203132333435363738393041424344454647484940515253545556575859506162636465666768696071727374757677787970818283848586878889809192939495969798999'
pub const min_i8 = i8(-128)
pub const max_i8 = i8(127)
pub const min_i16 = i16(-32768)
pub const max_i16 = i16(32767)
pub const min_i32 = i32(-2147483648)
pub const max_i32 = i32(2147483647)
// -9223372036854775808 is wrong, because C compilers parse literal values
// without sign first, and 9223372036854775808 overflows i64, hence the
// consecutive subtraction by 1
pub const min_i64 = i64(-9223372036854775807 - 1)
pub const max_i64 = i64(9223372036854775807)
pub const min_int = $if new_int ? && x64 { int(min_i64) } $else { int(min_i32) }
pub const max_int = $if new_int ? && x64 { int(max_i64) } $else { int(max_i32) }
pub const min_u8 = u8(0)
pub const max_u8 = u8(255)
pub const min_u16 = u16(0)
pub const max_u16 = u16(65535)
pub const min_u32 = u32(0)
pub const max_u32 = u32(4294967295)
pub const min_u64 = u64(0)
pub const max_u64 = u64(18446744073709551615)
// str_l returns the string representation of the integer nn with max chars.
@[direct_array_access; inline]
fn (nn int) str_l(max int) string {
// This implementation is the quickest with gcc -O2
unsafe {
mut n := i64(nn)
mut d := 0
if n == 0 {
return '0'
}
// overflow protect
$if new_int ? && x64 {
if n == min_i64 {
return '-9223372036854775808'
}
} $else {
if n == min_i32 {
return '-2147483648'
}
}
mut is_neg := false
if n < 0 {
n = -n
is_neg = true
}
mut index := max
mut buf := malloc_noscan(max + 1)
buf[index] = 0
index--
for n > 0 {
n1 := int(n / 100)
// calculate the digit_pairs start index
d = int(u32(int(n) - (n1 * 100)) << 1)
n = n1
buf[index] = digit_pairs.str[d]
index--
d++
buf[index] = digit_pairs.str[d]
index--
}
index++
// remove head zero
if d < 20 {
index++
}
// Prepend - if it's negative
if is_neg {
index--
buf[index] = `-`
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
}
}
// str returns the value of the `i8` as a `string`.
// Example: assert i8(-2).str() == '-2'
pub fn (n i8) str() string {
return int(n).str_l(4)
}
// str returns the value of the `i16` as a `string`.
// Example: assert i16(-20).str() == '-20'
pub fn (n i16) str() string {
return int(n).str_l(6)
}
// str returns the value of the `u16` as a `string`.
// Example: assert u16(20).str() == '20'
pub fn (n u16) str() string {
return int(n).str_l(6)
}
pub fn (n i32) str() string {
return int(n).str_l(11)
}
pub fn (nn int) hex_full() string {
return u64_to_hex(u64(nn), 8)
}
// str returns the value of the `int` as a `string`.
// Example: assert int(-2020).str() == '-2020'
pub fn (n int) str() string {
$if new_int ? {
return impl_i64_to_string(n)
} $else {
return n.str_l(11)
}
}
// str returns the value of the `u32` as a `string`.
// Example: assert u32(20000).str() == '20000'
@[direct_array_access; inline]
pub fn (nn u32) str() string {
unsafe {
mut n := nn
mut d := u32(0)
if n == 0 {
return '0'
}
max := 10
mut buf := malloc_noscan(max + 1)
mut index := max
buf[index] = 0
index--
for n > 0 {
n1 := n / u32(100)
d = ((n - (n1 * u32(100))) << u32(1))
n = n1
buf[index] = digit_pairs[int(d)]
index--
d++
buf[index] = digit_pairs[int(d)]
index--
}
index++
// remove head zero
if d < u32(20) {
index++
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
}
}
// str returns the value of the `int_literal` as a `string`.
@[inline]
pub fn (n int_literal) str() string {
return impl_i64_to_string(n)
}
// str returns the value of the `i64` as a `string`.
// Example: assert i64(-200000).str() == '-200000'
@[inline]
pub fn (nn i64) str() string {
return impl_i64_to_string(nn)
}
@[direct_array_access]
fn impl_i64_to_string(nn i64) string {
unsafe {
mut n := nn
mut d := i64(0)
if n == 0 {
return '0'
} else if n == min_i64 {
return '-9223372036854775808'
}
max := 20
mut buf := malloc_noscan(max + 1)
mut is_neg := false
if n < 0 {
n = -n
is_neg = true
}
mut index := max
buf[index] = 0
index--
for n > 0 {
n1 := n / i64(100)
d = (u32(n - (n1 * i64(100))) << i64(1))
n = n1
buf[index] = digit_pairs[int(d)]
index--
d++
buf[index] = digit_pairs[int(d)]
index--
}
index++
// remove head zero
if d < i64(20) {
index++
}
// Prepend - if it's negative
if is_neg {
index--
buf[index] = `-`
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
}
}
// str returns the value of the `u64` as a `string`.
// Example: assert u64(2000000).str() == '2000000'
@[direct_array_access; inline]
pub fn (nn u64) str() string {
unsafe {
mut n := nn
mut d := u64(0)
if n == 0 {
return '0'
}
max := 20
mut buf := malloc_noscan(max + 1)
mut index := max
buf[index] = 0
index--
for n > 0 {
n1 := n / 100
d = ((n - (n1 * 100)) << 1)
n = n1
buf[index] = digit_pairs[int(d)]
index--
d++
buf[index] = digit_pairs[int(d)]
index--
}
index++
// remove head zero
if d < 20 {
index++
}
diff := max - index
vmemmove(buf, voidptr(buf + index), diff + 1)
return tos(buf, diff)
}
}
// str returns the value of the `bool` as a `string`.
// Example: assert (2 > 1).str() == 'true'
pub fn (b bool) str() string {
if b {
return 'true'
}
return 'false'
}
// u64_to_hex converts the number `nn` to a (zero padded if necessary) hexadecimal `string`.
@[direct_array_access; inline]
fn u64_to_hex(nn u64, len u8) string {
mut n := nn
mut buf := [17]u8{}
buf[len] = 0
mut i := 0
for i = len - 1; i >= 0; i-- {
d := u8(n & 0xF)
buf[i] = if d < 10 { d + `0` } else { d + 87 }
n = n >> 4
}
return unsafe { tos(memdup(&buf[0], len + 1), len) }
}
// u64_to_hex_no_leading_zeros converts the number `nn` to hexadecimal `string`.
@[direct_array_access; inline]
fn u64_to_hex_no_leading_zeros(nn u64, len u8) string {
mut n := nn
mut buf := [17]u8{}
buf[len] = 0
mut i := 0
for i = len - 1; i >= 0; i-- {
d := u8(n & 0xF)
buf[i] = if d < 10 { d + `0` } else { d + 87 }
n = n >> 4
if n == 0 {
break
}
}
res_len := len - i
return unsafe { tos(memdup(&buf[i], res_len + 1), res_len) }
}
// hex returns the value of the `byte` as a hexadecimal `string`.
// Note that the output is zero padded for values below 16.
// Example: assert u8(2).hex() == '02'
// Example: assert u8(15).hex() == '0f'
// Example: assert u8(255).hex() == 'ff'
pub fn (nn u8) hex() string {
if nn == 0 {
return '00'
}
return u64_to_hex(nn, 2)
}
// hex returns a hexadecimal representation of `c` (as an 8 bit unsigned number).
// The output is zero padded for values below 16.
// Example: assert char(`A`).hex() == '41'
// Example: assert char(`Z`).hex() == '5a'
// Example: assert char(` `).hex() == '20'
pub fn (c char) hex() string {
return u8(c).hex()
}
// hex returns a hexadecimal representation of the rune `r` (as a 32 bit unsigned number).
// Example: assert `A`.hex() == '41'
// Example: assert `💣`.hex() == '1f4a3'
pub fn (r rune) hex() string {
return u32(r).hex()
}
// hex returns the value of the `i8` as a hexadecimal `string`.
// Note that the output is zero padded for values below 16.
// Example: assert i8(8).hex() == '08'
// Example: assert i8(10).hex() == '0a'
// Example: assert i8(15).hex() == '0f'
pub fn (nn i8) hex() string {
if nn == 0 {
return '00'
}
return u64_to_hex(u64(nn), 2)
}
// hex returns the value of the `u16` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// Example: assert u16(2).hex() == '2'
// Example: assert u16(200).hex() == 'c8'
pub fn (nn u16) hex() string {
if nn == 0 {
return '0'
}
return u64_to_hex_no_leading_zeros(nn, 4)
}
// hex returns the value of the `i16` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// Example: assert i16(2).hex() == '2'
// Example: assert i16(200).hex() == 'c8'
pub fn (nn i16) hex() string {
return u16(nn).hex()
}
// hex returns the value of the `u32` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// Example: assert u32(2).hex() == '2'
// Example: assert u32(200).hex() == 'c8'
pub fn (nn u32) hex() string {
if nn == 0 {
return '0'
}
return u64_to_hex_no_leading_zeros(nn, 8)
}
// hex returns the value of the `int` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// Example: assert int(2).hex() == '2'
// Example: assert int(200).hex() == 'c8'
pub fn (nn int) hex() string {
return u32(nn).hex()
}
// hex2 returns the value of the `int` as a `0x`-prefixed hexadecimal `string`.
// Note that the output after `0x` is ***not*** zero padded.
// Example: assert int(8).hex2() == '0x8'
// Example: assert int(15).hex2() == '0xf'
// Example: assert int(18).hex2() == '0x12'
pub fn (n int) hex2() string {
return '0x' + n.hex()
}
// hex returns the value of the `u64` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// Example: assert u64(2).hex() == '2'
// Example: assert u64(2000).hex() == '7d0'
pub fn (nn u64) hex() string {
if nn == 0 {
return '0'
}
return u64_to_hex_no_leading_zeros(nn, 16)
}
// hex returns the value of the `i64` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// Example: assert i64(2).hex() == '2'
// Example: assert i64(-200).hex() == 'ffffffffffffff38'
// Example: assert i64(2021).hex() == '7e5'
pub fn (nn i64) hex() string {
return u64(nn).hex()
}
// hex returns the value of the `int_literal` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
pub fn (nn int_literal) hex() string {
return u64(nn).hex()
}
// hex returns the value of the `voidptr` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
pub fn (nn voidptr) str() string {
return '0x' + u64(nn).hex()
}
// hex returns the value of the `byteptr` as a hexadecimal `string`.
// Note that the output is ***not*** zero padded.
// pub fn (nn byteptr) str() string {
pub fn (nn byteptr) str() string {
return '0x' + u64(nn).hex()
}
pub fn (nn charptr) str() string {
return '0x' + u64(nn).hex()
}
pub fn (nn u8) hex_full() string {
return u64_to_hex(u64(nn), 2)
}
pub fn (nn i8) hex_full() string {
return u64_to_hex(u64(nn), 2)
}
pub fn (nn u16) hex_full() string {
return u64_to_hex(u64(nn), 4)
}
pub fn (nn i16) hex_full() string {
return u64_to_hex(u64(nn), 4)
}
pub fn (nn u32) hex_full() string {
return u64_to_hex(u64(nn), 8)
}
pub fn (nn i64) hex_full() string {
return u64_to_hex(u64(nn), 16)
}
pub fn (nn voidptr) hex_full() string {
return u64_to_hex(u64(nn), 16)
}
pub fn (nn int_literal) hex_full() string {
return u64_to_hex(u64(nn), 16)
}
// hex_full returns the value of the `u64` as a *full* 16-digit hexadecimal `string`.
// Example: assert u64(2).hex_full() == '0000000000000002'
// Example: assert u64(255).hex_full() == '00000000000000ff'
pub fn (nn u64) hex_full() string {
return u64_to_hex(nn, 16)
}
// str returns the contents of `byte` as a zero terminated `string`.
// See also: [`byte.ascii_str`](#byte.ascii_str)
// Example: assert u8(111).str() == '111'
pub fn (b u8) str() string {
return int(b).str_l(4)
}
// ascii_str returns the contents of `byte` as a zero terminated ASCII `string` character.
// Example: assert u8(97).ascii_str() == 'a'
pub fn (b u8) ascii_str() string {
mut str := string{
str: unsafe { malloc_noscan(2) }
len: 1
}
unsafe {
str.str[0] = b
str.str[1] = 0
}
return str
}
// str_escaped returns the contents of `byte` as an escaped `string`.
// Example: assert u8(0).str_escaped() == r'`\0`'
@[manualfree]
pub fn (b u8) str_escaped() string {
str := match b {
0 {
r'`\0`'
}
7 {
r'`\a`'
}
8 {
r'`\b`'
}
9 {
r'`\t`'
}
10 {
r'`\n`'
}
11 {
r'`\v`'
}
12 {
r'`\f`'
}
13 {
r'`\r`'
}
27 {
r'`\e`'
}
32...126 {
b.ascii_str()
}
else {
xx := b.hex()
yy := '0x' + xx
unsafe { xx.free() }
yy
}
}
return str
}
// is_capital returns `true`, if the byte is a Latin capital letter.
// Example: assert u8(`H`).is_capital() == true
// Example: assert u8(`h`).is_capital() == false
@[inline]
pub fn (c u8) is_capital() bool {
return c >= `A` && c <= `Z`
}
// bytestr produces a string from *all* the bytes in the array.
// Note: the returned string will have .len equal to the array.len,
// even when some of the array bytes were `0`.
// If you want to get a V string, that contains only the bytes till
// the first `0` byte, use `tos_clone(&u8(array.data))` instead.
pub fn (b []u8) bytestr() string {
unsafe {
buf := malloc_noscan(b.len + 1)
vmemcpy(buf, b.data, b.len)
buf[b.len] = 0
return tos(buf, b.len)
}
}
// byterune attempts to decode a sequence of bytes, from utf8 to utf32.
// It return the result as a rune.
// It will produce an error, if there are more than four bytes in the array.
pub fn (b []u8) byterune() !rune {
r := b.utf8_to_utf32()!
return rune(r)
}
// repeat returns a new string with `count` number of copies of the byte it was called on.
pub fn (b u8) repeat(count int) string {
if count <= 0 {
return ''
} else if count == 1 {
return b.ascii_str()
}
mut bytes := unsafe { malloc_noscan(count + 1) }
unsafe {
vmemset(bytes, b, count)
bytes[count] = 0
}
return unsafe { bytes.vstring_with_len(count) }
}
// for atomic ints, internal
fn _Atomic__int_str(x int) string {
return x.str()
}
// int_min returns the smallest `int` of input `a` and `b`.
// Example: assert int_min(2,3) == 2
@[inline]
pub fn int_min(a int, b int) int {
return if a < b { a } else { b }
}
// int_max returns the largest `int` of input `a` and `b`.
// Example: assert int_max(2,3) == 3
@[inline]
pub fn int_max(a int, b int) int {
return if a > b { a } else { b }
}
+1
View File
@@ -0,0 +1 @@
module builtin
+276
View File
@@ -0,0 +1,276 @@
const a = 3
const u = u64(1)
fn test_const() {
b := (true && true) || false
assert b == true
assert a == 3
assert u == u64(1)
assert u == 1 // make sure this works without the cast
}
fn test_str_methods() {
assert i8(1).str() == '1'
assert i8(-1).str() == '-1'
assert i16(1).str() == '1'
assert i16(-1).str() == '-1'
assert int(1).str() == '1'
assert int(-1).str() == '-1'
assert int(2147483647).str() == '2147483647'
// TODO: ARM64 backend uses 64-bit int registers, so int(u32(2147483648)) doesn't wrap to negative
// assert int(u32(2147483648)).str() == $if new_int ? && x64 {
// '2147483648'
// } $else {
// '-2147483648'
// }
assert int(-2147483648).str() == '-2147483648'
assert i64(1).str() == '1'
assert i64(-1).str() == '-1'
assert u16(1).str() == '1'
// TODO: ARM64 backend doesn't truncate to narrower int widths
// assert u16(-1).str() == '65535'
assert u32(1).str() == '1'
// assert u32(-1).str() == '4294967295'
assert u64(1).str() == '1'
assert u64(-1).str() == '18446744073709551615'
// TODO: ARM64 backend doesn't handle pointer type .str() method calls on casts
// assert voidptr(-1).str() == '0xffffffffffffffff'
// assert voidptr(1).str() == '0x1'
// assert (&u8(voidptr(-1))).str() == 'ffffffffffffffff'
// assert (&u8(voidptr(1))).str() == '1'
// assert byteptr(-1).str() == '0xffffffffffffffff'
// assert byteptr(1).str() == '0x1'
// assert charptr(-1).str() == '0xffffffffffffffff'
// assert charptr(1).str() == '0x1'
}
fn test_str_length() {
assert i8(-125).str() == '-125'
assert i16(-32760).str() == '-32760'
assert i32(-2147483610).str() == '-2147483610'
assert int(-2147483620).str() == '-2147483620'
assert i64(-9223372036854775801).str() == '-9223372036854775801'
// TODO: ARM64 backend truncation issues with narrow unsigned types and large u64 literals
// assert u8(250).str() == '250'
// assert u16(65530).str() == '65530'
// assert u32(4294967250).str() == '4294967250'
// assert u64(18446744073709551611).str() == '18446744073709551611'
}
fn test_and_precedence() {
assert (2 & 0 == 0) == ((2 & 0) == 0)
assert (2 & 0 != 0) == ((2 & 0) != 0)
assert (0 & 0 >= 0) == ((0 & 0) >= 0)
assert (0 & 0 <= 0) == ((0 & 0) <= 0)
assert (0 & 0 < 1) == ((0 & 0) < 1)
assert (1 & 2 > 0) == ((1 & 2) > 0)
}
fn test_or_precedence() {
assert (1 | 0 == 0) == ((1 | 0) == 0)
assert (1 | 0 != 1) == ((1 | 0) != 1)
assert (1 | 0 >= 2) == ((1 | 0) >= 2)
assert (1 | 0 <= 0) == ((1 | 0) <= 0)
assert (1 | 0 < 0) == ((1 | 0) < 0)
assert (1 | 0 > 1) == ((1 | 0) > 1)
}
fn test_xor_precedence() {
assert (1 ^ 0 == 2) == ((1 ^ 0) == 2)
assert (1 ^ 0 != 2) == ((1 ^ 0) != 2)
assert (1 ^ 0 >= 0) == ((1 ^ 0) >= 0)
assert (1 ^ 0 <= 1) == ((1 ^ 0) <= 1)
assert (1 ^ 0 < 0) == ((1 ^ 0) < 0)
assert (1 ^ 0 > 1) == ((1 ^ 0) > 1)
}
fn test_left_shift_precedence() {
// TODO: ARM64 backend shift/bitwise precedence issue
// assert (2 << 4 | 3) == ((2 << 4) | 3)
// assert (2 << 4 | 3) != (2 << (4 | 3))
assert true
}
fn test_right_shift_precedence() {
// TODO: ARM64 backend shift/bitwise precedence issue
// assert (256 >> 4 | 3) == ((256 >> 4) | 3)
// assert (256 >> 4 | 3) != (256 >> (4 | 3))
assert true
}
fn test_i8_print() {
b := i8(0)
println(b)
c := i16(7)
println(c)
d := u16(6)
println(d)
assert true
}
/*
fn test_cmp() {
assert 1 ≠ 2
assert 1 ⩽ 2
assert 1 ⩾ 0
}
*/
type MyInt = int
fn test_int_alias() {
i := MyInt(2)
assert i + 10 == 12
}
fn test_hex() {
x := u64(10)
assert x.hex() == 'a'
b := 1234
assert b.hex() == '4d2'
// TODO: ARM64 backend 64-bit register truncation
// b1 := -1
// assert b1.hex() == 'ffffffff'
// unsigned tests
// assert u8(12).hex() == '0c'
// assert u8(255).hex() == 'ff'
// assert u16(65535).hex() == 'ffff'
// assert u32(-1).hex() == 'ffffffff'
assert u64(-1).hex() == 'ffffffffffffffff'
// signed tests
// assert i8(-1).hex() == 'ff'
// assert i8(12).hex() == '0c'
// assert i16(32767).hex() == '7fff'
assert int(2147483647).hex() == '7fffffff'
assert i64(9223372036854775807).hex() == '7fffffffffffffff'
}
fn test_bin() {
x1 := 0b10
assert x1 == 2
x2 := 0b10101010
assert x2 == 0xAA
x3 := -0b0000001
assert x3 == -1
x4 := 0b11111111
assert x4 == 255
x5 := u8(0b11111111)
assert x5 == 255
x6 := char(0b11111111)
assert u8(x6) == 255
x7 := 0b0
assert x7 == 0
x8 := -0b0
assert x8 == 0
}
fn test_oct() {
x1 := 0o12
assert x1 == 10
x2 := 0o350
assert x2 == 232
x3 := 0o00073
assert x3 == 59
x4 := 0
assert x4 == 0
x5 := 195
assert x5 == 195
x6 := -0o744
assert x6 == -484
x7 := -0o000042
assert x7 == -34
x8 := -112
assert x8 == -112
x9 := -0
assert x9 == 0
}
fn test_num_separator() {
// int
assert 100_000_0 == 1000000
assert -2_23_4_6 == -22346
// bin
assert 0b0_11 == 3
assert -0b0_100 == -4
// oct
assert 0o1_73 == 123
assert -0o17_5 == -125
assert -0o175 == -125
// hex
assert 0xFF == 255
assert 0xF_F == 255
// TODO: ARM64 backend float number separators not handled
// assert 312_2.55 == 3122.55
// assert 312_2.55 == 3122.55
}
fn test_int_decl() {
x1 := 0
x2 := 1333
x3 := -88955
x4 := 2000000000
x5 := -1999999999
assert typeof(x1).name == 'int'
assert typeof(x2).name == 'int'
assert typeof(x3).name == 'int'
assert typeof(x4).name == 'int'
assert typeof(x5).name == 'int'
x7 := u64(-321314588900011)
assert typeof(x7).name == 'u64'
}
fn test_int_to_hex() {
// array hex
st := [u8(`V`), `L`, `A`, `N`, `G`]
assert st.hex() == '564c414e47'
assert st.hex().len == 10
st1 := [u8(0x41)].repeat(100)
assert st1.hex() == '41'.repeat(100)
// TODO: ARM64 backend int truncation issues with narrow types
// Skipping 8/16/32-bit hex tests that depend on proper width truncation
assert u64(0).hex() == '0'
assert i64(9223372036854775807).hex() == '7fffffffffffffff'
assert u64(9223372036854775807).hex() == '7fffffffffffffff'
assert i64(-1).hex() == 'ffffffffffffffff'
}
fn test_repeat() {
b := u8(`V`)
assert b.repeat(5) == 'VVVVV'
assert b.repeat(1) == b.ascii_str()
assert b.repeat(0) == ''
}
fn test_int_min() {
assert int_min(1, 2) == 1
assert int_min(2, 1) == 1
assert int_min(-2, -1) == -2
assert int_min(-1, -2) == -2
assert int_min(0, 5) == 0
assert int_min(5, 0) == 0
assert int_min(-5, 5) == -5
assert int_min(5, -5) == -5
}
fn test_int_max() {
assert int_max(1, 2) == 2
assert int_max(2, 1) == 2
assert int_max(-2, -1) == -1
assert int_max(-1, -2) == -1
assert int_max(0, 5) == 5
assert int_max(5, 0) == 5
assert int_max(-5, 5) == 5
assert int_max(5, -5) == 5
}
fn test_big_int() {
x := i64(2147483647)
if x > -2147483649 && x < 2147483648 {
assert true
} else {
assert false
}
}
+19
View File
@@ -0,0 +1,19 @@
fn test_isnil_byteptr() {
pb := &u8(unsafe { nil })
assert isnil(pb)
}
fn test_isnil_voidptr() {
pv := unsafe { nil }
assert isnil(pv)
}
fn test_isnil_charptr() {
pc := &char(unsafe { nil })
assert isnil(pc)
}
fn test_isnil_intptr() {
pi := &int(unsafe { nil })
assert isnil(pi)
}
+645
View File
@@ -0,0 +1,645 @@
module builtin
import strings
/// Internal representation of `array` type. It is used to implement slices and to make slices behave correctly
/// it simply stores reference to original array and to index them properly it does index array relative to `index_start`.
struct array_buffer {
arr JS.Array
index_start int
len int
cap int
has_slice bool
}
fn (mut a array_buffer) make_copy() {
if a.index_start != 0 || a.has_slice {
mut new_arr := JS.makeEmptyJSArray()
for i in 0 .. a.len {
#new_arr.push(a.val.get(i))
mut x := i
x = x
}
new_arr = new_arr
#a.val.arr = new_arr
#a.val.index_start = new int(0)
#a.val.has_slice = new bool(false)
}
}
#array_buffer.prototype.make_copy = function() { return array_buffer_make_copy(this) }
// TODO(playX): Should this be implemented fully in JS, use generics or just voidptr?
fn (a array_buffer) get(ix int) voidptr {
mut res := unsafe { nil }
#res = a.arr[a.index_start.val + ix.val];
return res
}
fn (mut a array_buffer) set(ix int, val voidptr) {
#a.val.arr[a.val.index_start.valueOf() + ix.valueOf()] = val;
}
#array_buffer.prototype.get = function(ix) { return array_buffer_get(this,ix);}
#array_buffer.prototype.set = function(ix,val) { array_buffer_set(this,ix,val); }
struct array {
pub mut:
arr array_buffer
len int
cap int
}
fn v_sort(mut arr array, comparator fn (voidptr, voidptr) int) {
mut need_iter := true
for need_iter {
need_iter = false
for i := 1; i < arr.len; i++ {
if comparator(arr[i], arr[i - 1]) != 1 {
tmp := arr[i]
arr[i] = arr[i - 1]
arr[i - 1] = tmp
need_iter = true
}
}
}
}
// trim trims the array length to "index" without modifying the allocated data. If "index" is greater than len nothing will be changed.
pub fn (mut a array) trim(index int) {
if index < a.len {
a.len = index
}
}
#function flatIntoArray(target, source, sourceLength, targetIndex, depth) {
#"use strict";
#
#for (var sourceIndex = 0; sourceIndex < sourceLength; ++sourceIndex) {
#if (sourceIndex in source) {
#var element = source[sourceIndex];
#if (depth > 0 && Array.isArray(element))
#targetIndex = flatIntoArray(target, element, element.length, targetIndex, depth - 1);
#else {
#target[targetIndex] = element;
#++targetIndex;
#}
#}
#}
#return targetIndex;
#}
#function flatArray(target,depth) {
#var array = target
#var length = array.length;
#var depthNum = 1;
#
#if (depth !== undefined)
#depthNum = +depth
#
#var result = []
#
#flatIntoArray(result, array, length, 0, depthNum);
#return result;
#}
@[unsafe]
pub fn (a array) repeat_to_depth(count int, depth int) array {
if count < 0 {
panic('array.repeat: count is negative: ${count}')
}
mut arr := empty_array()
if a.len > 0 {
for _ in 0 .. count {
for i in 0 .. a.len {
if depth > 0 {
// TODO
} else {
arr.push(a.arr.get(i))
}
}
}
}
return arr
}
// last returns the last element of the array.
pub fn (a array) last() voidptr {
mut res := unsafe { nil }
#res = a.arr.get(new int(a.len-1));
return res
}
fn (a array) get(ix int) voidptr {
if ix < 0 || ix >= a.len {
return unsafe { nil }
}
mut result := unsafe { nil }
#result = a.arr.get(ix)
return result
}
pub fn (a array) repeat(count int) array {
unsafe {
return a.repeat_to_depth(count, 0)
}
}
#function makeEmptyArray() { return new array(new array_buffer({ arr: [], len: new int(0), index_start: new int(0), cap: new int(0) })); }
#function makeEmptyJSArray() { return new Array(); }
fn JS.makeEmptyArray() array
fn JS.makeEmptyJSArray() JS.Array
fn empty_array() array {
return JS.makeEmptyArray()
}
#function v_clone_for_array_value(value) {
#if (value instanceof $ref || value instanceof voidptr || typeof value === 'function') return value;
#return v_clone_value(value);
#}
fn (a &array) set_len(i int) {
#a.arr.arr.length=i
}
pub fn (mut a array) sort_with_compare(compare voidptr) {
#v_sort(a, compare instanceof voidptr ? compare.val : compare)
}
pub fn (mut a array) sort_with_compare_old(compare voidptr) {
#a.val.arr.arr.sort(compare instanceof voidptr ? compare.val : compare)
}
pub fn (mut a array) sort() {
#a.val.arr.arr.sort($sortComparator)
}
pub fn (a array) index(v string) int {
for i in 0 .. a.len {
#if (a.arr.get(i).toString() == v.toString())
{
return i
}
}
return -1
}
pub fn (a array) last_index(v string) int {
for i := a.len - 1; i >= 0; i-- {
#if (a.arr.get(i).toString() == v.toString())
{
return i
}
}
return -1
}
pub fn (a array) slice(start int, end int) array {
mut result := a
#let slice = a.arr.arr.slice(start,end)
#result = new array(new array_buffer({arr: a.arr.arr, len: new int(slice.length),cap: new int(slice.length),index_start: new int(start),has_slice: new bool(true)}))
#a.arr.has_slice = true
//#v_makeSlice(result)
return result
}
pub fn (mut a array) insert(i int, val voidptr) {
#a.val.arr.make_copy()
#a.val.arr.arr.splice(i,0,v_clone_for_array_value(val))
#a.val.arr.len.val = a.val.arr.arr.length
}
pub fn (mut a array) insert_many(i int, val voidptr, size int) {
#a.val.arr.make_copy()
#a.val.arr.arr.splice(i,0,...val.arr.slice(0,+size).map(v_clone_for_array_value))
#a.val.arr.len.val = a.val.arr.arr.length
}
fn (mut a array) push(val voidptr) {
#a.val.arr.make_copy()
#if (arguments[2] && arguments[2].valueOf()) {a.val.arr.arr.push(...val.map(v_clone_for_array_value))} else {
#a.val.arr.arr.push(v_clone_for_array_value(val))
#}
#a.val.arr.len.val = a.val.arr.arr.length
}
fn v_filter(arr array, callback fn (voidptr) bool) array {
mut filtered := empty_array()
for i := 0; i < arr.arr.len; i++ {
if callback(arr.arr.get(i)) {
filtered.push(arr.arr.get(i))
}
}
return filtered
}
fn v_map(arr array, callback fn (voidptr) voidptr) array {
mut mapped := empty_array()
for i := 0; i < arr.arr.len; i++ {
mapped.push(callback(arr.arr.get(i)))
}
return mapped
}
struct array_iterator {
ix int
end int
arr JS.Array
}
#array_iterator.prototype.next = function () {
#if (this.ix.val < this.end.val) {
#this.ix.val++;
#return {done: false, value: this.arr.arr.get(new int(this.ix.val-1))}
#} else {
#return {done: true, value: undefined}
#}
#}
#array_iterator.prototype[Symbol.iterator] = function () { return this; }
#array.prototype[Symbol.iterator] = function () { return new array_iterator({ix: new int(0),end: new int(this.arr.len),arr: this}); }
#array.prototype.entries = function () { let result = []; for (let key = this.arr.index_start.val;key < this.arr.len.val;key++) { result.push([new int(key), this.arr.get(new int(key))]); } return result[Symbol.iterator](); }
#array.prototype.map = function(callback) { return v_map(this,callback); }
#array.prototype.filter = function(callback) { return v_filter(this,callback); }
#Object.defineProperty(array.prototype,'cap',{ get: function () { return this.len; } })
#array.prototype.any = function (value) {
#let val ;if (typeof value == 'function') { val = function (x) { return value(x); } } else { val = function (x) { return vEq(x,value); } }
#for (let i = 0;i < this.arr.arr.length;i++)
#if (val(this.arr.get(i)))
#return true;
#
#return false;
#}
#array.prototype.all = function (value) {
#let val ;if (typeof value == 'function') { val = function (x) { return value(x); } } else { val = function (x) { return vEq(x,value); } }
#for (let i = 0;i < this.arr.arr.length;i++)
#if (!val(this.arr.get(i)))
#return false;
#
#return true;
#}
//#Object.defineProperty(array_buffer.prototype,"len", { get: function() {return new int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } })
//#Object.defineProperty(array_buffer.prototype,"cap", { get: function() {return new int(this.arr.length);}, set: function(l) { this.arr.length = l.valueOf(); } })
#
#
#function v_makeSlice(array) { Object.defineProperty(array,'len', {get: function() { return this.arr.len; }, set: function(l) { this.arr.len = l; }}) }
// delete deletes array element at index `i`.
pub fn (mut a array) delete(i int) {
a.delete_many(i, 1)
}
fn arr_copy(mut dst array, src array, count int) {
for i := 0; i < count; i++ {
dst.arr.set(i, src.arr.get(i))
}
}
// delete_many deletes `size` elements beginning with index `i`.
pub fn (mut a array) delete_many(i int, size int) {
#a.val.arr.make_copy()
#a.val.arr.arr.splice(i.valueOf(),size.valueOf())
#a.val.arr.len.val = a.val.arr.arr.length
}
// prepend prepends one value to the array.
pub fn (mut a array) prepend(val voidptr) {
a.insert(0, val)
}
// prepend_many prepends another array to this array.
@[unsafe]
pub fn (mut a array) prepend_many(val voidptr, size int) {
unsafe { a.insert_many(0, val, size) }
}
pub fn (a array) reverse() array {
mut res := empty_array()
#res.arr.arr = Array.from(a.arr).reverse()
return res
}
pub fn (mut a array) reverse_in_place() {
#a.val.arr.arr.reverse()
}
#array.prototype.$includes = function (elem) { return this.arr.arr.find(function(e) { return vEq(elem,e); }) !== undefined;}
pub fn (mut a array) clear() {
#a.val.arr.make_copy()
#a.val.arr.arr.length = 0
#a.val.arr.len.val = 0
}
// reduce executes a given reducer function on each element of the array, resulting in a single output value.
pub fn (a array) reduce(iter fn (int, int) int, accum_start int) int {
mut accum_ := accum_start
/*#for (let i = 0;i < a.arr.length;i++) {
#accum_ = iter(accum_, a.arr[i])
#}*/
for i in 0 .. a.len {
accum_ = iter(accum_, a.get(i))
}
return accum_
}
pub fn (mut a array) pop() voidptr {
mut res := unsafe { nil }
#a.val.arr.make_copy()
#res = a.val.arr.arr.pop()
#a.val.arr.len.val = a.val.arr.arr.length
return res
}
pub fn (a array) first() voidptr {
mut res := unsafe { nil }
#res = a.arr.get(new int(0))
return res
}
#array.prototype.toString = function () {
#let res = "["
#for (let i = 0; i < this.arr.arr.length;i++) {
#res += this.arr.get(i).toString();
#if (i != this.arr.arr.length-1)
#res += ', '
#}
#res += ']'
#return res;
#
#}
pub fn (a array) contains(key voidptr) bool
// delete_last effectively removes last element of an array.
pub fn (mut a array) delete_last() {
#a.val.arr.arr.pop();
}
@[unsafe]
pub fn (a &array) free() {
}
// todo: once (a []u8) will work rewrite this
pub fn (a array) bytestr() string {
res := ''
#for (let i = 0;i < a.arr.len.valueOf();i++) res.str += String.fromCharCode(a.arr.get(new int(i)))
return res
}
pub fn (a []string) str() string {
mut sb := strings.new_builder(a.len * 3)
sb.write_string('[')
for i in 0 .. a.len {
val := a[i]
sb.write_string("'")
sb.write_string(val)
sb.write_string("'")
if i < a.len - 1 {
sb.write_string(', ')
}
}
sb.write_string(']')
res := sb.str()
return res
}
pub fn (a array) to_js_array() JS.Array {
tmp := JS.Array.prototype.constructor()
for i in 0 .. a.len {
tmp.push(a.arr.get(i))
}
return tmp
}
pub fn (a array) to_number_array() JS.Array {
tmp := JS.Array.prototype.constructor()
for i in 0 .. a.len {
elem := a.arr.get(i)
_ := elem
#tmp.push(Number(elem.valueOf()));
}
return tmp
}
// push_many - appends multiple values to the end of the array.
pub fn (mut a array) push_many(val voidptr, size int) {
a.insert_many(a.len, val, size)
}
type EveryFn = fn (JS.Number, JS.Number) JS.Boolean
type BigEveryFn = fn (JS.BigInt, JS.Number) JS.Boolean
pub interface JS.TypedArray {
mut:
byteLength JS.Number
byteOffset JS.Number
length JS.Number
}
pub interface JS.Uint8Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.Uint16Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.Uint32Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.BigUint64Array {
JS.TypedArray
at(index JS.Number) JS.BigInt
every(JS.BigEveryFn) JS.Boolean
}
pub interface JS.Int8Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.Int16Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.Int32Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.BigInt64Array {
JS.TypedArray
at(index JS.Number) JS.BigInt
every(JS.BigEveryFn) JS.Boolean
}
pub interface JS.Float32Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub interface JS.Float64Array {
JS.TypedArray
at(index JS.Number) JS.Number
every(JS.EveryFn) JS.Boolean
}
pub fn uint8_array(arr []u8) JS.Uint8Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut uint_arr := JS.Uint8Array{}
#uint_arr = new Uint8Array(tmp)
return uint_arr
}
pub fn uint16_array(arr []u16) JS.Uint16Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut uint_arr := JS.Uint16Array{}
#uint_arr = new Uint16Array(tmp)
return uint_arr
}
pub fn uint32_array(arr []u32) JS.Uint32Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut uint_arr := JS.Uint32Array{}
#uint_arr = new Uint32Array(tmp)
return uint_arr
}
pub fn int8_array(arr []i8) JS.Int8Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut int_arr := JS.Int8Array{}
#int_arr = new Int8Array(tmp)
return int_arr
}
pub fn int16_array(arr []i16) JS.Int16Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut int_arr := JS.Int16Array{}
#int_arr = new Int16Array(tmp)
return int_arr
}
pub fn int32_array(arr []int) JS.Int32Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut int_arr := JS.Int32Array{}
#int_arr = new Int32Array(tmp)
return int_arr
}
pub fn int64_array(arr []i64) JS.BigInt64Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut int_arr := JS.BigInt64Array{}
#int_arr = new BigInt64Array(tmp)
return int_arr
}
pub fn uint64_array(arr []u64) JS.BigUint64Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut int_arr := JS.BigUint64Array{}
#int_arr = new BigUint64Array(tmp)
return int_arr
}
pub fn float32_array(arr []f32) JS.Float32Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut float_arr := JS.Float32Array{}
#float_arr = new Float32Array(tmp)
return float_arr
}
pub fn float64_array(arr []f64) JS.Float64Array {
#let tmp = new Array();
for elem in arr {
_ := elem
#tmp.push(elem.val)
}
mut float_arr := JS.Float64Array{}
#float_arr = new Float64Array(tmp)
return float_arr
}
File diff suppressed because it is too large. Load diff
+183
View File
@@ -0,0 +1,183 @@
module builtin
// used to generate JS throw statements.
$if js_node {
#var $fs = require('fs');
}
@[noreturn]
pub fn js_throw(s any) {
#throw s
for {}
}
#let globalPrint, globalWrite;
$if js_freestanding {
#globalPrint = globalThis.print
#globalWrite = (typeof globalThis.write === 'function')? write: globalThis.print
}
pub fn flush_stdout() {
// needed for parity with builtin.c.v
}
pub fn flush_stderr() {
// needed for parity with builtin.c.v
}
pub fn println(s string) {
$if js_freestanding {
#globalPrint(s.str)
} $else {
#console.log(s.str)
}
}
pub fn print(s string) {
$if js_node {
#$process.stdout.write(s.str)
} $else $if js_freestanding {
#globalWrite(s.str)
} $else {
panic('Cannot `print` in a browser, use `println` instead')
}
}
pub fn eprintln(s string) {
$if js_freestanding {
#globalPrint(s.str)
} $else {
#console.error(s.str)
}
}
pub fn eprint(s string) {
$if js_node {
#$process.stderr.write(s.str)
} $else {
panic('Cannot `eprint` in a browser, use `println` instead')
}
}
// input_character gives back a single character, read from the standard input.
// It returns -1 on error (when the input is finished (EOF), on a broken pipe etc).
pub fn input_character() int {
$if js_node {
mut ch := -1
#try {
#const read_buffer = Buffer.alloc(1)
#const nbytes = $fs.readSync(0, read_buffer, 0, 1, null)
#if (nbytes > 0) { ch.val = read_buffer[0] }
#} catch (e) {}
return ch
} $else {
return -1
}
}
// print_character writes the single character `ch` to the standard output.
// It returns the written character value, or panics if the active JS runtime
// does not support stdout writes.
pub fn print_character(ch u8) int {
print(ch.ascii_str())
return ch
}
// Exits the process in node, and halts execution in the browser
// because `process.exit` is undefined. Workaround for not having
// a 'real' way to exit in the browser.
@[noreturn]
pub fn exit(c int) {
JS.process.exit(c)
js_throw('exit(${c})')
}
fn opt_ok(data voidptr, option Option) {
#option.state = 0
#option.err = none__
#option.data = data
}
pub fn unwrap(opt string) string {
mut o := Option{}
#o = opt
if o.state != 0 {
js_throw(o.err)
}
mut res := ''
#res = opt.data
return res
}
fn js_stacktrace() string {
stacktrace := ''
#let err = new TypeError();
#err.name = 'stacktrace: '
#stacktrace.str = err.stack
return stacktrace
}
// v_clone_value preserves standalone JS semantics for V clone methods.
#function v_clone_value(value) {
#if (value === null || value === undefined) return value;
#if (value instanceof $ref) return new $ref(v_clone_value(value.val));
#if (value instanceof array) return array_clone(value);
#if (value instanceof map) {
#let cloned = {}
#for (const key in value.map) cloned[key] = { key: v_clone_value(value.map[key].key), val: v_clone_value(value.map[key].val) }
#return new map(cloned);
#}
#if (typeof value !== 'object') return value;
#if (typeof value.$toJS === 'function') {
#let cloned = Object.create(Object.getPrototypeOf(value) || Object.prototype);
#for (const key of Object.keys(value)) cloned[key] = v_clone_value(value[key]);
#return cloned;
#}
#let cloned;
#try {
#cloned = typeof value.constructor === 'function' ? new value.constructor({}) : Object.create(Object.getPrototypeOf(value));
#} catch (e) {
#cloned = Object.create(Object.getPrototypeOf(value) || Object.prototype);
#}
#for (const key of Object.keys(value)) cloned[key] = v_clone_value(value[key]);
#return cloned;
#}
pub fn print_backtrace() {
println(js_stacktrace())
}
pub fn (a array) clone() array {
mut res := empty_array()
#const source = a instanceof $ref ? a.val : a
#const cloned = source.arr.arr.slice(source.arr.index_start.valueOf(), source.arr.index_start.valueOf() + source.len.valueOf()).map(v_clone_value)
#res = new array(new array_buffer({arr: cloned, len: new int(cloned.length), cap: new int(cloned.length), index_start: new int(0), has_slice: new bool(false)}))
return res
}
// Check for nil value
pub fn isnil(val voidptr) bool {
res := false
// This one is kinda weird. In C and native backend we can cast booleans and integers to pointers
// so we just check *for* all possible NULL-like values here.
#if (typeof val == 'function') { res.val = false; } else {
#val = val instanceof voidptr ? val.valueOf().val : val;
#res.val = val === null || val === undefined || val === false || val === 0 || val === BigInt(0) || (val instanceof int ? val.val == 0 : false)
#}
return res
}
pub fn (f float_literal) str() string {
res := ''
#res.str += f.valueOf()
return res
}
+157
View File
@@ -0,0 +1,157 @@
// Copyright (c) 2019-2024 Alexander Medvednikov. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.
module builtin
fn (a any) toString()
// panic prints an error message, then exits the process with exit code of 1.
@[noreturn]
pub fn panic(s string) {
eprintln('V panic: ' + s)
eprintln(js_stacktrace())
exit(1)
}
// panic_n prints an error message, followed by the given number, then exits the process with exit code of 1.
@[noreturn]
pub fn panic_n(s string, n i64) {
eprintln('V panic: ' + s)
eprintln(js_stacktrace())
exit(1)
}
// IError holds information about an error instance.
pub interface IError {
msg() string
code() int
}
// str returns the message of IError.
pub fn (err IError) str() string {
return match err {
None__ {
'none'
}
Error {
err.msg()
}
MessageError {
err.str()
}
else {
'${err.type_name()}: ${err.msg()}'
}
}
}
// Error is the empty default implementation of `IError`.
pub struct Error {}
// msg returns the message of Error.
pub fn (err Error) msg() string {
return ''
}
// code returns the code of Error.
pub fn (err Error) code() int {
return 0
}
// MessageError is the default implementation of the `IError` interface that is returned by the `error()` function.
struct MessageError {
pub:
msg string
code int
}
// str returns the message and code of the MessageError.
pub fn (err MessageError) str() string {
return err.msg
}
// msg returns the message of the MessageError.
pub fn (err MessageError) msg() string {
return err.msg
}
// code returns the code of MessageError.
pub fn (err MessageError) code() int {
return err.code
}
pub const none__ = IError(&None__{})
struct None__ {
Error
}
fn (_ None__) str() string {
return 'none'
}
pub struct Option {
state u8
err IError = none__
}
// str returns the Option type: ok, none, or error.
pub fn (o Option) str() string {
if o.state == 0 {
return 'Option{ ok }'
}
if o.state == 1 {
return 'Option{ none }'
}
return 'Option{ error: "${o.err}" }'
}
pub struct _option {
state u8
err IError = none__
}
// str returns the Option type: ok, none, or error.
pub fn (o _option) str() string {
if o.state == 0 {
return 'Option{ ok }'
}
if o.state == 1 {
return 'Option{ none }'
}
return 'Option{ error: "${o.err}" }'
}
// trace_error prints to stderr a string and a backtrace of the error.
fn trace_error(x string) {
eprintln('> ${@FN} | ${x}')
}
// error returns a default error instance containing the error given in `message`.
// Example: if ouch { return error('an error occurred') }
@[inline]
pub fn error(message string) IError {
// trace_error(message)
return &MessageError{
msg: message
}
}
// error_with_code returns a default error instance containing the given `message` and error `code`.
// Example: if ouch { return error_with_code('an error occurred', 1) }
@[inline]
pub fn error_with_code(message string, code int) IError {
// trace_error('${message} | code: ${code}')
return &MessageError{
msg: message
code: code
}
}
// free allows for manually freeing memory allocated at the address `ptr`.
// However, this is a no-op on JS backend
@[unsafe]
pub fn free(ptr voidptr) {
_ := ptr
}
+101
View File
@@ -0,0 +1,101 @@
module builtin
pub fn (b u8) is_space() bool {
mut result := false
#result.val = /^\s*$/.test(String.fromCharCode(b))
return result
}
pub fn (c u8) str() string {
res := ''
#res.str = c.val.toString()
return res
}
pub fn (c u8) ascii_str() string {
res := ''
#res.str = String.fromCharCode(c.val)
return res
}
pub fn (c u8) repeat(count int) string {
mut res := ''
for _ in 0 .. count {
res += c.ascii_str()
}
return res
}
@[inline]
pub fn (c u8) is_digit() bool {
return c >= `0` && c <= `9`
}
// is_hex_digit returns `true` if the byte is either in range 0-9, a-f or A-F and `false` otherwise.
// Example: assert u8(`F`) == true
@[inline]
pub fn (c u8) is_hex_digit() bool {
return c.is_digit() || (c >= `a` && c <= `f`) || (c >= `A` && c <= `F`)
}
// is_oct_digit returns `true` if the byte is in range 0-7 and `false` otherwise.
// Example: assert u8(`7`) == true
@[inline]
pub fn (c u8) is_oct_digit() bool {
return c >= `0` && c <= `7`
}
// is_bin_digit returns `true` if the byte is a binary digit (0 or 1) and `false` otherwise.
// Example: assert u8(`0`) == true
@[inline]
pub fn (c u8) is_bin_digit() bool {
return c == `0` || c == `1`
}
// is_letter returns `true` if the byte is in range a-z or A-Z and `false` otherwise.
// Example: assert u8(`V`) == true
@[inline]
pub fn (c u8) is_letter() bool {
return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`)
}
// is_alnum returns `true` if the byte is in range a-z, A-Z, 0-9 and `false` otherwise.
// Example: assert u8(`V`) == true
@[inline]
pub fn (c u8) is_alnum() bool {
return (c >= `a` && c <= `z`) || (c >= `A` && c <= `Z`) || (c >= `0` && c <= `9`)
}
// is_capital returns `true`, if the byte is a Latin capital letter.
// Example: assert `H`.is_capital() == true
// Example: assert `h`.is_capital() == false
@[inline]
pub fn (c u8) is_capital() bool {
return c >= `A` && c <= `Z`
}
// str_escaped returns the contents of `byte` as an escaped `string`.
// Example: assert u8(0).str_escaped() == r'`\0`'
pub fn (b u8) str_escaped() string {
mut str := ''
match b {
0 { str = r'`\0`' }
7 { str = r'`\a`' }
8 { str = r'`\b`' }
9 { str = r'`\t`' }
10 { str = r'`\n`' }
11 { str = r'`\v`' }
12 { str = r'`\f`' }
13 { str = r'`\r`' }
27 { str = r'`\e`' }
32...126 { str = b.ascii_str() }
else { str = '0x' + b.hex() }
}
return str
}
+15
View File
@@ -0,0 +1,15 @@
module builtin
pub fn (x f32) str() string {
res := ''
#res.str = x.val + ''
return res
}
pub fn (x f64) str() string {
res := ''
#res.str = x.val + ''
return res
}
+224
View File
@@ -0,0 +1,224 @@
module builtin
pub type byte = u8
pub const min_i8 = i8(-128)
pub const max_i8 = i8(127)
pub const min_i16 = i16(-32768)
pub const max_i16 = i16(32767)
pub const min_i32 = i32(-2147483648)
pub const max_i32 = i32(2147483647)
// -9223372036854775808 is wrong, because C compilers parse literal values
// without sign first, and 9223372036854775808 overflows i64, hence the
// consecutive subtraction by 1
pub const min_i64 = i64(-9223372036854775807 - 1)
pub const max_i64 = i64(9223372036854775807)
pub const min_int = $if new_int ? && x64 { int(min_i64) } $else { int(min_i32) }
pub const max_int = $if new_int ? && x64 { int(max_i64) } $else { int(max_i32) }
pub const min_u8 = u8(0)
pub const max_u8 = u8(255)
pub const min_u16 = u16(0)
pub const max_u16 = u16(65535)
pub const min_u32 = u32(0)
pub const max_u32 = u32(4294967295)
pub const min_u64 = u64(0)
pub const max_u64 = u64(18446744073709551615)
pub fn (i i8) str() string {
mut res := ''
#res.str = i.val.toString()
return res
}
pub fn (i i16) str() string {
mut res := ''
#res.str = i.val.toString()
return res
}
pub fn (i u16) str() string {
mut res := ''
#res.str = i.val.toString()
return res
}
pub fn (i int) str() string {
mut res := ''
#res = new string( i+'' )
return res
}
pub fn (i i64) str() string {
mut res := ''
#res = new string( i + '')
return res
}
pub fn (i u32) str() string {
mut res := ''
#res = new string( i + '')
return res
}
pub fn (i u64) str() string {
mut res := ''
#res = new string( i + '')
return res
}
pub fn (i bool) str() string {
mut res := ''
#res = new string( i + '')
return res
}
pub fn (i any) str() string {
mut res := ''
#res = new string( i.toString() )
return res
}
pub fn (i int_literal) str() string {
res := ''
#res.str = i.val.toString()
return res
}
pub fn (x u64) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x u64) hex_full() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x i64) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x u32) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x u16) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x i8) hex() string {
mut res := ''
#res.str = x.val.toString(16)
if res.len < 2 {
res = '0' + res
}
return res
}
pub fn (x i16) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x int) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x int_literal) hex() string {
res := ''
#res.str = x.val.toString(16)
return res
}
pub fn (x u8) hex() string {
mut res := ''
#res.str = x.val.toString(16)
if res.len < 2 {
res = '0' + res
}
return res
}
// hex returns a string with the hexadecimal representation of the byte elements of the array.
pub fn (b []u8) hex() string {
mut hex := ''
for i in b {
mut z := i
z = z
#let n0 = i.val >> 4
#hex.str += n0 < 10 ? String.fromCharCode(n0) : String.fromCharCode(n0 + 87)
#let n1 = i.val & 0xF
#hex.str += n1 < 10 ? String.fromCharCode(n1) : String.fromCharCode(n1 + 87)
}
return hex
}
pub fn (i int) hex2() string {
return '0x' + i.hex()
}
pub fn (i i8) hex2() string {
return '0x' + i.hex()
}
pub fn (i i16) hex2() string {
return '0x' + i.hex()
}
pub fn (i i64) hex2() string {
return '0x' + i.hex()
}
pub fn (i u8) hex2() string {
return '0x' + i.hex()
}
pub fn (i u16) hex2() string {
return '0x' + i.hex()
}
pub fn (i u32) hex2() string {
return '0x' + i.hex()
}
pub fn (i u64) hex2() string {
return '0x' + i.hex()
}
+246
View File
@@ -0,0 +1,246 @@
// vtest retry: 3
// vtest build: present_node?
const a = 3
const u = u64(1)
fn test_const() {
b := (true && true) || false
assert b == true
assert a == 3
assert u == u64(1)
assert u == 1 // make sure this works without the cast
}
fn test_str_methods() {
assert i8(1).str() == '1'
assert i8(-1).str() == '-1'
assert i16(1).str() == '1'
assert i16(-1).str() == '-1'
assert int(1).str() == '1'
assert int(-1).str() == '-1'
assert int(2147483647).str() == '2147483647'
// todo: overflow check for integers
// assert int(2147483648).str() == '-2147483648'
// assert int(-2147483648).str() == '-2147483648'
assert i64(1).str() == '1'
assert i64(-1).str() == '-1'
assert u16(1).str() == '1'
// assert u16(-1).str() == '65535'
assert u32(1).str() == '1'
// assert u32(-1).str() == '4294967295'
assert u64(1).str() == '1'
// assert u64(-1).str() == '18446744073709551615'
}
fn test_and_precedence() {
assert (2 & 0 == 0) == ((2 & 0) == 0)
assert (2 & 0 != 0) == ((2 & 0) != 0)
assert (0 & 0 >= 0) == ((0 & 0) >= 0)
assert (0 & 0 <= 0) == ((0 & 0) <= 0)
assert (0 & 0 < 1) == ((0 & 0) < 1)
assert (1 & 2 > 0) == ((1 & 2) > 0)
}
fn test_or_precedence() {
assert (1 | 0 == 0) == ((1 | 0) == 0)
assert (1 | 0 != 1) == ((1 | 0) != 1)
assert (1 | 0 >= 2) == ((1 | 0) >= 2)
assert (1 | 0 <= 0) == ((1 | 0) <= 0)
assert (1 | 0 < 0) == ((1 | 0) < 0)
assert (1 | 0 > 1) == ((1 | 0) > 1)
}
fn test_xor_precedence() {
assert (1 ^ 0 == 2) == ((1 ^ 0) == 2)
assert (1 ^ 0 != 2) == ((1 ^ 0) != 2)
assert (1 ^ 0 >= 0) == ((1 ^ 0) >= 0)
assert (1 ^ 0 <= 1) == ((1 ^ 0) <= 1)
assert (1 ^ 0 < 0) == ((1 ^ 0) < 0)
assert (1 ^ 0 > 1) == ((1 ^ 0) > 1)
}
fn test_left_shift_precedence() {
assert (2 << 4 | 3) == ((2 << 4) | 3)
assert (2 << 4 | 3) != (2 << (4 | 3))
}
fn test_right_shift_precedence() {
assert (256 >> 4 | 3) == ((256 >> 4) | 3)
assert (256 >> 4 | 3) != (256 >> (4 | 3))
}
fn test_i8_print() {
b := i8(0)
println(b)
c := i16(7)
println(c)
d := u16(6)
println(d)
assert true
}
/*
fn test_cmp() {
assert 1 ≠ 2
assert 1 ⩽ 2
assert 1 ⩾ 0
}
*/
type MyInt = int
fn test_int_alias() {
i := MyInt(2)
assert i + 10 == 12
}
fn test_hex() {
x := u64(10)
assert x.hex() == 'a'
b := 1234
assert b.hex() == '4d2'
b1 := -1
println(b1)
// assert b1.hex() == 'ffffffff'
// unsigned tests
// assert u8(12).hex() == '0c'
// assert u8(255).hex() == 'ff'
assert u16(65535).hex() == 'ffff'
// assert u32(-1).hex() == 'ffffffff'
// assert u64(-1).hex() == 'ffffffffffffffff'
// signed tests
// assert i8(-1).hex() == 'ff'
assert i8(12).hex() == '0c'
assert i16(32767).hex() == '7fff'
assert int(2147483647).hex() == '7fffffff'
assert i64(9223372036854775807).hex() == '7fffffffffffffff'
}
fn test_bin() {
x1 := 0b10
assert x1 == 2
x2 := 0b10101010
assert x2 == 0xAA
x3 := -0b0000001
assert x3 == -1
x4 := 0b11111111
assert x4 == 255
x5 := u8(0b11111111)
assert x5 == 255
x6 := char(0b11111111)
assert int(x6) == -1
x7 := 0b0
assert x7 == 0
x8 := -0b0
assert x8 == 0
}
fn test_oct() {
x1 := 0o12
assert x1 == 10
x2 := 0o350
assert x2 == 232
x3 := 0o00073
assert x3 == 59
x4 := 0
assert x4 == 0
x5 := 195
assert x5 == 195
x6 := -0o744
assert x6 == -484
x7 := -0o000042
assert x7 == -34
x8 := -112
assert x8 == -112
x9 := -0
assert x9 == 0
}
fn test_num_separator() {
// int
assert 100_000_0 == 1000000
assert -2_23_4_6 == -22346
// bin
assert 0b0_11 == 3
assert -0b0_100 == -4
// oct
assert 0o1_73 == 123
assert -0o17_5 == -125
assert -0o175 == -125
// hex
assert 0xFF == 255
assert 0xF_F == 255
// f32 or f64
assert 312_2.55 == 3122.55
assert 312_2.55 == 3122.55
}
fn test_int_decl() {
x1 := 0
x2 := 1333
x3 := -88955
x4 := 2000000000
x5 := -1999999999
assert typeof(x1).name == 'int'
assert typeof(x2).name == 'int'
assert typeof(x3).name == 'int'
assert typeof(x4).name == 'int'
assert typeof(x5).name == 'int'
x7 := u64(-321314588900011)
assert typeof(x7).name == 'u64'
}
fn test_int_to_hex() {
// array hex
/*
st := [u8(`V`), `L`, `A`, `N`, `G`]
assert st.hex() == '564c414e47'
assert st.hex().len == 10
st1 := [u8(0x41)].repeat(100)
assert st1.hex() == '41'.repeat(100)*/
// --- int to hex tests
c := 12
// 8Bit
assert u8(0).hex() == '00'
assert u8(c).hex() == '0c'
assert i8(c).hex() == '0c'
assert u8(127).hex() == '7f'
assert i8(127).hex() == '7f'
assert u8(255).hex() == 'ff'
// assert u8(-1).hex() == 'ff'
// 16bit
assert u16(0).hex() == '0'
assert i16(c).hex() == 'c'
assert u16(c).hex() == 'c'
assert i16(32767).hex() == '7fff'
assert u16(32767).hex() == '7fff'
// assert i16(-1).hex() == 'ffff'
assert u16(65535).hex() == 'ffff'
// 32bit
assert u32(0).hex() == '0'
assert c.hex() == 'c'
assert u32(c).hex() == 'c'
assert 2147483647.hex() == '7fffffff'
assert u32(2147483647).hex() == '7fffffff'
// assert (-1).hex() == 'ffffffffffffffff'
// assert u32(4294967295).hex() == 'ffffffff'
// 64 bit
assert u64(0).hex() == '0'
assert i64(c).hex() == 'c'
assert u64(c).hex() == 'c'
assert i64(9223372036854775807).hex() == '7fffffffffffffff'
assert u64(9223372036854775807).hex() == '7fffffffffffffff'
// assert i64(-1).hex() == 'ffffffffffffffff'
assert u64(18446744073709551615).hex() == 'ffffffffffffffff'
}
fn test_repeat() {
b := u8(`V`)
assert b.repeat(5) == 'VVVVV'
assert b.repeat(1) == b.ascii_str()
assert b.repeat(0) == ''
}
Loaded 100 of 11957 files, more files were not shown because too many files have changed in this diff. Show more